@cyanheads/pubmed-mcp-server 2.10.13 → 2.10.15

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.
Files changed (154) hide show
  1. package/AGENTS.md +24 -6
  2. package/CLAUDE.md +24 -6
  3. package/README.md +7 -7
  4. package/changelog/2.0.x/2.0.0.md +32 -0
  5. package/changelog/2.0.x/2.0.1.md +32 -0
  6. package/changelog/2.1.x/2.1.0.md +17 -0
  7. package/changelog/2.1.x/2.1.1.md +29 -0
  8. package/changelog/2.1.x/2.1.2.md +18 -0
  9. package/changelog/2.1.x/2.1.3.md +10 -0
  10. package/changelog/2.1.x/2.1.4.md +12 -0
  11. package/changelog/2.1.x/2.1.5.md +18 -0
  12. package/changelog/2.1.x/2.1.6.md +15 -0
  13. package/changelog/2.10.x/2.10.0.md +17 -0
  14. package/changelog/2.10.x/2.10.1.md +14 -0
  15. package/changelog/2.10.x/2.10.10.md +22 -0
  16. package/changelog/2.10.x/2.10.11.md +21 -0
  17. package/changelog/2.10.x/2.10.12.md +14 -0
  18. package/changelog/2.10.x/2.10.13.md +28 -0
  19. package/changelog/2.10.x/2.10.14.md +29 -0
  20. package/changelog/2.10.x/2.10.15.md +28 -0
  21. package/changelog/2.10.x/2.10.2.md +13 -0
  22. package/changelog/2.10.x/2.10.3.md +26 -0
  23. package/changelog/2.10.x/2.10.4.md +11 -0
  24. package/changelog/2.10.x/2.10.5.md +31 -0
  25. package/changelog/2.10.x/2.10.6.md +30 -0
  26. package/changelog/2.10.x/2.10.7.md +17 -0
  27. package/changelog/2.10.x/2.10.8.md +22 -0
  28. package/changelog/2.10.x/2.10.9.md +15 -0
  29. package/changelog/2.2.x/2.2.0.md +67 -0
  30. package/changelog/2.2.x/2.2.1.md +10 -0
  31. package/changelog/2.2.x/2.2.2.md +20 -0
  32. package/changelog/2.2.x/2.2.3.md +17 -0
  33. package/changelog/2.2.x/2.2.4.md +34 -0
  34. package/changelog/2.2.x/2.2.5.md +10 -0
  35. package/changelog/2.2.x/2.2.6.md +17 -0
  36. package/changelog/2.3.x/2.3.0.md +27 -0
  37. package/changelog/2.3.x/2.3.1.md +15 -0
  38. package/changelog/2.3.x/2.3.10.md +20 -0
  39. package/changelog/2.3.x/2.3.11.md +21 -0
  40. package/changelog/2.3.x/2.3.2.md +27 -0
  41. package/changelog/2.3.x/2.3.3.md +38 -0
  42. package/changelog/2.3.x/2.3.4.md +21 -0
  43. package/changelog/2.3.x/2.3.5.md +24 -0
  44. package/changelog/2.3.x/2.3.6.md +26 -0
  45. package/changelog/2.3.x/2.3.7.md +31 -0
  46. package/changelog/2.3.x/2.3.8.md +19 -0
  47. package/changelog/2.3.x/2.3.9.md +22 -0
  48. package/changelog/2.4.x/2.4.0.md +34 -0
  49. package/changelog/2.4.x/2.4.1.md +32 -0
  50. package/changelog/2.5.x/2.5.0.md +35 -0
  51. package/changelog/2.5.x/2.5.1.md +32 -0
  52. package/changelog/2.5.x/2.5.2.md +23 -0
  53. package/changelog/2.5.x/2.5.3.md +22 -0
  54. package/changelog/2.5.x/2.5.5.md +52 -0
  55. package/changelog/2.5.x/2.5.6.md +33 -0
  56. package/changelog/2.6.x/2.6.0.md +32 -0
  57. package/changelog/2.6.x/2.6.1.md +26 -0
  58. package/changelog/2.6.x/2.6.10.md +16 -0
  59. package/changelog/2.6.x/2.6.11.md +24 -0
  60. package/changelog/2.6.x/2.6.12.md +29 -0
  61. package/changelog/2.6.x/2.6.2.md +23 -0
  62. package/changelog/2.6.x/2.6.3.md +17 -0
  63. package/changelog/2.6.x/2.6.4.md +21 -0
  64. package/changelog/2.6.x/2.6.5.md +30 -0
  65. package/changelog/2.6.x/2.6.6.md +25 -0
  66. package/changelog/2.6.x/2.6.7.md +37 -0
  67. package/changelog/2.6.x/2.6.8.md +15 -0
  68. package/changelog/2.6.x/2.6.9.md +36 -0
  69. package/changelog/2.7.x/2.7.0.md +41 -0
  70. package/changelog/2.7.x/2.7.1.md +21 -0
  71. package/changelog/2.7.x/2.7.10.md +13 -0
  72. package/changelog/2.7.x/2.7.11.md +15 -0
  73. package/changelog/2.7.x/2.7.2.md +22 -0
  74. package/changelog/2.7.x/2.7.3.md +18 -0
  75. package/changelog/2.7.x/2.7.4.md +15 -0
  76. package/changelog/2.7.x/2.7.5.md +34 -0
  77. package/changelog/2.7.x/2.7.6.md +14 -0
  78. package/changelog/2.7.x/2.7.7.md +14 -0
  79. package/changelog/2.7.x/2.7.8.md +18 -0
  80. package/changelog/2.7.x/2.7.9.md +16 -0
  81. package/changelog/2.8.x/2.8.0.md +23 -0
  82. package/changelog/2.9.x/2.9.0.md +21 -0
  83. package/changelog/2.9.x/2.9.1.md +12 -0
  84. package/changelog/2.9.x/2.9.10.md +15 -0
  85. package/changelog/2.9.x/2.9.2.md +21 -0
  86. package/changelog/2.9.x/2.9.3.md +11 -0
  87. package/changelog/2.9.x/2.9.4.md +24 -0
  88. package/changelog/2.9.x/2.9.5.md +20 -0
  89. package/changelog/2.9.x/2.9.6.md +22 -0
  90. package/changelog/2.9.x/2.9.7.md +26 -0
  91. package/changelog/2.9.x/2.9.8.md +15 -0
  92. package/changelog/2.9.x/2.9.9.md +35 -0
  93. package/changelog/template.md +151 -0
  94. package/dist/index.js +1 -0
  95. package/dist/index.js.map +1 -1
  96. package/dist/mcp-server/tools/definitions/convert-ids.tool.d.ts +7 -2
  97. package/dist/mcp-server/tools/definitions/convert-ids.tool.d.ts.map +1 -1
  98. package/dist/mcp-server/tools/definitions/fetch-articles.tool.d.ts +7 -2
  99. package/dist/mcp-server/tools/definitions/fetch-articles.tool.d.ts.map +1 -1
  100. package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.d.ts +14 -5
  101. package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.d.ts.map +1 -1
  102. package/dist/mcp-server/tools/definitions/find-related.tool.d.ts +0 -60
  103. package/dist/mcp-server/tools/definitions/find-related.tool.d.ts.map +1 -1
  104. package/dist/mcp-server/tools/definitions/find-related.tool.js +4 -4
  105. package/dist/mcp-server/tools/definitions/find-related.tool.js.map +1 -1
  106. package/dist/mcp-server/tools/definitions/format-citations.tool.d.ts +7 -2
  107. package/dist/mcp-server/tools/definitions/format-citations.tool.d.ts.map +1 -1
  108. package/dist/mcp-server/tools/definitions/lookup-citation.tool.d.ts +7 -2
  109. package/dist/mcp-server/tools/definitions/lookup-citation.tool.d.ts.map +1 -1
  110. package/dist/mcp-server/tools/definitions/lookup-mesh.tool.d.ts +7 -2
  111. package/dist/mcp-server/tools/definitions/lookup-mesh.tool.d.ts.map +1 -1
  112. package/dist/mcp-server/tools/definitions/pubmed-europepmc-fetch.tool.d.ts +6 -3
  113. package/dist/mcp-server/tools/definitions/pubmed-europepmc-fetch.tool.d.ts.map +1 -1
  114. package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.d.ts +6 -3
  115. package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.d.ts.map +1 -1
  116. package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.js +1 -1
  117. package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.js.map +1 -1
  118. package/dist/mcp-server/tools/definitions/search-articles.tool.d.ts +7 -2
  119. package/dist/mcp-server/tools/definitions/search-articles.tool.d.ts.map +1 -1
  120. package/dist/mcp-server/tools/definitions/spell-check.tool.d.ts +7 -2
  121. package/dist/mcp-server/tools/definitions/spell-check.tool.d.ts.map +1 -1
  122. package/dist/services/error-contracts.d.ts +30 -13
  123. package/dist/services/error-contracts.d.ts.map +1 -1
  124. package/dist/services/error-contracts.js +30 -13
  125. package/dist/services/error-contracts.js.map +1 -1
  126. package/dist/services/europe-pmc/api-client.d.ts +14 -2
  127. package/dist/services/europe-pmc/api-client.d.ts.map +1 -1
  128. package/dist/services/europe-pmc/api-client.js +30 -4
  129. package/dist/services/europe-pmc/api-client.js.map +1 -1
  130. package/dist/services/europe-pmc/europe-pmc-service.d.ts +20 -1
  131. package/dist/services/europe-pmc/europe-pmc-service.d.ts.map +1 -1
  132. package/dist/services/europe-pmc/europe-pmc-service.js +98 -50
  133. package/dist/services/europe-pmc/europe-pmc-service.js.map +1 -1
  134. package/dist/services/ncbi/ncbi-service.d.ts +18 -26
  135. package/dist/services/ncbi/ncbi-service.d.ts.map +1 -1
  136. package/dist/services/ncbi/ncbi-service.js +111 -127
  137. package/dist/services/ncbi/ncbi-service.js.map +1 -1
  138. package/dist/services/ncbi/request-queue.d.ts +22 -30
  139. package/dist/services/ncbi/request-queue.d.ts.map +1 -1
  140. package/dist/services/ncbi/request-queue.js +29 -128
  141. package/dist/services/ncbi/request-queue.js.map +1 -1
  142. package/dist/services/ncbi/response-handler.d.ts +14 -1
  143. package/dist/services/ncbi/response-handler.d.ts.map +1 -1
  144. package/dist/services/ncbi/response-handler.js +65 -9
  145. package/dist/services/ncbi/response-handler.js.map +1 -1
  146. package/dist/services/openalex/openalex-service.d.ts.map +1 -1
  147. package/dist/services/openalex/openalex-service.js +3 -10
  148. package/dist/services/openalex/openalex-service.js.map +1 -1
  149. package/package.json +20 -11
  150. package/server.json +3 -3
  151. package/dist/services/retry-policy.d.ts +0 -18
  152. package/dist/services/retry-policy.d.ts.map +0 -1
  153. package/dist/services/retry-policy.js +0 -21
  154. package/dist/services/retry-policy.js.map +0 -1
@@ -0,0 +1,29 @@
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` ^5.0.0 → ^5.0.1
27
+ - `@vitest/coverage-istanbul` ^5.0.0 → ^5.0.1
28
+ - `fast-check` ^4.9.0 → ^4.10.0
29
+ - `tsc-alias` ^1.9.4 → ^1.9.5
@@ -0,0 +1,28 @@
1
+ ---
2
+ summary: "Retries NCBI's proxy_stream() backend relays and Europe PMC's empty-envelope and outage responses instead of surfacing them as caller errors, and closes the NCBI request queue on a 429 for every caller at once."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.10.15 — 2026-09-22
8
+
9
+ ## Changed
10
+
11
+ - **`pubmed_find_related` declares only `all_providers_failed` in its error contract.** Its handler catches every NCBI, Europe PMC, and OpenAlex failure and reports it inside `data.attempted` or the `coverageFailures` enrichment, so the service reasons it used to spread never reached the caller as `data.reason`.
12
+ - **Tooling and docs:** every service-layer entry in `src/services/error-contracts.ts` carries `thrownBy: 'service'` for mcp-ts-core 0.13.6's `error-contract-unthrown` lint rule; `scripts/lint-mcp.ts` reads its rule options from the `lint` block of `devcheck.config.json`; the README and `.env.example` describe the new queue and deadline, and `.env.example` shows the actual `NCBI_MAX_RETRIES` default (6); `framework-skills/` synced to mcp-ts-core 0.13.6.
13
+
14
+ ## Fixed
15
+
16
+ - **EFetch's `proxy_stream()` backend relays now retry as `ncbi_unreachable`** instead of a bare HTTP 400 — the same envelope NCBI's backend proxy prefixes onto a relayed 502 page or a "Failed to connect to PubOne service" message. [#155](https://github.com/cyanheads/pubmed-mcp-server/issues/155)
17
+ - **An all-invalid EFetch ID list (`id=00000000`, `pubmed`/`pmc` only) now resolves as the empty set NCBI returns for a well-formed but unknown ID**, instead of an `ID list is empty!` error. [#155](https://github.com/cyanheads/pubmed-mcp-server/issues/155)
18
+ - **The NCBI request queue is rebuilt on the framework pacer**, closing one shared cooldown gate for every queued caller on an NCBI 429 instead of each caller retrying independently into the same throttle. The gate holds one second, doubles per consecutive 429 up to 15 seconds, and resets on the first success; a `Retry-After` is honored within the same ceiling. Each retry attempt now re-queues and re-paces under one deadline covering queue wait, attempts, and backoff; a call that cannot start in time sheds as `queue_full` with `retryAfter`. [#157](https://github.com/cyanheads/pubmed-mcp-server/issues/157)
19
+ - **A Europe PMC `/search` 404 now retries and ends as `ServiceUnavailable` with `europepmc_unreachable`** instead of a bare `NotFound` — a failed search is an outage, not "no match"; a genuine zero-hit query stays HTTP 200. A 500, 502, or 503 ends the same way. Only a `ServiceUnavailable` now carries that reason: a Europe PMC call whose retries run out on a `Timeout` (a 504) or a `RateLimited` (a 429) keeps its code with no reason, and a 429 keeps its `retryAfter`. [#152](https://github.com/cyanheads/pubmed-mcp-server/issues/152)
20
+ - **Europe PMC's intermittent empty `{version}` envelope now retries as transient upstream noise** instead of failing fast as invalid input; one that persists through every attempt ends as retryable `europepmc_unreachable`. A `sort` with a key outside the four documented fields or without an `asc`/`desc` direction — each key of a comma-separated sort checked on its own — still fails fast as `europepmc_invalid_input` naming the sort. A non-default `cursorMark` whose envelope persists through the full retry budget ends as `europepmc_invalid_input` naming the cursor. The `pubmed_europepmc_search` `sort` description now covers multi-key sorts and undocumented fields. [#159](https://github.com/cyanheads/pubmed-mcp-server/issues/159)
21
+
22
+ ## Dependencies
23
+
24
+ - `@cyanheads/mcp-ts-core` ^0.13.4 → ^0.13.6
25
+ - `defuddle` ^0.19.3 → ^0.19.4
26
+ - `@biomejs/biome` ^2.5.13 → ^2.5.14
27
+ - `@types/node` ^26.5.1 → ^26.6.2
28
+ - `fast-check` ^4.10.0 → ^4.10.2
@@ -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,10 @@
1
+ ---
2
+ summary: "Maintenance — `@cyanheads/mcp-ts-core` bumped to ^0.2.8. No runtime API changes."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.2.5 — 2026-03-28
7
+
8
+ ## Updated
9
+
10
+ - `@cyanheads/mcp-ts-core` to ^0.2.8
@@ -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
@@ -0,0 +1,38 @@
1
+ ---
2
+ summary: "Output enrichments — `pubmed_search_articles` adds `effectiveQuery` and `appliedFilters`, `pubmed_lookup_citation` adds per-citation `status`, `pubmed_format_citations` adds partial-result counters."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.3.3 — 2026-04-09
7
+
8
+ ## Added
9
+
10
+ - **`pubmed_search_articles`**: Returned `effectiveQuery` and normalized `appliedFilters` metadata so clients can inspect the exact filters sent to PubMed
11
+ - **`pubmed_lookup_citation`**: Returned per-citation `status` (`matched`, `not_found`, `ambiguous`) and ECitMatch detail for non-exact outcomes
12
+ - **`pubmed_format_citations`**: Returned `totalSubmitted`, `totalFormatted`, and `unavailablePmids` for partial-result handling
13
+ - **Skill**: Added the `code-simplifier` agent skill for cleanup/refinement passes after edits
14
+ - **Test coverage**: Added `tests/index.test.ts` for `createApp()` registration/setup and expanded tool/service tests for search, citation lookup, citation formatting, related articles, and fulltext flows, including regression coverage for normalized `appliedFilters` output and summary clamping in `pubmed_search_articles`
15
+
16
+ ## Fixed
17
+
18
+ - **`pubmed_search_articles`**: History-backed summary fetches now clamp to the returned PMID page, and `appliedFilters` now reports the normalized/sanitized values actually sent to PubMed
19
+
20
+ ## Changed
21
+
22
+ - **`pubmed_search_articles`**: Format output now shows the effective query and a normalized Applied Filters section
23
+ - **`pubmed_lookup_citation`**: Format output now gives next-step guidance for ambiguous and unmatched citations instead of a flat status table
24
+
25
+ ## Updated
26
+
27
+ - `@cyanheads/mcp-ts-core` to ^0.3.4
28
+ - `fast-xml-parser` to ^5.5.11
29
+ - `vitest` to ^4.1.4
30
+ - Added `@vitest/coverage-istanbul` for coverage support
31
+
32
+ ## Security
33
+
34
+ - Added/raised overrides for `@hono/node-server` (>=1.19.13), `hono` (>=4.12.12), and `vite` (>=8.0.8)
35
+
36
+ ## Docs
37
+
38
+ - Marked `docs/design.md` as historical and regenerated `docs/tree.md` for the current project layout