The full upstream README, mirrored here for reference. Install config, tool schemas, adoption signals, and an original overview live on the Pubmed MCP Server listing page.
Search PubMed/Europe PMC, fetch articles and full text (PMC/EPMC/Unpaywall), citations, MeSH terms via MCP. STDIO or Streamable HTTP.
Public Hosted Server: https://pubmed.caseyjhand.com/mcp
11 tools for working with PubMed, PubMed Central, and Europe PMC data:
| Tool | Description |
|---|---|
pubmed_search_articles | Search PubMed with full query syntax, field-specific filters, date ranges, pagination, and optional brief summaries |
pubmed_europepmc_search | Search Europe PMC for preprints, patents, Agricola, and EPMC-only OA records that don't surface in PubMed. Cursor-based pagination. |
pubmed_europepmc_fetch | Fetch complete Europe PMC records — including the untruncated abstract — by source + epmcId, the only identifier many preprint, patent, and Agricola records carry |
pubmed_fetch_articles | Fetch full article metadata by PMIDs — abstract, authors, journal, MeSH terms, grants |
pubmed_fetch_fulltext | Fetch full-text articles via a chain: NCBI PMC EFetch → Europe PMC fullTextXML → Unpaywall. Accepts PMIDs, PMCIDs, or DOIs. |
pubmed_format_citations | Generate formatted citations in APA 7th, MLA 9th, BibTeX, RIS, or Vancouver (ICMJE/NLM) |
pubmed_find_related | Find similar articles, citing articles, or references for a given PMID |
pubmed_spell_check | Spell-check biomedical queries using NCBI's ESpell service |
pubmed_lookup_mesh | Search and explore MeSH vocabulary — tree numbers, scope notes, entry terms |
pubmed_lookup_citation | Resolve partial bibliographic references to PubMed IDs via ECitMatch |
pubmed_convert_ids | Convert between DOI, PMID, and PMCID using the PMC ID Converter API |
pubmed_search_articlesSearch PubMed with full NCBI query syntax and filters.
pubmed_fetch_articlesFetch full article metadata by PubMed IDs.
maxResponseCharacters keeps complete article records in response order until the next one would cross it, then defers the rest whole and lists their PMIDs in deferred.ids. Re-call with those PMIDs to resume exactly where the response stopped — no article is split, skipped, or duplicated. Each article is measured as the JSON record it is returned as, so a ceiling under the first article returns zero articles, the full deferred list, and the size to clearpubmed_fetch_fulltextFetch full-text articles via a three-stage chain: NCBI PMC EFetch → Europe PMC fullTextXML → Unpaywall.
pmcids (direct PMC IDs), pmids (PubMed IDs, auto-resolved), or dois (auto-resolved to PMC via the ID Converter; preprints and EPMC-only OA fall through to Europe PMC / Unpaywall)viaSource: "pmc" | "europepmc" | "unpaywall"EUROPEPMC_ENABLED=false) recovers PMC-counterpart records that NCBI PMC EFetch missed, and resolves DOI input to PMC counterparts when one exists. EPMC's fullTextXML is PMC-keyed, so preprints (PPR), patents (PAT), and Agricola (AGR) are reachable via pubmed_europepmc_search for metadata but have no full text via this chain.UNPAYWALL_EMAIL) resolves DOIs to legal OA copies; extracts HTML landing pages to Markdown via Defuddle or PDFs to text via unpdfsource: "pmc" (structured sections, regardless of whether it came from PMC or EPMC) or source: "unpaywall" (best-effort body + contentFormat: html-markdown or pdf-text)not-found, no-pmc-fallback-disabled, no-epmc-fulltext, no-body, no-doi, no-oa, fetch-failed, parse-failed, service-error) so callers can retry or explain to users without parsing textunavailable entry also carries unqueriedTiers when the chain skipped a tier this deployment has not configured and that tier could have served the id — the search was incomplete, and a deployment with those tiers configured may still resolve itunavailable entry carries idType (pmid / pmcid / doi) and triedTiers — per-tier outcomes (not-attempted, miss, no-fulltext, service-error, …) in execution order, so callers can see which stage failed and why["methods", "results"]) and configurable max sections apply to PMC outputmaxCharacters caps body text per article (PMC sections and subsections, or the Unpaywall body), maxCharactersPerSection caps a single PMC section, and overflowMode picks between truncate (fill sections in document order) and outline (split the budget evenly so every heading survives with an excerpt). Budgets run after the semantic filters, and a truncation object reports per-article and per-section character counts whenever anything was shortenedmaxResponseCharacters bounds the whole response instead of each body: every field of a returned record counts (abstract, references, metadata, body), one ledger across PMC-, Europe PMC-, and Unpaywall-served articles. Articles past the ceiling are deferred whole, with their ids — in the branch they were requested under — in deferred.ids for a follow-up callpubmed_europepmc_searchSearch Europe PMC (EBI/EMBL-EBI), a broader open-access biomedical corpus than PubMed alone.
source: PPR), patents (source: PAT), Agricola (source: AGR), plus everything in PubMed (MED) and PMC (PMC). On recent queries this can mean dozens of relevant hits with zero PubMed overlap.["MED", "PMC", "PPR"]; pass sources to include PAT / AGRcursorMark (unlike pubmed_search_articles, which uses offset) — * for the first page, return nextCursorMark for the nextsource plus optional pmid / pmcId / doi cross-walkingabstractSnippet is capped at 400 characters to keep a page bounded; abstractTruncated says whether it was cut, and pubmed_europepmc_fetch returns the whole abstract for the records worth reading in fullEUROPEPMC_ENABLED=false; tool is not registered in that casepubmed_europepmc_fetchFetch complete Europe PMC records by source + epmcId, the detail counterpart to pubmed_europepmc_search.
source and epmcId of a search hit, the only identifier preprint (PPR), patent (PAT), and Agricola (AGR) records reliably carry — pubmed_fetch_articles needs a PMID and pubmed_fetch_fulltext needs a PMCID, PMID, or DOInotFound instead of failing the batchEUROPEPMC_ENABLED=false; tool is not registered in that casepubmed_format_citationsGenerate formatted citations for articles.
pubmed_find_relatedFind articles related to a source article via ELink.
similar (content similarity), cited_by, referencesall_providers_failed error instead of an empty resultpubmed_spell_checkSpell-check a biomedical query using NCBI's ESpell.
pubmed_lookup_meshSearch and explore the MeSH (Medical Subject Headings) vocabulary.
pubmed_lookup_citationResolve partial bibliographic references to PubMed IDs via NCBI ECitMatch.
matched, not_found, and ambiguous statuses with recovery detailpubmed_convert_idsConvert between article identifiers (DOI, PMID, PMCID) using the PMC ID Converter API.
| Type | Name | Description |
|---|---|---|
| Resource | pubmed://database/info | PubMed database metadata via EInfo (field list, record count, last update) |
| Prompt | research_plan | Generate a structured 4-phase biomedical research plan outline |
Built on @cyanheads/mcp-ts-core:
none, jwt, oauth)in-memory, filesystem, Supabase, Cloudflare KV/R2/D1PubMed-specific:
isArray hints for PubMed's inconsistent XML structureAgent-friendly output:
source: "pmc" | "unpaywall", typed unavailable reasons, viaSource and triedTiers fields — callers branch on data, not string parsingA public instance is available at https://pubmed.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
Add the following to your MCP client configuration file.
Or with npx (no Bun required):
Or with Docker:
For Streamable HTTP, set the transport and start the server:
All configuration is validated at startup via Zod schemas in src/config/server-config.ts. Key environment variables:
| Variable | Description | Default |
|---|---|---|
MCP_TRANSPORT_TYPE | Transport: stdio or http | stdio |
MCP_HTTP_PORT | HTTP server port | 3010 |
MCP_HTTP_ENDPOINT_PATH | HTTP endpoint path where the MCP server is mounted | /mcp |
MCP_SESSION_MODE | HTTP session mode: stateless, stateful, or auto (resolves to stateful). This server ships stateless — it has no ctx.requestInput call sites. | stateless |
MCP_PUBLIC_URL | Public origin override for TLS-terminating reverse-proxy deployments (landing page, Server Card, RFC 9728 metadata). | none |
MCP_AUTH_MODE | Authentication: none, jwt, or oauth | none |
MCP_LOG_LEVEL | Log level (debug, info, warning, error, etc.) | info |
MCP_GC_PRESSURE_INTERVAL_MS | Opt-in Bun-only forced-GC pressure loop (ms). Drains the per-request McpServer/McpSessionTransport cycle under sustained low-traffic HTTP. Recommended starting point if heap growth is observed: 60000. | 0 (disabled) |
LOGS_DIR | Directory for log files (Node.js only). Relative paths resolve against the application root. | <app-root>/logs |
STORAGE_PROVIDER_TYPE | Storage backend: in-memory, filesystem, supabase, cloudflare-kv/r2/d1 | in-memory |
NCBI_API_KEY | NCBI API key for higher rate limits (10 req/s vs 3 req/s) | none |
NCBI_ADMIN_EMAIL | Contact email sent with NCBI requests (recommended by NCBI) | none |
NCBI_REQUEST_DELAY_MS | Minimum gap between NCBI request starts in ms | 334 (100 with key) |
NCBI_MAX_CONCURRENT | Max concurrent in-flight NCBI requests | 8 |
NCBI_MAX_RETRIES | Retry attempts for failed NCBI requests | 6 |
NCBI_TIMEOUT_MS | Per-request HTTP timeout in ms | 30000 |
NCBI_TOTAL_DEADLINE_MS | Total deadline across all retry attempts for one NCBI call, in ms | 60000 |
UNPAYWALL_EMAIL | Contact email for Unpaywall. When set, pubmed_fetch_fulltext falls back to Unpaywall open-access copies for non-PMC DOIs | none |
UNPAYWALL_TIMEOUT_MS | Per-request HTTP timeout for Unpaywall lookups and content fetches, in ms | 20000 |
EUROPEPMC_ENABLED | Enable Europe PMC search tool and the pubmed_fetch_fulltext JATS fallback chain. Set false to disable all EPMC calls and skip tool registration. | true |
EUROPEPMC_EMAIL | Optional contact email sent with Europe PMC requests (EBI courtesy). | none |
EUROPEPMC_REQUEST_DELAY_MS | Minimum gap between Europe PMC request starts in ms | 200 |
EUROPEPMC_MAX_RETRIES | Retry attempts for failed Europe PMC requests | 3 |
EUROPEPMC_TIMEOUT_MS | Per-request HTTP timeout for Europe PMC calls, in ms | 20000 |
OTEL_ENABLED | Enable OpenTelemetry | false |
Build and run the production version:
Run checks and tests:
| Directory | Purpose |
|---|---|
src/mcp-server/tools | Tool definitions (*.tool.ts). Eleven tools across PubMed, PMC, and Europe PMC. |
src/mcp-server/resources | Resource definitions. Database info resource. |
src/mcp-server/prompts | Prompt definitions. Research plan prompt. |
src/services/ncbi | NCBI E-utilities service layer — API client, queue, parser, formatter. |
src/services/europe-pmc | Europe PMC service — search + fullTextXML JATS retrieval. Reuses the NCBI JATS parser. |
src/services/unpaywall | Unpaywall service — DOI → OA location resolution and content fetch (HTML/PDF). |
src/config | Server-specific environment variable parsing and validation with Zod. |
tests/ | Unit and integration tests, mirroring the src/ structure. |
See CLAUDE.md for development guidelines and architectural rules. The short version:
try/catch in tool logicctx.log for logging, ctx.state for storagecreateApp() arraysIssues and pull requests are welcome. Run checks and tests before submitting:
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.