pubmed-mcp-server
Literatura biomédica de PubMed
Documentación
@cyanheads/pubmed-mcp-server
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
Overview
The biomedical literature via NCBI's E-utilities, PubMed Central, and Europe PMC. Search it, fetch metadata and full text, resolve identifiers and partial citations, format references, and ground queries in MeSH vocabulary. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
Tools
| 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 a PubMed query via NCBI ESpell — every misspelled token corrected in one call; the recovery step after a zero-hit or thin search |
pubmed_lookup_mesh | Search MeSH by heading — tree numbers, scope notes, entry terms — for building controlled-vocabulary queries |
pubmed_lookup_citation | Resolve partial bibliographic references — one citation or a batch of up to 25 — to PubMed IDs via ECitMatch |
pubmed_convert_ids | Convert between DOI, PMID, and PMCID using the PMC ID Converter API |
Resources
| Resource | Description |
|---|---|
pubmed://database/info | PubMed database metadata via EInfo (field list, record count, last update) |
Prompts
| Prompt | Description |
|---|---|
research_plan | Generate a structured 4-phase biomedical research plan outline |
Capability reference
pubmed_search_articles tool
- Full PubMed boolean and field-tag syntax, plus structured filters: author, journal, MeSH terms, language, species, publication type, has-abstract, free-full-text
- Date ranges by publication, modification, or Entrez date; sort by relevance, date, author, or journal; offset pagination
- Optional brief summaries for the top N results via ESummary
- NCBI Bookshelf hits carry
bookTitle,publisherName,docType, andeditorsin place of the emptysource; the rendered summary shows the doc type only for these, not for ordinary journal articles (citation) - Reports
totalCount, stated in the header beside the page (Returned: 3 of 2924); echoes the original query, the fully applied PubMed query, and normalized filter metadata - A query with no search term — blank, markup only, a bare field tag like
[pdat], or empty()— is rejected asblank_queryrather than sent upstream limitis accepted formaxResults
pubmed_fetch_articles tool
- Up to 200 PMIDs per call (POST for batches of 100 or more);
idsis accepted forpmids - A zero-padded PMID (
00000001) resolves as the PMID it spells;unavailablePmidslists misses as you sent them - Title, abstract, authors with deduplicated affiliations, journal info, DOI, PubMed/PMC links; optional MeSH terms, grants, and publication types
- Tolerant of PubMed's inconsistent XML — structured abstracts, missing fields, varying date formats
- Bookshelf chapters and books are first-class:
recordType(journal-article/book-chapter/book) plus abookobject (title, publisher, editors, ISBNs, Bookshelf accession);journalInfois absent on them - Article-number journals report
journalInfo.elocationId+elocationIdTyperather than a page range - Opt-in
maxResponseCharacterskeeps whole records in order until the ceiling, then defers the rest todeferred.idsfor a follow-up call
pubmed_fetch_fulltext tool
- Exactly one of
pmcids,pmids, ordois(one id per element), up to 10 per request; a zero-padded PMID or PMC ID resolves as the ID it spells, DOIs match case-insensitively, and PMC IDs match with or without thePMCprefix in any case. Each record is fetched once however many spellings name it;unavailable[].idkeeps your PMID or DOI spelling, and reports a PMC ID asPMC<digits> - Three-tier chain: NCBI PMC EFetch → Europe PMC
fullTextXML(EUROPEPMC_ENABLED, default on) → Unpaywall (needsUNPAYWALL_EMAIL);viaSourcenames which tier served each article - Preprints, patents, and Agricola records have metadata via
pubmed_europepmc_searchbut no full text through this chain — Europe PMC'sfullTextXMLis PMC-keyed source: "pmc"returns structured sections plustables[](cells, caption, label, footnotes) andassets[](figures and supplementary material, with[Figure: <label>]markers left in the body, and the file pointer read through<alternatives>when a figure offers several formats);source: "unpaywall"returns a best-effort body withcontentFormat(html-markdown/pdf-text), atitlefrom Unpaywall's record (else the Europe PMC record, else the HTML page), andjournalName/yearwhen Unpaywall has them- A titled list, definition list, or boxed text that sits in the body outside any section, or is the only content of an untitled section, such as an abbreviations list, becomes a section under its own title, and a figure or table inside it names that section; a section with no title is headed
untitled section, the same name thetruncationledger gives it, and section titles in headings and ledger lines are Markdown-escaped - Unavailable entries carry a typed
reason(not-found,no-doi,doi-lookup-failed,no-oa,service-error, …),idType,triedTiers(per-tier outcome in execution order), andunqueriedTierswhen an unconfigured tier could have served the id - Filters and budgets:
sections(case-insensitive title match),maxSections,includeTables,includeAssets,maxCharacters,maxCharactersPerSection,overflowMode(truncate/outline), andmaxResponseCharacters, which defers whole articles past the ceiling todeferred.ids; atruncationobject reports what was shortened or omitted, per section and subsection - A budget cut ends at a word boundary;
truncatedrops every section and subsection past the cut, whileoutlinekeeps each heading and marks one the budget left empty
pubmed_europepmc_search tool
- Reaches records PubMed can't: preprints (
PPR), patents (PAT), Agricola (AGR), alongsideMEDandPMC; defaultsourcesis["MED", "PMC", "PPR"] - Cursor pagination via
cursorMark—*for the first page, thennextCursorMark;pageSizeup to 100, withmax_resultsandlimitaccepted for it - Hits carry
sourcepluspmid/pmcId/doiwhen known;abstractSnippetis capped at 400 characters, withabstractTruncatedflagging the cut totalCountreports the full hit count, stated in the header beside the page;searchUrlopens the same source-filtered query on europepmc.org- Not registered when
EUROPEPMC_ENABLED=false
pubmed_europepmc_fetch tool
- Full records with the untruncated plain-text abstract, addressed by
source+epmcId— the only identifier preprint, patent, and Agricola records reliably carry - Up to 25 per call in one Europe PMC request; unresolved ids come back in
notFoundrather than failing the batch - Not registered when
EUROPEPMC_ENABLED=false
pubmed_format_citations tool
- APA 7th, MLA 9th, BibTeX, RIS, Vancouver (ICMJE/NLM); several styles per article in one call, up to 50 articles
- Bookshelf chapters and books cite in each style's edited-book form; articles without a page range cite by electronic locator in each style's convention
- Hand-rolled formatters — zero dependencies, Workers-compatible
- Reports formatted counts and unavailable PMIDs;
idsis accepted forpmids, and a zero-padded PMID resolves as the PMID it spells
pubmed_find_related tool
similar,cited_by, orreferencesfor a PMID, in NCBI relevance order, enriched with title, authors, date, and source (or Bookshelf book title and publisher)- Falls back to Europe PMC, then OpenAlex, when NCBI can't answer; the response names the provider. Fails with a typed
all_providers_failederror rather than an empty result maxResultsup to 50 (limitalso accepted) with offset pagination;totalCountreports the full match count, stated in the header beside the page- A zero-padded source PMID resolves as the PMID it spells and is never listed among its own related articles
pubmed_spell_check tool
- Runs a PubMed query through NCBI ESpell and returns
original,corrected, andhasSuggestion; every misspelled token is corrected in one call (alzhiemer diseese treatmnt outcomse→alzheimer disease treatment outcomes) - Reach for it after a zero-hit or thin
pubmed_search_articlesresult, or when a drug, gene, disease, or author name may be misspelled, then re-run the search withcorrected - A blank or whitespace-only query is rejected rather than sent upstream
pubmed_lookup_mesh tool
- Looks up MeSH descriptors by name or free-text term, pinning the exact-heading match to the top of the first page
- Records carry
meshId(DescriptorUI),entrezUid, and, withincludeDetails(default on), tree numbers, scope notes, and entry terms maxResultsup to 50 (limitalso accepted) with offset pagination vianextOffset;totalCountreports the upstream match count
pubmed_lookup_citation tool
- Match on journal, year, volume, first page, and/or author — journal or year required, more fields for better precision
citationstakes an array of up to 25 or a single citation object;citationis accepted for it- Pipes and line breaks are rejected at the schema (ECitMatch's wire format is pipe-delimited); the free-form
keylabel is exempt - Explicit
matched,not_found, andambiguousstatuses with recovery detail
pubmed_convert_ids tool
- Up to 50 DOIs, PMIDs, or PMCIDs per call, all one type; only PMC-indexed articles resolve
- One id per element — a packed
"23193287,37952131"is rejected rather than expanded - One success/error row per submitted element, in order, with
requestedIdexactly as sent — repeats, a bare-digit PMCID, and a DOI's casing included; a partial batch never fails as a whole - A zero-padded PMID resolves as the PMID it spells
pubmed://database/info resource
- Live EInfo call for the
pubmeddatabase, returned asapplication/json dbName,description,count,lastUpdate, andfields[]— each field's shortname(the tag usable inpubmed_search_articlesqueries),fullName, anddescription- No parameters
research_plan prompt
- Arguments:
title,goal,keywords(comma-separated) required;organismandincludeAgentPrompts("true"/"false") optional - Returns two messages: an assistant framing message (biomedical research planning assistant, grounds recommendations in the PubMed tools when available) and a user message carrying the plan
- The plan walks four phases — Conception & Planning, Data Collection & Processing, Analysis & Interpretation, Dissemination — with sub-steps under each
includeAgentPrompts: "true"adds an agent-guidance block under each sub-step, several of which point atpubmed_search_articlesandpubmed_lookup_mesh
Features
Built on @cyanheads/mcp-ts-core: stdio and Streamable HTTP transports, pluggable auth (none / jwt / oauth), swappable storage (in-memory, filesystem, Supabase, Cloudflare KV/R2/D1), structured logging with optional OpenTelemetry tracing.
PubMed-specific:
- Complete NCBI E-utilities integration (ESearch, EFetch, ESummary, ELink, ESpell, EInfo, ECitMatch) plus PMC ID Converter
- Shared NCBI request queue — paced request starts, capped concurrency, a cooldown that holds every caller after an NCBI 429, and one deadline covering queue wait and retries
- NCBI-specific XML parser with
isArrayhints for PubMed's inconsistent XML structure - Hand-rolled citation formatters (APA, MLA, BibTeX, RIS, Vancouver) — zero deps, Workers-compatible
Agent-friendly output:
- Provenance on every response — source labels, license fields, best-effort warnings on Unpaywall results, and effective-query echo on searches so agents can reason about trust
- Graceful partial failure — batch tools return per-item success/error rows instead of failing the request, with structured status codes and actionable next-step text
- Discriminated output contracts —
source: "pmc" | "unpaywall", typedunavailablereasons,viaSourceandtriedTiersfields — callers branch on data, not string parsing
Getting started
Public Hosted Instance
A public instance is available at https://pubmed.caseyjhand.com/mcp — no installation required. Point any MCP client at it via Streamable HTTP:
{
"mcpServers": {
"pubmed-mcp-server": {
"type": "streamable-http",
"url": "https://pubmed.caseyjhand.com/mcp"
}
}
}
Self-Hosted / Local
Add the following to your MCP client configuration file.
{
"mcpServers": {
"pubmed-mcp-server": {
"type": "stdio",
"command": "bunx",
"args": ["@cyanheads/pubmed-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"NCBI_API_KEY": "your-key-here"
}
}
}
}
Or with npx (no Bun required):
{
"mcpServers": {
"pubmed-mcp-server": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@cyanheads/pubmed-mcp-server@latest"],
"env": {
"MCP_TRANSPORT_TYPE": "stdio",
"MCP_LOG_LEVEL": "info",
"NCBI_API_KEY": "your-key-here"
}
}
}
}
Or with Docker:
{
"mcpServers": {
"pubmed-mcp-server": {
"type": "stdio",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "MCP_TRANSPORT_TYPE=stdio", "ghcr.io/cyanheads/pubmed-mcp-server:latest"]
}
}
}
For Streamable HTTP, set the transport and start the server:
MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
# Server listens at http://localhost:3010/mcp
Prerequisites
- Bun v1.4.0 or higher.
- Optional: NCBI API key for higher rate limits (10 req/s vs 3 req/s).
Installation
- Clone the repository:
git clone https://github.com/cyanheads/pubmed-mcp-server.git
- Navigate into the directory:
cd pubmed-mcp-server
- Install dependencies:
bun install
Configuration
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 | 400 (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 for one NCBI call — queue wait, retry attempts, and backoff — in ms. A call the queue cannot start before it is rejected at once | 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 |
Running the server
Local development
-
Build and run the production version:
# One-time build bun run rebuild # Run the built server bun run start:http # or bun run start:stdio -
Run checks and tests:
bun run devcheck # Lints, formats, type-checks, and more bun run test # Runs the test suite
Project structure
| 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. |
Development guide
See CLAUDE.md for development guidelines and architectural rules. The short version:
- Handlers throw, framework catches — no
try/catchin tool logic - Use
ctx.logfor logging,ctx.statefor storage - Register new tools and resources in the
createApp()arrays
Contributing
Issues are welcome. Run checks and tests before submitting:
bun run devcheck
bun run test
License
This project is licensed under the Apache 2.0 License. See the LICENSE file for details.