@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
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "pubmed_fetch_articles and pubmed_format_citations return NCBI Bookshelf chapters and books instead of reporting them unavailable, journal article locators are preserved through metadata and every citation style, and pubmed_convert_ids and pubmed_lookup_citation reject inputs that could shift NCBI's own field parsing."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.11 — 2026-09-10
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **NCBI Bookshelf records** (`PubmedBookArticle`) are now first-class: `pubmed_fetch_articles` and `pubmed_format_citations` return a `recordType` discriminator (`journal-article`, `book-chapter`, `book`) and a `book` object — title, publisher, place, dates, medium, edition, series, ISBNs, book DOI, editors, Bookshelf accession — instead of reporting a Bookshelf PMID unavailable. ([#114](https://github.com/cyanheads/pubmed-mcp-server/issues/114))
|
|
12
|
+
- **Citation styles gained a book form** — Vancouver's `In: … editors.` pattern, APA's chapter-in-edited-book, MLA's `edited by`, BibTeX `@incollection`/`@book`, RIS `CHAP`/`BOOK` — and an edited book that credits no authors of its own is cited from the editor position instead, which APA marks `(Ed.)`/`(Eds.)`. `pubmed_search_articles`/`pubmed_find_related` report a book's `bookTitle`, `publisherName`, `docType`, and `editors` in place of the empty journal source ESummary leaves on these records; `pubmed_find_related` requests ESummary `version=2.0` to reach them, since the version 1 DocSum format carries no book elements at all. ([#114](https://github.com/cyanheads/pubmed-mcp-server/issues/114))
|
|
13
|
+
- **Electronic article locators** — `journalInfo.elocationId`/`elocationIdType`, and the JATS equivalent on `pubmed_fetch_fulltext` — preserve a journal's publisher-assigned article number when it carries no page range, rendered in each style's own convention: Vancouver's trailing `pii:` note, APA's `Article <n>`, MLA's `art. <n>`, BibTeX's `eid`, RIS's `C7`. ([#121](https://github.com/cyanheads/pubmed-mcp-server/issues/121))
|
|
14
|
+
|
|
15
|
+
## Fixed
|
|
16
|
+
|
|
17
|
+
- **`pubmed_convert_ids` accepted an `ids` element packed with a comma-joined list of identifiers** and returned more records than were submitted; each element is now checked against its declared `idType` before the request, with a new `malformed_id` error reason naming the offending value. `pubmed_fetch_fulltext`'s `dois` and `pmcids` inputs carry the same per-element constraint. ([#120](https://github.com/cyanheads/pubmed-mcp-server/issues/120))
|
|
18
|
+
- **`pubmed_lookup_citation` let a `|` in the caller's `key`** (or in `journal`, `year`, `volume`, `firstPage`, `authorName`) shift ECitMatch's pipe-delimited field layout, turning a real match into `not_found`; the wire now submits each citation's positional index rather than its label, and the five bibliographic fields reject `|`, `\r`, and `\n` at the schema. ([#125](https://github.com/cyanheads/pubmed-mcp-server/issues/125))
|
|
19
|
+
- **`pubmed_lookup_citation` rendered identical `content[]` headings for citations sharing a `key`**; each heading now leads with the citation's 1-based submission index, so no two can collide regardless of what `key` contains. ([#128](https://github.com/cyanheads/pubmed-mcp-server/issues/128))
|
|
20
|
+
- **A chapter no longer cites its containing book's DOI** — that identifier resolves to the book, so a chapter falls back to the Bookshelf URL instead; `Book/ELocationID`'s DOI is a fallback on a whole-book record only. `®`/`™` inside an EFetch `<sup>` — `Book/BookTitle` arrives as `GeneReviews<sup>®</sup>` — no longer render with a stray `^` (e.g. `GeneReviews^®`), and `Book/Isbn` leading zeros are preserved instead of being coerced away as a number. ([#114](https://github.com/cyanheads/pubmed-mcp-server/issues/114))
|
|
21
|
+
- **`unavailablePmids` and the empty-result notices on `pubmed_fetch_articles` and `pubmed_format_citations`** no longer claim a missing PMID "may be invalid, unpublished, or withdrawn" — PubMed omits an unrecognized PMID silently, with no error and no reason, so the wording now says only that. ([#114](https://github.com/cyanheads/pubmed-mcp-server/issues/114))
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "pubmed_fetch_fulltext no longer emits LaTeX preambles or duplicate renderings for <alternatives>-wrapped formulas, APA citations for authorless records open on the title instead of the year, and internal NCBI response-parsing helpers are hardened against defects that were silently inert until now."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.12 — 2026-09-11
|
|
8
|
+
|
|
9
|
+
## Fixed
|
|
10
|
+
|
|
11
|
+
- **`pubmed_fetch_fulltext` no longer emits the LaTeX preamble publishers wrap around every formula.** Publishers deposit `<tex-math>` as a complete document — `\documentclass[12pt]{minimal}\usepackage{amsmath}…\begin{document}$$…$$\end{document}` — and every character of it used to reach `sections[].text`, `tables[].rows`, and `content[]`, once per formula. Only the body between `\begin{document}` and `\end{document}` is kept now (delimiters included, so it still reads as math); a body deposited with no wrapper is unchanged, and a deposit that opens the document and never closes it still yields its expression. A JATS `<alternatives>` — offering, say, a TeX and a MathML rendering of the same formula — now contributes exactly one child instead of every rendering: its `<tex-math>` when that carries an expression, otherwise the first non-pointer child carrying text, otherwise a pointer's own text. On one *Scientific Reports* record carrying 312 of them this dropped the rendered section and table text from 168,009 to 92,505 bytes. ([#135](https://github.com/cyanheads/pubmed-mcp-server/issues/135))
|
|
12
|
+
- **`pubmed_format_citations`'s APA style no longer opens a reference on the year for a record crediting no author.** APA 7 §9.12 moves the title into the author position instead — `Title. (Year). …` rather than `(Year). Title. …` — for a journal article, a whole book crediting neither authors nor editors, and a chapter with no authors of its own, whose book editors stay in the `In …` clause; an edited book still cites from the editor position (`Adam, M. P. (Ed.). (Year). …`), unchanged. NCBI Bookshelf records made the defect common, since a whole-book record frequently credits neither authors nor editors. ([#139](https://github.com/cyanheads/pubmed-mcp-server/issues/139))
|
|
13
|
+
- **`getText`/`getAttribute` in the NCBI XML parsing helpers dropped the `string | undefined` overloads they could never satisfy** — a JavaScript default parameter fires on an explicit `undefined`, so both always returned a string and the type invited a `??` that could never take its right branch. Two new helpers, `getOptionalText`/`getOptionalAttribute`, report a missing *or empty* element as `undefined`. Every call site that depended on the old overload migrated, which also fixes a live defect in ESummary author parsing: an author with no `<AuthType>` now parses with no `authtype` key instead of `authtype: ''`, and `clusterid: ''` no longer appears on every author (the read looked for `ClusterId`/`clusterid` while NCBI ships `<ClusterID/>`, so it always missed and wrote the empty default anyway). ([#137](https://github.com/cyanheads/pubmed-mcp-server/issues/137))
|
|
14
|
+
- **`NCBI_ARRAY_JPATHS` entries are now spelled as full root-relative paths.** The set is matched against fast-xml-parser's exact dotted path from the document root, and most entries named only their last segments, so they could never fire, and nothing depended on them: nearly every read site normalizes with `ensureArray` anyway. Entries are now grouped by E-utility under a header comment stating the convention. Three groups were removed rather than expanded: the JATS entries, which can never fire under any spelling because the ordered parser used for PMC full text declares no `isArray` callback at all; the dead duplicate `IdList.Id`; and `Link.Id`, which would have returned an empty PMID for every related record had it been made to fire, since `find-related` reads a link's id without `ensureArray`. `eLinkResult.LinkSet.LinkSetDb` was added, since `find-related` reads it as a list and it was absent; the recursively nested ESummary v1 `<Item>` is now covered by one root-anchored pattern instead of a fixed path list, since an item of type `List` or `Structure` holds further items and MeSH lookup walks three levels of them. `ensureArray` stays at every read site that has it. ([#138](https://github.com/cyanheads/pubmed-mcp-server/issues/138))
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "NCBI EFetch backend-timeout envelopes now retry instead of failing as invalid input. Adopts mcp-ts-core 0.13.0: the skill tree moves to framework-skills/, and blank/placeholder env values read as unset without a per-field guard."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.13 — 2026-09-13
|
|
8
|
+
|
|
9
|
+
## Changed
|
|
10
|
+
|
|
11
|
+
- **Skill tree moved from `skills/` to `framework-skills/`.** Claude Code and Codex auto-load a plugin's root `skills/`, so a server shipping `.claude-plugin/` or `.codex-plugin/` handed its development skills to every installing agent. No tool behavior changed.
|
|
12
|
+
- **`NCBI_API_KEY`, `NCBI_ADMIN_EMAIL`, `UNPAYWALL_EMAIL`, `EUROPEPMC_EMAIL` drop their per-field blank/placeholder guard** in favor of the framework's `parseEnvConfig`, which reads an empty, whitespace-only, or whole-value `${…}` placeholder value as unset.
|
|
13
|
+
- **Bun engines floor raised to `>=1.4.0`.**
|
|
14
|
+
- **Tooling and docs:** `devcheck` parses Bun 1.4 `bun audit` output, `lint:packaging` rejects empty plugin `env` values and checks MCPB `user_config` wiring, new `audit:fix` and `test:coverage` scripts, a restructured README capability reference, and new `.github/CONTRIBUTING.md` and `CODE_OF_CONDUCT.md` with refreshed issue templates.
|
|
15
|
+
|
|
16
|
+
## Fixed
|
|
17
|
+
|
|
18
|
+
- **NCBI EFetch backend-timeout envelopes are now retried instead of treated as invalid input.** The `<eFetchResult><ERROR>…Status: Timeout</ERROR></eFetchResult>` envelope arrives under both HTTP 400 (classified as caller error, skipping the retry policy) and HTTP 200 (parsed as a missing article); both now reclassify to a retryable `ServiceUnavailable`, while a genuine invalid-parameter 400 stays non-retryable. ([#153](https://github.com/cyanheads/pubmed-mcp-server/issues/153))
|
|
19
|
+
- **A blank numeric or boolean setting (`NCBI_REQUEST_DELAY_MS`, `NCBI_TIMEOUT_MS`, `EUROPEPMC_ENABLED`, …) now takes its default instead of failing config validation.** A blank `NCBI_MAX_RETRIES` or `EUROPEPMC_MAX_RETRIES` no longer means zero retries, and with `NCBI_API_KEY` set a blank `NCBI_REQUEST_DELAY_MS` gets the 100ms keyed delay.
|
|
20
|
+
- **`.claude-plugin/plugin.json` and `.codex-plugin/mcp.json` no longer set blank `NCBI_API_KEY`, `NCBI_ADMIN_EMAIL`, `UNPAYWALL_EMAIL` values**, which replaced a variable the user had already exported. Claude Code now collects them through `userConfig`; Codex forwards them through `env_vars`.
|
|
21
|
+
|
|
22
|
+
## Dependencies
|
|
23
|
+
|
|
24
|
+
- `@cyanheads/mcp-ts-core` ^0.12.8 → ^0.13.0
|
|
25
|
+
- `zod` ^4.5.4 → ^4.6.1
|
|
26
|
+
- `@biomejs/biome` ^2.5.12 → ^2.5.13
|
|
27
|
+
- `@types/node` ^26.4.1 → ^26.5.1
|
|
28
|
+
- `ignore` ^7.0.8 → ^7.0.9
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Adopts mcp-ts-core 0.13.4: argument rejections carry a Recovery hint, a mistyped key or JSON-stringified array is repaired before validation, and tool-argument copying is hardened against __proto__ injection."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.14 — 2026-09-18
|
|
8
|
+
|
|
9
|
+
## Changed
|
|
10
|
+
|
|
11
|
+
- **Retry gates for NCBI, Europe PMC, and OpenAlex now use the framework's `defaultIsTransient`** in place of the server's own `retry-policy.ts` mirror (removed). Same retryable codes, same `retryable: false` opt-out — no behavior change.
|
|
12
|
+
- **`createApp` declares `sessionMode: 'stateless'`** in `src/index.ts`, matching the existing `.env.example`/Dockerfile/README posture instead of leaving it to deployment config alone.
|
|
13
|
+
- **Argument validation now repairs recoverable input before rejecting it** (mcp-ts-core 0.13.4). An undeclared key whose case-folded form names exactly one declared key is rewritten — `max_results` is accepted as `maxResults` on `pubmed_search_articles`. A JSON-stringified array is parsed after a failed direct match — `pmids` sent as `"[\"33300001\"]"` succeeds on `pubmed_fetch_articles`. Every tool's advertised `inputSchema`/`outputSchema` is unchanged.
|
|
14
|
+
- **An argument rejection's error text now ends with `Recovery: <hint>`** (mcp-ts-core 0.13.3), e.g. naming a missing required field and the keys the tool accepts; `data.reason` carries `"invalid_arguments"`.
|
|
15
|
+
- **A call unwound after its request is cancelled now classifies as `RequestCancelled` (-32011)** instead of a generic error (mcp-ts-core 0.13.3).
|
|
16
|
+
- **Tooling and docs:** adds `.github/workflows/codeql.yml`, adds `changelog/` to the npm `files` allowlist, refreshes issue-template field guidance, and syncs `framework-skills/` to mcp-ts-core 0.13.4.
|
|
17
|
+
|
|
18
|
+
## Security
|
|
19
|
+
|
|
20
|
+
- **Tool-argument copying can no longer be re-prototyped via a caller-supplied `__proto__` key** (mcp-ts-core 0.13.4).
|
|
21
|
+
|
|
22
|
+
## Dependencies
|
|
23
|
+
|
|
24
|
+
- `@cyanheads/mcp-ts-core` ^0.13.0 → ^0.13.4
|
|
25
|
+
- `zod` ^4.6.1 → ^4.6.5
|
|
26
|
+
- `@vitest/coverage-istanbul` ^5.0.0 → ^5.0.1
|
|
27
|
+
- `fast-check` ^4.9.0 → ^4.10.0
|
|
28
|
+
- `tsc-alias` ^1.9.4 → ^1.9.5
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Markup strip no longer eats statistical notation like P<0.001 in Europe PMC abstracts; pubmed_europepmc_fetch now resolves source: \"PMC\" refs for articles also indexed in PubMed; doi fields across the tool catalog disclose that casing differs by upstream."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.2 — 2026-07-26
|
|
8
|
+
|
|
9
|
+
## Fixed
|
|
10
|
+
|
|
11
|
+
- **`MARKUP_TAG_RE` consumed abstract text between a stray `<` and the next real tag** — statistical notation like `P<0.001` opened a match that closed on the following tag's `>`, silently dropping everything in between. The pattern now requires a tag-name letter after the optional `/` and forbids a further `<` inside the tag body, so `toDisplayText()` (used by `pubmed_europepmc_search` and `pubmed_europepmc_fetch`) leaves such notation intact. ([#94](https://github.com/cyanheads/pubmed-mcp-server/issues/94))
|
|
12
|
+
- **`pubmed_europepmc_fetch` couldn't resolve `source: "PMC"` for an article also indexed in PubMed** — `SRC:PMC` is Europe PMC's PMC-only corpus; a PubMed-indexed article's canonical record lives under `MED` with the PMCID as a field. `recordLookupQuery` now ORs in a bare `PMCID:<id>` clause for `PMC` refs, and the tool's response reconciliation registers a `PMC:<pmcid>` alias so such a record isn't reported in both `records` and `notFound`. The `notFound` notice now points a PMCID-shaped miss at `pubmed_fetch_fulltext`. ([#94](https://github.com/cyanheads/pubmed-mcp-server/issues/94))
|
|
13
|
+
- **`doi` fields didn't disclose upstream casing differences** — DOIs are case-insensitive by spec and none of the tools normalize case, so the same DOI can arrive differently cased from NCBI versus Europe PMC. `pubmed_europepmc_fetch`, `pubmed_europepmc_search`, `pubmed_fetch_articles`, `pubmed_fetch_fulltext`, and `pubmed_convert_ids` now state this on their `doi` output field and name the sibling tool where the mismatch shows up. ([#94](https://github.com/cyanheads/pubmed-mcp-server/issues/94))
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Adopts mcp-ts-core 0.12.3 and the v2 MCP SDK packages; pubmed_fetch_fulltext gains a truncated enrichment flag."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.3 — 2026-08-21
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`pubmed_fetch_fulltext` `enrichment.truncated`** — a boolean set when a character budget shortened at least one returned body, so a caller can detect truncation without reading the notice prose. Per-article accounting stays in `truncation`.
|
|
12
|
+
|
|
13
|
+
## Changed
|
|
14
|
+
|
|
15
|
+
- **Service log context** — the extra fields handed to `requestContextService.createRequestContext` now sit under `additionalContext`, and `esummary-parser` merges onto an existing context with `withExtra`. Emitted log payloads are unchanged.
|
|
16
|
+
- **Production Docker image** — both `bun install` steps take `--omit=peer`, dropping the framework's optional peer tiers that nothing here imports at runtime; base image `oven/bun` 1.3.14 → 1.4.0.
|
|
17
|
+
- **Test suite typechecking** — `tsconfig.json` now includes `tests/**` and emits nothing, with the `src`-only emit settings moved to `tsconfig.build.json`. New `tests/_helpers.ts` narrows the `ContentBlock` and `PromptMessage` unions the assertions read.
|
|
18
|
+
- **Template scripts and skills re-synced** — `lint-packaging` follows the SDK's v2 package rename to `@modelcontextprotocol/server`, `lint-mcp` drops the `taskHandlers` branch from tool detection, and `tree` honors directory-only ignore patterns (which prunes `.storage/` and `announcements/` from `docs/tree.md`).
|
|
19
|
+
- **Security contact** — `.github/SECURITY.md` directs vulnerability reports to `security@caseyjhand.com`.
|
|
20
|
+
|
|
21
|
+
## Dependencies
|
|
22
|
+
|
|
23
|
+
- `@cyanheads/mcp-ts-core` ^0.11.0 → ^0.12.3 — pulls the v2 MCP SDK (`@modelcontextprotocol/server` and `@modelcontextprotocol/client` ^2.0.0) in place of `@modelcontextprotocol/sdk` ^1.x
|
|
24
|
+
- `bun` (packageManager) 1.3.14 → 1.4.0
|
|
25
|
+
- `fast-xml-parser` ^5.10.1 → ^5.11.0, `sanitize-html` ^2.17.6 → ^2.17.7, `unpdf` ^1.6.2 → ^1.8.1
|
|
26
|
+
- `@biomejs/biome` ^2.5.5 → ^2.5.9, `@types/node` ^26.1.1 → ^26.2.0, `@vitest/coverage-istanbul` and `vitest` ^4.1.10 → ^4.1.11, `tsc-alias` ^1.9.1 → ^1.9.2
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Pins the Docker build stage to $BUILDPLATFORM so the multi-arch image publishes again — the emulated linux/amd64 leg aborted tsc."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.4 — 2026-08-21
|
|
8
|
+
|
|
9
|
+
## Fixed
|
|
10
|
+
|
|
11
|
+
- **Docker build stage runs natively on the builder** — the stage is now `FROM --platform=$BUILDPLATFORM`. Under emulation the `linux/amd64` leg aborted `tsc` (`qemu: uncaught target signal 6`, exit 134), so no `linux/amd64,linux/arm64` image was published for 2.10.3. `dist/` is pure JavaScript and identical across targets; the production stage still builds per target and installs its own runtime dependencies there.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Adopts mcp-ts-core 0.12.5: no HTTP status classifies as InternalError anymore (500/501 join the rest of 5xx as ServiceUnavailable), and an SSRF DNS-guard bypass on Bun 1.4 Linux is closed. The NCBI, OpenAlex, and Europe PMC retry loops now honor the framework's retryable:false opt-out so a 501 fails on the first attempt instead of retrying."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: true
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.5 — 2026-09-02
|
|
8
|
+
|
|
9
|
+
## Changed
|
|
10
|
+
|
|
11
|
+
- **`@cyanheads/mcp-ts-core` `^0.12.3` → `^0.12.5`** — no HTTP status classifies as `InternalError` anymore; 500 and 501 join the rest of the 5xx range as `ServiceUnavailable` (504 stays `Timeout`), and a caller disconnect now classifies as `RequestCancelled` (-32011) rather than `InternalError`.
|
|
12
|
+
- **Shipped skills re-synced** — `add-tool` 2.21, `api-config` 1.15, `api-context` 2.2, `api-errors` 1.8, `api-linter` 1.13, `api-utils` 2.8, `design-mcp-server` 2.23, `maintenance` 2.6, `release-and-publish` 2.13, `setup` 1.10.
|
|
13
|
+
|
|
14
|
+
## Fixed
|
|
15
|
+
|
|
16
|
+
- **Retry loops stop retrying a non-retryable 501** — new `src/services/retry-policy.ts` adds `isTransient()`, honoring the framework's `data.retryable === false` opt-out ahead of code-based classification. Folding 501 into the transient `ServiceUnavailable` code otherwise made the NCBI, OpenAlex, and Europe PMC retry loops re-ask a method the upstream declared unimplemented. `NcbiApiClient`'s local 500→`ServiceUnavailable` `codeOverride` is removed — the framework now classifies it natively.
|
|
17
|
+
- **`.env.example` / README now show `MCP_SESSION_MODE=stateless`** — matching the Dockerfile default this server has shipped with; the prior `auto` example resolved to `stateful`, a mode this server has no `ctx.requestInput` call sites to use.
|
|
18
|
+
|
|
19
|
+
## Security
|
|
20
|
+
|
|
21
|
+
- **SSRF DNS-guard bypass on Bun 1.4 Linux** (inherited from `@cyanheads/mcp-ts-core` 0.12.4) — `assertDnsNotPrivate` now queries both `node:dns` resolvers, closing a gap where a hostname only the system resolver could see (`/etc/hosts`, split DNS, an NSS module) passed the guard and connected.
|
|
22
|
+
|
|
23
|
+
## Dependencies
|
|
24
|
+
|
|
25
|
+
- `@cyanheads/mcp-ts-core` `^0.12.3` → `^0.12.5`
|
|
26
|
+
- `defuddle` `^0.19.2` → `^0.19.3`
|
|
27
|
+
- `fast-xml-parser` `^5.11.0` → `^5.11.1`
|
|
28
|
+
- `zod` `^4.4.3` → `^4.5.4`
|
|
29
|
+
- `@biomejs/biome` (dev) `^2.5.9` → `^2.5.11`
|
|
30
|
+
- `@types/node` (dev) `^26.2.0` → `^26.4.0`
|
|
31
|
+
- `ignore` (dev) `^7.0.6` → `^7.0.7`
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Five bug fixes: pubmed_find_related no longer returns false-empty Europe PMC pages or a fake empty success when every provider fails, Europe PMC title/author/journal markup is stripped and Markdown-escaped, an all-numeric spell_check query round-trips as text, and Unpaywall HTML is no longer parsed as a PDF. Adopts mcp-ts-core 0.12.7."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.6 — 2026-09-08
|
|
8
|
+
|
|
9
|
+
## Changed
|
|
10
|
+
|
|
11
|
+
- **`@cyanheads/mcp-ts-core` `^0.12.5` → `^0.12.7`** — an argument rejection now carries `structuredContent.error` classified as `InvalidParams` rather than `ValidationError`, on every transport; Node and workerd type checking run as separate programs, lifting the `@cloudflare/workers-types` hold. Packaging validation adopted here checks each present plugin manifest's `version` against `package.json`'s.
|
|
12
|
+
- **Shipped skills re-synced** — `add-tool` 2.22, `api-errors` 1.9, `api-workers` 1.8, `design-mcp-server` 2.24, `field-test` 2.10, `git-wrapup` 1.12.
|
|
13
|
+
|
|
14
|
+
## Fixed
|
|
15
|
+
|
|
16
|
+
- **`pubmed_find_related`'s Europe PMC fallback no longer returns false-empty pages** (#101) — `epmcProvider()` pages Europe PMC (up to 1000 rows/page, up to 10 pages) until the requested window of PubMed-addressable rows is filled instead of fetching a single page capped at 100. `EuropePmcService.citations()`/`references()` now report `droppedNoPmid` (non-`MED` rows with no PubMed PMID) and `hitCount` separately from the addressable `totalCount`; the tool discloses excluded rows and a page-cap shortfall in its enrichment notice instead of silently returning an empty window.
|
|
17
|
+
- **`pubmed_find_related` fails instead of faking an empty success when every provider fails** (#103) — a chain where NCBI, Europe PMC, and OpenAlex all fail now throws a typed `all_providers_failed` error carrying each attempted provider's reason (`data.attempted`), rather than returning `articles: []` with an invented `source: "ncbi"`. A provider disabled by server configuration (`EUROPEPMC_ENABLED=false`, no `OpenAlexService`) is now reported as `provider_disabled` and excluded from the retry hint; a provider that genuinely serves zero related records still succeeds unchanged.
|
|
18
|
+
- **Europe PMC `title`/`authors`/`journal` markup no longer leaks into either output surface** (#102) — `pubmed_europepmc_fetch` and `pubmed_europepmc_search` now run all three through `toDisplayText()`, the same JATS/HTML-stripping, entity-decoding pass already applied to `abstractText`. A new `escapeMarkdownInline()` (`src/mcp-server/tools/definitions/_text.ts`) additionally neutralizes Markdown-significant characters at every title-as-heading interpolation in `content[]` — both Europe PMC tools plus `pubmed_fetch_articles`, `pubmed_fetch_fulltext`, `pubmed_search_articles`, and `pubmed_find_related` — so upstream text can no longer alter heading structure, toggle emphasis, or inject a link; `structuredContent` keeps the unescaped plain-text value.
|
|
19
|
+
- **`pubmed_spell_check` no longer fails output validation on an all-numeric query** (#108) — `NcbiService.eSpell` now parses its response with a new verbatim (`parseTagValue: false`) XML parser, so a query like `33306283` round-trips as the string `"33306283"` instead of being coerced to a number, and `"007"` / `"1e5"` round-trip byte-identical instead of losing their leading zero or exponential form.
|
|
20
|
+
- **`pubmed_fetch_fulltext` no longer parses an HTML paywall page as a PDF** (#104) — `UnpaywallService.fetchAs` classifies a fetched Unpaywall response by its bytes' `%PDF-` magic header instead of the requested kind, so a `url_for_pdf` response that is actually HTML (a publisher paywall/interstitial) falls through to the `location.url` landing page instead of failing with a misleading `Invalid PDF structure` error; a genuine PDF served under an unexpected `content-type` still parses.
|
|
21
|
+
|
|
22
|
+
## Dependencies
|
|
23
|
+
|
|
24
|
+
- `@cyanheads/mcp-ts-core` `^0.12.5` → `^0.12.7`
|
|
25
|
+
- `@biomejs/biome` (dev) `^2.5.11` → `^2.5.12`
|
|
26
|
+
- `@types/node` (dev) `^26.4.0` → `^26.4.1`
|
|
27
|
+
- `@vitest/coverage-istanbul` (dev) `^4.1.11` → `^5.0.0`
|
|
28
|
+
- `ignore` (dev) `^7.0.7` → `^7.0.8`
|
|
29
|
+
- `tsc-alias` (dev) `^1.9.2` → `^1.9.4`
|
|
30
|
+
- `vitest` (dev) `^4.1.11` → `^5.0.0`
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "pubmed_fetch_articles and pubmed_fetch_fulltext gain an opt-in maxResponseCharacters whole-response budget with deferred-article continuation; fetch_fulltext reports unqueriedTiers when a search skipped an unconfigured tier; format_citations names accepted format values on an invalid input."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.7 — 2026-09-09
|
|
8
|
+
|
|
9
|
+
## Added
|
|
10
|
+
|
|
11
|
+
- **`maxResponseCharacters`** on `pubmed_fetch_articles` (#99) and `pubmed_fetch_fulltext` (#100) — an opt-in whole-response character ceiling, distinct from `fetch_fulltext`'s existing per-article `maxCharacters`. Articles are kept in response order until the next one would cross the ceiling; the rest are deferred whole (never partially populated) and listed in the new `deferred.ids`, keyed under the branch they were requested on (`pmids`/`pmcids`/`dois` for `fetch_fulltext`). Re-calling with `deferred.ids` resumes exactly where the response stopped. Omitting the field is byte-identical to prior output. Shared accounting lives in the new `src/mcp-server/tools/definitions/_budget.ts` (`fitWholeItems`, `serializedCharacters`).
|
|
12
|
+
- **`unavailable[].unqueriedTiers`** on `pubmed_fetch_fulltext` (#110) — lists tiers (`europepmc`, `unpaywall`) the chain skipped because this deployment has not configured them and that could have served the id, so a caller can tell an incomplete search from a settled miss. A tier inapplicable to the id (no DOI for Unpaywall) is never listed.
|
|
13
|
+
|
|
14
|
+
## Changed
|
|
15
|
+
|
|
16
|
+
- **`pubmed_fetch_fulltext`'s `reason` no longer folds an unconfigured tier's absence into the reported signal** (#110) — `reason` now always reflects the most specific content signal a tier that actually answered reported; the incompleteness moved to `unqueriedTiers` instead of silently overriding `reason`. Two reason values shift as a result: a DOI-less record with Unpaywall unconfigured now reports `no-doi` (was `no-pmc-fallback-disabled`), and a PMCID/PMID no tier indexes now reports `not-found` even when Unpaywall's last chain entry is `no-doi`.
|
|
17
|
+
- **`pubmed_format_citations`'s `format` union now names its accepted values on a top-level validation failure** — an invalid value (e.g. `"chicago"`) surfaces `Invalid option: expected one of "apa"|"mla"|"bibtex"|"ris"|"vancouver", or a non-empty array of those values` in `content[]` directly, instead of a generic `Invalid input` that buried the accepted values inside `structuredContent.error`.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "ECitMatch reconciliation now keys on per-request wire tokens so duplicate caller keys no longer misassign results; pubmed_fetch_fulltext folds JATS sections nested three or more levels deep into the deepest surviving subsection's text; mixed-citation references separate zero-gap adjacent pub-ids and title/volume pairs."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.8 — 2026-09-09
|
|
8
|
+
|
|
9
|
+
## Changed
|
|
10
|
+
|
|
11
|
+
- **`.env.example` Supabase keys** — `SUPABASE_SERVICE_ROLE_KEY` is what the `supabase` storage provider requires and `SUPABASE_ANON_KEY` is optional; each line now names its role.
|
|
12
|
+
|
|
13
|
+
## Fixed
|
|
14
|
+
|
|
15
|
+
- **`pubmed_lookup_citation` duplicate-key result misassignment** — `eCitMatch` reconciled response rows by the caller-supplied `key`, which may repeat; a duplicate (or a caller key colliding with an auto-assigned one) collapsed two citations onto one row and handed the second citation's PMID and validation context to the first. Reconciliation now runs on a per-request wire token unique within the call, restoring the caller's key on the way out. ([#113](https://github.com/cyanheads/pubmed-mcp-server/issues/113))
|
|
16
|
+
- **`pubmed_fetch_fulltext` sections nested three or more levels deep** — the output schema declares two levels of JATS section nesting; deeper `<sec>` elements were silently stripped by output validation. They now fold into the deepest surviving subsection's `text`, each heading on its own line; the schema stays at two levels since `format-parity`'s 8-hop walker already spends its budget reaching `sections[].subsections[]`. The character-budget report's `originalCharacters` now counts those folded heading lines, since they're real characters in the returned text. ([#112](https://github.com/cyanheads/pubmed-mcp-server/issues/112))
|
|
17
|
+
- **`pubmed_fetch_fulltext` reference citations with zero-gap adjacent elements** — JATS `mixed-citation` rendered via flat `textContent()`, which glues together elements with no separating text: adjacent typed `<pub-id>`s fused into one token, and an inline italic title running straight into a following bold volume fused the same way. `extractReferences` now renders `mixed-citation` child-by-child, labeling typed pub-ids and inserting a single space between zero-gap adjacent elements. ([#115](https://github.com/cyanheads/pubmed-mcp-server/issues/115), [#123](https://github.com/cyanheads/pubmed-mcp-server/issues/123))
|
|
18
|
+
|
|
19
|
+
## Dependencies
|
|
20
|
+
|
|
21
|
+
- `@cyanheads/mcp-ts-core` ^0.12.7 → ^0.12.8 — releases now run through a gated release PR: `git-wrapup` stops at a pushed `release/<version>` branch with an open PR, and `release-and-publish` fast-forwards `main` and tags its tip.
|
|
22
|
+
- Skills synced from 0.12.8 — new `release-pr-review` 1.0; `api-config` 1.16, `api-services` 1.5, `api-telemetry` 1.8, `api-utils` 2.9, `code-simplifier` 1.4, `field-test` 2.12, `git-wrapup` 1.13, `orchestrations` 1.8, `release-and-publish` 2.14.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "pubmed_fetch_fulltext now extracts JATS tables as structured cells, finds Europe PMC references nested under <body> at any depth, and matches a sections filter against subsection titles too; mixed-citation author names and Europe PMC's numeric-text coercion are also fixed."
|
|
3
|
+
breaking: false
|
|
4
|
+
security: false
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 2.10.9 — 2026-09-10
|
|
8
|
+
|
|
9
|
+
## Fixed
|
|
10
|
+
|
|
11
|
+
- **`pubmed_fetch_fulltext` JATS tables dropped or fused into prose** — a `<table-wrap>` beside a section's paragraphs was silently absent from the response, and one nested inside a `<p>` had its cells concatenated into the surrounding sentence with no delimiter. Tables are now extracted into a new `tables[]` field (article-level, covering body, `<floats-group>`, `<back>`, and appendix deposits) with `colspan`/`rowspan` expanded to one cell per grid column; a deposit with no readable markup (graphic-only, CALS `<tgroup>`) is returned labeled with `unextractableReason` instead of silently missing. New `includeTables` input (default `true`); table text now counts against `maxCharacters` and a table that doesn't fit is dropped whole rather than cut mid-row. ([#111](https://github.com/cyanheads/pubmed-mcp-server/issues/111))
|
|
12
|
+
- **`pubmed_fetch_fulltext` Europe PMC references nested under `<body>` discarded** — `extractReferences` searched only a direct `<back>` child for `<ref-list>`, missing the majority Europe PMC placement (`body/sec/sec/ref-list`) entirely. It now finds `<ref-list>` at any depth under the article, deduplicating by `<ref>` id so a document exposing the same list under both `<back>` and `<body>` still yields each reference once. ([#116](https://github.com/cyanheads/pubmed-mcp-server/issues/116))
|
|
13
|
+
- **`pubmed_fetch_fulltext` mixed-citation author names glued together** — a `<name>`/`<string-name>`/`<person-group>` with no text between `<surname>` and `<given-names>` rendered joined (`NybakkenJW`); `renderMixedCitation` now applies its zero-gap element spacing rule inside author-name wrappers, not just between a citation's direct children. ([#124](https://github.com/cyanheads/pubmed-mcp-server/issues/124))
|
|
14
|
+
- **`pubmed_fetch_fulltext` `sections` filter matched only top-level titles** — a filter term naming a subsection heading (e.g. `"Demographics"` nested under `"Results"`) returned nothing, with no indication that subsections were unmatchable. Matching is now recursive across nesting depth; a section kept only because a descendant matched is returned as a breadcrumb, with its own text cleared and just the matching branch beneath it. ([#126](https://github.com/cyanheads/pubmed-mcp-server/issues/126))
|
|
15
|
+
- **Europe PMC full-text parser coerced numeric-looking text** — `EuropePmcService`'s XML parser ran `parseTagValue: true` against a comment claiming parity with the PMC parser, so bibliographic tokens (page ranges, reference labels like `"1."`) were coerced through `Number` on the Europe PMC path but not the PMC EFetch path. Both parsers now build from one shared `ORDERED_XML_PARSER_OPTIONS` constant, keeping the two paths byte-identical in configuration — which also gives the Europe PMC path the `maxTotalExpansions` entity-expansion cap it had been running without. ([#127](https://github.com/cyanheads/pubmed-mcp-server/issues/127))
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "The server was migrated to use the `@cyanheads/mcp-ts-core` framework for MCP plumbing."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.2.0 — 2026-03-23
|
|
7
|
+
|
|
8
|
+
## Framework Migration
|
|
9
|
+
|
|
10
|
+
The server was migrated to use the `@cyanheads/mcp-ts-core` framework for MCP plumbing. This will simplify and streamline future development.
|
|
11
|
+
|
|
12
|
+
## Tool Renames
|
|
13
|
+
|
|
14
|
+
All tools were renamed for clarity. Schemas and capabilities are unchanged.
|
|
15
|
+
|
|
16
|
+
| Previous (v2.1.x) | New (v2.2.0) |
|
|
17
|
+
|:-------------------|:-------------|
|
|
18
|
+
| `pubmed_search` | `pubmed_search_articles` |
|
|
19
|
+
| `pubmed_fetch` | `pubmed_fetch_articles` |
|
|
20
|
+
| `pubmed_pmc_fetch` | `pubmed_fetch_fulltext` |
|
|
21
|
+
| `pubmed_related` | `pubmed_find_related` |
|
|
22
|
+
| `pubmed_cite` | `pubmed_format_citations` |
|
|
23
|
+
| `pubmed_mesh_lookup` | `pubmed_lookup_mesh` |
|
|
24
|
+
| `pubmed_spell` | `pubmed_spell_check` |
|
|
25
|
+
|
|
26
|
+
## Changed
|
|
27
|
+
|
|
28
|
+
- **Framework migration**: Replaced inline framework code (~58k lines) with `@cyanheads/mcp-ts-core` package dependency. All tools, resources, and prompts now use the framework's declarative builders (`tool()`, `resource()`, `prompt()`)
|
|
29
|
+
- **Tool definitions**: Rewritten from handler-factory pattern to single-file `tool()` builder definitions with Zod input/output schemas, `format` functions, and `annotations`
|
|
30
|
+
- **Resource definition**: `database-info.resource.ts` migrated from custom `ResourceDefinition` type to framework's `resource()` builder with `handler(params, ctx)` pattern
|
|
31
|
+
- **Prompt definition**: `research-plan.prompt.ts` migrated from custom `PromptDefinition` type to framework's `prompt()` builder
|
|
32
|
+
- **Entry point**: `src/index.ts` simplified from DI container + server bootstrap to single `createApp()` call with tool/resource/prompt arrays
|
|
33
|
+
- **NCBI service**: Flattened from `services/ncbi/core/` subdirectory to `services/ncbi/` top-level; uses framework's `logger` instead of custom logger
|
|
34
|
+
- **Config**: Replaced monolithic `src/config/index.ts` with focused `src/config/server-config.ts` (NCBI-specific env vars only; framework handles transport, auth, storage)
|
|
35
|
+
- **Build**: Switched from custom build scripts to framework-provided `tsconfig.base.json`, `biome.json`, and `vitest.config.ts` extensions
|
|
36
|
+
- **Tool file renames**: Files renamed to match tool names (e.g., `pubmed-search.tool.ts` → `search-articles.tool.ts`, `pubmed-spell.tool.ts` → `spell-check.tool.ts`)
|
|
37
|
+
- **CLAUDE.md**: Replaced generic placeholder patterns with actual server examples (spell-check tool, database-info resource, NCBI config), updated structure tree, removed unused context properties
|
|
38
|
+
- **README.md**: Updated all tool names and descriptions to match renames, updated config section
|
|
39
|
+
- **Dockerfile**: Fixed image title/description labels, added `source` label, corrected log directory name and default port
|
|
40
|
+
- **Default HTTP port**: Reverted to `3010` across `.env.example`, `Dockerfile`, `README.md`, and `server.json` (was changed to `3017` in 2.0.1)
|
|
41
|
+
- **server-config.ts**: Replaced `z.string().email()` with `z.email()` shorthand
|
|
42
|
+
- **.env.example**: Added `NCBI_TIMEOUT_MS` entry
|
|
43
|
+
|
|
44
|
+
## Added
|
|
45
|
+
|
|
46
|
+
- **Test suite**: 178 tests across 17 files in `tests/` mirroring `src/` structure — covers config, NCBI service layer, XML/JSON parsers, citation formatters, all 7 tools, 1 resource, and 1 prompt using `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
|
|
47
|
+
- **Skills directory**: Framework skill files for development workflows (add-tool, add-resource, devcheck, field-test, etc.)
|
|
48
|
+
- **MCP definition linter**: `bun run lint:mcp` validates tool/resource/prompt definitions against the MCP spec at build time
|
|
49
|
+
- **devcheck.config.json**: Centralized devcheck configuration
|
|
50
|
+
|
|
51
|
+
## Fixed
|
|
52
|
+
|
|
53
|
+
- **fetch-articles**: Added `unavailablePmids` to output — surfaces which requested PMIDs returned no article data
|
|
54
|
+
- **fetch-fulltext**: Added `unavailablePmcIds` to output — tracks which PMC IDs returned no data; fetch failures now return a graceful empty result instead of throwing
|
|
55
|
+
- **research-plan prompt**: Corrected tool reference from `pubmed_mesh_lookup` to `pubmed_lookup_mesh`; clarified `includeAgentPrompts` description
|
|
56
|
+
|
|
57
|
+
## Security
|
|
58
|
+
|
|
59
|
+
- **package.json**: Added `overrides` to pin transitive dependencies `express-rate-limit` (>=8.2.2) and `hono` (>=4.12.7) to patched versions
|
|
60
|
+
|
|
61
|
+
## Removed
|
|
62
|
+
|
|
63
|
+
- **Inline framework code**: DI container, transport layer (stdio/HTTP/Workers), storage providers, auth strategies, error handler, logger, telemetry, utilities — all now provided by `@cyanheads/mcp-ts-core`
|
|
64
|
+
- **Legacy tests**: Old test suite removed (covered framework internals, not server logic); replaced by new `tests/` suite
|
|
65
|
+
- **Worker entry point**: `src/worker.ts` removed (framework handles Workers deployment via `createWorkerHandler()`)
|
|
66
|
+
- **Cloudflare config**: `wrangler.toml`, `schemas/cloudflare-d1-schema.sql` removed
|
|
67
|
+
- **Misc**: `.husky/pre-commit`, `smithery.yaml`, `repomix.config.json`, `typedoc.json`, `tsdoc.json`, various README docs in `src/`
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Fix: adds missing `mcpName` field to `package.json` required by the MCP registry for publishing."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.2.1 — 2026-03-23
|
|
7
|
+
|
|
8
|
+
## Fixed
|
|
9
|
+
|
|
10
|
+
- **package.json**: Added `mcpName` field required by the MCP registry for publishing
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Format improvements for `fetch-articles`, `fetch-fulltext`, and `find-related`; NCBI raw exception traces replaced with user-friendly messages; `@cyanheads/mcp-ts-core` 0.1.29."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.2.2 — 2026-03-24
|
|
7
|
+
|
|
8
|
+
## Changed
|
|
9
|
+
|
|
10
|
+
- **fetch-articles format**: Now displays authors (first 3 + "et al."), journal info (abbreviation, year, volume, issue, pages), publication types, and unavailable PMIDs
|
|
11
|
+
- **fetch-fulltext format**: Renders subsections within body sections
|
|
12
|
+
- **find-related**: Added `source` and `pubDate` fields to output schema and format display
|
|
13
|
+
|
|
14
|
+
## Fixed
|
|
15
|
+
|
|
16
|
+
- **NCBI error messages**: Raw C++ exception traces from NCBI are now replaced with concise, user-friendly messages
|
|
17
|
+
|
|
18
|
+
## Updated
|
|
19
|
+
|
|
20
|
+
- `@cyanheads/mcp-ts-core` to 0.1.29
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Retry logic moved to `NcbiService.performRequest` to cover XML-level NCBI errors; backoff changed to 1s base; HTML rate-limit responses now throw `ServiceUnavailable`. 8 new retry integration tests."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.2.3 — 2026-03-24
|
|
7
|
+
|
|
8
|
+
## Changed
|
|
9
|
+
|
|
10
|
+
- **Retry logic**: Moved retry with exponential backoff from `NcbiApiClient` (HTTP-only) to `NcbiService.performRequest`, so retries now cover both HTTP-level failures and XML-level NCBI errors (e.g., 200 OK with C++ exception traces in the response body)
|
|
11
|
+
- **Backoff timing**: Retry delays changed from 200ms base (200, 400, 800ms) to 1s base (1s, 2s, 4s) for more conservative backoff
|
|
12
|
+
- **`api-client`**: Simplified to single-attempt; now checks `response.ok` and throws `ServiceUnavailable` for non-OK HTTP status codes
|
|
13
|
+
|
|
14
|
+
## Added
|
|
15
|
+
|
|
16
|
+
- **HTML response detection**: `NcbiResponseHandler` now detects HTML responses from NCBI (typically rate-limiting pages) and throws `ServiceUnavailable` instead of an opaque XML parse error
|
|
17
|
+
- **Retry integration tests**: New colocated test file `src/services/ncbi/ncbi-service.test.ts` — 8 tests covering HTTP retry, XML-level retry, timeout retry, non-retryable error passthrough, exhaustion messaging, and backoff timing
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Format output enriched — `pubmed_fetch_articles` gains affiliations, MeSH/grants; `pubmed_fetch_fulltext` gains authors, journal, references. Deps: `mcp-ts-core` →0.2.3, `biome` →2.4.9."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.2.4 — 2026-03-28
|
|
7
|
+
|
|
8
|
+
## Added
|
|
9
|
+
|
|
10
|
+
- **fetch-articles format**: Affiliations, keywords, MeSH terms (with major topic markers and qualifiers), and grant information now rendered in format output
|
|
11
|
+
- **fetch-fulltext format**: Authors, affiliations, journal info, article type, publication date, PubMed URL, keywords, and reference list now rendered in format output; unavailable PMC IDs surfaced
|
|
12
|
+
- **Skills**: `report-issue-framework` and `report-issue-local` for filing bugs/feature requests against the framework or this server
|
|
13
|
+
|
|
14
|
+
## Changed
|
|
15
|
+
|
|
16
|
+
- **polish-docs-meta skill**: Updated to v1.2 — added GitHub repo metadata sync step, description propagation rule (`package.json` → README header, `server.json`, Dockerfile), renumbered checklist steps
|
|
17
|
+
|
|
18
|
+
## Refactored
|
|
19
|
+
|
|
20
|
+
- Optional chaining cleanup in `article-parser.ts` and `fetch-articles.tool.ts` (replaced `x && x.y` with `x?.y`)
|
|
21
|
+
|
|
22
|
+
## Updated
|
|
23
|
+
|
|
24
|
+
- `@cyanheads/mcp-ts-core` to ^0.2.3
|
|
25
|
+
- `@biomejs/biome` to ^2.4.9
|
|
26
|
+
- `vitest` to ^4.1.2
|
|
27
|
+
|
|
28
|
+
## Security
|
|
29
|
+
|
|
30
|
+
- Added overrides for `brace-expansion` (>=2.0.3), `path-to-regexp` (>=8.4.0), `picomatch` (>=4.0.4), `yaml` (>=2.8.3)
|
|
31
|
+
|
|
32
|
+
## Docs
|
|
33
|
+
|
|
34
|
+
- Added `LOGS_DIR` env var to README and reference docs
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Skill updates — `add-tool` v1.1 expands `format()` template and adds Tool Response Design section; `add-resource` and `design-mcp-server` gain coverage guidance and tools-first patterns. Bumps `mcp-ts-core` ^0.2.10, `biome` ^2.4.10."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.2.6 — 2026-03-30
|
|
7
|
+
|
|
8
|
+
## Changed
|
|
9
|
+
|
|
10
|
+
- **add-tool skill** (v1.1): Content-complete `format()` template; new Tool Response Design section covering batch input, partial success, empty results, error classification, operational metadata, and context budget
|
|
11
|
+
- **add-resource skill** (v1.1): Added tool coverage guidance — verify data is reachable via the tool surface for tool-only clients
|
|
12
|
+
- **design-mcp-server skill** (v2.1): Tools-first design philosophy, live API probing step, batch input design patterns, convenience shortcuts, error design table with classification, resilience and API efficiency planning, naming convention refinement
|
|
13
|
+
|
|
14
|
+
## Updated
|
|
15
|
+
|
|
16
|
+
- `@cyanheads/mcp-ts-core` to ^0.2.10
|
|
17
|
+
- `@biomejs/biome` to ^2.4.10
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Adds `pubmed_lookup_citation` (ECitMatch, batch 25) and `pubmed_convert_ids` (DOI/PMID/PMCID via PMC ID Converter, batch 50). Refactored retry logic and HTTP error classification in `NcbiApiClient`."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.3.0 — 2026-03-31
|
|
7
|
+
|
|
8
|
+
## Added
|
|
9
|
+
|
|
10
|
+
- **`pubmed_lookup_citation` tool**: Resolve partial bibliographic references (journal, year, volume, page, author) to PubMed IDs via NCBI ECitMatch. Batch up to 25 citations per request with deterministic matching.
|
|
11
|
+
- **`pubmed_convert_ids` tool**: Convert between DOI, PMID, and PMCID using the PMC ID Converter API. Batch up to 50 IDs per request; returns all available identifier mappings.
|
|
12
|
+
- **`NcbiService.eCitMatch()`**: ECitMatch service method — formats bdata pipe-delimited strings, parses multi-line responses, handles NOT_FOUND/AMBIGUOUS results.
|
|
13
|
+
- **`NcbiService.idConvert()`**: PMC ID Converter service method — JSON-based external API call with error classification.
|
|
14
|
+
- **`NcbiApiClient.makeExternalRequest()`**: HTTP client method for non-eutils NCBI endpoints (e.g., PMC ID Converter). Uses plain fetch with `AbortSignal.timeout` for response body access on error status codes.
|
|
15
|
+
- **Test coverage**: Full test suites for both new tools and service methods (eCitMatch parsing, idConvert JSON handling, input validation, format output)
|
|
16
|
+
|
|
17
|
+
## Changed
|
|
18
|
+
|
|
19
|
+
- **Retry logic**: Extracted inline retry loop from `performRequest` into reusable `withRetry()` method, shared by both eutils and external API calls
|
|
20
|
+
- **HTTP error classification**: `NcbiApiClient` now distinguishes 4xx (InvalidRequest) from 5xx (ServiceUnavailable) errors instead of treating all non-OK responses as ServiceUnavailable
|
|
21
|
+
- **Endpoint suffix handling**: `api-client.ts` skips `.fcgi` suffix for endpoints that already contain a dot (e.g., `ecitmatch.cgi`)
|
|
22
|
+
|
|
23
|
+
## Docs
|
|
24
|
+
|
|
25
|
+
- Updated README tool count (7 → 9), added tool descriptions and detail sections for both new tools
|
|
26
|
+
- Updated CLAUDE.md tool count (7 → 9)
|
|
27
|
+
- Regenerated `docs/tree.md` with new files
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "Fix `pubmed_search_articles` ignoring empty `dateRange` strings — skips date clause instead of producing a malformed NCBI query (closes [#14](https://github.com/cyanheads/pubmed-mcp-server/issues/14))."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.3.1 — 2026-04-01
|
|
7
|
+
|
|
8
|
+
## Fixed
|
|
9
|
+
|
|
10
|
+
- **`pubmed_search_articles`**: Empty `dateRange` strings (e.g., `{ minDate: "", maxDate: "" }`) no longer produce a malformed NCBI query returning 0 results — the handler now skips the date clause when either date is empty ([#14](https://github.com/cyanheads/pubmed-mcp-server/issues/14))
|
|
11
|
+
- **`pubmed_search_articles`**: Date field descriptions updated to reflect accepted NCBI formats (`YYYY/MM/DD`, `YYYY/MM`, or `YYYY`)
|
|
12
|
+
|
|
13
|
+
## Added
|
|
14
|
+
|
|
15
|
+
- **Test coverage**: 7 new tests for `dateRange` handling — empty strings, omitted dateRange, partial dates, valid dates, and dash-to-slash conversion
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "PMC full-text parser preserves document order via `preserveOrder: true` (closes [#19](https://github.com/cyanheads/pubmed-mcp-server/issues/19)); all-invalid-ID fetches now surface failures (closes #20)."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.3.10 — 2026-04-20
|
|
7
|
+
|
|
8
|
+
## Fixed
|
|
9
|
+
|
|
10
|
+
- **`pubmed_fetch_fulltext` — PMC parser lost document order on mixed-content markup** (`pmc-article-parser.ts`, `response-handler.ts`, `pmc-xml-helpers.ts`): `extractTextContent` consumed `fast-xml-parser` output in `preserveOrder: false` mode, which collapses `#text` fragments and reorders inline children by property-key order. Older Science/Nature PMC deposits (e.g. `PMC4089965`) came back with garbled abstracts — sentences interleaved, gene lists detached — and `sections: []` because the body's direct `<p>` children were skipped whenever a trailing supplementary `<sec>` was present. Added a second `FastXmlParser` instance with `preserveOrder: true, trimValues: false` dedicated to PMC, routed via a new `useOrderedParser` flag on `NcbiRequestOptions`. New `pmc-xml-helpers.ts` module provides typed helpers (`tagNameOf`, `childrenOf`, `attrOf`, `textContent`, `findOne`, `findAll`) over the ordered shape. Rewrote `pmc-article-parser.ts` around them; `extractBodySections` now walks body children in document order so mixed `<p>` + `<sec>` bodies preserve their main text. PubMed E-utilities parsing is unchanged — the ordered parser is opt-in.
|
|
11
|
+
- **`pubmed_fetch_articles` / `pubmed_fetch_fulltext` — silent empty payload when all requested IDs are invalid** (`fetch-articles.tool.ts`, `fetch-fulltext.tool.ts`): Both tools short-circuited before computing `unavailablePmids` / `unavailablePmcIds`, so a caller passing a single bad ID received `{ articles: [], totalReturned: 0 }` with no signal about what failed. The mixed-input path (some valid, some invalid) worked correctly; only the all-invalid path regressed. Dropped the early returns; the existing post-parse set-difference logic now covers every case uniformly. Also narrowed the ordered-mode error tag regex in `response-handler.ts` to case-sensitive `<ERROR>` so PMC's lowercase per-ID `<error id="…">` element (returned for missing PMCIDs) falls through as data rather than triggering a retry-throw cascade.
|
|
12
|
+
|
|
13
|
+
## Changed
|
|
14
|
+
|
|
15
|
+
- **Removed obsolete `XmlJats*` types** (`types.ts`): The unordered JATS element types no longer describe the parser output for PMC responses. Replaced with a short note pointing readers at `pmc-xml-helpers.ts` for the `JatsNode` / `JatsNodeList` interface.
|
|
16
|
+
|
|
17
|
+
## References
|
|
18
|
+
|
|
19
|
+
- Closes [#19](https://github.com/cyanheads/pubmed-mcp-server/issues/19) — PMC full-text parser lost document order on complex inline markup.
|
|
20
|
+
- Closes [#20](https://github.com/cyanheads/pubmed-mcp-server/issues/20) — `fetch_articles` / `fetch_fulltext` silently returned empty when all IDs were invalid.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "`pubmed_fetch_articles` format() renders all schema fields in `content[]` (closes [#26](https://github.com/cyanheads/pubmed-mcp-server/issues/26)); actionable PMID validation errors (#27); parser omits empty arrays for absent fields (#28)."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.3.11 — 2026-04-20
|
|
7
|
+
|
|
8
|
+
## Fixed
|
|
9
|
+
|
|
10
|
+
- **`pubmed_fetch_articles` — `format()` silently dropped schema fields from `content[]`** (`fetch-articles.tool.ts`): The rendered markdown that most LLM clients forward to the model omitted `articleDates`, per-author `firstName` / `orcid` / `affiliationIndices`, journal `issn` / `eIssn` / full publication date (month, day, medlineDate), the raw `pmcId`, grant `acronym`, and collapsed authors beyond the third to `et al.` — even though every value was present in `structuredContent`. Rewrote the formatter: authors render as a bulleted list with `firstName lastName`, 1-based affiliation markers that point into a new numbered `Affiliations` section, and an inline ORCID suffix when present; the journal line now renders year/month/day (or `medlineDate`) and the preferred ISSN; a new `Article Dates` line surfaces electronic/received/revised dates; grants show the acronym alongside the ID; and empty-result responses include a one-line hint that points the caller at `pubmed_search_articles`. Field-tested end-to-end over HTTP with PMIDs 13054692, 17960126, 36813558 to confirm every field in the output schema now appears in `content[]`.
|
|
11
|
+
|
|
12
|
+
## Changed
|
|
13
|
+
|
|
14
|
+
- **PMID schema validation error message — actionable across all tools accepting PMIDs** (`fetch-articles.tool.ts`, `fetch-fulltext.tool.ts`, `find-related.tool.ts`, `format-citations.tool.ts`): The shared `z.string().regex(/^\d+$/)` guard previously produced the raw `Invalid string: must match pattern /^\d+$/` message for every failure mode — trailing whitespace, comma-joined IDs, and non-digit input all looked identical. Supplied an explicit regex message that names the domain concept, shows an example (`"13054692"`), and lists the common pitfalls (whitespace, commas, non-digit characters) so the caller can self-correct without inspecting the regex.
|
|
15
|
+
- **Article parser omits empty arrays for absent optional fields** (`article-parser.ts`): `parseFullArticle` no longer returns `publicationTypes: []`, `keywords: []`, `articleDates: []`, `meshTerms: []` (when `includeMesh: true` but none present), or `grantList: []` (when `includeGrants: true` but none present). The schema already marked these `.optional()`; now the runtime matches the types. Reduces payload size on older papers (Watson & Crick 1953 no longer carries three empty arrays) and tightens the contract between parser output and the output schema.
|
|
16
|
+
|
|
17
|
+
## References
|
|
18
|
+
|
|
19
|
+
- Closes [#26](https://github.com/cyanheads/pubmed-mcp-server/issues/26) — `fetch_articles` format() dropped fields from `content[]` that were present in `structuredContent`.
|
|
20
|
+
- Closes [#27](https://github.com/cyanheads/pubmed-mcp-server/issues/27) — PMID validation error was opaque; now surfaces actionable guidance.
|
|
21
|
+
- Closes [#28](https://github.com/cyanheads/pubmed-mcp-server/issues/28) — Parser returned empty arrays for absent fields instead of omitting them.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
summary: "ESummary date parsing fix (`parseNcbiDate`), `pubmed_fetch_fulltext` now propagates eFetch errors, `pubmed_search_articles` uses `effectiveQuery` for PubMed link, `pubmed_format_citations` adds POST mode for large batches."
|
|
3
|
+
breaking: false
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 2.3.2 — 2026-04-04
|
|
7
|
+
|
|
8
|
+
## Fixed
|
|
9
|
+
|
|
10
|
+
- **ESummary date parsing**: Added dedicated `parseNcbiDate()` for NCBI's non-standard date formats (`YYYY Mon`, `YYYY Mon DD`, `YYYY Mon-Mon`, `YYYY`). chrono-node's `forwardDate` option was misinterpreting past months as future dates (e.g., "2018 Jun" resolved to a future June). The new parser handles all known NCBI formats as a fast path, falling back to chrono-node only for unrecognized strings.
|
|
11
|
+
- **`pubmed_search_articles`**: Search URL now uses `effectiveQuery` (post-filter) instead of raw `input.query`, so the PubMed link matches the actual search executed
|
|
12
|
+
- **`pubmed_fetch_fulltext`**: Removed try/catch that silently swallowed eFetch errors and returned empty results — errors now propagate per the "handlers throw" convention
|
|
13
|
+
- **`pubmed_format_citations`**: Added explicit `retmode: 'xml'` and POST mode for batches >= 25 PMIDs
|
|
14
|
+
|
|
15
|
+
## Changed
|
|
16
|
+
|
|
17
|
+
- **`pubmed_fetch_articles`**: Lowered POST threshold from > 200 to >= 100 PMIDs for more reliable large batch requests
|
|
18
|
+
|
|
19
|
+
## Added
|
|
20
|
+
|
|
21
|
+
- **Test coverage**: Comprehensive `parseNcbiDate` unit tests covering all 12 months, year-only, month ranges (dash/slash separators), whitespace handling, and rejection of invalid formats. Integration tests through `standardizeESummaryDate` and `extractBriefSummaries`. Optional live NCBI API integration tests (`NCBI_INTEGRATION=1`).
|
|
22
|
+
|
|
23
|
+
## Updated
|
|
24
|
+
|
|
25
|
+
- `@cyanheads/mcp-ts-core` to ^0.2.12
|
|
26
|
+
- `fast-xml-parser` to ^5.5.10
|
|
27
|
+
- `@types/node` to ^25.5.2
|