PyEuropePMC
PyEuropePMC is a Python client for Europe PMC. It searches the literature, downloads open-access full text, and parses JATS XML into metadata, plain text and structured sections. It also ships a command-line tool and an MCP server for AI agents.
✨ Features
- Europe PMC search with plain queries or a fluent
QueryBuilder, pagination, and results as JSON, XML or Dublin Core. Search guide - Ten more sources in one search, with duplicates merged: PubMed, arXiv, ClinicalTrials.gov, OpenAlex, Semantic Scholar, CORE, DBLP, DOAJ, HAL and Zenodo. Semantic Scholar needs the
semanticscholarextra and CORE an API key. Multi-source search - Full text: XML (Europe PMC first, then other open sources), PDF and HTML, plus bulk PDF downloads from the Europe PMC FTP site. Full-text retrieval
- JATS XML parsing: metadata, authors, tables, figures and references; plain text and Markdown; typed sections for retrieval-augmented generation (RAG); JATS normalization and BioC export. XML parsing, JATS normalization
- Text-mining annotations for genes, diseases, chemicals and their relationships. Examples
- Citations and metadata: walk citation graphs, and enrich records from Crossref, Unpaywall, OpenAlex, Semantic Scholar, DataCite, ORCID, ROR and iCite. Citation walking, Enrichment
- Analysis: pandas DataFrames, citation statistics, duplicate detection, plots, and PRISMA-style search logs for systematic reviews. Analytics, Review tracking
- RDF knowledge graphs from parsed articles, mapped by the that ships with the package; pass or to use your own.
📦 Installation
pip install pyeuropepmc # core
pip install "pyeuropepmc[analytics,visualization]" # add extras (quote the brackets)
pip install "pyeuropepmc[all]" # every optional feature
PyEuropePMC supports Python 3.10 to 3.13. The core install covers search, full-text download, XML parsing, RDF, the command line and the MCP server. The extras add:
Upgrading from 1.x? The migration guide lists what moved into extras.
🚀 Quick start
from pyeuropepmc import FullTextClient, FullTextXMLParser, QueryBuilder, SearchClient
# 1. Search Europe PMC for open-access articles ("CRISPR AND OPEN_ACCESS:y")
query = QueryBuilder().keyword("CRISPR").and_().field("open_access", True).build()
with SearchClient() as search:
hits = search.search_and_parse(query, pageSize=10)
pmcid = next(hit["pmcid"] for hit in hits if hit.get("pmcid"))
# 2. Download the article's JATS XML (raises an error if no source has it)
with FullTextClient() as fulltext:
xml_path = fulltext.download_xml_by_pmcid(pmcid, output_path=f"articles/{pmcid}.xml")
# 3. Parse it
parser = FullTextXMLParser(xml_path.read_text(encoding="utf-8"))
metadata = parser.extract_metadata()
print(metadata["title"])
print(", ".join(metadata["authors"][:3]))
text = parser.to_plaintext() # or parser.to_markdown()
# 4. Collect text blocks with their section path, ready to chunk and embed for RAG
chunks = []
for section in parser.get_full_text_sections_structured():
if section["section_type"] not in ("front", "body"): # skip back matter and appendices
continue
for block in section["content"]:
if block.get("text"):
path = section.get("section_path", section["title"])
chunks.append({"section": path, "type": block["type"], "text": block["text"]})
print(f"{len(chunks)} text blocks")
section_type is front for the title and abstract, then body, back or appendix. Each block has a type such as paragraph, list, table or figure. Tables and figures carry label and caption, and tables also carry rows. The XML parsing guide covers the other extractors.
🔒 Safe XML parsing
PyEuropePMC parses every XML document it reads with defusedxml: full-text articles, Europe PMC search results, arXiv and PubMed responses, and local files. A DOCTYPE declaration is accepted, but a document that declares entities, internal or external, is refused rather than expanded; FullTextXMLParser raises ParsingError. lxml is not used and does not need to be installed.
💻 Command line
pyeuropepmc unified_search search "CRISPR base editing" --limit 10 --output results.json
pyeuropepmc benchmark list-datasets
unified_search search queries Europe PMC, PubMed and arXiv unless you pass --source. Add --help to any command for its options.
🤖 MCP server
pyeuropepmc-mcp serves 42 tools over the Model Context Protocol: multi-source search with date, open-access and source filters, a query linter, builder, PubMed translator and evaluator (Europe PMC answers a malformed query with a different result instead of an error), a service-status probe and cache controls, BibTeX, RIS, CSL-JSON, CSV and Markdown export, hit counts, paper details (also for up to 100 papers at once), citations and references, open-access full text as markdown or typed sections (with a section filter and a size cap, so a long paper fits in one response), free copies of papers that are not open access in Europe PMC (via Unpaywall), text-mined entity annotations (genes, chemicals, diseases) of a paper or across papers, data and lab links of a paper (accession numbers, BioStudies, model-organism databases), citation-graph walking, ClinicalTrials.gov search, figure extraction, a local full-text index, bibliography conversion and LLM-powered analysis. The server is part of the core install. pip install "pyeuropepmc[all]" enables every tool; otherwise the bib_* tools need bibliography, and the LLM tools need agentic plus an OpenAI-compatible API key (see Configuration below).
pyeuropepmc-mcp # stdio, for Claude Desktop and similar clients
pyeuropepmc mcp # the same server, through the CLI
pyeuropepmc-mcp --transport streamable-http # HTTP at http://127.0.0.1:8000/mcp
For Claude Desktop and other clients that start the server themselves:
{
"mcpServers": {
"pyeuropepmc": {
"command": "pyeuropepmc-mcp"
}
}
}
Without an install, "command": "uvx" with "args": ["pyeuropepmc", "mcp"] does the same; this is what the MCP Registry entry tells clients to run. For Claude Code, one command adds it:
claude mcp add pyeuropepmc -- uvx pyeuropepmc mcp
Cursor (.cursor/mcp.json) and VS Code (.vscode/mcp.json, under "servers" instead of "mcpServers") take the same command and args.
The server has no authentication of its own, so keep the HTTP transport on 127.0.0.1 or put an authenticating proxy in front of it. The MCP server guide lists every tool. The server is listed in the MCP Registry as io.github.JonasHeinickeBio/pyeuropepmc.
⚙️ Configuration
Europe PMC needs no API key. Other services read these environment variables. A value passed in code, such as FullTextClient(email=...) or EnrichmentConfig(unpaywall_email=...), takes precedence.
The pyeuropepmc command also loads the first .env file it finds in the current directory, one of its parents, or ~/.config/pyeuropepmc/. Variables that are already set keep their values.
📚 Documentation
The guides are in docs/. Start with installation and the quick start, or go straight to the API reference, caching, the example scripts and the changelog.
Parser benchmark. On the 55 JATS articles in benchmark_xmls/xml, the parser's mean composite quality score is 0.998 (measured on 2026-09-15). The benchmarking guide explains the metrics. To reproduce the score from a source checkout, or to score a published dataset:
pyeuropepmc benchmark run local --local-path benchmark_xmls/xml --limit 55
pyeuropepmc benchmark download PLOS_1000 # 1,000 articles, 1.3 GB
pyeuropepmc benchmark run PLOS_1000
The weekly benchmark workflow times the API clients and opens a pull request that refreshes the section below.
📊 Performance
Last updated: 2026-09-14
🚀 pyEuropePMC Benchmark Suite
Generated: 2026-09-14 07:45:39
📊 Summary
- Total Benchmarks: 6
- Total Methods: 10
- Successful Runs: 10
- Failed Runs: 0
- Cache Enabled: 5
ArticleClient_NoCache
ArticleClient_Cached
SearchClient_NoCache
SearchClient_Cached
FullTextClient_NoCache
FullTextClient_Cached
🔁 Cache vs No-Cache Comparison
ArticleClient — cached vs no-cache
FullTextClient — cached vs no-cache
SearchClient — cached vs no-cache
- Average speedup for SearchClient (no-cache / cached): 18093.91x
Notes: Means are computed over measured iterations; '-' indicates missing data. Values like '<1ms' indicate very fast cached responses. Speedups shown as lower-bounds when cached times are too small to measure precisely.
⚙️ How to reproduce
Run the modular benchmark locally and regenerate these artifacts:
pytest tests/benchmark_article_client.py::test_modular_benchmark_system -m benchmark --force-enable-socket --timeout=3600
- Detailed JSON results:
MODULAR_BENCHMARK_RESULTS.json
🤝 Contributing
Contributions are welcome. The development guide covers setup, testing, code quality and the release process. Report bugs and suggest features in the issue tracker.
📝 Citation
If PyEuropePMC supports your research, please cite it and give the version you used (pip show pyeuropepmc prints it):
@software{pyeuropepmc,
author = {Heinicke, Jonas},
title = {{PyEuropePMC}: a Python toolkit for Europe PMC},
url = {https://github.com/JonasHeinickeBio/pyEuropePMC}
}
The literature itself comes from Europe PMC; please acknowledge it as your data source.
📄 License
PyEuropePMC is released under the MIT License. Articles you retrieve keep their own licences, which FullTextXMLParser.extract_license() reads from the XML.