.codex-plugin — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited .codex-plugin (MCP Server) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
<div align="center"> <h1>@cyanheads/pubmed-mcp-server</h1> <p><b>Search PubMed/Europe PMC, fetch articles and full text (PMC/EPMC/Unpaywall), citations, MeSH terms via MCP. STDIO or Streamable HTTP.</b> <div>10 Tools • 1 Resource • 1 Prompt</div> </p> </div>
<div align="center">
</div>
<div align="center">
</div>
<div align="center">
Public Hosted Server: https://pubmed.caseyjhand.com/mcp
</div>
10 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_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.
pubmed_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-doi, no-oa, fetch-failed, parse-failed, service-error) so callers can retry or explain to users without parsing textunavailable 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 outputpubmed_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-walkingEUROPEPMC_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, referencespubmed_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:
{
"mcpServers": {
"pubmed-mcp-server": {
"type": "streamable-http",
"url": "https://pubmed.caseyjhand.com/mcp"
}
}
}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/mcpgit clone https://github.com/cyanheads/pubmed-mcp-server.gitcd pubmed-mcp-serverbun installAll 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_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). | <project-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 |
# One-time build
bun run rebuild
# Run the built server
bun run start:http
# or
bun run start:stdio bun run devcheck # Lints, formats, type-checks, and more
bun run test # Runs the test suite| Directory | Purpose |
|---|---|
src/mcp-server/tools | Tool definitions (*.tool.ts). Ten 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:
bun run devcheck
bun run testThis project is licensed under the Apache 2.0 License. See the LICENSE file for details.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.