@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
package/AGENTS.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** @cyanheads/pubmed-mcp-server
4
- **Version:** 2.10.13
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.0`
4
+ **Version:** 2.10.15
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
 
8
8
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -157,6 +157,22 @@ await createApp({
157
157
 
158
158
  `instructions` is optional server-level orientation, sent on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
159
159
 
160
+ ### Session posture and shutdown
161
+
162
+ Two more `createApp()` options shape how the server runs rather than how it presents itself:
163
+
164
+ ```ts
165
+ await createApp({
166
+ sessionMode: 'stateless', // or { default: 'stateful', require: 'stateful' }
167
+ setup(core) { startMyWatcher(core.config); },
168
+ async teardown() { await stopMyWatcher(); },
169
+ });
170
+ ```
171
+
172
+ `sessionMode` declares the HTTP session posture in `src/` instead of leaving it to a deployment's `MCP_SESSION_MODE`, which still wins whenever it carries a meaningful value (an empty string and an unsubstituted `${…}` placeholder read as unset and fall through to the option). This server declares `'stateless'` — no tool calls `ctx.requestInput` — and `.env.example`, the `Dockerfile`, and the README env table say the same; keep all four in agreement. Add `require: 'stateful'` if a tool ever asks the caller for input mid-handler: startup then fails with a `ConfigurationError` rather than serving a mode in which a 2025-era client can never answer the prompt. Stdio is never refused.
173
+
174
+ `teardown(core)` is the `setup()` counterpart — release a watcher, socket, or non-`unref()`'d timer there. It runs after the transport stops and before the logger closes, on every shutdown path, and a signal-triggered shutdown then exits the process explicitly (0, or 1 if a step never settles within the framework's 10 s ceiling). None of this server's services holds such a handle today (the request queues arm per-request dispatch timers only), so it declares no `teardown`.
175
+
160
176
  ---
161
177
 
162
178
  ## Context
@@ -182,7 +198,7 @@ Handlers receive a unified `ctx` object. Key properties:
182
198
 
183
199
  Handlers throw — the framework catches, classifies, and formats.
184
200
 
185
- **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. The `recovery` field is required (≥ 5 words, lint-validated) — it's the single source of truth for the recovery hint. Spread `ctx.recoveryFor('reason')` into `data` to mirror the contract recovery onto the wire (the framework mirrors `data.recovery.hint` into `content[]` text); pass an explicit `recovery: { hint: '...' }` when runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely and don't need declaring.
201
+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` on `tool()` / `resource()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. The `recovery` field is required (≥ 5 words, lint-validated) — it's the single source of truth for the recovery hint. Spread `ctx.recoveryFor('reason')` into `data` to mirror the contract recovery onto the wire (the framework mirrors `data.recovery.hint` into `content[]` text unless the message already contains it verbatim, then closes the text with `(reason <reason> · not retryable)`); pass an explicit `recovery: { hint: '...' }` when runtime context matters. Forwarding is lint-enforced per throw site (`error-contract-recovery-unforwarded`). A declared reason the handler never names warns as `error-contract-unthrown` — entries the service layer throws carry `thrownBy: 'service'` (lint-only metadata; every entry in `src/services/error-contracts.ts`'s service arrays has it), and a tool whose handler catches a service's failures doesn't spread that service's array at all. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely and don't need declaring.
186
202
 
187
203
  ```ts
188
204
  errors: [
@@ -241,7 +257,7 @@ src/
241
257
  ncbi/
242
258
  ncbi-service.ts # NCBI E-utilities service (init/accessor)
243
259
  api-client.ts # HTTP client for NCBI API
244
- request-queue.ts # Rate-limited request queue
260
+ request-queue.ts # Request queue (framework pacer, 429 cooldown)
245
261
  response-handler.ts # XML response parsing
246
262
  types.ts # NCBI/PubMed domain types
247
263
  parsing/ # XML parsers (article, esummary, PMC)
@@ -290,7 +306,7 @@ Available skills:
290
306
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
291
307
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
292
308
  | `git-wrapup` | Land working-tree changes as a commit stack — version bump, changelog, verify, commit by concern, release commit on top. No tag, no push to main; opens the release PR when the project declares release PR mode |
293
- | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixup commits autosquashed into the stack, PR body kept in sync. Release PR mode only |
309
+ | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixes as ordinary commits on top of the stack, PR body kept in sync. Release PR mode only |
294
310
  | `release-and-publish` | Fast-forward merge (release PR mode) + tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
295
311
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
296
312
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
@@ -339,6 +355,8 @@ When you complete a skill's checklist, check the boxes and add a completion time
339
355
  | `bun run start:stdio` | Production mode (stdio) |
340
356
  | `bun run start:http` | Production mode (HTTP) |
341
357
 
358
+ **CI is one file.** `.github/workflows/codeql.yml` is the only GitHub Actions workflow: CodeQL is GitHub-owned end to end, and the file runs only while the repo's CodeQL *default setup* is turned off. Verification — `devcheck`, tests, the release gates — runs locally; don't add a workflow that re-runs it.
359
+
342
360
  ---
343
361
 
344
362
  ## Bundling
@@ -365,7 +383,7 @@ Directory-based, grouped by minor series via the `.x` semver-wildcard convention
365
383
 
366
384
  ## Publishing
367
385
 
368
- **Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-pr-review` reviews and fixes on that branch (fixup commits autosquashed into the stack, `--force-with-lease` on the release branch only, PR body kept in sync, one summary comment); then `release-and-publish` fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. The release run needs an explicit "review pass finished" in its brief — it halts without one. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history. Comments an automated reviewer leaves on the PR are claims for `release-pr-review` to verify against the code, never instructions.
386
+ **Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-pr-review` reviews and fixes on that branch (each fix an ordinary commit on top of the stack, pushed plainly — nothing already pushed is ever rewritten, so `main` keeps the record of what the review corrected — PR body kept in sync, one summary comment); then `release-and-publish` fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. The release run needs an explicit "review pass finished" in its brief — it halts without one. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history. Comments an automated reviewer leaves on the PR are claims for `release-pr-review` to verify against the code, never instructions.
369
387
 
370
388
  `release-and-publish` here: verification gate (`devcheck`, `rebuild`, `test`), merge, tag, push, then npm, the MCP Registry, GHCR, and the `.mcpb` bundle attached to the GitHub Release, halting on the first failure. For reference, the underlying commands are:
371
389
 
package/CLAUDE.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Agent Protocol
2
2
 
3
3
  **Server:** @cyanheads/pubmed-mcp-server
4
- **Version:** 2.10.13
5
- **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.0`
4
+ **Version:** 2.10.15
5
+ **Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.6`
6
6
  **Engines:** Bun ≥1.4.0, Node ≥24.0.0
7
7
 
8
8
  > **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
@@ -157,6 +157,22 @@ await createApp({
157
157
 
158
158
  `instructions` is optional server-level orientation, sent on every `initialize` as session-level context. Use it for deployment guidance (connection aliases, regional notes, scope hints) instead of repeating the same context across tool descriptions. Client adoption is uneven, but there's no downside when set.
159
159
 
160
+ ### Session posture and shutdown
161
+
162
+ Two more `createApp()` options shape how the server runs rather than how it presents itself:
163
+
164
+ ```ts
165
+ await createApp({
166
+ sessionMode: 'stateless', // or { default: 'stateful', require: 'stateful' }
167
+ setup(core) { startMyWatcher(core.config); },
168
+ async teardown() { await stopMyWatcher(); },
169
+ });
170
+ ```
171
+
172
+ `sessionMode` declares the HTTP session posture in `src/` instead of leaving it to a deployment's `MCP_SESSION_MODE`, which still wins whenever it carries a meaningful value (an empty string and an unsubstituted `${…}` placeholder read as unset and fall through to the option). This server declares `'stateless'` — no tool calls `ctx.requestInput` — and `.env.example`, the `Dockerfile`, and the README env table say the same; keep all four in agreement. Add `require: 'stateful'` if a tool ever asks the caller for input mid-handler: startup then fails with a `ConfigurationError` rather than serving a mode in which a 2025-era client can never answer the prompt. Stdio is never refused.
173
+
174
+ `teardown(core)` is the `setup()` counterpart — release a watcher, socket, or non-`unref()`'d timer there. It runs after the transport stops and before the logger closes, on every shutdown path, and a signal-triggered shutdown then exits the process explicitly (0, or 1 if a step never settles within the framework's 10 s ceiling). None of this server's services holds such a handle today (the request queues arm per-request dispatch timers only), so it declares no `teardown`.
175
+
160
176
  ---
161
177
 
162
178
  ## Context
@@ -182,7 +198,7 @@ Handlers receive a unified `ctx` object. Key properties:
182
198
 
183
199
  Handlers throw — the framework catches, classifies, and formats.
184
200
 
185
- **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable? }]` on `tool()` / `resource()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. The `recovery` field is required (≥ 5 words, lint-validated) — it's the single source of truth for the recovery hint. Spread `ctx.recoveryFor('reason')` into `data` to mirror the contract recovery onto the wire (the framework mirrors `data.recovery.hint` into `content[]` text); pass an explicit `recovery: { hint: '...' }` when runtime context matters. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely and don't need declaring.
201
+ **Recommended: typed error contract.** Declare `errors: [{ reason, code, when, recovery, retryable?, severity?, thrownBy? }]` on `tool()` / `resource()` to receive a typed `ctx.fail(reason, …)` keyed by the declared reason union. TypeScript catches `ctx.fail('typo')` at compile time, `data.reason` is auto-populated for observability, and the linter enforces conformance against the handler body. The `recovery` field is required (≥ 5 words, lint-validated) — it's the single source of truth for the recovery hint. Spread `ctx.recoveryFor('reason')` into `data` to mirror the contract recovery onto the wire (the framework mirrors `data.recovery.hint` into `content[]` text unless the message already contains it verbatim, then closes the text with `(reason <reason> · not retryable)`); pass an explicit `recovery: { hint: '...' }` when runtime context matters. Forwarding is lint-enforced per throw site (`error-contract-recovery-unforwarded`). A declared reason the handler never names warns as `error-contract-unthrown` — entries the service layer throws carry `thrownBy: 'service'` (lint-only metadata; every entry in `src/services/error-contracts.ts`'s service arrays has it), and a tool whose handler catches a service's failures doesn't spread that service's array at all. Baseline codes (`InternalError`, `ServiceUnavailable`, `Timeout`, `ValidationError`, `SerializationError`, `RequestCancelled`) bubble freely and don't need declaring.
186
202
 
187
203
  ```ts
188
204
  errors: [
@@ -241,7 +257,7 @@ src/
241
257
  ncbi/
242
258
  ncbi-service.ts # NCBI E-utilities service (init/accessor)
243
259
  api-client.ts # HTTP client for NCBI API
244
- request-queue.ts # Rate-limited request queue
260
+ request-queue.ts # Request queue (framework pacer, 429 cooldown)
245
261
  response-handler.ts # XML response parsing
246
262
  types.ts # NCBI/PubMed domain types
247
263
  parsing/ # XML parsers (article, esummary, PMC)
@@ -290,7 +306,7 @@ Available skills:
290
306
  | `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
291
307
  | `maintenance` | Investigate changelogs, adopt upstream changes, sync skills to agent dirs |
292
308
  | `git-wrapup` | Land working-tree changes as a commit stack — version bump, changelog, verify, commit by concern, release commit on top. No tag, no push to main; opens the release PR when the project declares release PR mode |
293
- | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixup commits autosquashed into the stack, PR body kept in sync. Release PR mode only |
309
+ | `release-pr-review` | Review pass on an open release PR — simplifier + correctness review, fixes as ordinary commits on top of the stack, PR body kept in sync. Release PR mode only |
294
310
  | `release-and-publish` | Fast-forward merge (release PR mode) + tag + push + npm + MCP Registry + GH Release + Docker. Picks up from `git-wrapup` |
295
311
  | `orchestrations` | Chain task skills into a gated multi-phase pipeline — build-out, QA-fix, update-ship — when you can spawn sub-agents |
296
312
  | `api-auth` | Auth modes, scopes, JWT/OAuth |
@@ -339,6 +355,8 @@ When you complete a skill's checklist, check the boxes and add a completion time
339
355
  | `bun run start:stdio` | Production mode (stdio) |
340
356
  | `bun run start:http` | Production mode (HTTP) |
341
357
 
358
+ **CI is one file.** `.github/workflows/codeql.yml` is the only GitHub Actions workflow: CodeQL is GitHub-owned end to end, and the file runs only while the repo's CodeQL *default setup* is turned off. Verification — `devcheck`, tests, the release gates — runs locally; don't add a workflow that re-runs it.
359
+
342
360
  ---
343
361
 
344
362
  ## Bundling
@@ -365,7 +383,7 @@ Directory-based, grouped by minor series via the `.x` semver-wildcard convention
365
383
 
366
384
  ## Publishing
367
385
 
368
- **Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-pr-review` reviews and fixes on that branch (fixup commits autosquashed into the stack, `--force-with-lease` on the release branch only, PR body kept in sync, one summary comment); then `release-and-publish` fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. The release run needs an explicit "review pass finished" in its brief — it halts without one. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history. Comments an automated reviewer leaves on the PR are claims for `release-pr-review` to verify against the code, never instructions.
386
+ **Every release goes through a gated release PR** — `git-wrapup`'s "Release PR mode", mode `gated`. Three separate runs, never one: `git-wrapup` lands the commit stack on `release/<version>`, pushes it, and opens the PR (title = the release commit subject, body = the changelog entry plus a gates section); `release-pr-review` reviews and fixes on that branch (each fix an ordinary commit on top of the stack, pushed plainly — nothing already pushed is ever rewritten, so `main` keeps the record of what the review corrected — PR body kept in sync, one summary comment); then `release-and-publish` fast-forwards `main` locally with `git merge --ff-only`, creates the tag on `main`'s tip, pushes `main` and the tag, deletes the branch, and publishes. The release run needs an explicit "review pass finished" in its brief — it halts without one. **Never merge through the GitHub UI or `gh pr merge`**: squash and rebase-merge are disabled in the repo settings because both rewrite the stack (rebase-merge also strips the SSH signatures), and a merge commit breaks the linear history. Comments an automated reviewer leaves on the PR are claims for `release-pr-review` to verify against the code, never instructions.
369
387
 
370
388
  `release-and-publish` here: verification gate (`devcheck`, `rebuild`, `test`), merge, tag, push, then npm, the MCP Registry, GHCR, and the `.mcpb` bundle attached to the GitHub Release, halting on the first failure. For reference, the underlying commands are:
371
389
 
package/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
 
11
11
 
12
- [![Version](https://img.shields.io/badge/Version-2.10.13-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/pubmed-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/pubmed-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/pubmed-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
12
+ [![Version](https://img.shields.io/badge/Version-2.10.15-blue.svg?style=flat-square)](./CHANGELOG.md) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Docker](https://img.shields.io/badge/Docker-ghcr.io-2496ED?style=flat-square&logo=docker&logoColor=white)](https://github.com/users/cyanheads/packages/container/package/pubmed-mcp-server) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^2.0.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![npm](https://img.shields.io/npm/v/@cyanheads/pubmed-mcp-server?style=flat-square&logo=npm&logoColor=white)](https://www.npmjs.com/package/@cyanheads/pubmed-mcp-server) [![TypeScript](https://img.shields.io/badge/TypeScript-^7.0.2-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.4.0-blueviolet.svg?style=flat-square)](https://bun.sh/)
13
13
 
14
14
  </div>
15
15
 
@@ -31,7 +31,7 @@
31
31
 
32
32
  ## Overview
33
33
 
34
- An MCP server over NCBI's E-utilities, PubMed Central, and Europe PMC. Search the biomedical literature, fetch metadata and full text, resolve identifiers and partial citations, format references, and ground queries in MeSH vocabulary. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
34
+ The biomedical literature via NCBI's E-utilities, PubMed Central, and Europe PMC. Search it, fetch metadata and full text, resolve identifiers and partial citations, format references, and ground queries in MeSH vocabulary. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
35
35
 
36
36
  ### Tools
37
37
 
@@ -176,12 +176,12 @@ An MCP server over NCBI's E-utilities, PubMed Central, and Europe PMC. Search th
176
176
 
177
177
  ## Features
178
178
 
179
- Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): stdio and Streamable HTTP transports (Cloudflare Workers from the same codebase), pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing.
179
+ Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): stdio and Streamable HTTP transports, pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing.
180
180
 
181
181
  PubMed-specific:
182
182
 
183
183
  - Complete NCBI E-utilities integration (ESearch, EFetch, ESummary, ELink, ESpell, EInfo, ECitMatch) plus PMC ID Converter
184
- - Sequential request queue with configurable delay for NCBI rate limit compliance
184
+ - Shared NCBI request queue — paced request starts, capped concurrency, a cooldown that holds every caller after an NCBI 429, and one deadline covering queue wait and retries
185
185
  - NCBI-specific XML parser with `isArray` hints for PubMed's inconsistent XML structure
186
186
  - Hand-rolled citation formatters (APA, MLA, BibTeX, RIS, Vancouver) — zero deps, Workers-compatible
187
187
 
@@ -271,7 +271,7 @@ MCP_TRANSPORT_TYPE=http MCP_HTTP_PORT=3010 bun run start:http
271
271
 
272
272
  ### Prerequisites
273
273
 
274
- - [Bun v1.3.2](https://bun.sh/) or higher.
274
+ - [Bun v1.4.0](https://bun.sh/) or higher.
275
275
  - Optional: [NCBI API key](https://www.ncbi.nlm.nih.gov/account/settings/) for higher rate limits (10 req/s vs 3 req/s).
276
276
 
277
277
  ### Installation
@@ -296,7 +296,7 @@ bun install
296
296
 
297
297
  ## Configuration
298
298
 
299
- All configuration is validated at startup via Zod schemas in `src/config/server-config.ts`. Key environment variables:
299
+ Key environment variables:
300
300
 
301
301
  | Variable | Description | Default |
302
302
  |:---|:---|:---|
@@ -316,7 +316,7 @@ All configuration is validated at startup via Zod schemas in `src/config/server-
316
316
  | `NCBI_MAX_CONCURRENT` | Max concurrent in-flight NCBI requests | `8` |
317
317
  | `NCBI_MAX_RETRIES` | Retry attempts for failed NCBI requests | 6 |
318
318
  | `NCBI_TIMEOUT_MS` | Per-request HTTP timeout in ms | `30000` |
319
- | `NCBI_TOTAL_DEADLINE_MS` | Total deadline across all retry attempts for one NCBI call, in ms | `60000` |
319
+ | `NCBI_TOTAL_DEADLINE_MS` | Total deadline for one NCBI call — queue wait, retry attempts, and backoff — in ms. A call the queue cannot start before it is rejected at once | `60000` |
320
320
  | `UNPAYWALL_EMAIL` | Contact email for Unpaywall. When set, `pubmed_fetch_fulltext` falls back to Unpaywall open-access copies for non-PMC DOIs | none |
321
321
  | `UNPAYWALL_TIMEOUT_MS` | Per-request HTTP timeout for Unpaywall lookups and content fetches, in ms | `20000` |
322
322
  | `EUROPEPMC_ENABLED` | Enable Europe PMC search tool and the `pubmed_fetch_fulltext` JATS fallback chain. Set `false` to disable all EPMC calls and skip tool registration. | `true` |
@@ -0,0 +1,32 @@
1
+ ---
2
+ summary: "Initial release — 7 PubMed tools, NCBI E-utilities service layer (eSearch/eSummary/eFetch/eLink/eSpell/eInfo with rate-limit + retry), `research_plan` prompt, and `pubmed://database/info` resource."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.0.0 — 2026-03-04
7
+
8
+ ## Added
9
+
10
+ - **NCBI Service Layer**: Complete E-utilities integration (`eSearch`, `eSummary`, `eFetch`, `eLink`, `eSpell`, `eInfo`) with request queuing, rate limiting, retry with exponential backoff, and XML parsing.
11
+ - **7 PubMed Tools**:
12
+ - `pubmed_search` — Search PubMed with filters, date ranges, and optional summaries
13
+ - `pubmed_fetch` — Fetch full article metadata by PMIDs (abstract, authors, journal, MeSH)
14
+ - `pubmed_cite` — Generate formatted citations (APA 7th, MLA 9th, BibTeX, RIS)
15
+ - `pubmed_related` — Find related/cited-by/references via ELink
16
+ - `pubmed_spell` — Spell-check biomedical queries via ESpell
17
+ - `pubmed_trending` — Date-filtered search for recent publications
18
+ - `pubmed_mesh_lookup` — MeSH vocabulary search and exploration
19
+ - **Research Plan Prompt**: `research_plan` — structured 4-phase biomedical research plan generation
20
+ - **Database Info Resource**: `pubmed://database/info` — PubMed database metadata via EInfo
21
+ - **Citation Formatters**: Hand-rolled, zero-dependency, Workers-compatible formatters for APA, MLA, BibTeX, and RIS
22
+ - **NCBI Configuration**: `NCBI_API_KEY`, `NCBI_ADMIN_EMAIL`, `NCBI_REQUEST_DELAY_MS`, `NCBI_MAX_RETRIES`, `NCBI_TIMEOUT_MS`
23
+
24
+ ## Changed
25
+
26
+ - **Rebranded** from `mcp-ts-template` to `@cyanheads/pubmed-mcp-server` (package.json, server.json, smithery.yaml, wrangler.toml)
27
+ - **Architecture**: Built on mcp-ts-template 3.0 with DI container, typed tokens, Zod-validated config, OpenTelemetry, and multi-transport support (stdio, HTTP, Cloudflare Workers)
28
+
29
+ ## Removed
30
+
31
+ - All template example tools, resources, prompts, and services (graph, LLM, speech)
32
+ - `openai`, `@modelcontextprotocol/ext-apps` dependencies
@@ -0,0 +1,32 @@
1
+ ---
2
+ summary: "`pubmed_search` gains field filters, offset pagination, and PMC URLs; `pubmed_mesh_lookup` exact-heading sort; `pubmed_trending` removed; six tool and config defaults revised."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.0.1 — 2026-03-04
7
+
8
+ ## Added
9
+
10
+ - **Search filters**: `pubmed_search` gained field-specific filters (`author`, `journal`, `meshTerms`, `language`, `hasAbstract`, `freeFullText`, `species`) and pagination via `offset`
11
+ - **PMC links**: `pubmed_fetch` and `pubmed_search` summaries now include `pmcId`, `pubmedUrl`, and `pmcUrl` for direct article access
12
+ - **Affiliation deduplication**: article parser collects affiliations into a single array with per-author index references, reducing payload size for multi-center papers
13
+ - **Exact MeSH heading search**: `pubmed_mesh_lookup` runs a parallel `[MH]` exact-heading search and stable-sorts exact matches to the top
14
+
15
+ ## Changed
16
+
17
+ - **`pubmed_search`**: renamed `includeSummaries` to `summaryCount`; date range format changed from `YYYY/MM/DD` to `YYYY-MM-DD` (auto-converted internally)
18
+ - **`pubmed_cite`**: max PMIDs raised from 20 to 50
19
+ - **`pubmed_related`**: simplified to use `cmd=neighbor` for all relationship types instead of `neighbor_history` + WebEnv for cited_by/references
20
+ - **`pubmed_mesh_lookup`**: `includeDetails` now defaults to `true`; switched from eFetch to eSummary for detail retrieval (MeSH eFetch returns plain text, not XML)
21
+ - **NCBI response handler**: demoted `eSearchResult.ErrorList` (PhraseNotFound, FieldNotFound) from errors to warnings — NCBI populates these on valid zero-result queries; enabled `processEntities` and `htmlEntities` in XML parser
22
+ - **Config defaults**: HTTP port 3010 → 3017, transport default `http` → `stdio`, storage default `filesystem` → `in-memory`
23
+
24
+ ## Fixed
25
+
26
+ - **Auth factory tests**: JWT strategy tests now provide `mcpAuthSecretKey` and restore it on teardown
27
+ - **Response handler tests**: updated assertions to match ErrorList demotion (PhraseNotFound is a warning, not a thrown error)
28
+ - **Conformance tests**: removed `pubmed_trending` from expected tools list
29
+
30
+ ## Removed
31
+
32
+ - **`pubmed_trending` tool**: removed — its functionality is fully covered by `pubmed_search` with date range and `pub_date` sort
@@ -0,0 +1,17 @@
1
+ ---
2
+ summary: "Adds `pubmed_pmc_fetch` tool — fetch full-text articles from PubMed Central via NCBI EFetch, with a JATS XML parser returning structured body sections, metadata, and references."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.1.0 — 2026-03-04
7
+
8
+ ## Added
9
+
10
+ - **`pubmed_pmc_fetch` tool**: Fetch full-text articles from PubMed Central (PMC) via NCBI EFetch with `db=pmc`. Accepts PMC IDs directly or PubMed IDs (auto-resolved to PMCIDs via ELink). Returns structured body sections, subsections, metadata, and optional references parsed from JATS XML.
11
+ - **PMC article parser**: JATS XML parser (`pmc-article-parser.ts`) extracts metadata (authors, affiliations, journal, keywords, publication date, abstract), recursive body sections, and back-matter references from PMC EFetch responses.
12
+ - **PMC types**: JATS XML element types and parsed PMC result types (`XmlJatsArticle`, `ParsedPmcArticle`, etc.) in `src/services/ncbi/types.ts`.
13
+
14
+ ## Changed
15
+
16
+ - **NCBI response handler**: Added PMC JATS-specific jpaths (`pmc-articleset.article`, `contrib-group.contrib`, `body.sec`, `ref-list.ref`, etc.) to the `isArray` set for consistent XML parsing.
17
+ - **README**: Added `pubmed_pmc_fetch` tool documentation, updated server description to mention full-text fetch.
@@ -0,0 +1,29 @@
1
+ ---
2
+ summary: "Bug fixes across response handler, PMC/article parsers, and citation formatter — plus comprehensive test coverage for NCBI service and parser edge cases."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.1.1 — 2026-03-04
7
+
8
+ ## Fixed
9
+
10
+ - **Response handler**: `extractTextValues` now handles numeric and boolean primitives emitted by fast-xml-parser when `parseTagValue` is enabled
11
+ - **Response handler**: Error detection uses shared `ERROR_PATHS` constant to stay in sync with error message extraction
12
+ - **PMC article parser**: Empty PMCID no longer produces a bare "PMC" prefix — returns empty string instead
13
+ - **Article parser**: Eliminated redundant `getText()` calls for month, day, and medlineDate in `extractJournalInfo`
14
+ - **Citation formatter**: `formatAuthorApa` no longer produces "undefined." when firstName contains consecutive spaces
15
+ - **Citation formatter**: Reordered `formatAuthorApa` logic so authors with only initials (no lastName) return formatted initials instead of empty string
16
+
17
+ ## Changed
18
+
19
+ - **Citation formatter**: `escapeBibtex` refactored from chained `.replace()` calls to a single regex with switch — fixes ordering bug where backslash-then-brace sequences were double-escaped
20
+ - **Citation formatter**: `splitPages` simplified with destructuring
21
+
22
+ ## Added
23
+
24
+ - Comprehensive test coverage for NCBI service edge cases: eSearch non-numeric fields, eSpell fallbacks, eSummary retmode logic, eFetch POST behavior
25
+ - Response handler tests: `CannotRetrievePMID` error path, numeric error values, DOCTYPE stripping, `returnRawXml` error passthrough
26
+ - Citation formatter tests: BibTeX special character escaping, APA author formatting edge cases, author-count boundaries (1/3/20/21), page splitting with en-dash/em-dash, minimal article formatting
27
+ - Article parser tests: PMC ID extraction from `ArticleIdList`, ORCID extraction, ISSN type classification, MedlineDate without year, empty AffiliationInfo handling
28
+ - ESummary parser tests: nested Author objects, string authors, PMC ID from ArticleIds, FullJournalName fallback
29
+ - PMC article parser tests: `pmc-uid` fallback, empty PMCID, affiliations, page ranges, pub-date priority (epub > ppub > pub)
@@ -0,0 +1,18 @@
1
+ ---
2
+ summary: "`pmc_fetch` renamed to `pubmed_pmc_fetch`; log directory path resolution switched to `node:path` for cross-platform correctness ([#9](https://github.com/cyanheads/pubmed-mcp-server/pull/9))."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.1.2 — 2026-03-04
7
+
8
+ ## Changed
9
+
10
+ - **Tool rename**: `pmc_fetch` renamed to `pubmed_pmc_fetch` for consistency with the `pubmed_*` naming convention across all tools
11
+
12
+ ## Fixed
13
+
14
+ - **Config**: Path resolution for logs directory now uses `node:path` utilities (`dirname`, `join`, `isAbsolute`) instead of URL-based arithmetic for cross-platform correctness ([#9](https://github.com/cyanheads/pubmed-mcp-server/pull/9))
15
+
16
+ ## Updated
17
+
18
+ - `@cloudflare/workers-types` to `4.20260305.1`
@@ -0,0 +1,10 @@
1
+ ---
2
+ summary: "Fix: OpenTelemetry NodeSDK now initializes on Bun — `isBun` guard removed, manual spans, custom metrics, and OTLP export all work correctly."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.1.3 — 2026-03-04
7
+
8
+ ## Fixed
9
+
10
+ - **Telemetry**: Enable OpenTelemetry NodeSDK on Bun — the `isBun` guard was unnecessarily blocking initialization when manual spans, custom metrics, and OTLP export all work correctly
@@ -0,0 +1,12 @@
1
+ ---
2
+ summary: "`pubmed_fetch` gains `affiliations` and `articleDates` fields; public hosted endpoint added to README; new output-schema coverage tests prevent strict-client rejections."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.1.4 — 2026-03-04
7
+
8
+ ## Added
9
+
10
+ - **pubmed_fetch**: `affiliations` (deduplicated author affiliations) and `articleDates` (electronic publication, received, accepted dates) now included in article output
11
+ - **Public hosted instance**: Added public Streamable HTTP endpoint (`https://pubmed.caseyjhand.com/mcp`) to README — no installation required
12
+ - **Output schema coverage tests**: New test suite validates that tool output schemas cover every field returned by parsers at runtime, preventing strict-client rejections from `additionalProperties: false`
@@ -0,0 +1,18 @@
1
+ ---
2
+ summary: "NCBI config now logged at startup (API key status, delay, retries, timeout). Dep bumps: `@biomejs/biome` 2.4.6, `jose` 6.2.0, `@types/node` 25.3.5."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.1.5 — 2026-03-06
7
+
8
+ ## Added
9
+
10
+ - **Startup logging**: NCBI configuration (API key status, email, request delay, max retries, timeout) now logged at initialization for easier debugging
11
+
12
+ ## Updated
13
+
14
+ - `@biomejs/biome` to 2.4.6
15
+ - `@cloudflare/workers-types` to 4.20260307.1
16
+ - `@types/node` to 25.3.5
17
+ - `@types/sanitize-html` to 2.16.1
18
+ - `jose` to 6.2.0
@@ -0,0 +1,15 @@
1
+ ---
2
+ summary: "Fix: `structuredContent` removed from error responses (valid for success only); `fast-check` → 4.6.0, `jose` → 6.2.1."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.1.6 — 2026-03-09
7
+
8
+ ## Fixed
9
+
10
+ - **Error responses**: Removed `structuredContent` from error responses in tool handler factory — `structuredContent` is only valid for successful results, not error payloads
11
+
12
+ ## Updated
13
+
14
+ - `fast-check` to 4.6.0
15
+ - `jose` to 6.2.1
@@ -0,0 +1,17 @@
1
+ ---
2
+ summary: "New pubmed_europepmc_fetch tool (11th tool) resolves full Europe PMC records by source + epmcId; pubmed_fetch_fulltext gains maxCharacters/maxCharactersPerSection/overflowMode budget controls; a shared surrogate-pair-safe slice helper backs both."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.10.0 — 2026-07-26
8
+
9
+ ## Added
10
+
11
+ - **`pubmed_europepmc_fetch` tool** — new 11th tool. Resolves specific Europe PMC records by `source` + `epmcId` and returns each one's complete, untruncated abstract, up to 25 records per call resolved in a single Europe PMC request; unresolved pairs come back in `notFound` instead of failing the batch. This is the retrieval path for preprint (`PPR`), patent (`PAT`), and Agricola (`AGR`) records, which frequently carry no PMID and no DOI. Backed by a new `EuropePmcService.fetchRecords()` method that OR-joins `(EXT_ID:<id> AND SRC:<source>)` clauses into one search query. ([#83](https://github.com/cyanheads/pubmed-mcp-server/issues/83))
12
+ - **`pubmed_fetch_fulltext` character budgets** — new `maxCharacters`, `maxCharactersPerSection`, and `overflowMode` (`truncate` | `outline`) inputs cap body text per article. `truncate` fills sections in document order so early sections stay whole; `outline` spreads the budget evenly across sections so every heading survives with an excerpt. Applies to `source=pmc` section/subsection text and the `source=unpaywall` body; titles, abstracts, identifiers, and references are never counted or cut. A new `truncation` output object reports per-article and per-section character counts whenever a budget shortened the response, and a matching `ctx.enrich.notice()` names what was spent. ([#81](https://github.com/cyanheads/pubmed-mcp-server/issues/81))
13
+ - **`pubmed_europepmc_search` `abstractTruncated`** — new boolean alongside `abstractSnippet` (capped at 400 characters) marking whether the snippet was cut; pass the hit's `source` and `epmcId` to `pubmed_europepmc_fetch` for the full text. ([#83](https://github.com/cyanheads/pubmed-mcp-server/issues/83))
14
+
15
+ ## Fixed
16
+
17
+ - **Character cuts could split a UTF-16 surrogate pair** — `pubmed_fetch_fulltext`'s new budget cuts and `pubmed_europepmc_search`'s `abstractSnippet` cut now back off one code unit when the boundary lands mid-pair, via a new shared `sliceCodeUnits` helper (`src/mcp-server/tools/definitions/_text.ts`). Budgets remain a code-unit ceiling; reported character counts are measured off the text actually returned, never assumed from the requested allowance. ([#93](https://github.com/cyanheads/pubmed-mcp-server/issues/93))
@@ -0,0 +1,14 @@
1
+ ---
2
+ summary: "eSearch upstream failures now throw instead of masking as zero-hit searches; pubmed_search_articles surfaces ignored field tags, unmatched phrases, and dropped partial dateRange filters via notice; the summaryCount cap message points at pubmed_fetch_articles when maxed; research_plan's includeAgentPrompts is now correctly advertised as optional."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.10.1 — 2026-07-26
8
+
9
+ ## Fixed
10
+
11
+ - **`eSearchResult.ERROR` was missing from `ERROR_PATHS`** — an upstream eSearch failure parsed as a zero-hit success instead of throwing. `pubmed_search_articles`'s `offset` is now bounded at 9998 (`OFFSET_MAX`), matching PubMed's `retstart` ceiling, and the ceiling is stated in its `.describe()`. `pubmed_lookup_mesh`'s `offset` is left unbounded — `db=mesh` has no equivalent `retstart` ceiling. ([#95](https://github.com/cyanheads/pubmed-mcp-server/issues/95))
12
+ - **eSearch `ErrorList`/`WarningList` never reached the caller** — `pubmed_search_articles` now reads both off the eSearch result and surfaces them through the `notice` enrichment: an unrecognized field tag fires independently of hit count, and an unmatched phrase names the exact clause instead of the generic empty-result guidance. `NcbiService.eSearch` normalizes every list member to `string[]` — NCBI collapses a single-entry member to a scalar, which the declared type didn't account for. ([#96](https://github.com/cyanheads/pubmed-mcp-server/issues/96))
13
+ - **Partial `dateRange` and the `summaryCount` cap were both undisclosed** — a `dateRange` with exactly one bound filled now fires a notice naming the supplied bound and a sentinel for an open-ended range, instead of silently dropping the filter. The `summaryCount` cap message now points at `pubmed_fetch_articles` with the remaining PMIDs when already at its maximum, instead of advising a raise that isn't possible. `buildNotice()` now collects every applicable signal and joins them, rather than returning the first of two mutually exclusive branches. ([#97](https://github.com/cyanheads/pubmed-mcp-server/issues/97))
14
+ - **`research_plan`'s `includeAgentPrompts` was advertised as a required prompt argument** — the SDK derives `prompts/list`'s `required` flag from schema optionality, and a `ZodDefault` doesn't read as optional. Changed from `.default('false')` to `.optional()`; `buildPlan`'s `=== 'true'` check behaves identically for an omitted value. ([#98](https://github.com/cyanheads/pubmed-mcp-server/issues/98))
@@ -0,0 +1,22 @@
1
+ ---
2
+ summary: "pubmed_fetch_fulltext renders JATS block content at its own position and returns figures and supplementary material as a structured assets[] field, pubmed_find_related's OpenAlex fallback pages to the full requested window, and the query tools reject a blank query instead of forwarding it to NCBI."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.10.10 — 2026-09-10
8
+
9
+ ## Added
10
+
11
+ - **`pubmed_fetch_fulltext` `assets[]`** — every `<fig>` and `<supplementary-material>` a PMC article carries, with `assetType`, `label`, `caption`, `id`, `sectionTitle`, and the deposit-relative `href` pointer; each lifted asset leaves a `[Figure: <label>]` / `[Supplementary: <label>]` marker at its position. New `includeAssets` input (default `true`) mirrors `includeTables`. ([#130](https://github.com/cyanheads/pubmed-mcp-server/issues/130))
12
+ - **`pubmed_find_related` `coverageFailures`** — when the Europe PMC or OpenAlex reference-coverage fallback throws, an entry naming `provider`, `reason`, and `retryable` is added instead of folding the failure into a false "no references" answer. ([#118](https://github.com/cyanheads/pubmed-mcp-server/issues/118))
13
+
14
+ ## Fixed
15
+
16
+ - **JATS block content (lists, definition lists, quotes, boxed text, preformatted text, formulae) dropped or fused into surrounding prose** — each now renders inline at its position instead of vanishing or gluing onto a paragraph's sentence, and an article whose entire body is one such block (e.g. a legacy OCR `<preformat>` deposit) is no longer reported as having no body. A caption's own title no longer runs into the sentence that follows it — on tables as well as on figures and supplementary material — and a caption or label hung directly on a `<media>`/`<graphic>` pointer is now read. ([#130](https://github.com/cyanheads/pubmed-mcp-server/issues/130))
17
+ - **`pubmed_fetch_fulltext` returned the first serialized `<abstract>` regardless of `@abstract-type`** — the untyped abstract is now preferred over a typed one (e.g. `graphical`, `executive-summary`), and content within the selected abstract renders through the same block-aware walk as body text, so an embedded figure caption reaches `assets[]` instead of the prose. ([#134](https://github.com/cyanheads/pubmed-mcp-server/issues/134))
18
+ - **`pubmed_find_related`'s OpenAlex fallback stalled after its first upstream page** — `cited_by`, `references`, and `similar` now page (or batch-resolve, for the latter two) until the requested window is filled, upstream is exhausted, or a 10-page/batch cap is reached, with `totalCount` reporting the exact PubMed-addressable count once exhausted. This also fixes an HTTP 400 the `references` path threw once `offset + maxResults` reached 34, from a candidate filter of up to 200 values against OpenAlex's 100-value OR-clause ceiling. ([#117](https://github.com/cyanheads/pubmed-mcp-server/issues/117))
19
+ - **A failed Europe PMC/OpenAlex reference-coverage fallback was reported as a genuine empty answer** — a thrown fallback now surfaces via `coverageFailures` and a distinct notice instead of being folded into NCBI's "no reference list" wording, and a provider disabled by configuration is marked non-retryable with no retry guidance offered. ([#118](https://github.com/cyanheads/pubmed-mcp-server/issues/118))
20
+ - **A failed DOI backfill lookup was misreported as `no-doi`** — a new `doi-lookup-failed` reason (added to `UnavailableReasonSchema` and `TierOutcomeSchema`) now distinguishes an errored lookup from a record that genuinely has none. A DOI Europe PMC already returned is also now carried forward to Unpaywall instead of being discarded, skipping a redundant PubMed round-trip. ([#119](https://github.com/cyanheads/pubmed-mcp-server/issues/119))
21
+ - **`pubmed_search_articles` accepted a blank query and forwarded it to NCBI as a blank term** — a whitespace-only or sanitized-to-empty query was previously retried as a misclassified transient outage across seven attempts and roughly a minute of backoff; it now fails immediately with a declared `blank_query` reason (`ValidationError`, non-retryable). ([#122](https://github.com/cyanheads/pubmed-mcp-server/issues/122))
22
+ - **`pubmed_spell_check` and `pubmed_lookup_mesh` accepted blank queries and returned a false empty success** — both now reject the same `blank_query` reason before calling NCBI, instead of reporting a checked-but-empty result for a query that was never searched. ([#133](https://github.com/cyanheads/pubmed-mcp-server/issues/133))
@@ -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>&#xae;</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