@cyanheads/pubmed-mcp-server 2.10.12 → 2.10.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +39 -20
- package/CLAUDE.md +39 -20
- package/README.md +89 -121
- package/changelog/2.0.x/2.0.0.md +32 -0
- package/changelog/2.0.x/2.0.1.md +32 -0
- package/changelog/2.1.x/2.1.0.md +17 -0
- package/changelog/2.1.x/2.1.1.md +29 -0
- package/changelog/2.1.x/2.1.2.md +18 -0
- package/changelog/2.1.x/2.1.3.md +10 -0
- package/changelog/2.1.x/2.1.4.md +12 -0
- package/changelog/2.1.x/2.1.5.md +18 -0
- package/changelog/2.1.x/2.1.6.md +15 -0
- package/changelog/2.10.x/2.10.0.md +17 -0
- package/changelog/2.10.x/2.10.1.md +14 -0
- package/changelog/2.10.x/2.10.10.md +22 -0
- package/changelog/2.10.x/2.10.11.md +21 -0
- package/changelog/2.10.x/2.10.12.md +14 -0
- package/changelog/2.10.x/2.10.13.md +28 -0
- package/changelog/2.10.x/2.10.14.md +28 -0
- package/changelog/2.10.x/2.10.2.md +13 -0
- package/changelog/2.10.x/2.10.3.md +26 -0
- package/changelog/2.10.x/2.10.4.md +11 -0
- package/changelog/2.10.x/2.10.5.md +31 -0
- package/changelog/2.10.x/2.10.6.md +30 -0
- package/changelog/2.10.x/2.10.7.md +17 -0
- package/changelog/2.10.x/2.10.8.md +22 -0
- package/changelog/2.10.x/2.10.9.md +15 -0
- package/changelog/2.2.x/2.2.0.md +67 -0
- package/changelog/2.2.x/2.2.1.md +10 -0
- package/changelog/2.2.x/2.2.2.md +20 -0
- package/changelog/2.2.x/2.2.3.md +17 -0
- package/changelog/2.2.x/2.2.4.md +34 -0
- package/changelog/2.2.x/2.2.5.md +10 -0
- package/changelog/2.2.x/2.2.6.md +17 -0
- package/changelog/2.3.x/2.3.0.md +27 -0
- package/changelog/2.3.x/2.3.1.md +15 -0
- package/changelog/2.3.x/2.3.10.md +20 -0
- package/changelog/2.3.x/2.3.11.md +21 -0
- package/changelog/2.3.x/2.3.2.md +27 -0
- package/changelog/2.3.x/2.3.3.md +38 -0
- package/changelog/2.3.x/2.3.4.md +21 -0
- package/changelog/2.3.x/2.3.5.md +24 -0
- package/changelog/2.3.x/2.3.6.md +26 -0
- package/changelog/2.3.x/2.3.7.md +31 -0
- package/changelog/2.3.x/2.3.8.md +19 -0
- package/changelog/2.3.x/2.3.9.md +22 -0
- package/changelog/2.4.x/2.4.0.md +34 -0
- package/changelog/2.4.x/2.4.1.md +32 -0
- package/changelog/2.5.x/2.5.0.md +35 -0
- package/changelog/2.5.x/2.5.1.md +32 -0
- package/changelog/2.5.x/2.5.2.md +23 -0
- package/changelog/2.5.x/2.5.3.md +22 -0
- package/changelog/2.5.x/2.5.5.md +52 -0
- package/changelog/2.5.x/2.5.6.md +33 -0
- package/changelog/2.6.x/2.6.0.md +32 -0
- package/changelog/2.6.x/2.6.1.md +26 -0
- package/changelog/2.6.x/2.6.10.md +16 -0
- package/changelog/2.6.x/2.6.11.md +24 -0
- package/changelog/2.6.x/2.6.12.md +29 -0
- package/changelog/2.6.x/2.6.2.md +23 -0
- package/changelog/2.6.x/2.6.3.md +17 -0
- package/changelog/2.6.x/2.6.4.md +21 -0
- package/changelog/2.6.x/2.6.5.md +30 -0
- package/changelog/2.6.x/2.6.6.md +25 -0
- package/changelog/2.6.x/2.6.7.md +37 -0
- package/changelog/2.6.x/2.6.8.md +15 -0
- package/changelog/2.6.x/2.6.9.md +36 -0
- package/changelog/2.7.x/2.7.0.md +41 -0
- package/changelog/2.7.x/2.7.1.md +21 -0
- package/changelog/2.7.x/2.7.10.md +13 -0
- package/changelog/2.7.x/2.7.11.md +15 -0
- package/changelog/2.7.x/2.7.2.md +22 -0
- package/changelog/2.7.x/2.7.3.md +18 -0
- package/changelog/2.7.x/2.7.4.md +15 -0
- package/changelog/2.7.x/2.7.5.md +34 -0
- package/changelog/2.7.x/2.7.6.md +14 -0
- package/changelog/2.7.x/2.7.7.md +14 -0
- package/changelog/2.7.x/2.7.8.md +18 -0
- package/changelog/2.7.x/2.7.9.md +16 -0
- package/changelog/2.8.x/2.8.0.md +23 -0
- package/changelog/2.9.x/2.9.0.md +21 -0
- package/changelog/2.9.x/2.9.1.md +12 -0
- package/changelog/2.9.x/2.9.10.md +15 -0
- package/changelog/2.9.x/2.9.2.md +21 -0
- package/changelog/2.9.x/2.9.3.md +11 -0
- package/changelog/2.9.x/2.9.4.md +24 -0
- package/changelog/2.9.x/2.9.5.md +20 -0
- package/changelog/2.9.x/2.9.6.md +22 -0
- package/changelog/2.9.x/2.9.7.md +26 -0
- package/changelog/2.9.x/2.9.8.md +15 -0
- package/changelog/2.9.x/2.9.9.md +35 -0
- package/changelog/template.md +151 -0
- package/dist/config/server-config.d.ts +4 -4
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +9 -24
- package/dist/config/server-config.js.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/services/europe-pmc/europe-pmc-service.d.ts.map +1 -1
- package/dist/services/europe-pmc/europe-pmc-service.js +3 -10
- package/dist/services/europe-pmc/europe-pmc-service.js.map +1 -1
- package/dist/services/ncbi/ncbi-service.d.ts +0 -2
- package/dist/services/ncbi/ncbi-service.d.ts.map +1 -1
- package/dist/services/ncbi/ncbi-service.js +9 -13
- package/dist/services/ncbi/ncbi-service.js.map +1 -1
- package/dist/services/ncbi/response-handler.d.ts +9 -0
- package/dist/services/ncbi/response-handler.d.ts.map +1 -1
- package/dist/services/ncbi/response-handler.js +33 -1
- package/dist/services/ncbi/response-handler.js.map +1 -1
- package/dist/services/openalex/openalex-service.d.ts.map +1 -1
- package/dist/services/openalex/openalex-service.js +3 -10
- package/dist/services/openalex/openalex-service.js.map +1 -1
- package/package.json +23 -12
- package/server.json +3 -3
- package/dist/services/retry-policy.d.ts +0 -18
- package/dist/services/retry-policy.d.ts.map +0 -1
- package/dist/services/retry-policy.js +0 -21
- package/dist/services/retry-policy.js.map +0 -1
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/pubmed-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/pubmed-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
@@ -29,9 +29,11 @@
|
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
-
##
|
|
32
|
+
## Overview
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
### Tools
|
|
35
37
|
|
|
36
38
|
| Tool | Description |
|
|
37
39
|
|:---|:---|
|
|
@@ -42,173 +44,139 @@
|
|
|
42
44
|
| `pubmed_fetch_fulltext` | Fetch full-text articles via a chain: NCBI PMC EFetch → Europe PMC `fullTextXML` → Unpaywall. Accepts PMIDs, PMCIDs, or DOIs. |
|
|
43
45
|
| `pubmed_format_citations` | Generate formatted citations in APA 7th, MLA 9th, BibTeX, RIS, or Vancouver (ICMJE/NLM) |
|
|
44
46
|
| `pubmed_find_related` | Find similar articles, citing articles, or references for a given PMID |
|
|
45
|
-
| `pubmed_spell_check` | Spell-check biomedical
|
|
46
|
-
| `pubmed_lookup_mesh` | Search
|
|
47
|
+
| `pubmed_spell_check` | Spell-check a biomedical query via NCBI ESpell — returns the corrected query and whether a suggestion was found |
|
|
48
|
+
| `pubmed_lookup_mesh` | Search MeSH by heading — tree numbers, scope notes, entry terms — for building controlled-vocabulary queries |
|
|
47
49
|
| `pubmed_lookup_citation` | Resolve partial bibliographic references to PubMed IDs via ECitMatch |
|
|
48
50
|
| `pubmed_convert_ids` | Convert between DOI, PMID, and PMCID using the PMC ID Converter API |
|
|
49
51
|
|
|
50
|
-
###
|
|
52
|
+
### Resources
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
| Resource | Description |
|
|
55
|
+
|:---|:---|
|
|
56
|
+
| `pubmed://database/info` | PubMed database metadata via EInfo (field list, record count, last update) |
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
- Field-specific filters: author, journal, MeSH terms, language, species
|
|
56
|
-
- Common filters: has abstract, free full text
|
|
57
|
-
- Date range filtering by publication, modification, or Entrez date
|
|
58
|
-
- Publication type filtering (Review, Clinical Trial, Meta-Analysis, etc.)
|
|
59
|
-
- Sort by relevance, publication date, author, or journal
|
|
60
|
-
- Pagination via offset for paging through large result sets
|
|
61
|
-
- Optional brief summaries for top N results via ESummary
|
|
62
|
-
- NCBI Bookshelf results carry their own venue — `bookTitle`, `publisherName`, and `docType` (`chapter`, `book`, or `citation`) — because PubMed leaves `source` empty on them; the book's editors are reported in `editors`, apart from the chapter's own authors
|
|
63
|
-
- Returns the original query plus the fully applied PubMed query and normalized filter metadata
|
|
58
|
+
### Prompts
|
|
64
59
|
|
|
65
|
-
|
|
60
|
+
| Prompt | Description |
|
|
61
|
+
|:---|:---|
|
|
62
|
+
| `research_plan` | Generate a structured 4-phase biomedical research plan outline |
|
|
66
63
|
|
|
67
|
-
|
|
64
|
+
## Capability reference
|
|
68
65
|
|
|
69
|
-
|
|
66
|
+
### `pubmed_search_articles` <sub>tool</sub>
|
|
70
67
|
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
- NCBI Bookshelf chapters and whole books are returned as first-class records, not reported unavailable: `recordType` (`journal-article`, `book-chapter`, `book`) tells them apart, and a `book` object carries the book title, publisher, place, dates, medium, edition, series, ISBNs, book DOI, editors, and Bookshelf accession. `journalInfo` is absent on those records — a book title is never reported as a journal
|
|
77
|
-
- Journals that assign article numbers instead of page ranges often carry no pagination at all; the number is reported as `journalInfo.elocationId` with its `journalInfo.elocationIdType` (`pii`), never merged into `journalInfo.pages` and never confused with the DOI
|
|
78
|
-
- Opt-in whole-response ceiling: `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 clear
|
|
68
|
+
- Full PubMed boolean and field-tag syntax, plus structured filters: author, journal, MeSH terms, language, species, publication type, has-abstract, free-full-text
|
|
69
|
+
- Date ranges by publication, modification, or Entrez date; sort by relevance, date, author, or journal; offset pagination
|
|
70
|
+
- Optional brief summaries for the top N results via ESummary
|
|
71
|
+
- NCBI Bookshelf hits carry `bookTitle`, `publisherName`, `docType`, and `editors` in place of the empty `source`
|
|
72
|
+
- Echoes the original query, the fully applied PubMed query, and normalized filter metadata
|
|
79
73
|
|
|
80
74
|
---
|
|
81
75
|
|
|
82
|
-
### `
|
|
83
|
-
|
|
84
|
-
Fetch full-text articles via a three-stage chain: NCBI PMC EFetch → Europe PMC `fullTextXML` → Unpaywall.
|
|
85
|
-
|
|
86
|
-
- 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). One identifier per element in every branch — a DOI carrying a comma or whitespace is rejected at the schema
|
|
87
|
-
- NCBI PMC and Europe PMC both return structured JATS; output records origin via `viaSource: "pmc" | "europepmc" | "unpaywall"`
|
|
88
|
-
- 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.
|
|
89
|
-
- 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
|
|
90
|
-
- 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`)
|
|
91
|
-
- Structured unavailable reasons (`not-found`, `no-pmc-fallback-disabled`, `no-epmc-fulltext`, `no-body`, `no-doi`, `doi-lookup-failed`, `no-oa`, `fetch-failed`, `parse-failed`, `service-error`) so callers can retry or explain to users without parsing text. `no-doi` and `doi-lookup-failed` are the settled and unsettled halves of the same gap: the first means the DOI lookup ran and the record has none, the second that the lookup itself errored, so a DOI may well exist and the request is worth retrying
|
|
92
|
-
- An `unavailable` 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 it
|
|
93
|
-
- 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
|
|
94
|
-
- Section filtering by title (case-insensitive substring match at any nesting depth, e.g. `["methods", "results"]`) and configurable max sections apply to PMC output. A section that matches directly is returned whole; one kept only because a nested subsection matched keeps its heading as a breadcrumb with its own text cleared
|
|
95
|
-
- Tables are returned as structured cells (`tables[]` on each PMC article — rows, caption, label, footnotes, and the enclosing section, named for back-matter and appendix tables as well as body ones), covering `<floats-group>`, `<back>` and appendix deposits alongside body tables. `colspan` and `rowspan` are expanded to one entry per grid column, so a value stays under the header it belongs to on both output surfaces; a cell spanning several columns or rows repeats across the cells it covers. A deposit with no readable markup comes back labelled with an `unextractableReason` rather than silently missing. Turn them off with `includeTables: false`
|
|
96
|
-
- Figures and supplementary material come back as structured entries (`assets[]` on each PMC article — `assetType`, label, caption, the enclosing section, and the `<graphic>`/`<media>` pointer exactly as deposited, which is a name inside the PMC deposit rather than a fetchable URL), covering `<floats-group>`, `<back>` and appendix placements alongside body ones. Each one lifted out of the body leaves a `[Figure: <label>]` / `[Supplementary: <label>]` marker at its position, so reading order survives the lift. Turn them off with `includeAssets: false`, which removes the markers with them. Prose-shaped blocks — lists, definition lists, block quotes, boxed text, preformatted blocks, displayed formulae — render into the section text at their document position instead, and no block is ever concatenated into a neighbouring sentence
|
|
97
|
-
- Character budgets keep context size predictable: `maxCharacters` caps body text per article (PMC sections and subsections, inline blocks included, plus table content — cell, caption, label and footnote text, not the Markdown grid rendered around it — and asset label, caption and pointer text; 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). Sections are served first, then tables, then assets, each spending what is left in document order until one does not fit; that entry and the rest are dropped whole rather than cut mid-row or returned with a shortened caption, and named in `truncation.articles[].omittedTableNames` / `omittedAssetNames`. Budgets run after the semantic filters, and a `truncation` object reports per-article and per-section character counts whenever anything was shortened
|
|
98
|
-
- `maxResponseCharacters` 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 call
|
|
99
|
-
- Up to 10 articles per request
|
|
76
|
+
### `pubmed_fetch_articles` <sub>tool</sub>
|
|
100
77
|
|
|
101
|
-
|
|
78
|
+
- Up to 200 PMIDs per call (POST for batches of 100 or more)
|
|
79
|
+
- Title, abstract, authors with deduplicated affiliations, journal info, DOI, PubMed/PMC links; optional MeSH terms, grants, and publication types
|
|
80
|
+
- Tolerant of PubMed's inconsistent XML — structured abstracts, missing fields, varying date formats
|
|
81
|
+
- Bookshelf chapters and books are first-class: `recordType` (`journal-article` / `book-chapter` / `book`) plus a `book` object (title, publisher, editors, ISBNs, Bookshelf accession); `journalInfo` is absent on them
|
|
82
|
+
- Article-number journals report `journalInfo.elocationId` + `elocationIdType` rather than a page range
|
|
83
|
+
- Opt-in `maxResponseCharacters` keeps whole records in order until the ceiling, then defers the rest to `deferred.ids` for a follow-up call
|
|
102
84
|
|
|
103
|
-
|
|
85
|
+
---
|
|
104
86
|
|
|
105
|
-
|
|
87
|
+
### `pubmed_fetch_fulltext` <sub>tool</sub>
|
|
106
88
|
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
89
|
+
- Exactly one of `pmcids`, `pmids`, or `dois` (one id per element), up to 10 per request
|
|
90
|
+
- Three-tier chain: NCBI PMC EFetch → Europe PMC `fullTextXML` (`EUROPEPMC_ENABLED`, default on) → Unpaywall (needs `UNPAYWALL_EMAIL`); `viaSource` names which tier served each article
|
|
91
|
+
- Preprints, patents, and Agricola records have metadata via `pubmed_europepmc_search` but no full text through this chain — Europe PMC's `fullTextXML` is PMC-keyed
|
|
92
|
+
- `source: "pmc"` returns structured sections plus `tables[]` (cells, caption, label, footnotes) and `assets[]` (figures and supplementary material, with `[Figure: <label>]` markers left in the body); `source: "unpaywall"` returns a best-effort body with `contentFormat` (`html-markdown` / `pdf-text`)
|
|
93
|
+
- 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), and `unqueriedTiers` when an unconfigured tier could have served the id
|
|
94
|
+
- Filters and budgets: `sections` (case-insensitive title match), `maxSections`, `includeTables`, `includeAssets`, `maxCharacters`, `maxCharactersPerSection`, `overflowMode` (`truncate` / `outline`), and `maxResponseCharacters`, which defers whole articles past the ceiling to `deferred.ids`; a `truncation` object reports what was shortened or omitted
|
|
113
95
|
|
|
114
96
|
---
|
|
115
97
|
|
|
116
|
-
### `
|
|
117
|
-
|
|
118
|
-
Fetch complete Europe PMC records by `source` + `epmcId`, the detail counterpart to `pubmed_europepmc_search`.
|
|
98
|
+
### `pubmed_europepmc_search` <sub>tool</sub>
|
|
119
99
|
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
- Disabled when `EUROPEPMC_ENABLED=false`; tool is not registered in that case
|
|
100
|
+
- Reaches records PubMed can't: preprints (`PPR`), patents (`PAT`), Agricola (`AGR`), alongside `MED` and `PMC`; default `sources` is `["MED", "PMC", "PPR"]`
|
|
101
|
+
- Cursor pagination via `cursorMark` — `*` for the first page, then `nextCursorMark`
|
|
102
|
+
- Hits carry `source` plus `pmid` / `pmcId` / `doi` when known; `abstractSnippet` is capped at 400 characters, with `abstractTruncated` flagging the cut
|
|
103
|
+
- Not registered when `EUROPEPMC_ENABLED=false`
|
|
125
104
|
|
|
126
105
|
---
|
|
127
106
|
|
|
128
|
-
### `
|
|
129
|
-
|
|
130
|
-
Generate formatted citations for articles.
|
|
107
|
+
### `pubmed_europepmc_fetch` <sub>tool</sub>
|
|
131
108
|
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
- Request multiple styles per article in a single call
|
|
136
|
-
- Hand-rolled formatters — zero external dependencies, fully Workers-compatible
|
|
137
|
-
- Up to 50 articles per request
|
|
138
|
-
- Reports formatted counts and unavailable PMIDs for partial-result handling
|
|
109
|
+
- Full records with the untruncated plain-text abstract, addressed by `source` + `epmcId` — the only identifier preprint, patent, and Agricola records reliably carry
|
|
110
|
+
- Up to 25 per call in one Europe PMC request; unresolved ids come back in `notFound` rather than failing the batch
|
|
111
|
+
- Not registered when `EUROPEPMC_ENABLED=false`
|
|
139
112
|
|
|
140
113
|
---
|
|
141
114
|
|
|
142
|
-
### `
|
|
115
|
+
### `pubmed_format_citations` <sub>tool</sub>
|
|
143
116
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
- Results returned in NCBI's relevance order
|
|
149
|
-
- Falls back to Europe PMC, then OpenAlex, when NCBI cannot answer; the response names which provider served it. A request no provider can answer fails with a typed `all_providers_failed` error instead of an empty result
|
|
117
|
+
- APA 7th, MLA 9th, BibTeX, RIS, Vancouver (ICMJE/NLM); several styles per article in one call, up to 50 articles
|
|
118
|
+
- 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
|
|
119
|
+
- Hand-rolled formatters — zero dependencies, Workers-compatible
|
|
120
|
+
- Reports formatted counts and unavailable PMIDs
|
|
150
121
|
|
|
151
122
|
---
|
|
152
123
|
|
|
153
|
-
### `
|
|
154
|
-
|
|
155
|
-
Spell-check a biomedical query using NCBI's ESpell.
|
|
124
|
+
### `pubmed_find_related` <sub>tool</sub>
|
|
156
125
|
|
|
157
|
-
-
|
|
158
|
-
-
|
|
126
|
+
- `similar`, `cited_by`, or `references` for a PMID, in NCBI relevance order, enriched with title, authors, date, and source (or Bookshelf book title and publisher)
|
|
127
|
+
- Falls back to Europe PMC, then OpenAlex, when NCBI can't answer; the response names the provider. Fails with a typed `all_providers_failed` error rather than an empty result
|
|
159
128
|
|
|
160
129
|
---
|
|
161
130
|
|
|
162
|
-
### `
|
|
131
|
+
### `pubmed_spell_check` <sub>tool</sub>
|
|
163
132
|
|
|
164
|
-
|
|
133
|
+
- Runs a query through NCBI ESpell and returns `original`, `corrected`, and `hasSuggestion`
|
|
134
|
+
- A blank or whitespace-only query is rejected rather than sent upstream
|
|
165
135
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### `pubmed_lookup_mesh` <sub>tool</sub>
|
|
139
|
+
|
|
140
|
+
- Looks up MeSH descriptors by name or free-text term, pinning the exact-heading match to the top of the first page
|
|
141
|
+
- Records carry `meshId` (DescriptorUI), `entrezUid`, and, with `includeDetails` (default on), tree numbers, scope notes, and entry terms
|
|
142
|
+
- `maxResults` up to 50 with offset pagination via `nextOffset`; `totalCount` reports the upstream match count
|
|
169
143
|
|
|
170
144
|
---
|
|
171
145
|
|
|
172
|
-
### `pubmed_lookup_citation`
|
|
146
|
+
### `pubmed_lookup_citation` <sub>tool</sub>
|
|
173
147
|
|
|
174
|
-
|
|
148
|
+
- Match on journal, year, volume, first page, and/or author — at least one field, more fields for better precision; up to 25 per call
|
|
149
|
+
- Pipes and line breaks are rejected at the schema (ECitMatch's wire format is pipe-delimited); the free-form `key` label is exempt
|
|
150
|
+
- Explicit `matched`, `not_found`, and `ambiguous` statuses with recovery detail
|
|
151
|
+
|
|
152
|
+
---
|
|
175
153
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
- Returns explicit `matched`, `not_found`, and `ambiguous` statuses with recovery detail
|
|
154
|
+
### `pubmed_convert_ids` <sub>tool</sub>
|
|
155
|
+
|
|
156
|
+
- Up to 50 DOIs, PMIDs, or PMCIDs per call, all one type; only PMC-indexed articles resolve
|
|
157
|
+
- One id per element — a packed `"23193287,37952131"` is rejected rather than expanded
|
|
158
|
+
- Per-id success/error rows; a partial batch never fails as a whole
|
|
182
159
|
|
|
183
160
|
---
|
|
184
161
|
|
|
185
|
-
### `
|
|
162
|
+
### `pubmed://database/info` <sub>resource</sub>
|
|
186
163
|
|
|
187
|
-
|
|
164
|
+
- Live EInfo call for the `pubmed` database, returned as `application/json`
|
|
165
|
+
- `dbName`, `description`, `count`, `lastUpdate`, and `fields[]` — each field's short `name` (the tag usable in `pubmed_search_articles` queries), `fullName`, and `description`
|
|
166
|
+
- No parameters
|
|
188
167
|
|
|
189
|
-
|
|
190
|
-
- Accepts DOIs, PMIDs, or PMCIDs (all IDs must be the same type)
|
|
191
|
-
- One identifier per array element, checked against `idType` before the request — a packed value like `"23193287,37952131"` is rejected rather than expanded into extra records, since a comma is the converter's list delimiter in any encoding
|
|
192
|
-
- Only resolves articles indexed in PubMed Central
|
|
193
|
-
- Per-ID success/error reporting — partial batches return resolved mappings alongside structured errors for unresolvable IDs, not a batch-level failure
|
|
168
|
+
---
|
|
194
169
|
|
|
195
|
-
|
|
170
|
+
### `research_plan` <sub>prompt</sub>
|
|
196
171
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
172
|
+
- Arguments: `title`, `goal`, `keywords` (comma-separated) required; `organism` and `includeAgentPrompts` (`"true"` / `"false"`) optional
|
|
173
|
+
- 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
|
|
174
|
+
- The plan walks four phases — Conception & Planning, Data Collection & Processing, Analysis & Interpretation, Dissemination — with sub-steps under each
|
|
175
|
+
- `includeAgentPrompts: "true"` adds an agent-guidance block under each sub-step, several of which point at `pubmed_search_articles` and `pubmed_lookup_mesh`
|
|
201
176
|
|
|
202
177
|
## Features
|
|
203
178
|
|
|
204
|
-
Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core):
|
|
205
|
-
|
|
206
|
-
- Declarative tool definitions — single file per tool, framework handles registration and validation
|
|
207
|
-
- Unified error handling across all tools
|
|
208
|
-
- Pluggable auth (`none`, `jwt`, `oauth`)
|
|
209
|
-
- Swappable storage backends: `in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`
|
|
210
|
-
- Structured logging with optional OpenTelemetry tracing
|
|
211
|
-
- Runs locally (stdio/HTTP) or on Cloudflare Workers from the same codebase
|
|
179
|
+
Built on [`@cyanheads/mcp-ts-core`](https://github.com/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.
|
|
212
180
|
|
|
213
181
|
PubMed-specific:
|
|
214
182
|
|
|
@@ -303,7 +271,7 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
|
|
|
303
271
|
|
|
304
272
|
### Prerequisites
|
|
305
273
|
|
|
306
|
-
- [Bun v1.
|
|
274
|
+
- [Bun v1.4.0](https://bun.sh/) or higher.
|
|
307
275
|
- Optional: [NCBI API key](https://www.ncbi.nlm.nih.gov/account/settings/) for higher rate limits (10 req/s vs 3 req/s).
|
|
308
276
|
|
|
309
277
|
### Installation
|
|
@@ -328,7 +296,7 @@ bun install
|
|
|
328
296
|
|
|
329
297
|
## Configuration
|
|
330
298
|
|
|
331
|
-
|
|
299
|
+
Key environment variables:
|
|
332
300
|
|
|
333
301
|
| Variable | Description | Default |
|
|
334
302
|
|:---|:---|:---|
|
|
@@ -403,7 +371,7 @@ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rule
|
|
|
403
371
|
|
|
404
372
|
## Contributing
|
|
405
373
|
|
|
406
|
-
Issues
|
|
374
|
+
Issues are welcome. Run checks and tests before submitting:
|
|
407
375
|
|
|
408
376
|
```sh
|
|
409
377
|
bun run devcheck
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Initial release — 7 PubMed tools, NCBI E-utilities service layer (eSearch/eSummary/eFetch/eLink/eSpell/eInfo with rate-limit + retry), `research_plan` prompt, and `pubmed://database/info` resource."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.0.0 — 2026-03-04
|
|
7
|
+
|
|
8
|
+
## Added
|
|
9
|
+
|
|
10
|
+
- **NCBI Service Layer**: Complete E-utilities integration (`eSearch`, `eSummary`, `eFetch`, `eLink`, `eSpell`, `eInfo`) with request queuing, rate limiting, retry with exponential backoff, and XML parsing.
|
|
11
|
+
- **7 PubMed Tools**:
|
|
12
|
+
- `pubmed_search` — Search PubMed with filters, date ranges, and optional summaries
|
|
13
|
+
- `pubmed_fetch` — Fetch full article metadata by PMIDs (abstract, authors, journal, MeSH)
|
|
14
|
+
- `pubmed_cite` — Generate formatted citations (APA 7th, MLA 9th, BibTeX, RIS)
|
|
15
|
+
- `pubmed_related` — Find related/cited-by/references via ELink
|
|
16
|
+
- `pubmed_spell` — Spell-check biomedical queries via ESpell
|
|
17
|
+
- `pubmed_trending` — Date-filtered search for recent publications
|
|
18
|
+
- `pubmed_mesh_lookup` — MeSH vocabulary search and exploration
|
|
19
|
+
- **Research Plan Prompt**: `research_plan` — structured 4-phase biomedical research plan generation
|
|
20
|
+
- **Database Info Resource**: `pubmed://database/info` — PubMed database metadata via EInfo
|
|
21
|
+
- **Citation Formatters**: Hand-rolled, zero-dependency, Workers-compatible formatters for APA, MLA, BibTeX, and RIS
|
|
22
|
+
- **NCBI Configuration**: `NCBI_API_KEY`, `NCBI_ADMIN_EMAIL`, `NCBI_REQUEST_DELAY_MS`, `NCBI_MAX_RETRIES`, `NCBI_TIMEOUT_MS`
|
|
23
|
+
|
|
24
|
+
## Changed
|
|
25
|
+
|
|
26
|
+
- **Rebranded** from `mcp-ts-template` to `@cyanheads/pubmed-mcp-server` (package.json, server.json, smithery.yaml, wrangler.toml)
|
|
27
|
+
- **Architecture**: Built on mcp-ts-template 3.0 with DI container, typed tokens, Zod-validated config, OpenTelemetry, and multi-transport support (stdio, HTTP, Cloudflare Workers)
|
|
28
|
+
|
|
29
|
+
## Removed
|
|
30
|
+
|
|
31
|
+
- All template example tools, resources, prompts, and services (graph, LLM, speech)
|
|
32
|
+
- `openai`, `@modelcontextprotocol/ext-apps` dependencies
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "`pubmed_search` gains field filters, offset pagination, and PMC URLs; `pubmed_mesh_lookup` exact-heading sort; `pubmed_trending` removed; six tool and config defaults revised."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.0.1 — 2026-03-04
|
|
7
|
+
|
|
8
|
+
## Added
|
|
9
|
+
|
|
10
|
+
- **Search filters**: `pubmed_search` gained field-specific filters (`author`, `journal`, `meshTerms`, `language`, `hasAbstract`, `freeFullText`, `species`) and pagination via `offset`
|
|
11
|
+
- **PMC links**: `pubmed_fetch` and `pubmed_search` summaries now include `pmcId`, `pubmedUrl`, and `pmcUrl` for direct article access
|
|
12
|
+
- **Affiliation deduplication**: article parser collects affiliations into a single array with per-author index references, reducing payload size for multi-center papers
|
|
13
|
+
- **Exact MeSH heading search**: `pubmed_mesh_lookup` runs a parallel `[MH]` exact-heading search and stable-sorts exact matches to the top
|
|
14
|
+
|
|
15
|
+
## Changed
|
|
16
|
+
|
|
17
|
+
- **`pubmed_search`**: renamed `includeSummaries` to `summaryCount`; date range format changed from `YYYY/MM/DD` to `YYYY-MM-DD` (auto-converted internally)
|
|
18
|
+
- **`pubmed_cite`**: max PMIDs raised from 20 to 50
|
|
19
|
+
- **`pubmed_related`**: simplified to use `cmd=neighbor` for all relationship types instead of `neighbor_history` + WebEnv for cited_by/references
|
|
20
|
+
- **`pubmed_mesh_lookup`**: `includeDetails` now defaults to `true`; switched from eFetch to eSummary for detail retrieval (MeSH eFetch returns plain text, not XML)
|
|
21
|
+
- **NCBI response handler**: demoted `eSearchResult.ErrorList` (PhraseNotFound, FieldNotFound) from errors to warnings — NCBI populates these on valid zero-result queries; enabled `processEntities` and `htmlEntities` in XML parser
|
|
22
|
+
- **Config defaults**: HTTP port 3010 → 3017, transport default `http` → `stdio`, storage default `filesystem` → `in-memory`
|
|
23
|
+
|
|
24
|
+
## Fixed
|
|
25
|
+
|
|
26
|
+
- **Auth factory tests**: JWT strategy tests now provide `mcpAuthSecretKey` and restore it on teardown
|
|
27
|
+
- **Response handler tests**: updated assertions to match ErrorList demotion (PhraseNotFound is a warning, not a thrown error)
|
|
28
|
+
- **Conformance tests**: removed `pubmed_trending` from expected tools list
|
|
29
|
+
|
|
30
|
+
## Removed
|
|
31
|
+
|
|
32
|
+
- **`pubmed_trending` tool**: removed — its functionality is fully covered by `pubmed_search` with date range and `pub_date` sort
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Adds `pubmed_pmc_fetch` tool — fetch full-text articles from PubMed Central via NCBI EFetch, with a JATS XML parser returning structured body sections, metadata, and references."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.1.0 — 2026-03-04
|
|
7
|
+
|
|
8
|
+
## Added
|
|
9
|
+
|
|
10
|
+
- **`pubmed_pmc_fetch` tool**: Fetch full-text articles from PubMed Central (PMC) via NCBI EFetch with `db=pmc`. Accepts PMC IDs directly or PubMed IDs (auto-resolved to PMCIDs via ELink). Returns structured body sections, subsections, metadata, and optional references parsed from JATS XML.
|
|
11
|
+
- **PMC article parser**: JATS XML parser (`pmc-article-parser.ts`) extracts metadata (authors, affiliations, journal, keywords, publication date, abstract), recursive body sections, and back-matter references from PMC EFetch responses.
|
|
12
|
+
- **PMC types**: JATS XML element types and parsed PMC result types (`XmlJatsArticle`, `ParsedPmcArticle`, etc.) in `src/services/ncbi/types.ts`.
|
|
13
|
+
|
|
14
|
+
## Changed
|
|
15
|
+
|
|
16
|
+
- **NCBI response handler**: Added PMC JATS-specific jpaths (`pmc-articleset.article`, `contrib-group.contrib`, `body.sec`, `ref-list.ref`, etc.) to the `isArray` set for consistent XML parsing.
|
|
17
|
+
- **README**: Added `pubmed_pmc_fetch` tool documentation, updated server description to mention full-text fetch.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Bug fixes across response handler, PMC/article parsers, and citation formatter — plus comprehensive test coverage for NCBI service and parser edge cases."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.1.1 — 2026-03-04
|
|
7
|
+
|
|
8
|
+
## Fixed
|
|
9
|
+
|
|
10
|
+
- **Response handler**: `extractTextValues` now handles numeric and boolean primitives emitted by fast-xml-parser when `parseTagValue` is enabled
|
|
11
|
+
- **Response handler**: Error detection uses shared `ERROR_PATHS` constant to stay in sync with error message extraction
|
|
12
|
+
- **PMC article parser**: Empty PMCID no longer produces a bare "PMC" prefix — returns empty string instead
|
|
13
|
+
- **Article parser**: Eliminated redundant `getText()` calls for month, day, and medlineDate in `extractJournalInfo`
|
|
14
|
+
- **Citation formatter**: `formatAuthorApa` no longer produces "undefined." when firstName contains consecutive spaces
|
|
15
|
+
- **Citation formatter**: Reordered `formatAuthorApa` logic so authors with only initials (no lastName) return formatted initials instead of empty string
|
|
16
|
+
|
|
17
|
+
## Changed
|
|
18
|
+
|
|
19
|
+
- **Citation formatter**: `escapeBibtex` refactored from chained `.replace()` calls to a single regex with switch — fixes ordering bug where backslash-then-brace sequences were double-escaped
|
|
20
|
+
- **Citation formatter**: `splitPages` simplified with destructuring
|
|
21
|
+
|
|
22
|
+
## Added
|
|
23
|
+
|
|
24
|
+
- Comprehensive test coverage for NCBI service edge cases: eSearch non-numeric fields, eSpell fallbacks, eSummary retmode logic, eFetch POST behavior
|
|
25
|
+
- Response handler tests: `CannotRetrievePMID` error path, numeric error values, DOCTYPE stripping, `returnRawXml` error passthrough
|
|
26
|
+
- Citation formatter tests: BibTeX special character escaping, APA author formatting edge cases, author-count boundaries (1/3/20/21), page splitting with en-dash/em-dash, minimal article formatting
|
|
27
|
+
- Article parser tests: PMC ID extraction from `ArticleIdList`, ORCID extraction, ISSN type classification, MedlineDate without year, empty AffiliationInfo handling
|
|
28
|
+
- ESummary parser tests: nested Author objects, string authors, PMC ID from ArticleIds, FullJournalName fallback
|
|
29
|
+
- PMC article parser tests: `pmc-uid` fallback, empty PMCID, affiliations, page ranges, pub-date priority (epub > ppub > pub)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "`pmc_fetch` renamed to `pubmed_pmc_fetch`; log directory path resolution switched to `node:path` for cross-platform correctness ([#9](https://github.com/cyanheads/pubmed-mcp-server/pull/9))."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.1.2 — 2026-03-04
|
|
7
|
+
|
|
8
|
+
## Changed
|
|
9
|
+
|
|
10
|
+
- **Tool rename**: `pmc_fetch` renamed to `pubmed_pmc_fetch` for consistency with the `pubmed_*` naming convention across all tools
|
|
11
|
+
|
|
12
|
+
## Fixed
|
|
13
|
+
|
|
14
|
+
- **Config**: Path resolution for logs directory now uses `node:path` utilities (`dirname`, `join`, `isAbsolute`) instead of URL-based arithmetic for cross-platform correctness ([#9](https://github.com/cyanheads/pubmed-mcp-server/pull/9))
|
|
15
|
+
|
|
16
|
+
## Updated
|
|
17
|
+
|
|
18
|
+
- `@cloudflare/workers-types` to `4.20260305.1`
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Fix: OpenTelemetry NodeSDK now initializes on Bun — `isBun` guard removed, manual spans, custom metrics, and OTLP export all work correctly."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.1.3 — 2026-03-04
|
|
7
|
+
|
|
8
|
+
## Fixed
|
|
9
|
+
|
|
10
|
+
- **Telemetry**: Enable OpenTelemetry NodeSDK on Bun — the `isBun` guard was unnecessarily blocking initialization when manual spans, custom metrics, and OTLP export all work correctly
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "`pubmed_fetch` gains `affiliations` and `articleDates` fields; public hosted endpoint added to README; new output-schema coverage tests prevent strict-client rejections."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.1.4 — 2026-03-04
|
|
7
|
+
|
|
8
|
+
## Added
|
|
9
|
+
|
|
10
|
+
- **pubmed_fetch**: `affiliations` (deduplicated author affiliations) and `articleDates` (electronic publication, received, accepted dates) now included in article output
|
|
11
|
+
- **Public hosted instance**: Added public Streamable HTTP endpoint (`https://pubmed.caseyjhand.com/mcp`) to README — no installation required
|
|
12
|
+
- **Output schema coverage tests**: New test suite validates that tool output schemas cover every field returned by parsers at runtime, preventing strict-client rejections from `additionalProperties: false`
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "NCBI config now logged at startup (API key status, delay, retries, timeout). Dep bumps: `@biomejs/biome` 2.4.6, `jose` 6.2.0, `@types/node` 25.3.5."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.1.5 — 2026-03-06
|
|
7
|
+
|
|
8
|
+
## Added
|
|
9
|
+
|
|
10
|
+
- **Startup logging**: NCBI configuration (API key status, email, request delay, max retries, timeout) now logged at initialization for easier debugging
|
|
11
|
+
|
|
12
|
+
## Updated
|
|
13
|
+
|
|
14
|
+
- `@biomejs/biome` to 2.4.6
|
|
15
|
+
- `@cloudflare/workers-types` to 4.20260307.1
|
|
16
|
+
- `@types/node` to 25.3.5
|
|
17
|
+
- `@types/sanitize-html` to 2.16.1
|
|
18
|
+
- `jose` to 6.2.0
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Fix: `structuredContent` removed from error responses (valid for success only); `fast-check` → 4.6.0, `jose` → 6.2.1."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.1.6 — 2026-03-09
|
|
7
|
+
|
|
8
|
+
## Fixed
|
|
9
|
+
|
|
10
|
+
- **Error responses**: Removed `structuredContent` from error responses in tool handler factory — `structuredContent` is only valid for successful results, not error payloads
|
|
11
|
+
|
|
12
|
+
## Updated
|
|
13
|
+
|
|
14
|
+
- `fast-check` to 4.6.0
|
|
15
|
+
- `jose` to 6.2.1
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "New pubmed_europepmc_fetch tool (11th tool) resolves full Europe PMC records by source + epmcId; pubmed_fetch_fulltext gains maxCharacters/maxCharactersPerSection/overflowMode budget controls; a shared surrogate-pair-safe slice helper backs both."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.0 — 2026-07-26
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`pubmed_europepmc_fetch` tool** — new 11th tool. Resolves specific Europe PMC records by `source` + `epmcId` and returns each one's complete, untruncated abstract, up to 25 records per call resolved in a single Europe PMC request; unresolved pairs come back in `notFound` instead of failing the batch. This is the retrieval path for preprint (`PPR`), patent (`PAT`), and Agricola (`AGR`) records, which frequently carry no PMID and no DOI. Backed by a new `EuropePmcService.fetchRecords()` method that OR-joins `(EXT_ID:<id> AND SRC:<source>)` clauses into one search query. ([#83](https://github.com/cyanheads/pubmed-mcp-server/issues/83))
|
|
12
|
+
- **`pubmed_fetch_fulltext` character budgets** — new `maxCharacters`, `maxCharactersPerSection`, and `overflowMode` (`truncate` | `outline`) inputs cap body text per article. `truncate` fills sections in document order so early sections stay whole; `outline` spreads the budget evenly across sections so every heading survives with an excerpt. Applies to `source=pmc` section/subsection text and the `source=unpaywall` body; titles, abstracts, identifiers, and references are never counted or cut. A new `truncation` output object reports per-article and per-section character counts whenever a budget shortened the response, and a matching `ctx.enrich.notice()` names what was spent. ([#81](https://github.com/cyanheads/pubmed-mcp-server/issues/81))
|
|
13
|
+
- **`pubmed_europepmc_search` `abstractTruncated`** — new boolean alongside `abstractSnippet` (capped at 400 characters) marking whether the snippet was cut; pass the hit's `source` and `epmcId` to `pubmed_europepmc_fetch` for the full text. ([#83](https://github.com/cyanheads/pubmed-mcp-server/issues/83))
|
|
14
|
+
|
|
15
|
+
## Fixed
|
|
16
|
+
|
|
17
|
+
- **Character cuts could split a UTF-16 surrogate pair** — `pubmed_fetch_fulltext`'s new budget cuts and `pubmed_europepmc_search`'s `abstractSnippet` cut now back off one code unit when the boundary lands mid-pair, via a new shared `sliceCodeUnits` helper (`src/mcp-server/tools/definitions/_text.ts`). Budgets remain a code-unit ceiling; reported character counts are measured off the text actually returned, never assumed from the requested allowance. ([#93](https://github.com/cyanheads/pubmed-mcp-server/issues/93))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "eSearch upstream failures now throw instead of masking as zero-hit searches; pubmed_search_articles surfaces ignored field tags, unmatched phrases, and dropped partial dateRange filters via notice; the summaryCount cap message points at pubmed_fetch_articles when maxed; research_plan's includeAgentPrompts is now correctly advertised as optional."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.1 — 2026-07-26
|
|
8
|
+
|
|
9
|
+
## Fixed
|
|
10
|
+
|
|
11
|
+
- **`eSearchResult.ERROR` was missing from `ERROR_PATHS`** — an upstream eSearch failure parsed as a zero-hit success instead of throwing. `pubmed_search_articles`'s `offset` is now bounded at 9998 (`OFFSET_MAX`), matching PubMed's `retstart` ceiling, and the ceiling is stated in its `.describe()`. `pubmed_lookup_mesh`'s `offset` is left unbounded — `db=mesh` has no equivalent `retstart` ceiling. ([#95](https://github.com/cyanheads/pubmed-mcp-server/issues/95))
|
|
12
|
+
- **eSearch `ErrorList`/`WarningList` never reached the caller** — `pubmed_search_articles` now reads both off the eSearch result and surfaces them through the `notice` enrichment: an unrecognized field tag fires independently of hit count, and an unmatched phrase names the exact clause instead of the generic empty-result guidance. `NcbiService.eSearch` normalizes every list member to `string[]` — NCBI collapses a single-entry member to a scalar, which the declared type didn't account for. ([#96](https://github.com/cyanheads/pubmed-mcp-server/issues/96))
|
|
13
|
+
- **Partial `dateRange` and the `summaryCount` cap were both undisclosed** — a `dateRange` with exactly one bound filled now fires a notice naming the supplied bound and a sentinel for an open-ended range, instead of silently dropping the filter. The `summaryCount` cap message now points at `pubmed_fetch_articles` with the remaining PMIDs when already at its maximum, instead of advising a raise that isn't possible. `buildNotice()` now collects every applicable signal and joins them, rather than returning the first of two mutually exclusive branches. ([#97](https://github.com/cyanheads/pubmed-mcp-server/issues/97))
|
|
14
|
+
- **`research_plan`'s `includeAgentPrompts` was advertised as a required prompt argument** — the SDK derives `prompts/list`'s `required` flag from schema optionality, and a `ZodDefault` doesn't read as optional. Changed from `.default('false')` to `.optional()`; `buildPlan`'s `=== 'true'` check behaves identically for an omitted value. ([#98](https://github.com/cyanheads/pubmed-mcp-server/issues/98))
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "pubmed_fetch_fulltext renders JATS block content at its own position and returns figures and supplementary material as a structured assets[] field, pubmed_find_related's OpenAlex fallback pages to the full requested window, and the query tools reject a blank query instead of forwarding it to NCBI."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.10 — 2026-09-10
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`pubmed_fetch_fulltext` `assets[]`** — every `<fig>` and `<supplementary-material>` a PMC article carries, with `assetType`, `label`, `caption`, `id`, `sectionTitle`, and the deposit-relative `href` pointer; each lifted asset leaves a `[Figure: <label>]` / `[Supplementary: <label>]` marker at its position. New `includeAssets` input (default `true`) mirrors `includeTables`. ([#130](https://github.com/cyanheads/pubmed-mcp-server/issues/130))
|
|
12
|
+
- **`pubmed_find_related` `coverageFailures`** — when the Europe PMC or OpenAlex reference-coverage fallback throws, an entry naming `provider`, `reason`, and `retryable` is added instead of folding the failure into a false "no references" answer. ([#118](https://github.com/cyanheads/pubmed-mcp-server/issues/118))
|
|
13
|
+
|
|
14
|
+
## Fixed
|
|
15
|
+
|
|
16
|
+
- **JATS block content (lists, definition lists, quotes, boxed text, preformatted text, formulae) dropped or fused into surrounding prose** — each now renders inline at its position instead of vanishing or gluing onto a paragraph's sentence, and an article whose entire body is one such block (e.g. a legacy OCR `<preformat>` deposit) is no longer reported as having no body. A caption's own title no longer runs into the sentence that follows it — on tables as well as on figures and supplementary material — and a caption or label hung directly on a `<media>`/`<graphic>` pointer is now read. ([#130](https://github.com/cyanheads/pubmed-mcp-server/issues/130))
|
|
17
|
+
- **`pubmed_fetch_fulltext` returned the first serialized `<abstract>` regardless of `@abstract-type`** — the untyped abstract is now preferred over a typed one (e.g. `graphical`, `executive-summary`), and content within the selected abstract renders through the same block-aware walk as body text, so an embedded figure caption reaches `assets[]` instead of the prose. ([#134](https://github.com/cyanheads/pubmed-mcp-server/issues/134))
|
|
18
|
+
- **`pubmed_find_related`'s OpenAlex fallback stalled after its first upstream page** — `cited_by`, `references`, and `similar` now page (or batch-resolve, for the latter two) until the requested window is filled, upstream is exhausted, or a 10-page/batch cap is reached, with `totalCount` reporting the exact PubMed-addressable count once exhausted. This also fixes an HTTP 400 the `references` path threw once `offset + maxResults` reached 34, from a candidate filter of up to 200 values against OpenAlex's 100-value OR-clause ceiling. ([#117](https://github.com/cyanheads/pubmed-mcp-server/issues/117))
|
|
19
|
+
- **A failed Europe PMC/OpenAlex reference-coverage fallback was reported as a genuine empty answer** — a thrown fallback now surfaces via `coverageFailures` and a distinct notice instead of being folded into NCBI's "no reference list" wording, and a provider disabled by configuration is marked non-retryable with no retry guidance offered. ([#118](https://github.com/cyanheads/pubmed-mcp-server/issues/118))
|
|
20
|
+
- **A failed DOI backfill lookup was misreported as `no-doi`** — a new `doi-lookup-failed` reason (added to `UnavailableReasonSchema` and `TierOutcomeSchema`) now distinguishes an errored lookup from a record that genuinely has none. A DOI Europe PMC already returned is also now carried forward to Unpaywall instead of being discarded, skipping a redundant PubMed round-trip. ([#119](https://github.com/cyanheads/pubmed-mcp-server/issues/119))
|
|
21
|
+
- **`pubmed_search_articles` accepted a blank query and forwarded it to NCBI as a blank term** — a whitespace-only or sanitized-to-empty query was previously retried as a misclassified transient outage across seven attempts and roughly a minute of backoff; it now fails immediately with a declared `blank_query` reason (`ValidationError`, non-retryable). ([#122](https://github.com/cyanheads/pubmed-mcp-server/issues/122))
|
|
22
|
+
- **`pubmed_spell_check` and `pubmed_lookup_mesh` accepted blank queries and returned a false empty success** — both now reject the same `blank_query` reason before calling NCBI, instead of reporting a checked-but-empty result for a query that was never searched. ([#133](https://github.com/cyanheads/pubmed-mcp-server/issues/133))
|