@cyanheads/pubmed-mcp-server 2.10.13 → 2.10.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. package/AGENTS.md +22 -4
  2. package/CLAUDE.md +22 -4
  3. package/README.md +5 -5
  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 +28 -0
  20. package/changelog/2.10.x/2.10.2.md +13 -0
  21. package/changelog/2.10.x/2.10.3.md +26 -0
  22. package/changelog/2.10.x/2.10.4.md +11 -0
  23. package/changelog/2.10.x/2.10.5.md +31 -0
  24. package/changelog/2.10.x/2.10.6.md +30 -0
  25. package/changelog/2.10.x/2.10.7.md +17 -0
  26. package/changelog/2.10.x/2.10.8.md +22 -0
  27. package/changelog/2.10.x/2.10.9.md +15 -0
  28. package/changelog/2.2.x/2.2.0.md +67 -0
  29. package/changelog/2.2.x/2.2.1.md +10 -0
  30. package/changelog/2.2.x/2.2.2.md +20 -0
  31. package/changelog/2.2.x/2.2.3.md +17 -0
  32. package/changelog/2.2.x/2.2.4.md +34 -0
  33. package/changelog/2.2.x/2.2.5.md +10 -0
  34. package/changelog/2.2.x/2.2.6.md +17 -0
  35. package/changelog/2.3.x/2.3.0.md +27 -0
  36. package/changelog/2.3.x/2.3.1.md +15 -0
  37. package/changelog/2.3.x/2.3.10.md +20 -0
  38. package/changelog/2.3.x/2.3.11.md +21 -0
  39. package/changelog/2.3.x/2.3.2.md +27 -0
  40. package/changelog/2.3.x/2.3.3.md +38 -0
  41. package/changelog/2.3.x/2.3.4.md +21 -0
  42. package/changelog/2.3.x/2.3.5.md +24 -0
  43. package/changelog/2.3.x/2.3.6.md +26 -0
  44. package/changelog/2.3.x/2.3.7.md +31 -0
  45. package/changelog/2.3.x/2.3.8.md +19 -0
  46. package/changelog/2.3.x/2.3.9.md +22 -0
  47. package/changelog/2.4.x/2.4.0.md +34 -0
  48. package/changelog/2.4.x/2.4.1.md +32 -0
  49. package/changelog/2.5.x/2.5.0.md +35 -0
  50. package/changelog/2.5.x/2.5.1.md +32 -0
  51. package/changelog/2.5.x/2.5.2.md +23 -0
  52. package/changelog/2.5.x/2.5.3.md +22 -0
  53. package/changelog/2.5.x/2.5.5.md +52 -0
  54. package/changelog/2.5.x/2.5.6.md +33 -0
  55. package/changelog/2.6.x/2.6.0.md +32 -0
  56. package/changelog/2.6.x/2.6.1.md +26 -0
  57. package/changelog/2.6.x/2.6.10.md +16 -0
  58. package/changelog/2.6.x/2.6.11.md +24 -0
  59. package/changelog/2.6.x/2.6.12.md +29 -0
  60. package/changelog/2.6.x/2.6.2.md +23 -0
  61. package/changelog/2.6.x/2.6.3.md +17 -0
  62. package/changelog/2.6.x/2.6.4.md +21 -0
  63. package/changelog/2.6.x/2.6.5.md +30 -0
  64. package/changelog/2.6.x/2.6.6.md +25 -0
  65. package/changelog/2.6.x/2.6.7.md +37 -0
  66. package/changelog/2.6.x/2.6.8.md +15 -0
  67. package/changelog/2.6.x/2.6.9.md +36 -0
  68. package/changelog/2.7.x/2.7.0.md +41 -0
  69. package/changelog/2.7.x/2.7.1.md +21 -0
  70. package/changelog/2.7.x/2.7.10.md +13 -0
  71. package/changelog/2.7.x/2.7.11.md +15 -0
  72. package/changelog/2.7.x/2.7.2.md +22 -0
  73. package/changelog/2.7.x/2.7.3.md +18 -0
  74. package/changelog/2.7.x/2.7.4.md +15 -0
  75. package/changelog/2.7.x/2.7.5.md +34 -0
  76. package/changelog/2.7.x/2.7.6.md +14 -0
  77. package/changelog/2.7.x/2.7.7.md +14 -0
  78. package/changelog/2.7.x/2.7.8.md +18 -0
  79. package/changelog/2.7.x/2.7.9.md +16 -0
  80. package/changelog/2.8.x/2.8.0.md +23 -0
  81. package/changelog/2.9.x/2.9.0.md +21 -0
  82. package/changelog/2.9.x/2.9.1.md +12 -0
  83. package/changelog/2.9.x/2.9.10.md +15 -0
  84. package/changelog/2.9.x/2.9.2.md +21 -0
  85. package/changelog/2.9.x/2.9.3.md +11 -0
  86. package/changelog/2.9.x/2.9.4.md +24 -0
  87. package/changelog/2.9.x/2.9.5.md +20 -0
  88. package/changelog/2.9.x/2.9.6.md +22 -0
  89. package/changelog/2.9.x/2.9.7.md +26 -0
  90. package/changelog/2.9.x/2.9.8.md +15 -0
  91. package/changelog/2.9.x/2.9.9.md +35 -0
  92. package/changelog/template.md +151 -0
  93. package/dist/index.js +1 -0
  94. package/dist/index.js.map +1 -1
  95. package/dist/services/europe-pmc/europe-pmc-service.d.ts.map +1 -1
  96. package/dist/services/europe-pmc/europe-pmc-service.js +3 -10
  97. package/dist/services/europe-pmc/europe-pmc-service.js.map +1 -1
  98. package/dist/services/ncbi/ncbi-service.d.ts +0 -2
  99. package/dist/services/ncbi/ncbi-service.d.ts.map +1 -1
  100. package/dist/services/ncbi/ncbi-service.js +2 -9
  101. package/dist/services/ncbi/ncbi-service.js.map +1 -1
  102. package/dist/services/openalex/openalex-service.d.ts.map +1 -1
  103. package/dist/services/openalex/openalex-service.js +3 -10
  104. package/dist/services/openalex/openalex-service.js.map +1 -1
  105. package/package.json +17 -8
  106. package/server.json +3 -3
  107. package/dist/services/retry-policy.d.ts +0 -18
  108. package/dist/services/retry-policy.d.ts.map +0 -1
  109. package/dist/services/retry-policy.js +0 -21
  110. package/dist/services/retry-policy.js.map +0 -1
@@ -0,0 +1,30 @@
1
+ ---
2
+ summary: Typed error contracts on NCBI tools, service-layer logger metadata typed end-to-end, fuzz coverage for all 9 tools, plus DX renames on `pubmed_format_citations` (`styles` → `format`) and `pubmed_lookup_mesh` (`term` → `query`).
3
+ breaking: true
4
+ ---
5
+
6
+ # 2.6.5 — 2026-04-28
7
+
8
+ Three issues land together: typed error contracts on the NCBI-driven tools, the last 23 `as never` logger casts swept out of the service layer, and property-based fuzz coverage for every tool. Two ergonomic input renames ship alongside — `pubmed_format_citations` and `pubmed_lookup_mesh` callers need to update their argument names.
9
+
10
+ ## Added
11
+
12
+ - **Typed error contracts on NCBI tools** ([#47](https://github.com/cyanheads/pubmed-mcp-server/issues/47)). New `src/mcp-server/tools/definitions/_error-contracts.ts` exports a shared `NCBI_SERVICE_ERRORS` tuple (`queue_full` / `ncbi_unreachable` / `ncbi_deadline_exceeded`). `pubmed_fetch_articles`, `pubmed_fetch_fulltext`, and `pubmed_find_related` now declare the baseline plus tool-specific reasons (`invalid_efetch_response`, `invalid_pmc_efetch_response`, `elink_error`) and route their failures through `ctx.fail(reason, …)`. Every contract is published in `tools/list` under `_meta["mcp-ts-core/errors"]`.
13
+ - **Service-layer reason tagging.** `request-queue.ts` stamps `reason: 'queue_full'` on overflow; `ncbi-service.ts` tags `ncbi_deadline_exceeded` on `runWithDeadline()` timeouts and `ncbi_unreachable` when retries-exhausted hits `ServiceUnavailable`. Wire payloads carry the same `error.data.reason` regardless of throw site, so callers can switch on a stable key.
14
+ - **Property-based fuzz coverage for all 9 tools** ([#49](https://github.com/cyanheads/pubmed-mcp-server/issues/49)). New `tests/mcp-server/tools/definitions/tools.fuzz.test.ts` uses a custom `fuzzToolStrict()` runner (in `_fuzz-helpers.ts`) that pre-parses generated inputs through each tool's Zod schema before invoking the handler — defaults resolve, `min(1)` constraints honor, and the framework's input-parsing gap is worked around locally. Pinned seed (`42`), `numRuns: 50`, `numAdversarial: 30`. Suite finishes in ~2.5s. Filed upstream as [cyanheads/mcp-ts-core#83](https://github.com/cyanheads/mcp-ts-core/issues/83).
15
+ - **`pubmed_lookup_mesh` recovery notice.** When no MeSH descriptors match, the tool now returns a `notice` field pointing the caller at `pubmed_spell_check` and `pubmed_search_articles` for free-text discovery. Rendered as a blockquote in `format()`.
16
+
17
+ ## Changed
18
+
19
+ - **`pubmed_format_citations` input renamed `styles` → `format`** ⚠️ **breaking**. Now accepts a single style as a string or multiple styles as an array (`format: 'apa'` or `format: ['apa', 'mla']`); default is `'apa'`. Empty arrays are rejected.
20
+ - **`pubmed_lookup_mesh` input renamed `term` → `query`** ⚠️ **breaking**. The output also renames `term` → `query` for parity. Description and field text reworded to clarify it accepts both descriptor names and free-text.
21
+ - **Replaced 23 `as never` casts** across 5 service files with proper `requestContextService.createRequestContext({ operation, ...meta })` calls ([#48](https://github.com/cyanheads/pubmed-mcp-server/issues/48)). Logger metadata is now type-checked against `RequestContext` end-to-end. Each call gets a domain-tagged `operation` field (`NcbiHttpRequest`, `NcbiQueueWait`, `NcbiXmlParseError`, etc.) so log lines self-identify their origin.
22
+ - **NCBI / Unpaywall HTTP error mapping switched to `httpErrorFromResponse()`** in `api-client.ts` and `unpaywall-service.ts`. Replaces hand-rolled 4xx/5xx → `JsonRpcErrorCode` ladders; picks up the full status table and `Retry-After` header capture. Hand-rolled `new McpError(...)` throws normalized to `serviceUnavailable()` / `serializationError()` / `internalError()` / `timeout()` factories with `cause:` plumbed through.
23
+ - **`pubmed_find_related` ELink error path** now uses `ctx.fail('elink_error', …)` and preserves the raw NCBI ERROR payload in `data.ncbiError` instead of stringifying it into the message.
24
+ - **Skill / agent-protocol sync.** `CLAUDE.md` and `AGENTS.md` updated to lead with the typed error contract pattern and reference the `add-app-tool` skill. Local skill copies (`skills/`, `.agents/skills/`, `.claude/skills/`) re-synced from upstream `mcp-ts-core` so `add-tool`, `add-service`, `api-errors`, `field-test`, `maintenance`, `release-and-publish`, `report-issue-framework`, `security-pass`, and `setup` carry the latest framework guidance.
25
+ - **`research-plan` prompt** description for `includeAgentPrompts` reworded to drop consumer-aware phrasing.
26
+
27
+ ## Tests
28
+
29
+ - **476 passed** / 4 skipped. Added: 9 fuzz cases (one per tool), 4 contract-reason assertions on `fetch-articles` / `fetch-fulltext` / `find-related`, 5 `pubmed_format_citations` input-shape cases (single/array/empty/unknown), 3 `pubmed_lookup_mesh` notice cases. Service tests extended their `@cyanheads/mcp-ts-core/utils` mocks to stub `requestContextService.createRequestContext`.
30
+ - Field-tested against the live HTTP server: every contract surfaces under `_meta["mcp-ts-core/errors"]` with correct `code` / `reason` / `when` / `retryable`. Happy paths verified for `spell_check`, `search_articles`, `fetch_articles`, `find_related`. Input validation returns clean `-32602` with Zod issue paths.
@@ -0,0 +1,25 @@
1
+ ---
2
+ summary: Recovery hints on every error contract; `pubmed_convert_ids` input renamed `idtype` → `idType` for camelCase parity; adopts `@cyanheads/mcp-ts-core` v0.8.6.
3
+ breaking: true
4
+ ---
5
+
6
+ # 2.6.6 — 2026-04-29
7
+
8
+ Polish patch on top of v2.6.5. Every error contract now declares a `recovery` hint that the framework mirrors into `content[]` text via `data.recovery.hint`, so callers see actionable next steps inline with the error message. Hints are LLM-targeted — each carries at least one concrete second action when the obvious "retry" doesn't recover.
9
+
10
+ ## Changed
11
+
12
+ - **Recovery hints on every error contract.** `_error-contracts.ts` (`queue_full`, `ncbi_unreachable`, `ncbi_deadline_exceeded`) plus the three tool-specific entries (`invalid_efetch_response`, `invalid_pmc_efetch_response`, `elink_error`) all carry `recovery: '...'`. Throw sites in `pubmed_fetch_articles`, `pubmed_fetch_fulltext`, and `pubmed_find_related` spread `ctx.recoveryFor('reason')` into `data` so the framework mirrors `data.recovery.hint` into the rendered text. Hints rewritten for an LLM caller — no "report the issue" or "check status" filler; each names a concrete next action.
13
+ - **`pubmed_convert_ids` input renamed `idtype` → `idType`** ⚠️ **breaking**. CamelCase parity with the rest of the input surface. The tool itself only shipped in v2.6.5 a day prior; the service-layer parameter stays lowercase to match NCBI's API verbatim.
14
+ - **`pubmed_format_citations` format union** describes both inner variants (single-style and array-of-styles) so `inputSchema.properties.format.anyOf[*].description` carries field-level guidance instead of relying solely on the parent `.describe()`. Conforms to the `describe-on-fields` linter rule for non-literal union members.
15
+ - **`@cyanheads/mcp-ts-core` 0.8.2 → 0.8.6**: typed `ctx.recoveryFor` resolver landed in 0.8.4–0.8.5, `_meta['mcp-ts-core/errors']` wire publication was dropped in 0.8.3 (replaced by `structuredContent.error` parity), and `dev:stdio` / `dev:http` watch scripts were removed framework-wide in 0.8.6. Smoke-test path standardized to `bun run rebuild && bun run start:stdio`.
16
+ - **`unpdf` 1.6.1 → 1.6.2.**
17
+ - **Skill / agent-protocol sync.** `AGENTS.md` and `CLAUDE.md` document `ctx.recoveryFor` on the Context table and rework the error-contract example to spread the resolver into `data`. Local skill copies re-synced from upstream `mcp-ts-core` (`add-tool`, `add-service`, `api-context`, `api-errors`, `design-mcp-server`, `field-test`).
18
+
19
+ ## Removed
20
+
21
+ - **`dev:stdio` / `dev:http` scripts** from `package.json`. Use `bun run rebuild && bun run start:stdio` (or `start:http`) for the same execution path as production smoke tests.
22
+
23
+ ## Tests
24
+
25
+ - 476 passed / 4 skipped. `convert-ids.tool.test.ts` updated for the `idType` rename. Field-tested against the live HTTP server: schema correctly advertises `idType`, the old `idtype` is rejected with a clean validation error referencing the new field, both single-string and array-of-styles forms of `pubmed_format_citations` work, and the `unpdf` bump survives the live PDF-extraction path used by the Unpaywall fallback.
@@ -0,0 +1,37 @@
1
+ ---
2
+ summary: "Adopts `@cyanheads/mcp-ts-core` 0.8.6 → 0.8.19 — HTTP SSE per-request leak fix, OTel double-write fix, `ctx.sessionId` / `ctx.auth.token`. Node engine ≥24.0.0; Dockerfile pinned to `oven/bun:1.3`."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.6.7 — 2026-05-09
8
+
9
+ Maintenance release rolling up 13 framework patch versions plus a dep refresh. No tool, resource, or prompt API changes — handlers, schemas, and wire formats are byte-identical to v2.6.6.
10
+
11
+ ## Changed
12
+
13
+ - **`@cyanheads/mcp-ts-core` ^0.8.6 → ^0.8.19.** Notable picks for this server's deployment shape:
14
+ - **HTTP SSE per-request retention leak** ([mcp-ts-core#50](https://github.com/cyanheads/mcp-ts-core/issues/50), 0.8.16) — per-request `McpServer` + `McpSessionTransport` pairs survived ungraceful client disconnects on SSE GET streams (~200 KB × GET-rate heap growth across 12h+ production telemetry). Cleanup now binds to the request `AbortSignal`, with failures tagged `trigger=sse-abort` on `mcp.http.close_failures`.
15
+ - **`ErrorHandler` OTel double-write fix** ([mcp-ts-core#93](https://github.com/cyanheads/mcp-ts-core/issues/93), 0.8.8) — eliminates two `Cannot execute the operation on ended Span` warnings per tool failure under HTTP+OTel.
16
+ - **Storage `decodeCursor` stack-trace leak** (0.8.8) — malformed cursors no longer surface server stacks through `McpError.data`.
17
+ - **`ctx.sessionId`** ([mcp-ts-core#116](https://github.com/cyanheads/mcp-ts-core/issues/116), 0.8.17) — HTTP handlers can now read the `Mcp-Session-Id` header off the public `Context` for session-scoped state. Fail-closed under stateless HTTP; opt in via `createApp({ context: { exposeStatelessSessionId: true } })`. Not consumed by this server yet; available for future session-keyed features.
18
+ - **`ctx.auth.token`** ([mcp-ts-core#121](https://github.com/cyanheads/mcp-ts-core/issues/121), 0.8.18) — `toAuthContext` no longer strips `info.token`, so PAT pass-through / on-behalf-of patterns have a public path to the validated bearer.
19
+ - **`disabledTool()` wrapper + landing-page `unspecified` mutability bucket** ([mcp-ts-core#92](https://github.com/cyanheads/mcp-ts-core/issues/92), 0.8.11) — feature-gated tools and unannotated tools are now first-class concepts; this server's nine tools all carry explicit `readOnlyHint: true` so the bucket change is a no-op here.
20
+ - **Telemetry visualization docs** ([mcp-ts-core#125](https://github.com/cyanheads/mcp-ts-core/issues/125), 0.8.19) — example Grafana dashboard JSON and vendor-agnostic query recipes ship with the framework, plus the new `api-telemetry` skill.
21
+ - **Node engine: `>=22.0.0` → `>=24.0.0`** (matches framework 0.8.19's engine bump). `Dockerfile` pinned to `oven/bun:1.3` / `oven/bun:1.3-slim` (was floating `:1` / `:1-slim`) so production images track the same Bun line we test against.
22
+ - **Local skill mirror.** Adds three skills synced from upstream `mcp-ts-core`: `api-canvas` (Tier 3 SQL workspace; not used here), `api-telemetry` (OTel catalog), `tool-defs-analysis` (read-only audit of definition language). Existing skills resynced from upstream.
23
+ - **Changelog frontmatter.** Adds optional `security: bool` flag rendered as a 🛡️ Security badge in the rollup. `scripts/build-changelog.ts` parses the new key; `changelog/template.md` rewritten as an authoring guide. One-shot `scripts/split-changelog.ts` retained for reference.
24
+
25
+ ### Dependency bumps
26
+
27
+ | Package | From | To |
28
+ |:---|:---|:---|
29
+ | `@cyanheads/mcp-ts-core` | `^0.8.6` | `^0.8.19` |
30
+ | `fast-xml-parser` | `^5.7.2` | `^5.7.3` |
31
+ | `@biomejs/biome` (dev) | `^2.4.13` | `^2.4.14` |
32
+ | `@types/node` (dev) | `^25.6.0` | `^25.6.2` |
33
+ | `tsc-alias` (dev) | `^1.8.16` | `^1.8.17` |
34
+
35
+ ## Tests
36
+
37
+ - 476 passed / 4 skipped. No suite changes — handlers, schemas, and outputs are unchanged. `bun run devcheck` clean.
@@ -0,0 +1,15 @@
1
+ ---
2
+ summary: "Fix `server.json` top-level `version` field missed in v2.6.7 — kept the registry stuck at 2.6.6 even though the two `packages[*].version` entries advanced. No code changes."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.6.8 — 2026-05-09
8
+
9
+ ## Fixed
10
+
11
+ - **`server.json` top-level `version`** — the v2.6.7 release bumped both `packages[*].version` entries but missed the top-level field, so `mcp-publisher` rejected the publish with `cannot publish duplicate version` and the MCP Registry stayed pinned to 2.6.6. v2.6.7 still ships on npm; the MCP Registry skips straight from 2.6.6 → 2.6.8.
12
+
13
+ ## Tests
14
+
15
+ - 476 passed / 4 skipped. No code changes.
@@ -0,0 +1,36 @@
1
+ ---
2
+ summary: "Service-layer error-contracts module with `recoveryFor()` helper; `pubmed_convert_ids` 400-leak rewrite; `pubmed_find_related` ELink `<ERROR>` folded into ESummary disambiguation. Adopts `@cyanheads/mcp-ts-core` 0.8.20."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.6.9 — 2026-05-10
8
+
9
+ ## Added
10
+
11
+ - **`src/services/error-contracts.ts`** — single source of truth for the failure modes both services throw and tools declare in `errors[]`. Exports `NCBI_SERVICE_ERRORS` (queue_full, ncbi_unreachable, ncbi_deadline_exceeded, ncbi_invalid_response, ncbi_resource_not_found), `UNPAYWALL_SERVICE_ERRORS` (unpaywall_unreachable), and a `recoveryFor(reason)` service-layer counterpart to `ctx.recoveryFor` so service throws carry the same actionable hint a tool-level `ctx.fail` would.
12
+ - **`pubmed_fetch_fulltext`** — `format()` now appends a notice when zero articles are returned, pointing to `pubmed_fetch_articles` for metadata-only paths and noting the open-access requirement.
13
+ - **Service tests.** ~600 lines added across `error-contracts`, `api-client`, `request-queue`, `response-handler`, `ncbi-service`, `pmc-article-parser`, `citation-formatter`, and `unpaywall-service` covering the new reason/recovery wiring, NotFound short-circuit patterns, HTML-rate-limit detection, and Unicode initials.
14
+
15
+ ## Changed
16
+
17
+ - **Error-contracts module relocation.** `src/mcp-server/tools/definitions/_error-contracts.ts` → `src/services/error-contracts.ts`. The old file held three NCBI reasons used only by tool definitions; the service layer is now the authoring site so handler-side and service-side throws share one declaration. Two new NCBI reasons added (`ncbi_invalid_response`, `ncbi_resource_not_found`) and Unpaywall lifted to its own array.
18
+ - **All 9 tools declare `errors: [...NCBI_SERVICE_ERRORS]`** (and `[...UNPAYWALL_SERVICE_ERRORS]` on `pubmed_fetch_fulltext`). The six previously-uncontracted tools (`spell_check`, `lookup_mesh`, `search_articles`, `convert_ids`, `format_citations`, `lookup_citation`) now expose the baseline failure surface to the LLM.
19
+ - **NCBI service throws stamp `data.reason` + `data.recovery.hint`** at every site — `api-client` (transport), `request-queue` (queue full), `response-handler` (XML/JSON parse, structured `<ERROR>` payloads, HTML-rate-limit detection), `ncbi-service` (deadline, retries, ID Converter parse). Wire payloads now carry the same actionable recovery hint clients see from handler-level `ctx.fail`.
20
+ - **`pubmed_convert_ids`** — PMC ID Converter `400 Bad Request` now rewrites to a typed `validationError` with idType-specific format hints (`pmid: numeric digits`, `pmcid: "PMC" + digits`, `doi: starts with "10."`). Drops the leaky upstream HTML/text body that previously surfaced in `data.body`.
21
+ - **`pubmed_find_related`** — ELink `<ERROR>` payload no longer throws. The branch now falls through to the existing empty-LinkSet ESummary disambiguation path, producing the same `{ articles: [], totalFound: 0, notice }` shape for both invalid PMIDs and valid-but-empty PMIDs. Drops `elink_error` from the contract; the previous `ServiceUnavailable` classification was wrong (the response is user-recoverable, not a service outage).
22
+ - **`pubmed_fetch_fulltext`** description tightened — drops the `UNPAYWALL_EMAIL` setup mention in favor of the configured behavior. `pubmed_lookup_citation` description leads with "deterministic citation matching" framing.
23
+ - **`response-handler.ts`** — NCBI errors matching `cannot get document summary`, `UID=…: not found`, or `Empty id list` now throw `notFound()` (`reason: ncbi_resource_not_found`) instead of `serviceUnavailable()`, so the retry loop short-circuits on permanent "no such record" responses. HTML-instead-of-XML detection moved before the XML validator so rate-limit pages don't pollute the parse-error path.
24
+ - **`citation-formatter.ts`** — author initials extraction is now Unicode-aware (`/[^\p{L}]/u`) so accented forms like `Á`, `Ö`, `É` survive. Previous `[A-Za-z]` regex stripped them.
25
+ - **`@cyanheads/mcp-ts-core` ^0.8.19 → ^0.8.20** ([0.8.20 changelog](https://github.com/cyanheads/mcp-ts-core/blob/main/changelog/0.8.x/0.8.20.md)) — adds `mcp_tool_scopes` JWT claim union and `MCP_AUTH_DISABLE_SCOPE_CHECKS` bypass for OIDC operator scope-injection escape hatches. `.env.example` documents the new bypass flag. No tool/resource/prompt API impact.
26
+ - **`@biomejs/biome` (dev) ^2.4.14 → ^2.4.15.**
27
+ - **Skill bumps mirrored from upstream.** `api-auth` 1.0 → 1.1, `api-config` 1.3 → 1.4, `security-pass` 1.3 → 1.4, `tool-defs-analysis` 1.0 → 1.1.
28
+
29
+ ## Removed
30
+
31
+ - **`src/mcp-server/tools/definitions/_error-contracts.ts`** — moved to `src/services/error-contracts.ts`.
32
+ - **`elink_error` contract entry** on `pubmed_find_related` — replaced by the unified empty-LinkSet disambiguation path.
33
+
34
+ ## Tests
35
+
36
+ - 539 passed / 4 skipped. `bun run devcheck` clean.
@@ -0,0 +1,41 @@
1
+ ---
2
+ summary: "New `pubmed_europepmc_search` tool surfaces preprints, patents, Agricola, and EPMC-only OA records. `pubmed_fetch_fulltext` chain expands to NCBI PMC → Europe PMC `fullTextXML` → Unpaywall with a `dois` input branch ([#52](https://github.com/cyanheads/pubmed-mcp-server/issues/52))."
3
+ breaking: true
4
+ security: false
5
+ ---
6
+
7
+ # 2.7.0 — 2026-05-18
8
+
9
+ Europe PMC integration ([#52](https://github.com/cyanheads/pubmed-mcp-server/issues/52)). One new tool, one expanded chain, one new service module. Default-on; `EUROPEPMC_ENABLED=false` reverts to PMC + Unpaywall and skips tool registration so no EBI calls are made.
10
+
11
+ ## Added
12
+
13
+ - **`pubmed_europepmc_search`** (`src/mcp-server/tools/definitions/pubmed-europepmc-search.tool.ts`). Searches Europe PMC across MED, PMC, PPR (preprints), PAT (patents), AGR (Agricola). Default `sources: ["MED", "PMC", "PPR"]`. Cursor-based pagination via `cursorMark` (`*` for the first page; return `nextCursorMark` for the next) — distinct from `pubmed_search_articles`'s offset paging because EPMC's deep results aren't offset-addressable. Output discriminator on `source` plus optional `pmid` / `pmcId` / `doi` cross-walking.
14
+ - **`dois` input branch on `pubmed_fetch_fulltext`** — third XOR alternative to `pmids` / `pmcids`. Resolves DOI → EPMC search → `/{epmcId}/fullTextXML` → Unpaywall. Closes the EPMC search → fetch workflow gap for records that lack PMID/PMCID (preprints, EPMC-only OA).
15
+ - **`viaSource` on every `pubmed_fetch_fulltext` article** — `"pmc" | "europepmc" | "unpaywall"`. Records origin tier separately from `source` (which still discriminates the payload schema: structured JATS sections vs Unpaywall HTML/PDF body). EPMC JATS reuses the `source: "pmc"` schema; `viaSource: "europepmc"` distinguishes origin.
16
+ - **`triedTiers[]` per-tier execution trace on every `unavailable` entry** — ordered list of `{ tier, outcome, detail }` covering `pmc`, `europepmc`, `unpaywall`. `outcome` is a separate enum (`not-attempted`, `miss`, `no-fulltext`, `no-doi`, `no-oa`, `fetch-failed`, `parse-failed`, `service-error`) from the terminal `reason`. Callers see which stage failed and why without parsing error text.
17
+ - **`idType` discriminator on every `unavailable` entry** — `"pmid" | "pmcid" | "doi"`. Replaces the parallel `unavailablePmcIds[]` and `unavailableDois[]` arrays from the spec with a unified `unavailable[]` keyed by input type.
18
+ - **`not-found` and `no-epmc-fulltext` reasons** on `pubmed_fetch_fulltext` `unavailable.reason`. `not-found` separates "upstream returned no record" from "record exists but no full text"; `no-epmc-fulltext` covers preprints-with-PDF-only and other EPMC records without a `fullTextXML`.
19
+ - **`epmcId` / `epmcSource` fields on `PmcArticleSchema`** — populated when the article came via EPMC. Lets callers correlate back to Europe PMC search results.
20
+ - **5 `EUROPEPMC_*` env vars** documented in `.env.example` and the README config table. `EUROPEPMC_ENABLED` (default `true`), `EUROPEPMC_EMAIL`, `EUROPEPMC_REQUEST_DELAY_MS` (default `200`), `EUROPEPMC_MAX_RETRIES` (default `3`), `EUROPEPMC_TIMEOUT_MS` (default `20000`).
21
+ - **`EUROPEPMC_SERVICE_ERRORS` contract pair** in `src/services/error-contracts.ts` — `europepmc_unreachable` (`ServiceUnavailable`, retryable) and `europepmc_invalid_response` (`SerializationError`, retryable). Folded into `ServiceErrorReason` and the `REASON_TO_RECOVERY` map.
22
+ - **`src/services/europe-pmc/` service module** — `api-client.ts`, `europe-pmc-service.ts`, `request-queue.ts`, `types.ts`. Rate-limited request queue is separate from NCBI's (different rate domain). Reuses the NCBI JATS parser for `fullTextXML` so EPMC JATS lands in the same `PmcArticle` schema as NCBI PMC EFetch output.
23
+ - **`envBoolean` preprocessor in `src/config/server-config.ts`** for `EUROPEPMC_ENABLED`. `z.coerce.boolean()` is unusable for env vars because JS truthy semantics coerce `"false"` to `true`; this mirrors the framework's helper so `EUROPEPMC_ENABLED=false` actually disables.
24
+
25
+ ## Changed
26
+
27
+ - **`pubmed_fetch_fulltext` chain order** is now NCBI PMC EFetch → Europe PMC `fullTextXML` → Unpaywall. EPMC recovers PMC-counterpart records that NCBI PMC EFetch missed, and resolves DOI input to PMC counterparts when one exists. EPMC's `fullTextXML` is PMC-keyed, so PPR / PAT / AGR records are reachable via `pubmed_europepmc_search` for metadata but produce `no-epmc-fulltext` here.
28
+ - **PMC EFetch failures fall through gracefully.** Previously a PMC EFetch error aborted the whole batch; now it's logged, recorded as `pmc:service-error` in `triedTiers[]`, and EPMC / Unpaywall still run for the remaining IDs. The old `invalid_pmc_efetch_response` error contract was removed in favor of this per-tier outcome.
29
+ - **Unpaywall `McpError(NotFound | ValidationError)` from `fetchWithTimeout` translates to `no-oa`** (`src/services/unpaywall/unpaywall-service.ts`). Mid-chain 404s and shape failures from the framework's transport layer now surface as a clean "no open-access copy" outcome rather than bubbling as raw `McpError`.
30
+ - **Server `instructions`** text rewritten to describe the broader surface: PMC + EPMC, when to broaden via `pubmed_europepmc_search`, when to prefer deterministic resolvers (`pubmed_lookup_citation`, `pubmed_convert_ids`).
31
+ - **`pubmed_fetch_fulltext` tool description** in the README rewrites the chain explainer and enumerates the new reasons and `triedTiers` outcomes.
32
+ - **`@types/node` ^25.8.0 → ^25.9.0.**
33
+
34
+ ## Removed
35
+
36
+ - **`unavailablePmcIds[]` and `unavailableDois[]` on `pubmed_fetch_fulltext` output.** Replaced by a unified `unavailable[]` with `idType` discriminator and `triedTiers[]` execution trace. Callers reading either array must migrate to `unavailable[].pmcid` / `unavailable[].doi` filtered by `idType`.
37
+ - **`invalid_pmc_efetch_response` error contract** (`NCBI_SERVICE_ERRORS`). Replaced by a per-tier `pmc:service-error` outcome inside `triedTiers[]`; the batch no longer aborts on PMC EFetch failures.
38
+
39
+ ## Tests
40
+
41
+ - 618 passed / 4 skipped. `bun run devcheck` clean. New coverage in `tests/services/europe-pmc/europe-pmc-service.test.ts` (282 lines), `tests/mcp-server/tools/definitions/pubmed-europepmc-search.tool.test.ts` (216 lines), plus expanded `tests/mcp-server/tools/definitions/fetch-fulltext.tool.test.ts` (+960 lines covering DOI input, EPMC tier, `triedTiers[]` outcomes, graceful PMC fall-through).
@@ -0,0 +1,21 @@
1
+ ---
2
+ summary: "`pubmed_europepmc_search` now surfaces silent EPMC rejections (invalid `sort` field, empty query) as `ValidationError` instead of falling through to a fake 0-hit response."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.7.1 — 2026-05-19
8
+
9
+ ## Fixed
10
+
11
+ - **`pubmed_europepmc_search` silent rejections.** EPMC returns a `{ version }`-only envelope (no `hitCount`, no `request` echo, no `resultList`) when it rejects an undocumented `sort` field, and a separate `{ errCode, errMsg }` shape when it rejects structured input like an empty query. Both formerly normalized to `hitCount: 0` with an empty `hits[]` — the caller saw "no matches" and never learned the request was malformed. `EuropePmcService.search` now detects both shapes and throws `ValidationError` with `data.reason: "europepmc_invalid_input"` and a sort-aware recovery hint.
12
+ - **`pubmed_europepmc_search` `sort` description.** Previously cited `FIRST_PIDATE desc` as an example, which EPMC silently rejects. The description now enumerates the documented sortable fields (`P_PDATE_D`, `CITED`, `AUTH_FIRST`, `PUB_YEAR`) with their semantics.
13
+
14
+ ## Added
15
+
16
+ - **`europepmc_invalid_input` error contract** in `EUROPEPMC_SERVICE_ERRORS` — `ValidationError`, non-retryable. Folded into `REASON_TO_RECOVERY`.
17
+ - **`errCode` / `errMsg` fields on `EuropePmcSearchResponse`** for the structured rejection shape.
18
+
19
+ ## Changed
20
+
21
+ - **`@types/node` ^25.9.0 → ^25.9.1.**
@@ -0,0 +1,13 @@
1
+ ---
2
+ summary: "Three ECitMatch fixes: HTTP 500 reclassified as retryable, dropped citations synthesized as not_found, citation validation requires journal or year"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.7.10 — 2026-06-04
8
+
9
+ ## Fixed
10
+
11
+ - **`NcbiApiClient.makeRequest`** — NCBI HTTP 500 now reclassified as `ServiceUnavailable` via `httpErrorFromResponse` `codeOverride`, so transient eutils proxy failures are retried under the existing bounded-deadline backoff; 501 Not Implemented stays `InternalError` (non-retryable). ([#62](https://github.com/cyanheads/pubmed-mcp-server/issues/62))
12
+ - **`NcbiService.eCitMatch`** — results are reconciled against submitted citations after parsing; any citation the upstream drops (NCBI omits lines it cannot classify) is synthesized as `{ matched: false, pmid: null, status: 'not_found' }`, guaranteeing `results.length === citations.length` and stable key-based correlation. ([#54](https://github.com/cyanheads/pubmed-mcp-server/issues/54))
13
+ - **`pubmed_lookup_citation` input validation** — citation `.refine()` now requires at least `journal` or `year`; author-only and volume-only inputs (which ECitMatch primary-keys on journal+volume+page and cannot match) are rejected at the schema boundary before the API round-trip. ([#39](https://github.com/cyanheads/pubmed-mcp-server/issues/39))
@@ -0,0 +1,15 @@
1
+ ---
2
+ summary: "fetch_fulltext: DOI→PMCID resolution via PMC ID Converter (#64), config-aware tool description (#65)"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.7.11 — 2026-06-04
8
+
9
+ ## Added
10
+
11
+ - **`pubmed_fetch_fulltext` config-aware description** — the tool description is now composed at startup from the fallback tiers actually enabled (`EUROPEPMC_ENABLED`, `UNPAYWALL_EMAIL`), so `tools/list` advertises only what the deployment can deliver; PMC EFetch is always present, Europe PMC and Unpaywall clauses appear only when their config is set. ([#65](https://github.com/cyanheads/pubmed-mcp-server/issues/65))
12
+
13
+ ## Fixed
14
+
15
+ - **`pubmed_fetch_fulltext` `dois` branch** — DOI input now calls the PMC ID Converter before PMC EFetch, mirroring the `pmids` branch; a DOI whose article is indexed in PMC returns structured JATS (`viaSource: "pmc"`) instead of a false `unavailable`. Previously the `dois` branch skipped PMC EFetch entirely, giving DOI input a narrower resolution path than `pmids` or `pmcids`. ([#64](https://github.com/cyanheads/pubmed-mcp-server/issues/64))
@@ -0,0 +1,22 @@
1
+ ---
2
+ summary: "Framework `^0.9.1 → ^0.9.3` (zod peer-dep migration). MCPB bundle scaffolding (`manifest.json` + install badges). `fast-xml-parser` unpinned to `^5.8.0` (closes #55)."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.7.2 — 2026-05-22
8
+
9
+ Maintenance release. Picks up `@cyanheads/mcp-ts-core` 0.9.2 + 0.9.3, unpins `fast-xml-parser` per [#55](https://github.com/cyanheads/pubmed-mcp-server/issues/55), and adopts the framework's MCPB bundle scaffolding for one-click Claude Desktop install. No server behavior changes.
10
+
11
+ ## Added
12
+
13
+ - **MCPB bundle support** — `manifest.json` (with `user_config` for `NCBI_API_KEY`, `NCBI_ADMIN_EMAIL`, `UNPAYWALL_EMAIL`), `.mcpbignore`, and `bundle` / `lint:packaging` / `list-skills` / `audit:refresh` npm scripts. Install badges (Claude Desktop / Cursor / VS Code) added to README. `Bundling` section added to `CLAUDE.md` / `AGENTS.md`. MCPB is stdio-only; HTTP and Docker deployments are unaffected.
14
+ - **`zod ^4.4.3`** declared as a direct dependency — `mcp-ts-core` 0.9.2 moved it to `peerDependencies`.
15
+
16
+ ## Changed
17
+
18
+ - **`@cyanheads/mcp-ts-core` `^0.9.1 → ^0.9.3`**. Notable upstream changes: MCPB scaffolding (adopted, above); `format-parity` lint now walks each union branch (no unions in our definitions, no impact); canvas API widened to `RequestContextLike` (no DataCanvas usage, no impact).
19
+ - **`fast-xml-parser` `~5.7.3 → ^5.8.0`** (closes [#55](https://github.com/cyanheads/pubmed-mcp-server/issues/55)). Upstream marked the in-tree `XMLValidator` as `@deprecated` in favor of a new sibling `fast-xml-validator` package. The export still works, and both call sites (`src/services/ncbi/response-handler.ts`, `src/services/europe-pmc/europe-pmc-service.ts`) strip `<!DOCTYPE …>` before validation, so 5.8.0's tightened DOCTYPE entity validation cannot reach us. Sibling package not mature enough to migrate to. Biome `noDeprecatedImports` suppressed at both import sites with a comment explaining the stance.
20
+ - **README badge layout** per `polish-docs-meta` 1.9 — Framework badge promoted to its own spotlight row (recolored cyan-300 `67E8F9`); remaining badges grouped across two rows. Docker (ghcr.io) badge added.
21
+ - **Dev deps**: `@vitest/coverage-istanbul` `^4.1.6 → ^4.1.7`, `vitest` `^4.1.6 → ^4.1.7`.
22
+ - **Project skills synced** from framework: `field-test` 2.4 → 2.5, `maintenance` 2.1 → 2.3, `polish-docs-meta` 1.8 → 1.9, `release-and-publish` 2.2 → 2.4. Framework scripts: `devcheck.ts` updated; `lint-packaging.ts` + `list-skills.ts` added.
@@ -0,0 +1,18 @@
1
+ ---
2
+ summary: "Framework `^0.9.3 → ^0.9.4`. Documents opt-in `MCP_GC_PRESSURE_INTERVAL_MS` for HTTP heap-growth mitigation."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.7.3 — 2026-05-22
8
+
9
+ Maintenance release. Picks up `@cyanheads/mcp-ts-core` 0.9.4 and surfaces the new opt-in Bun GC loop to operators of HTTP deployments.
10
+
11
+ ## Added
12
+
13
+ - **`MCP_GC_PRESSURE_INTERVAL_MS`** documented in `.env.example` and the README config table — opt-in Bun-only forced-GC loop ([cyanheads/mcp-ts-core#50](https://github.com/cyanheads/mcp-ts-core/issues/50)) for HTTP deployments exhibiting heap growth from the per-request `McpServer`/`McpSessionTransport` reference cycle. Default `0` (disabled); recommended starting point if growth is observed: `60000`. Relevant to the public hosted instance at `pubmed.caseyjhand.com`.
14
+
15
+ ## Changed
16
+
17
+ - **`@cyanheads/mcp-ts-core` `^0.9.3 → ^0.9.4`**. Notable upstream changes: the GC loop above (adopted, surfaced to operators); skill-versioning policy extended to cover all reference files under `skills/<name>/` (framework-internal policy, no consumer action); README install-button URL fix landed upstream — Cursor `https://cursor.com/en/install-mcp` + VS Code `https://vscode.dev/redirect?url=vscode:mcp/install?...` — already applied manually in 2.7.2, so this server's badges now match the framework template.
18
+ - **Framework version reference** bumped in `CLAUDE.md` + `AGENTS.md`.
@@ -0,0 +1,15 @@
1
+ ---
2
+ summary: "Framework `^0.9.4 → ^0.9.6`. `.mcpbignore` recursive-match fix. Skills synced."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.7.4 — 2026-05-23
8
+
9
+ Maintenance release. Picks up `@cyanheads/mcp-ts-core` 0.9.6 and fixes `.mcpbignore` pattern handling.
10
+
11
+ ## Changed
12
+
13
+ - **`@cyanheads/mcp-ts-core` `^0.9.4 → ^0.9.6`**. Notable upstream changes: `.mcpbignore` recursive-match fix (patterns like `node_modules` now correctly exclude nested paths); `maintenance` skill v2.4, `polish-docs-meta` skill v2.2, `release-and-publish` skill v2.5 with improved orchestration.
14
+ - **Skills synced** — `maintenance` 2.4, `polish-docs-meta` 2.2, `release-and-publish` 2.5 copied from updated framework.
15
+ - **keywords** added to `package.json` for npm discoverability.
@@ -0,0 +1,34 @@
1
+ ---
2
+ summary: "Framework `^0.9.6 → ^0.9.10`. Plugin metadata scaffolds. Field-test fixes: tool description ↔ enum alignment, PMC ID schema validation, EuropePMC cursor round-trip."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.7.5 — 2026-05-27
8
+
9
+ Framework upgrade plus a field-test pass across the live HTTP deployment.
10
+
11
+ ## Added
12
+
13
+ - **`.claude-plugin/plugin.json`**, **`.codex-plugin/plugin.json`**, **`.codex-plugin/mcp.json`** — Claude Code and Codex plugin metadata scaffolds adopted from `@cyanheads/mcp-ts-core` `^0.9.10` templates.
14
+ - **`git-wrapup` skill** synced from framework `^0.9.8`.
15
+ - **"Close the loop on issues"** core rule in `CLAUDE.md` / `AGENTS.md` (framework `^0.9.9` alignment).
16
+
17
+ ## Changed
18
+
19
+ - **`@cyanheads/mcp-ts-core`** `^0.9.6 → ^0.9.10`. Notable for this server's hosted HTTP instance: stateful HTTP mode now rejects non-`initialize` requests missing `Mcp-Session-Id` with HTTP 400 ([#154](https://github.com/cyanheads/mcp-ts-core/issues/154)); expected client errors (401/403/404) use `classifyOnly` + `logger.warning` instead of the full `ErrorHandler` pipeline, cutting stack-trace noise in error logs ([#158](https://github.com/cyanheads/mcp-ts-core/issues/158)); `fast-check` moved to optional peer dep with `loadFc()` lazy loader ([#83](https://github.com/cyanheads/mcp-ts-core/issues/83)).
20
+ - **`@biomejs/biome`** `^2.4.15 → ^2.4.16`.
21
+ - **`request-queue.ts`** — rate-limit throw migrated from `new McpError(JsonRpcErrorCode.RateLimited, …)` to the `rateLimited()` factory.
22
+ - **`tests/_fuzz-helpers.ts`** — `loadFc()` lazy import to track framework `^0.9.9` peer-dep change.
23
+ - **`pubmed_format_citations`** — description text aligned to enum casing (`apa, mla, bibtex, ris`) instead of mismatched uppercase prose.
24
+ - **`pubmed_find_related`** — description surfaces the relationship enum values (`similar`, `cited_by`, `references`) instead of prose ("citing articles") that didn't match the schema.
25
+ - **Skills** — 28+ files synced from the framework package (`maintenance`, `polish-docs-meta`, `release-and-publish`, `design-mcp-server`, `field-test`, `add-*`, `api-*`).
26
+ - **`scripts/check-framework-antipatterns.ts`** — header re-synced from framework `^0.9.9`.
27
+ - **`scripts/split-changelog.ts`** removed — no longer shipped by the framework (one-shot migration script).
28
+ - **README** — install-button URLs updated (`cursor.com/en/install-mcp`, `vscode.dev/redirect?url=…`).
29
+
30
+ ## Fixed
31
+
32
+ - **`pubmed_fetch_fulltext`** — `pmcids[]` items now validate `^(?:PMC)?\d+$/i` at the schema boundary. Garbage like `"NOT-A-PMC-ID"` is rejected up front instead of being auto-prefixed to `"PMCNOT-A-PMC-ID"` and triggering an upstream error. Bare-digit inputs (`"3531190"`) still resolve correctly.
33
+ - **`pubmed_fetch_fulltext`** — `format()` sanitizes URLs in `triedTiers.detail` strings to `<upstream>` in the rendered text. Raw URLs are preserved in `structuredContent` for debugging.
34
+ - **`EuropePmcService.search`** — cursorMark echo now uses the caller's raw input instead of EPMC's URL-encoded `request.cursorMark`. Also fixes a latent end-of-pagination bug where the equality check compared the encoded echo against the raw `nextCursorMark`, silently breaking detection for base64 cursors containing `/` or `=`.
@@ -0,0 +1,14 @@
1
+ ---
2
+ summary: "MCPB install fix — handle unsubstituted `${user_config.X}` env values when optional fields are left blank."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.7.6 — 2026-05-27
8
+
9
+ Patch fix for `.mcpb` bundle installs (Claude Desktop, other MCPB hosts). When all three `user_config` fields (`ncbi_api_key`, `ncbi_admin_email`, `unpaywall_email`) are optional and left blank, the host passes the literal `${user_config.X}` placeholder string through to the process instead of an empty value. `z.email()` then rejects it on first config load and the server exits silently after `initialize`.
10
+
11
+ ## Fixed
12
+
13
+ - **`emptyAsUndefined`** (`src/config/server-config.ts`) now treats strings matching `${...}` as `undefined`, alongside `''`. Covers `apiKey`, `adminEmail`, `unpaywallEmail`, `europepmcEmail` — every email-validated optional field.
14
+ - **`manifest.json`** — `ncbi_api_key`, `ncbi_admin_email`, `unpaywall_email` `user_config` entries now declare `"default": ""`. Conformant MCPB hosts will substitute empty string instead of the raw placeholder; the server-side stripper is the belt to that suspender.
@@ -0,0 +1,14 @@
1
+ ---
2
+ summary: "mcp-ts-core ^0.9.10 → ^0.9.13: 413 request-body cap, auth-gated landing page default, GET /mcp keywords; landing.requireAuth: false explicit opt-out for hosted instance"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.7.7 — 2026-05-28
8
+
9
+ ## Changed
10
+
11
+ - **`landing.requireAuth`** (`src/index.ts`) — explicitly set to `false`. Framework 0.9.13 changed the default: when `MCP_AUTH_MODE` is `jwt` or `oauth`, the landing page inventory (tool/resource listings, invocation snippets) is now gated behind authentication by default. The hosted pubmed instance is a public catalog, so `requireAuth: false` preserves the previous behavior.
12
+ - **`MCP_HTTP_MAX_BODY_BYTES`** (`.env.example`) — documented. Framework 0.9.13 adds a configurable inbound request-body cap on the HTTP endpoint; oversized requests are rejected with `413` before per-request allocation. Default 1 MiB; set to `0` to disable.
13
+ - **`GET /mcp` response** — `server.keywords` now included (from `package.json` `keywords`). Added `"stdio"` and `"streamable-http"` to the keywords list for better discovery.
14
+ - **`@cyanheads/mcp-ts-core`** `^0.9.10` → `^0.9.13`
@@ -0,0 +1,18 @@
1
+ ---
2
+ summary: "mcp-ts-core ^0.9.13 → ^0.9.16: result-set context (effective query, totals, applied filters, empty-result notices) moved to ctx.enrich and mirrored to structuredContent and content[] via enrichmentTrailer render/label"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.7.8 — 2026-05-29
8
+
9
+ ## Changed
10
+
11
+ - **Result-set context relocated to `ctx.enrich`** across `pubmed_search_articles`, `pubmed_europepmc_search`, `pubmed_find_related`, and `pubmed_lookup_mesh`. The effective query, match totals (`totalFound` / `hitCount`), applied filters, queried sources, and empty-result recovery notices now reach both client surfaces — `structuredContent` and a `content[]` trailer — instead of only the domain output. `structuredContent`-only and `content[]`-only clients see the same context.
12
+ - **`enrichmentTrailer` render/label** on the three tools carrying structured enrichment. `appliedFilters` (object) and `appliedSources` (array) render as markdown bullets in the `content[]` trailer rather than JSON blobs; scalar fields use Title-Case labels (`Effective Query`, `Total Found`, `Total Hits`). `structuredContent` always keeps the raw structured value.
13
+ - **`format` script split** — `format` runs safe Biome autofixes only; `format:unsafe` adds `--unsafe`. `AGENTS.md` ships in `package.json` `files[]`. Tracks mcp-ts-core 0.9.16 templates.
14
+ - **`@cyanheads/mcp-ts-core`** `^0.9.13` → `^0.9.16`
15
+
16
+ ## Fixed
17
+
18
+ - **`server-config` test** no longer trips Biome's `noTemplateCurlyInString` — literal MCPB `${user_config.X}` placeholders are built via an escaped template literal.
@@ -0,0 +1,16 @@
1
+ ---
2
+ summary: "mcp-ts-core ^0.9.16 → ^0.9.21: per-request log context fix, fetchWithTimeout secret-scrubbing, withRetry fail-fast on non-retryable errors; new scripts/release-github.ts, skill sync"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.7.9 — 2026-06-02
8
+
9
+ ## Changed
10
+
11
+ - **`@cyanheads/mcp-ts-core`** `^0.9.16` → `^0.9.21` — HTTP transport per-request log context fix (per-request logs and traces carry fresh request + trace/span IDs instead of the frozen boot context); `fetchWithTimeout` strips query-string secrets (e.g. `?api_key=`) from error messages and logs; `withRetry` fails fast on non-retryable errors; `ctx.fail` auto-populates the `retryable` flag.
12
+ - **`scripts/release-github.ts`** added — creates GitHub Releases from annotated tags, attaches `.mcpb` bundle; `release:github` script wired in `package.json`.
13
+ - **`scripts/devcheck.ts`** — Open-Indexed Interfaces and Skill Versions checks added.
14
+ - **Client-config server key** renamed from `"pubmed"` to `"pubmed-mcp-server"` in README install snippets (HTTP, bunx, npx, Docker).
15
+ - **Skills synced** — `add-service`, `add-tool`, `api-canvas`, `api-context`, `api-linter`, `api-utils`, `design-mcp-server`, `release-and-publish` updated; `api-mirror` and `orchestrations` added.
16
+ - **`vitest`** `^4.1.7` → `^4.1.8`, **`@vitest/coverage-istanbul`** `^4.1.7` → `^4.1.8`
@@ -0,0 +1,23 @@
1
+ ---
2
+ summary: "Vancouver citation style, full-text reference extraction fix, BibTeX MeSH keyword fix, empty-result notices"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.8.0 — 2026-06-08
8
+
9
+ ## Added
10
+
11
+ - **`pubmed_format_citations`**: Vancouver (ICMJE/NLM) style — fifth citation format alongside APA, MLA, BibTeX, and RIS ([#61](https://github.com/cyanheads/pubmed-mcp-server/issues/61)).
12
+ - **`pubmed_fetch_articles`**: empty-result recovery hint surfaced via `ctx.enrich.notice()` so it reaches `structuredContent`, not only `content[]` ([#58](https://github.com/cyanheads/pubmed-mcp-server/issues/58)).
13
+ - **`pubmed_format_citations`**: same empty-result `ctx.enrich.notice()` treatment ([#59](https://github.com/cyanheads/pubmed-mcp-server/issues/59)).
14
+
15
+ ## Fixed
16
+
17
+ - **`pubmed_fetch_fulltext`**: references wrapped in JATS `<citation-alternatives>` were silently dropped; parser now descends into the container element. Fixes 64 of 84 references missing for PMC8371605 — applies to both PMC EFetch and Europe PMC full-text paths ([#66](https://github.com/cyanheads/pubmed-mcp-server/issues/66)).
18
+ - **`pubmed_format_citations` BibTeX**: multi-word MeSH descriptors with internal commas (e.g. `Databases, Protein`) split into two keywords; each term is now brace-wrapped to stay one keyword ([#68](https://github.com/cyanheads/pubmed-mcp-server/issues/68)).
19
+
20
+ ## Changed
21
+
22
+ - `@types/node` `^25.9.1` → `^25.9.2`
23
+ - `devcheck.config.json`: `@cyanheads/mcp-ts-core` added to `outdated.allowlist` (held at `^0.9.21`).
@@ -0,0 +1,21 @@
1
+ ---
2
+ summary: "find_related: offset pagination, multi-source provider fallback (NCBI → EuropePMC → OpenAlex); europepmc_search: date-sort advisory for PPR-only results"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.9.0 — 2026-06-09
8
+
9
+ ## Added
10
+
11
+ - **`pubmed_find_related`**: `offset` input parameter (0-based, default 0) enables paging past the first result window — mirrors `pubmed_search_articles` semantics ([#36](https://github.com/cyanheads/pubmed-mcp-server/issues/36)).
12
+ - **`pubmed_find_related`**: `format()` header now emits `**Returned:** N | **Offset:** Z` alongside the existing `**Relationship:**` line, matching `search_articles` header parity ([#36](https://github.com/cyanheads/pubmed-mcp-server/issues/36)).
13
+ - **`pubmed_find_related`**: multi-source provider fallback — NCBI eLink (primary) → Europe PMC → OpenAlex; first success wins, no merging ([#63](https://github.com/cyanheads/pubmed-mcp-server/issues/63)).
14
+ - `similar`: NCBI `pubmed_pubmed` → OpenAlex `related_works`.
15
+ - `cited_by`: NCBI `citedin` → Europe PMC `/MED/{pmid}/citations` → OpenAlex `cites:`.
16
+ - `references`: NCBI `refs` (PMC-indexed only) → Europe PMC `/MED/{pmid}/references` → OpenAlex `referenced_works`. Supersedes the notice-only mitigation for non-PMC sources — actual coverage now served by EPMC/OpenAlex when NCBI has no PMC-indexed reference list.
17
+ - Provenance surfaced in `enrichment.source` (`"ncbi"` | `"europepmc"` | `"openalex"`); a notice is appended to `enrichment` only when a non-primary provider answered.
18
+ - Records without a PMID are dropped, never minted.
19
+ - **`src/services/openalex/`**: new OpenAlex API client — PMID→OA ID resolve, `related_works`/`referenced_works` batch PMID round-trip, `cites:` filter for `cited_by` ([#63](https://github.com/cyanheads/pubmed-mcp-server/issues/63)).
20
+ - **`src/services/europe-pmc/`**: `citations(pmid, options)` and `references(pmid, options)` methods added to the existing Europe PMC integration ([#63](https://github.com/cyanheads/pubmed-mcp-server/issues/63)).
21
+ - **`pubmed_europepmc_search`**: advisory notice via `ctx.enrich.notice()` when `sort` targets a date field (e.g. `P_PDATE_D`) and the effective sources are preprint-only (`PPR`) — EPMC accepts but silently ignores date sort on PPR records; notice names `firstPublicationDate` as the populated field available for page-local client-side sorting ([#67](https://github.com/cyanheads/pubmed-mcp-server/issues/67)).
@@ -0,0 +1,12 @@
1
+ ---
2
+ summary: "fix(fetch_fulltext): element-citation references now delimited; PMC page/bibliographic tokens preserved verbatim"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.9.1 — 2026-06-09
8
+
9
+ ## Fixed
10
+
11
+ - **`pubmed_fetch_fulltext`** (`includeReferences: true`): structured `<element-citation>` references now render field-by-field with separators — author names joined with `, `, typed `<pub-id>` nodes labeled (`PMID`, `DOI`, `PMCID`), fields separated by spaces. `<mixed-citation>` rendering is unchanged. ([#69](https://github.com/cyanheads/pubmed-mcp-server/issues/69))
12
+ - **`pubmed_fetch_fulltext`**: PMC JATS ordered parser (`orderedXmlParser`) now runs with `parseTagValue: false` — page tokens like `4002.e26` are preserved verbatim instead of being coerced to `4.002e+29`; zero-padded values retain their leading zeros. The regular parser (esearch/esummary numeric counts) is untouched. ([#69](https://github.com/cyanheads/pubmed-mcp-server/issues/69))
@@ -0,0 +1,15 @@
1
+ ---
2
+ summary: "pubmed_fetch_fulltext: EPMC/PMCID query builders fixed, front-matter-only records fall through the retrieval chain instead of being dropped, pmcids input reaches Unpaywall via a resolved DOI, and Unpaywall articles carry a pmcId backlink. Dead 404-handling branches removed from the Europe PMC and OpenAlex clients."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.9.10 — 2026-07-26
8
+
9
+ ## Fixed
10
+
11
+ - **`pubmed_europepmc_search` / `pubmed_fetch_fulltext` EPMC query builders** — `EXT_ID:"<pmid>" AND SRC:MED` and `PMCID:"PMC<digits>" AND SRC:PMC` matched zero records: Europe PMC requires identifier tokens combined with `AND SRC:` to be unquoted, and the canonical record for a PMCID lookup has `source: MED`, so `SRC:PMC` excluded the very record being sought. Query builders now emit `EXT_ID:<pmid> AND SRC:MED` and `PMCID:<PMC<digits>>` (the `SRC:PMC` filter dropped); `pubmed_europepmc_search`'s `query` `.describe()` updated to match. ([#85](https://github.com/cyanheads/pubmed-mcp-server/issues/85))
12
+ - **`pubmed_fetch_fulltext` front-matter-only PMC/EPMC records** — a PMC article with front matter and abstract but no `<body>` (publishers that block full-text XML download still return a populated `<front>`) was previously demoted like any other retrieved article. It's now detected pre-filter and routed through the fallback chain like a miss, so a later tier can still recover it. New `no-body` member on the `UnavailableReasonSchema` and `TierOutcomeSchema` output enums, plus an `enrichment.notice` naming the affected id(s) when no tier recovers a copy. ([#86](https://github.com/cyanheads/pubmed-mcp-server/issues/86))
13
+ - **`pubmed_fetch_fulltext` `pmcids` input skipped Unpaywall entirely** — PMC/EPMC misses on `pmcids` input never reached the Unpaywall fallback tier because the DOI needed to query it was never resolved. Now threads the DOI from the Europe PMC hit (when present) or the NCBI ID Converter through to Unpaywall. ([#88](https://github.com/cyanheads/pubmed-mcp-server/issues/88))
14
+ - **Unpaywall articles from `pmcids` input lacked a PMCID backlink** — `UnpaywallArticleSchema` gains an optional `pmcId` field so a batch's `articles[]` and `unavailable[]` key on the same identifier for `pmcids` input; `format()`'s heading and metadata block now surface the PMCID when present. ([#92](https://github.com/cyanheads/pubmed-mcp-server/issues/92))
15
+ - **Europe PMC / OpenAlex 404 handling** — `fetchWithTimeout` throws a status-mapped `McpError` for any non-2xx response rather than returning the failing `Response`, so the `response.status === 404` / `!response.ok` branches in `src/services/europe-pmc/api-client.ts` and `src/services/openalex/api-client.ts` (plus four sibling methods) were unreachable; a genuine 404 was rethrown as a generic error and surfaced as `service-error` instead of the documented `no-fulltext`/`null` outcome. Converted to catch-block classification on `error.code`, and corrected the test mocks that had resolved with a non-2xx `Response`. ([#90](https://github.com/cyanheads/pubmed-mcp-server/issues/90))
@@ -0,0 +1,21 @@
1
+ ---
2
+ summary: "mcp-ts-core ^0.10.5: ctx.enrich.total(), z.stringbool(), server identity fields; totalCount enrichment rename; .mcpbignore anchored"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.9.2 — 2026-06-11
8
+
9
+ ## Changed
10
+
11
+ - **`totalFound` enrichment key renamed to `totalCount`** across `pubmed_search_articles`, `pubmed_find_related`, `pubmed_lookup_mesh`, and `pubmed_europepmc_search`. Pagination overshoot notices updated to reference `totalCount`. This key appears in `structuredContent` enrichment blocks and is consumed by agents reading result-set metadata.
12
+ - **`PUBMED_EUROPEPMC_ENABLED` now uses `z.stringbool()` semantics.** Accepts `true/false/1/0/yes/no/on/off` (case-insensitive); `yes` and `on` now parse truthy. Unrecognized values fail at startup instead of silently coercing to `false`. Previously only `true`/`1` were truthy.
13
+ - **Server identity fields added to `createApp()`**: `name`, `title`, `websiteUrl`, and `description` are now set, surfaced in the `initialize` response and `/.well-known/mcp.json`.
14
+ - **`.mcpbignore` dev-dir patterns anchored to root**: `/.claude/`, `/.agents/`, `/skills/` now use leading `/` so they cannot strip nested `node_modules` runtime paths.
15
+ - **`ctx.enrich.total()` migration**: enrichment total counts are now set via the framework's `ctx.enrich.total()` helper rather than as inline `totalFound` fields in the `ctx.enrich()` call.
16
+
17
+ ## Dependencies
18
+
19
+ - `@cyanheads/mcp-ts-core` ^0.9.21 → ^0.10.5
20
+ - `sanitize-html` ^2.17.4 → ^2.17.5
21
+ - `@types/node` ^25.9.2 → ^25.9.3
@@ -0,0 +1,11 @@
1
+ ---
2
+ summary: "serverInfo title corrected to machine name pubmed-mcp-server"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.9.3 — 2026-06-11
8
+
9
+ ## Fixed
10
+
11
+ - **`createApp()` `title`** corrected to `pubmed-mcp-server` (was the Title Case display string `'PubMed MCP Server'` introduced in 2.9.2). MCP clients display `serverInfo.title` in their UI — this aligns the displayed name with the machine name convention.
@@ -0,0 +1,24 @@
1
+ ---
2
+ summary: "mcp-ts-core ^0.10.6: post-pack bundle cleaner, packaging identity checks; lint-packaging checks 8–9 synced; orchestrations skill v1.3"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.9.4 — 2026-06-11
8
+
9
+ ## Changed
10
+
11
+ - **`src/index.ts`** — removed `websiteUrl` and `description` from `createApp()` options; canonical source is `package.json` (framework derives description from it); identity pair `name`/`title` both set to `'pubmed-mcp-server'`.
12
+ - **`scripts/lint-packaging.ts`** — synced from mcp-ts-core 0.10.6 template: adds check 8 (post-bundle content guard: flags `node_modules/**` agent-doc entries in built `.mcpb`) and check 9 (entrypoint identity: `name`/`title` in `createApp()` / `createWorkerHandler()` and `manifest.json` `display_name` must equal the unscoped package name). `checkBundleContent` refactored to accept raw content string; `KNOWN_DEV_DIRS` / `CRITICAL_RUNTIME_PATHS` / `AGENT_DOC_ENTRY` exported for unit testing.
13
+ - **`package.json` `bundle` script** — appended `&& bun run scripts/clean-mcpb.ts dist/pubmed-mcp-server.mcpb` after `mcpb pack`; strips dependency-shipped agent docs from the bundle.
14
+ - **`skills/orchestrations` v1.3** — Phase 4 close-loop wording: one what-landed comment per shipped issue, then a bare close; bare "Fixed in v\<version\>" trailer replaced.
15
+ - **`skills/polish-docs-meta` v2.7** — adds `name`/`title` identity requirement and updated `bundle` description.
16
+ - **`CLAUDE.md` / `AGENTS.md`** — framework version `^0.10.5 → ^0.10.6`; identity example corrected; `bundle` description updated.
17
+
18
+ ## Added
19
+
20
+ - **`scripts/clean-mcpb.ts`** — post-pack bundle cleaner: runs `mcpb clean`, then strips `node_modules/**` agent-doc entries (`skills/`, `.claude/`, `.agents/` trees and `SKILL.md` files) that root-anchored `.mcpbignore` patterns cannot reach. Wired into the `bundle` script. (mcp-ts-core [#230](https://github.com/cyanheads/mcp-ts-core/issues/230))
21
+
22
+ ## Dependencies
23
+
24
+ - `@cyanheads/mcp-ts-core` ^0.10.5 → ^0.10.6
@@ -0,0 +1,20 @@
1
+ ---
2
+ summary: "Fixes: transient NCBI eutils 500s now reclassify to ServiceUnavailable and retry; pubmed_lookup_mesh returns the canonical MeSH DescriptorUI with a new entrezUid field"
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.9.5 — 2026-06-13
8
+
9
+ ## Fixed
10
+
11
+ - **`NcbiApiClient` eutils 500 retry** — `getRequest`/`postRequest` now issue plain `fetch` (via a shared `buildTimeoutSignal` helper) instead of `fetchWithTimeout`, which threw on any non-2xx before the status could be read. The `if (!response.ok)` reclassification in `makeRequest` is now reachable, so NCBI's transient HTTP 500s map to `ServiceUnavailable` and are retried by `NcbiService.withRetry`; HTTP 501 stays `InternalError` (not transient). ([#70](https://github.com/cyanheads/pubmed-mcp-server/issues/70))
12
+ - **`pubmed_lookup_mesh` `meshId`** — now returns the canonical MeSH DescriptorUI (e.g. `D003924`) instead of NCBI's Entrez UID. `decodeMeshDescriptorUi` maps the 8-digit UID's two-digit prefix to its letter (`67→C`, `68→D`, `81→Q`); non-decodable UIDs (e.g. 7-digit supplementary-concept IDs) fall back to the raw value. ([#71](https://github.com/cyanheads/pubmed-mcp-server/issues/71))
13
+
14
+ ## Added
15
+
16
+ - **`pubmed_lookup_mesh` `entrezUid`** — new output field carrying the raw NCBI Entrez UID, the join key for E-utilities (`eSummary`/`eFetch` `db=mesh`); rendered in `format()` as **Entrez UID**. ([#71](https://github.com/cyanheads/pubmed-mcp-server/issues/71))
17
+
18
+ ## Dependencies
19
+
20
+ - `@biomejs/biome` ^2.4.16 → ^2.5.0