{
  "markdown": "<div align=\"center\">\n  <h1>@cyanheads/pubmed-mcp-server</h1>\n  <p><b>Search PubMed/Europe PMC, fetch articles and full text (PMC/EPMC/Unpaywall), citations, MeSH terms via MCP. STDIO or Streamable HTTP.</b>\n  <div>11 Tools • 1 Resource • 1 Prompt</div>\n  </p>\n</div>\n\n<div align=\"center\">\n\n\n\n[![Version](https://img.shields.io/badge/Version-2.10.5-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/pubmed-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/pubmed-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/pubmed-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)\n\n</div>\n\n<div align=\"center\">\n\n[![Install in Claude Desktop](https://img.shields.io/badge/Install_in-Claude_Desktop-D97757?style=for-the-badge&logo=anthropic&logoColor=white)](https://github.com/cyanheads/pubmed-mcp-server/releases/latest/download/pubmed-mcp-server.mcpb) [![Install in Cursor](https://cursor.com/deeplink/mcp-install-dark.svg)](https://cursor.com/en/install-mcp?name=pubmed-mcp-server&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIkBjeWFuaGVhZHMvcHVibWVkLW1jcC1zZXJ2ZXIiXX0=) [![Install in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=for-the-badge&logo=visualstudiocode&logoColor=white)](https://vscode.dev/redirect?url=vscode:mcp/install?%7B%22name%22%3A%22pubmed-mcp-server%22%2C%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22-y%22%2C%22%40cyanheads%2Fpubmed-mcp-server%22%5D%7D)\n\n[![Framework](https://img.shields.io/badge/Built%20on-@cyanheads/mcp--ts--core-67E8F9?style=flat-square)](https://www.npmjs.com/package/@cyanheads/mcp-ts-core)\n\n</div>\n\n<div align=\"center\">\n\n**Public Hosted Server:** [https://pubmed.caseyjhand.com/mcp](https://pubmed.caseyjhand.com/mcp)\n\n</div>\n\n---\n\n## Tools\n\n11 tools for working with PubMed, PubMed Central, and Europe PMC data:\n\n| Tool | Description |\n|:---|:---|\n| `pubmed_search_articles` | Search PubMed with full query syntax, field-specific filters, date ranges, pagination, and optional brief summaries |\n| `pubmed_europepmc_search` | Search Europe PMC for preprints, patents, Agricola, and EPMC-only OA records that don't surface in PubMed. Cursor-based pagination. |\n| `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 |\n| `pubmed_fetch_articles` | Fetch full article metadata by PMIDs — abstract, authors, journal, MeSH terms, grants |\n| `pubmed_fetch_fulltext` | Fetch full-text articles via a chain: NCBI PMC EFetch → Europe PMC `fullTextXML` → Unpaywall. Accepts PMIDs, PMCIDs, or DOIs. |\n| `pubmed_format_citations` | Generate formatted citations in APA 7th, MLA 9th, BibTeX, RIS, or Vancouver (ICMJE/NLM) |\n| `pubmed_find_related` | Find similar articles, citing articles, or references for a given PMID |\n| `pubmed_spell_check` | Spell-check biomedical queries using NCBI's ESpell service |\n| `pubmed_lookup_mesh` | Search and explore MeSH vocabulary — tree numbers, scope notes, entry terms |\n| `pubmed_lookup_citation` | Resolve partial bibliographic references to PubMed IDs via ECitMatch |\n| `pubmed_convert_ids` | Convert between DOI, PMID, and PMCID using the PMC ID Converter API |\n\n### `pubmed_search_articles`\n\nSearch PubMed with full NCBI query syntax and filters.\n\n- Free-text queries with PubMed's full boolean and field-tag syntax\n- Field-specific filters: author, journal, MeSH terms, language, species\n- Common filters: has abstract, free full text\n- Date range filtering by publication, modification, or Entrez date\n- Publication type filtering (Review, Clinical Trial, Meta-Analysis, etc.)\n- Sort by relevance, publication date, author, or journal\n- Pagination via offset for paging through large result sets\n- Optional brief summaries for top N results via ESummary\n- Returns the original query plus the fully applied PubMed query and normalized filter metadata\n\n---\n\n### `pubmed_fetch_articles`\n\nFetch full article metadata by PubMed IDs.\n\n- Batch fetch up to 200 articles at once (auto-switches to POST for batches >= 100)\n- Returns structured data: title, abstract, authors with deduplicated affiliations, journal info, DOI\n- Direct links to PubMed and PubMed Central (when available)\n- Optional MeSH terms, grant information, and publication types\n- Handles PubMed's inconsistent XML (structured abstracts, missing fields, varying date formats)\n\n---\n\n### `pubmed_fetch_fulltext`\n\nFetch full-text articles via a three-stage chain: NCBI PMC EFetch → Europe PMC `fullTextXML` → Unpaywall.\n\n- Accepts exactly one of `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)\n- NCBI PMC and Europe PMC both return structured JATS; output records origin via `viaSource: \"pmc\" | \"europepmc\" | \"unpaywall\"`\n- Europe PMC layer (enabled by default; disable with `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.\n- Unpaywall layer (enabled by setting `UNPAYWALL_EMAIL`) resolves DOIs to legal OA copies; extracts HTML landing pages to Markdown via Defuddle or PDFs to text via unpdf\n- Discriminated output contract — `source: \"pmc\"` (structured sections, regardless of whether it came from PMC or EPMC) or `source: \"unpaywall\"` (best-effort body + `contentFormat`: `html-markdown` or `pdf-text`)\n- Structured unavailable reasons (`not-found`, `no-pmc-fallback-disabled`, `no-epmc-fulltext`, `no-doi`, `no-oa`, `fetch-failed`, `parse-failed`, `service-error`) so callers can retry or explain to users without parsing text\n- Each `unavailable` 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\n- Section filtering by title (case-insensitive match, e.g. `[\"methods\", \"results\"]`) and configurable max sections apply to PMC output\n- Character budgets keep context size predictable: `maxCharacters` 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 shortened\n- Up to 10 articles per request\n\n---\n\n### `pubmed_europepmc_search`\n\nSearch Europe PMC (EBI/EMBL-EBI), a broader open-access biomedical corpus than PubMed alone.\n\n- Surfaces records PubMed search can't reach — preprints (`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.\n- Default sources `[\"MED\", \"PMC\", \"PPR\"]`; pass `sources` to include `PAT` / `AGR`\n- Cursor-based pagination via `cursorMark` (unlike `pubmed_search_articles`, which uses offset) — `*` for the first page, return `nextCursorMark` for the next\n- Output discriminator on `source` plus optional `pmid` / `pmcId` / `doi` cross-walking\n- `abstractSnippet` 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 full\n- Disabled when `EUROPEPMC_ENABLED=false`; tool is not registered in that case\n\n---\n\n### `pubmed_europepmc_fetch`\n\nFetch complete Europe PMC records by `source` + `epmcId`, the detail counterpart to `pubmed_europepmc_search`.\n\n- Returns the full, untruncated abstract as display-ready plain text — markup stripped, HTML entities decoded\n- Addressed by the `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 DOI\n- Up to 25 records per call, resolved in a single Europe PMC request\n- Pairs unresolved requests back to the caller in `notFound` instead of failing the batch\n- Disabled when `EUROPEPMC_ENABLED=false`; tool is not registered in that case\n\n---\n\n### `pubmed_format_citations`\n\nGenerate formatted citations for articles.\n\n- Five citation styles: APA 7th, MLA 9th, BibTeX, RIS, Vancouver (ICMJE/NLM)\n- Request multiple styles per article in a single call\n- Hand-rolled formatters — zero external dependencies, fully Workers-compatible\n- Up to 50 articles per request\n- Reports formatted counts and unavailable PMIDs for partial-result handling\n\n---\n\n### `pubmed_find_related`\n\nFind articles related to a source article via ELink.\n\n- Three relationship types: `similar` (content similarity), `cited_by`, `references`\n- Results enriched with title, authors, publication date, and source via ESummary\n- Results returned in NCBI's relevance order\n\n---\n\n### `pubmed_spell_check`\n\nSpell-check a biomedical query using NCBI's ESpell.\n\n- Returns the original query, corrected query, and whether a suggestion was found\n- Useful for query refinement before searching\n\n---\n\n### `pubmed_lookup_mesh`\n\nSearch and explore the MeSH (Medical Subject Headings) vocabulary.\n\n- Search MeSH terms by name with exact-heading matching\n- Detailed records with tree numbers, scope notes, and entry terms by default\n- Useful for building precise PubMed queries with controlled vocabulary\n\n---\n\n### `pubmed_lookup_citation`\n\nResolve partial bibliographic references to PubMed IDs via NCBI ECitMatch.\n\n- Match citations by journal, year, volume, first page, and/or author name\n- More fields = better match accuracy; at least one field required\n- Batch up to 25 citations per request\n- Deterministic matching — more reliable than free-text search for known references\n- Returns explicit `matched`, `not_found`, and `ambiguous` statuses with recovery detail\n\n---\n\n### `pubmed_convert_ids`\n\nConvert between article identifiers (DOI, PMID, PMCID) using the PMC ID Converter API.\n\n- Batch up to 50 IDs per request\n- Accepts DOIs, PMIDs, or PMCIDs (all IDs must be the same type)\n- Only resolves articles indexed in PubMed Central\n- Per-ID success/error reporting — partial batches return resolved mappings alongside structured errors for unresolvable IDs, not a batch-level failure\n\n## Resource and prompt\n\n| Type | Name | Description |\n|:---|:---|:---|\n| Resource | `pubmed://database/info` | PubMed database metadata via EInfo (field list, record count, last update) |\n| Prompt | `research_plan` | Generate a structured 4-phase biomedical research plan outline |\n\n## Features\n\nBuilt on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core):\n\n- Declarative tool definitions — single file per tool, framework handles registration and validation\n- Unified error handling across all tools\n- Pluggable auth (`none`, `jwt`, `oauth`)\n- Swappable storage backends: `in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`\n- Structured logging with optional OpenTelemetry tracing\n- Runs locally (stdio/HTTP) or on Cloudflare Workers from the same codebase\n\nPubMed-specific:\n\n- Complete NCBI E-utilities integration (ESearch, EFetch, ESummary, ELink, ESpell, EInfo, ECitMatch) plus PMC ID Converter\n- Sequential request queue with configurable delay for NCBI rate limit compliance\n- NCBI-specific XML parser with `isArray` hints for PubMed's inconsistent XML structure\n- Hand-rolled citation formatters (APA, MLA, BibTeX, RIS, Vancouver) — zero deps, Workers-compatible\n\nAgent-friendly output:\n\n- 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\n- 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\n- Discriminated output contracts — `source: \"pmc\" | \"unpaywall\"`, typed `unavailable` reasons, `viaSource` and `triedTiers` fields — callers branch on data, not string parsing\n\n## Getting started\n\n### Public Hosted Instance\n\nA public instance is available at `https://pubmed.caseyjhand.com/mcp` — no installation required. Point any MCP client at it via Streamable HTTP:\n\n```json\n{\n  \"mcpServers\": {\n    \"pubmed-mcp-server\": {\n      \"type\": \"streamable-http\",\n      \"url\": \"https://pubmed.caseyjhand.com/mcp\"\n    }\n  }\n}\n```\n\n### Self-Hosted / Local\n\nAdd the following to your MCP client configuration file.\n\n```json\n{\n  \"mcpServers\": {\n    \"pubmed-mcp-server\": {\n      \"type\": \"stdio\",\n      \"command\": \"bunx\",\n      \"args\": [\"@cyanheads/pubmed-mcp-server@latest\"],\n      \"env\": {\n        \"MCP_TRANSPORT_TYPE\": \"stdio\",\n        \"MCP_LOG_LEVEL\": \"info\",\n        \"NCBI_API_KEY\": \"your-key-here\"\n      }\n    }\n  }\n}\n```\n\nOr with npx (no Bun required):\n\n```json\n{\n  \"mcpServers\": {\n    \"pubmed-mcp-server\": {\n      \"type\": \"stdio\",\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@cyanheads/pubmed-mcp-server@latest\"],\n      \"env\": {\n        \"MCP_TRANSPORT_TYPE\": \"stdio\",\n        \"MCP_LOG_LEVEL\": \"info\",\n        \"NCBI_API_KEY\": \"your-key-here\"\n      }\n    }\n  }\n}\n```\n\nOr with Docker:\n\n```json\n{\n  \"mcpServers\": {\n    \"pubmed-mcp-server\": {\n      \"type\": \"stdio\",\n      \"command\": \"docker\",\n      \"args\": [\"run\", \"-i\", \"--rm\", \"-e\", \"MCP_TRANSPORT_TYPE=stdio\", \"ghcr.io/cyanheads/pubmed-mcp-server:latest\"]\n    }\n  }\n}\n```\n\nFor Streamable HTTP, set the transport and start the server:\n\n```sh\nMCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http\n# Server listens at http://localhost:3010/mcp\n```\n\n### Prerequisites\n\n- [Bun v1.3.2](https://bun.sh/) or higher.\n- Optional: [NCBI API key](https://www.ncbi.nlm.nih.gov/account/settings/) for higher rate limits (10 req/s vs 3 req/s).\n\n### Installation\n\n1. **Clone the repository:**\n\n```sh\ngit clone https://github.com/cyanheads/pubmed-mcp-server.git\n```\n\n2. **Navigate into the directory:**\n\n```sh\ncd pubmed-mcp-server\n```\n\n3. **Install dependencies:**\n\n```sh\nbun install\n```\n\n## Configuration\n\nAll configuration is validated at startup via Zod schemas in `src/config/server-config.ts`. Key environment variables:\n\n| Variable | Description | Default |\n|:---|:---|:---|\n| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http` | `stdio` |\n| `MCP_HTTP_PORT` | HTTP server port | `3010` |\n| `MCP_HTTP_ENDPOINT_PATH` | HTTP endpoint path where the MCP server is mounted | `/mcp` |\n| `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` |\n| `MCP_PUBLIC_URL` | Public origin override for TLS-terminating reverse-proxy deployments (landing page, Server Card, RFC 9728 metadata). | none |\n| `MCP_AUTH_MODE` | Authentication: `none`, `jwt`, or `oauth` | `none` |\n| `MCP_LOG_LEVEL` | Log level (`debug`, `info`, `warning`, `error`, etc.) | `info` |\n| `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) |\n| `LOGS_DIR` | Directory for log files (Node.js only). Relative paths resolve against the application root. | `<app-root>/logs` |\n| `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv/r2/d1` | `in-memory` |\n| `NCBI_API_KEY` | NCBI API key for higher rate limits (10 req/s vs 3 req/s) | none |\n| `NCBI_ADMIN_EMAIL` | Contact email sent with NCBI requests (recommended by NCBI) | none |\n| `NCBI_REQUEST_DELAY_MS` | Minimum gap between NCBI request starts in ms | 334 (100 with key) |\n| `NCBI_MAX_CONCURRENT` | Max concurrent in-flight NCBI requests | `8` |\n| `NCBI_MAX_RETRIES` | Retry attempts for failed NCBI requests | 6 |\n| `NCBI_TIMEOUT_MS` | Per-request HTTP timeout in ms | `30000` |\n| `NCBI_TOTAL_DEADLINE_MS` | Total deadline across all retry attempts for one NCBI call, in ms | `60000` |\n| `UNPAYWALL_EMAIL` | Contact email for Unpaywall. When set, `pubmed_fetch_fulltext` falls back to Unpaywall open-access copies for non-PMC DOIs | none |\n| `UNPAYWALL_TIMEOUT_MS` | Per-request HTTP timeout for Unpaywall lookups and content fetches, in ms | `20000` |\n| `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` |\n| `EUROPEPMC_EMAIL` | Optional contact email sent with Europe PMC requests (EBI courtesy). | none |\n| `EUROPEPMC_REQUEST_DELAY_MS` | Minimum gap between Europe PMC request starts in ms | `200` |\n| `EUROPEPMC_MAX_RETRIES` | Retry attempts for failed Europe PMC requests | `3` |\n| `EUROPEPMC_TIMEOUT_MS` | Per-request HTTP timeout for Europe PMC calls, in ms | `20000` |\n| `OTEL_ENABLED` | Enable OpenTelemetry | `false` |\n\n## Running the server\n\n### Local development\n\n- **Build and run the production version**:\n\n  ```sh\n  # One-time build\n  bun run rebuild\n\n  # Run the built server\n  bun run start:http\n  # or\n  bun run start:stdio\n  ```\n\n- **Run checks and tests**:\n  ```sh\n  bun run devcheck  # Lints, formats, type-checks, and more\n  bun run test      # Runs the test suite\n  ```\n\n## Project structure\n\n| Directory | Purpose |\n|:---|:---|\n| `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). Eleven tools across PubMed, PMC, and Europe PMC. |\n| `src/mcp-server/resources` | Resource definitions. Database info resource. |\n| `src/mcp-server/prompts` | Prompt definitions. Research plan prompt. |\n| `src/services/ncbi` | NCBI E-utilities service layer — API client, queue, parser, formatter. |\n| `src/services/europe-pmc` | Europe PMC service — search + `fullTextXML` JATS retrieval. Reuses the NCBI JATS parser. |\n| `src/services/unpaywall` | Unpaywall service — DOI → OA location resolution and content fetch (HTML/PDF). |\n| `src/config` | Server-specific environment variable parsing and validation with Zod. |\n| `tests/` | Unit and integration tests, mirroring the `src/` structure. |\n\n## Development guide\n\nSee [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rules. The short version:\n\n- Handlers throw, framework catches — no `try/catch` in tool logic\n- Use `ctx.log` for logging, `ctx.state` for storage\n- Register new tools and resources in the `createApp()` arrays\n\n## Contributing\n\nIssues and pull requests are welcome. Run checks and tests before submitting:\n\n```sh\nbun run devcheck\nbun run test\n```\n\n## License\n\nThis project is licensed under the Apache 2.0 License. See the [LICENSE](./LICENSE) file for details.\n",
  "bytes": 19575,
  "sha": "8032c3c82bf92efbbede95496f50f988f801dd1159451a561546a42545dc044c",
  "repo_slug": "cyanheads/pubmed-mcp-server",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_cyanheads_pubmed_mcp_server_610e8145/readme"
}