@cyanheads/pubmed-mcp-server 2.10.13 → 2.10.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (154) hide show
  1. package/AGENTS.md +24 -6
  2. package/CLAUDE.md +24 -6
  3. package/README.md +7 -7
  4. package/changelog/2.0.x/2.0.0.md +32 -0
  5. package/changelog/2.0.x/2.0.1.md +32 -0
  6. package/changelog/2.1.x/2.1.0.md +17 -0
  7. package/changelog/2.1.x/2.1.1.md +29 -0
  8. package/changelog/2.1.x/2.1.2.md +18 -0
  9. package/changelog/2.1.x/2.1.3.md +10 -0
  10. package/changelog/2.1.x/2.1.4.md +12 -0
  11. package/changelog/2.1.x/2.1.5.md +18 -0
  12. package/changelog/2.1.x/2.1.6.md +15 -0
  13. package/changelog/2.10.x/2.10.0.md +17 -0
  14. package/changelog/2.10.x/2.10.1.md +14 -0
  15. package/changelog/2.10.x/2.10.10.md +22 -0
  16. package/changelog/2.10.x/2.10.11.md +21 -0
  17. package/changelog/2.10.x/2.10.12.md +14 -0
  18. package/changelog/2.10.x/2.10.13.md +28 -0
  19. package/changelog/2.10.x/2.10.14.md +29 -0
  20. package/changelog/2.10.x/2.10.15.md +28 -0
  21. package/changelog/2.10.x/2.10.2.md +13 -0
  22. package/changelog/2.10.x/2.10.3.md +26 -0
  23. package/changelog/2.10.x/2.10.4.md +11 -0
  24. package/changelog/2.10.x/2.10.5.md +31 -0
  25. package/changelog/2.10.x/2.10.6.md +30 -0
  26. package/changelog/2.10.x/2.10.7.md +17 -0
  27. package/changelog/2.10.x/2.10.8.md +22 -0
  28. package/changelog/2.10.x/2.10.9.md +15 -0
  29. package/changelog/2.2.x/2.2.0.md +67 -0
  30. package/changelog/2.2.x/2.2.1.md +10 -0
  31. package/changelog/2.2.x/2.2.2.md +20 -0
  32. package/changelog/2.2.x/2.2.3.md +17 -0
  33. package/changelog/2.2.x/2.2.4.md +34 -0
  34. package/changelog/2.2.x/2.2.5.md +10 -0
  35. package/changelog/2.2.x/2.2.6.md +17 -0
  36. package/changelog/2.3.x/2.3.0.md +27 -0
  37. package/changelog/2.3.x/2.3.1.md +15 -0
  38. package/changelog/2.3.x/2.3.10.md +20 -0
  39. package/changelog/2.3.x/2.3.11.md +21 -0
  40. package/changelog/2.3.x/2.3.2.md +27 -0
  41. package/changelog/2.3.x/2.3.3.md +38 -0
  42. package/changelog/2.3.x/2.3.4.md +21 -0
  43. package/changelog/2.3.x/2.3.5.md +24 -0
  44. package/changelog/2.3.x/2.3.6.md +26 -0
  45. package/changelog/2.3.x/2.3.7.md +31 -0
  46. package/changelog/2.3.x/2.3.8.md +19 -0
  47. package/changelog/2.3.x/2.3.9.md +22 -0
  48. package/changelog/2.4.x/2.4.0.md +34 -0
  49. package/changelog/2.4.x/2.4.1.md +32 -0
  50. package/changelog/2.5.x/2.5.0.md +35 -0
  51. package/changelog/2.5.x/2.5.1.md +32 -0
  52. package/changelog/2.5.x/2.5.2.md +23 -0
  53. package/changelog/2.5.x/2.5.3.md +22 -0
  54. package/changelog/2.5.x/2.5.5.md +52 -0
  55. package/changelog/2.5.x/2.5.6.md +33 -0
  56. package/changelog/2.6.x/2.6.0.md +32 -0
  57. package/changelog/2.6.x/2.6.1.md +26 -0
  58. package/changelog/2.6.x/2.6.10.md +16 -0
  59. package/changelog/2.6.x/2.6.11.md +24 -0
  60. package/changelog/2.6.x/2.6.12.md +29 -0
  61. package/changelog/2.6.x/2.6.2.md +23 -0
  62. package/changelog/2.6.x/2.6.3.md +17 -0
  63. package/changelog/2.6.x/2.6.4.md +21 -0
  64. package/changelog/2.6.x/2.6.5.md +30 -0
  65. package/changelog/2.6.x/2.6.6.md +25 -0
  66. package/changelog/2.6.x/2.6.7.md +37 -0
  67. package/changelog/2.6.x/2.6.8.md +15 -0
  68. package/changelog/2.6.x/2.6.9.md +36 -0
  69. package/changelog/2.7.x/2.7.0.md +41 -0
  70. package/changelog/2.7.x/2.7.1.md +21 -0
  71. package/changelog/2.7.x/2.7.10.md +13 -0
  72. package/changelog/2.7.x/2.7.11.md +15 -0
  73. package/changelog/2.7.x/2.7.2.md +22 -0
  74. package/changelog/2.7.x/2.7.3.md +18 -0
  75. package/changelog/2.7.x/2.7.4.md +15 -0
  76. package/changelog/2.7.x/2.7.5.md +34 -0
  77. package/changelog/2.7.x/2.7.6.md +14 -0
  78. package/changelog/2.7.x/2.7.7.md +14 -0
  79. package/changelog/2.7.x/2.7.8.md +18 -0
  80. package/changelog/2.7.x/2.7.9.md +16 -0
  81. package/changelog/2.8.x/2.8.0.md +23 -0
  82. package/changelog/2.9.x/2.9.0.md +21 -0
  83. package/changelog/2.9.x/2.9.1.md +12 -0
  84. package/changelog/2.9.x/2.9.10.md +15 -0
  85. package/changelog/2.9.x/2.9.2.md +21 -0
  86. package/changelog/2.9.x/2.9.3.md +11 -0
  87. package/changelog/2.9.x/2.9.4.md +24 -0
  88. package/changelog/2.9.x/2.9.5.md +20 -0
  89. package/changelog/2.9.x/2.9.6.md +22 -0
  90. package/changelog/2.9.x/2.9.7.md +26 -0
  91. package/changelog/2.9.x/2.9.8.md +15 -0
  92. package/changelog/2.9.x/2.9.9.md +35 -0
  93. package/changelog/template.md +151 -0
  94. package/dist/index.js +1 -0
  95. package/dist/index.js.map +1 -1
  96. package/dist/mcp-server/tools/definitions/convert-ids.tool.d.ts +7 -2
  97. package/dist/mcp-server/tools/definitions/convert-ids.tool.d.ts.map +1 -1
  98. package/dist/mcp-server/tools/definitions/fetch-articles.tool.d.ts +7 -2
  99. package/dist/mcp-server/tools/definitions/fetch-articles.tool.d.ts.map +1 -1
  100. package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.d.ts +14 -5
  101. package/dist/mcp-server/tools/definitions/fetch-fulltext.tool.d.ts.map +1 -1
  102. package/dist/mcp-server/tools/definitions/find-related.tool.d.ts +0 -60
  103. package/dist/mcp-server/tools/definitions/find-related.tool.d.ts.map +1 -1
  104. package/dist/mcp-server/tools/definitions/find-related.tool.js +4 -4
  105. package/dist/mcp-server/tools/definitions/find-related.tool.js.map +1 -1
  106. package/dist/mcp-server/tools/definitions/format-citations.tool.d.ts +7 -2
  107. package/dist/mcp-server/tools/definitions/format-citations.tool.d.ts.map +1 -1
  108. package/dist/mcp-server/tools/definitions/lookup-citation.tool.d.ts +7 -2
  109. package/dist/mcp-server/tools/definitions/lookup-citation.tool.d.ts.map +1 -1
  110. package/dist/mcp-server/tools/definitions/lookup-mesh.tool.d.ts +7 -2
  111. package/dist/mcp-server/tools/definitions/lookup-mesh.tool.d.ts.map +1 -1
  112. package/dist/mcp-server/tools/definitions/pubmed-europepmc-fetch.tool.d.ts +6 -3
  113. package/dist/mcp-server/tools/definitions/pubmed-europepmc-fetch.tool.d.ts.map +1 -1
  114. package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.d.ts +6 -3
  115. package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.d.ts.map +1 -1
  116. package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.js +1 -1
  117. package/dist/mcp-server/tools/definitions/pubmed-europepmc-search.tool.js.map +1 -1
  118. package/dist/mcp-server/tools/definitions/search-articles.tool.d.ts +7 -2
  119. package/dist/mcp-server/tools/definitions/search-articles.tool.d.ts.map +1 -1
  120. package/dist/mcp-server/tools/definitions/spell-check.tool.d.ts +7 -2
  121. package/dist/mcp-server/tools/definitions/spell-check.tool.d.ts.map +1 -1
  122. package/dist/services/error-contracts.d.ts +30 -13
  123. package/dist/services/error-contracts.d.ts.map +1 -1
  124. package/dist/services/error-contracts.js +30 -13
  125. package/dist/services/error-contracts.js.map +1 -1
  126. package/dist/services/europe-pmc/api-client.d.ts +14 -2
  127. package/dist/services/europe-pmc/api-client.d.ts.map +1 -1
  128. package/dist/services/europe-pmc/api-client.js +30 -4
  129. package/dist/services/europe-pmc/api-client.js.map +1 -1
  130. package/dist/services/europe-pmc/europe-pmc-service.d.ts +20 -1
  131. package/dist/services/europe-pmc/europe-pmc-service.d.ts.map +1 -1
  132. package/dist/services/europe-pmc/europe-pmc-service.js +98 -50
  133. package/dist/services/europe-pmc/europe-pmc-service.js.map +1 -1
  134. package/dist/services/ncbi/ncbi-service.d.ts +18 -26
  135. package/dist/services/ncbi/ncbi-service.d.ts.map +1 -1
  136. package/dist/services/ncbi/ncbi-service.js +111 -127
  137. package/dist/services/ncbi/ncbi-service.js.map +1 -1
  138. package/dist/services/ncbi/request-queue.d.ts +22 -30
  139. package/dist/services/ncbi/request-queue.d.ts.map +1 -1
  140. package/dist/services/ncbi/request-queue.js +29 -128
  141. package/dist/services/ncbi/request-queue.js.map +1 -1
  142. package/dist/services/ncbi/response-handler.d.ts +14 -1
  143. package/dist/services/ncbi/response-handler.d.ts.map +1 -1
  144. package/dist/services/ncbi/response-handler.js +65 -9
  145. package/dist/services/ncbi/response-handler.js.map +1 -1
  146. package/dist/services/openalex/openalex-service.d.ts.map +1 -1
  147. package/dist/services/openalex/openalex-service.js +3 -10
  148. package/dist/services/openalex/openalex-service.js.map +1 -1
  149. package/package.json +20 -11
  150. package/server.json +3 -3
  151. package/dist/services/retry-policy.d.ts +0 -18
  152. package/dist/services/retry-policy.d.ts.map +0 -1
  153. package/dist/services/retry-policy.js +0 -21
  154. package/dist/services/retry-policy.js.map +0 -1
@@ -0,0 +1,21 @@
1
+ ---
2
+ summary: "HTTP 429 now classified as `RateLimited` and retried; default `maxRetries` raised 3 → 6 with 30s backoff cap and ±25% jitter."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.3.4 — 2026-04-12
7
+
8
+ ## Fixed
9
+
10
+ - **HTTP 429 classification**: NCBI rate-limit responses (HTTP 429) were misclassified as `InvalidRequest` and failed immediately without retrying. Now correctly classified as `RateLimited`.
11
+ - **Retry resilience**: `RateLimited` errors are now included in the retryable error set alongside `ServiceUnavailable` and `Timeout`.
12
+
13
+ ## Changed
14
+
15
+ - **Retry defaults**: Increased default `maxRetries` from 3 to 6, extending the retry window from ~7s to ~45-75s before giving up.
16
+ - **Backoff strategy**: Added 30s cap on exponential backoff (prevents explosion at high retry counts) and ±25% jitter (prevents thundering herd on concurrent retries).
17
+
18
+ ## Updated
19
+
20
+ - `@biomejs/biome` to ^2.4.11
21
+ - `@types/node` to ^25.6.0
@@ -0,0 +1,24 @@
1
+ ---
2
+ summary: "XML handling — raised entity expansion ceiling, preserved diacritics, wrapped parser failures as `SerializationError`. Retry tightened: only transient `McpError` retries. `fast-xml-parser` ^5.5.12."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.3.5 — 2026-04-13
7
+
8
+ ## Fixed
9
+
10
+ - **XML response handling**: Raised the numeric entity expansion ceiling for trusted NCBI XML, preserved decoded punctuation/diacritics in parsed metadata, and wrapped parser failures as `SerializationError`.
11
+ - **Retry behavior**: Stopped retrying unexpected plain errors in `NcbiService`; only transient `McpError` responses are retried now.
12
+
13
+ ## Added
14
+
15
+ - **Regression coverage**: Added end-to-end and unit tests for Unicode metadata, en-dash page ranges, parser failure wrapping, and entity-heavy XML payloads.
16
+
17
+ ## Updated
18
+
19
+ - `@cyanheads/mcp-ts-core` to ^0.3.5
20
+ - `fast-xml-parser` to ^5.5.12
21
+
22
+ ## Docs
23
+
24
+ - Updated the `design-mcp-server` and `add-test` skills for MCP Apps planning guidance and default test layout guidance.
@@ -0,0 +1,26 @@
1
+ ---
2
+ summary: "Maintenance — five deps updated, `overrides` block removed (all nine pinned transitives patched upstream), tool description strings collapsed to single-paragraph convention."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.3.6 — 2026-04-19
7
+
8
+ ## Updated
9
+
10
+ - `@cyanheads/mcp-ts-core` to ^0.3.7
11
+ - `fast-xml-parser` to ^5.7.1
12
+ - `sanitize-html` to ^2.17.3
13
+ - `@biomejs/biome` to ^2.4.12
14
+ - `typescript` to ^6.0.3
15
+
16
+ ## Removed
17
+
18
+ - **Dependency overrides**: Removed the `overrides` block from `package.json`. All nine pinned transitive deps (`hono`, `@hono/node-server`, `brace-expansion`, `express-rate-limit`, `path-to-regexp`, `picomatch`, `vite`, `yaml`, `lodash`) have since shipped patched versions upstream, making the overrides dead weight. `bun audit` remains clean.
19
+
20
+ ## Changed
21
+
22
+ - **Tool descriptions**: Collapsed multi-line `+` string concatenation in `pubmed_search_articles`, `pubmed_fetch_fulltext`, and `pubmed_convert_ids` to single strings, aligning with the project's description convention and the updated `add-tool` / `design-mcp-server` skill guidance (single cohesive paragraph, no structural noise).
23
+
24
+ ## Docs
25
+
26
+ - Synced `add-tool` (v1.4) and `design-mcp-server` (v2.3) skills from the framework — both now emphasize single-paragraph tool descriptions over bullet lists or blank-line-separated sections.
@@ -0,0 +1,31 @@
1
+ ---
2
+ summary: "Citation formatter fixes — APA collective-author period, RIS page expansion, BibTeX double-period; adds pub-type mapping, ISSN, PMC URL, MeSH keywords (closes [#15](https://github.com/cyanheads/pubmed-mcp-server/issues/15))."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.3.7 — 2026-04-20
7
+
8
+ ## Fixed
9
+
10
+ - **APA — missing period before year with collective authors** (`citation-formatter.ts`): `formatApa` now coerces a trailing period on the author block. Individual author initials already end with `.`, but collective names (e.g., `ATLAS Collaboration`, `KEYNOTE-024 Investigators`, `ACTT-1 Study Group Members`) did not, producing `Name (Year).` instead of the APA 7 §9.8-compliant `Name. (Year).`. Fix mirrors the `endsWith('.')` idiom already used in `formatMla`.
11
+ - **RIS — truncated-end page ranges emitted as absolute pages** (`splitPages`): `737-8` now expands to `SP 737 / EP 738`, `1639-41` to `SP 1639 / EP 1641`, etc. PubMed uses a truncated-end convention for page ranges; downstream RIS importers (Zotero, EndNote, Mendeley) treat `EP` as an absolute page number, so the unexpanded form rendered wrong page ranges in compiled bibliographies.
12
+ - **BibTeX — trailing period retained inside `title = {...}`** (`formatBibtex`): titles ending with `.` are now stripped before emission. biblatex styles append their own terminal period, so the prior behavior produced `...Final Report..` (double period) in compiled bibliographies. Mirrors the existing APA/MLA title handling.
13
+
14
+ ## Changed
15
+
16
+ - **MLA `p.` vs `pp.`** (`formatMla`): single-page citations now use `p.`, page ranges continue to use `pp.`, per MLA 9 §6.56.
17
+ - **RIS abstract whitespace** (`formatRis`): structured-abstract newlines (`BACKGROUND:\n\nMETHODS:\n\n...`) are collapsed to single spaces before emission. Strict RIS parsers treat blank lines as record terminators, so the prior output could truncate records at the first `\n\n` boundary.
18
+ - **`getYear` fallback** (`citation-formatter.ts`): falls back to `articleDates` (typically the electronic pub date) when `journalInfo.publicationDate.year` is absent, instead of emitting `n.d.` prematurely.
19
+
20
+ ## Added
21
+
22
+ - **Publication type → entry/reference type mapping**: `publicationTypes` now drives BibTeX entry types (`@book`, `@inbook`, `@misc`) and RIS `TY` codes (`BOOK`, `CHAP`, `GEN`) for `Book`, `Book Chapter`, and `Preprint`. Unmapped types fall back to `@article` / `TY - JOUR`.
23
+ - **RIS `SN` (ISSN) tag**: `journalInfo.issn` (with `eIssn` fallback) now emitted in RIS records.
24
+ - **BibTeX `issn`, `pmcid` fields**: surfaced from parsed metadata when present.
25
+ - **PMC URL in RIS**: second `UR` tag emitted when `pmcId` is present (`https://pmc.ncbi.nlm.nih.gov/articles/PMC.../`).
26
+ - **Merged keywords + MeSH**: RIS `KW` tags and BibTeX `keywords` now include MeSH descriptor names alongside article keywords, deduplicated.
27
+ - **Test coverage**: 11 new test cases covering the three bug fixes, MLA `p.`/`pp.` branching, abstract whitespace normalization, pub-type mapping, ISSN, PMC URL, MeSH merging, and `articleDates` year fallback.
28
+
29
+ ## References
30
+
31
+ - Closes [#15](https://github.com/cyanheads/pubmed-mcp-server/issues/15) — field-testing report identifying the three APA/RIS/BibTeX correctness issues.
@@ -0,0 +1,19 @@
1
+ ---
2
+ summary: "`pubmed_fetch_fulltext` PMID→PMCID resolution switched from eLink to PMC ID Converter (closes [#16](https://github.com/cyanheads/pubmed-mcp-server/issues/16)); `@cyanheads/mcp-ts-core` 0.3.7 → 0.4.1."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.3.8 — 2026-04-20
7
+
8
+ ## Fixed
9
+
10
+ - **`pubmed_fetch_fulltext` — PMID→PMCID resolution via PMC ID Converter instead of eLink** (`fetch-fulltext.tool.ts`): `resolvePmidsToPmcIds` now calls `NcbiService.idConvert()` — the purpose-built DOI/PMID/PMCID mapping endpoint — rather than `eLink(cmd=neighbor, linkname=pubmed_pmc)`. Triggered by a sustained NCBI outage on 2026-04-20 where eLink's `exLinkSrv2` backend returned `Couldn't resolve #exLinkSrv2, the address table is empty.` for every request, breaking all fulltext calls; the ID Converter runs on a different backend and stayed up throughout. Equivalent coverage (both require the article be in PMC), batch-friendly (up to 200 IDs/request vs. the tool's 10 cap), and drops ~30 lines of ELink XML type shims.
11
+
12
+ ## Changed
13
+
14
+ - **Dependency updates**: `@cyanheads/mcp-ts-core` 0.3.7 → 0.4.1, picking up OTel prompt telemetry (0.4.1), Vitest 4 `projects` testing helpers (0.4.0), and the duplicate `"Error:"` prefix fix (0.3.8). No handler-facing API changes.
15
+ - **Skill sync**: `skills/api-utils` refreshed from the package — adds `withRetry` options reference and partial-success batch metric documentation.
16
+
17
+ ## References
18
+
19
+ - Closes [#16](https://github.com/cyanheads/pubmed-mcp-server/issues/16) — feature request to swap the fulltext resolution path off eLink, filed after the 2026-04-20 outage.
@@ -0,0 +1,22 @@
1
+ ---
2
+ summary: "`pubmed_search_articles` — restores DOI/PMC IDs in brief summaries (closes [#17](https://github.com/cyanheads/pubmed-mcp-server/issues/17)); adds date validation, filter docs, empty-result guidance (closes #18)."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.3.9 — 2026-04-20
7
+
8
+ ## Fixed
9
+
10
+ - **`pubmed_search_articles` — DOI and PMC IDs missing from every brief summary** (`esummary-parser.ts`): `parseSingleDocumentSummary` matched lowercase keys (`idtype`/`value`) from the JSON ESummary shape, but the call site requests `retmode=xml` and fast-xml-parser preserves element casing. Real NCBI XML returns `{ IdType, IdTypeN, Value }`, so every search summary silently dropped its DOI and PMC ID. Normalized via small accessor helpers that accept both shapes; widened `ESummaryArticleId` to reflect the dual casing. Test fixture updated to use the real XML shape (the prior lowercase fixture passed because it tested the implementation, not the behavior).
11
+
12
+ ## Changed
13
+
14
+ - **`pubmed_search_articles` — input validation and empty-result guidance** (`search-articles.tool.ts`):
15
+ - `dateRange.minDate`/`maxDate` now validated by regex (`YYYY`, `YYYY/MM`, or `YYYY/MM/DD` with `/`, `-`, or `.` separators). Empty strings still accepted for the MCP Inspector payload shape; obvious typos like `not-a-date` now fail at the schema boundary with an actionable message instead of degrading silently to 0 results.
16
+ - `publicationTypes` and `meshTerms` descriptions now state their join semantics (OR'd vs AND'd) — the asymmetry wasn't discoverable from the schema alone.
17
+ - New optional `notice` field surfaces guidance when the response would otherwise be a bare empty array: suggests `pubmed_spell_check` on no-filter misses, filter relaxation on filtered misses, and flags pagination overshoot (`offset >= totalFound`). Absent on successful pages. Rendered as a blockquote in `format()` so both human and LLM consumers see it.
18
+
19
+ ## References
20
+
21
+ - Closes [#17](https://github.com/cyanheads/pubmed-mcp-server/issues/17) — DOI/PMC extraction bug surfaced by field-testing `pubmed_search_articles`.
22
+ - Closes [#18](https://github.com/cyanheads/pubmed-mcp-server/issues/18) — UX polish for empty results, input validation, and filter-semantics docs from the same field-test.
@@ -0,0 +1,34 @@
1
+ ---
2
+ summary: "Extends the `content[]`-completeness work from #26 across the rest of the tool surface."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.4.0 — 2026-04-20
7
+
8
+ Extends the `content[]`-completeness work from #26 across the rest of the tool surface. Every tool that previously dropped schema fields from rendered markdown now renders what the LLM sees, and the shared PMID validation logic is deduplicated into a single schema. Also bumps `@cyanheads/mcp-ts-core` 0.4.1 → 0.5.0 and migrates the server config to the new `parseEnvConfig` helper for actionable startup errors.
9
+
10
+ ## Added
11
+
12
+ - **Shared `pmidStringSchema` export** (`src/mcp-server/tools/definitions/_schemas.ts`): Consolidates the `z.string().regex(/^\d+$/, <message>)` guard that four tool files (`fetch-articles`, `fetch-fulltext`, `find-related`, `format-citations`) each duplicated inline. The message is unified so it reads naturally in both array and scalar contexts; a future refinement now updates one file instead of four.
13
+ - **`pubmed_fetch_articles` — MeSH UIs in `content[]`** (`fetch-articles.tool.ts`): Rendered `descriptorUi` and `qualifierUi` inline with their names (`Breast Neoplasms [D001943] * (pathology [Q000473])`). The UI codes are canonical keys the LLM can hand directly to `pubmed_lookup_mesh` or use in `{ui}[MeSH Terms]` search filters without name-matching fuzziness. (#30)
14
+ - **`pubmed_search_articles` — raw PMCID in summaries** (`search-articles.tool.ts`): Summary entries now include `**PMCID:** PMC12345` alongside the existing `**PMC:** {url}` line, so the LLM can copy-paste the canonical ID into downstream `pubmed_fetch_fulltext` or `pubmed_convert_ids` calls without string-parsing the URL. Parallel to the raw-PMCID fix #26 applied to `pubmed_fetch_articles`. (#31)
15
+
16
+ ## Fixed
17
+
18
+ - **`pubmed_fetch_fulltext` — `format()` silently dropped schema fields from `content[]`** (`fetch-fulltext.tool.ts`): Parallel to #26 for `fetch_articles`. Authors now render as a bulleted list with full `givenNames lastName` — no more `first3 + "et al."` truncation that silently hid authors 4+ from the LLM. Collective authors render as `{name} (collective)`. The journal line now includes `ISSN {issn}` when present. Section and subsection headings are prefixed with their JATS `label` when present (`#### 1 Introduction`, `##### 1.1 Background`), aiding cross-reference navigation. Field-tested against `PMC9575052` — confirmed ISSN, section labels "1"/"2", and all four authors now appear in `content[]`. (#29)
19
+ - **`pubmed_convert_ids` — error rows overwrote the DOI column** (`convert-ids.tool.ts`): The prior format stuffed `errmsg` into the DOI cell of the markdown table (`| id | - | - | Error: msg |`), so an LLM parsing by column index would read the error as a DOI, and any partial pmid/pmcid data accompanying an error was silently discarded. Split into two distinct sections: a success table and a separate `### Errors` bulleted list (`- **{id}:** {errmsg}`). The structuredContent shape is unchanged. (#32)
20
+ - **`pubmed_fetch_articles` — grant with only `acronym` rendered `"NIH (NIH)"`** (`fetch-articles.tool.ts`): When a grant carried `acronym` without `grantId`, `format()` produced the acronym duplicated in both slots of the `"{grantId} ({acronym})"` template. Now renders `"NIH"` alone in that case; the happy-path `"R01 EY05922 (EY)"` rendering is unchanged. Not covered by any existing test — discovered during an audit of the surrounding code.
21
+
22
+ ## Changed
23
+
24
+ - **Framework bump — `@cyanheads/mcp-ts-core` 0.4.1 → 0.5.0** (minor) (`package.json`, `bun.lock`): Brings `parseEnvConfig` (opt-in env-var-aware config errors), framework-level ZodError conversion at startup (printed as a banner rather than a JSON dump), and a rewritten `maintenance` skill (v1.2 → v1.3).
25
+ - **`getServerConfig()` migrated to `parseEnvConfig`** (`src/config/server-config.ts`): Validation errors now name the actual environment variable at fault rather than the internal Zod path — `NCBI_REQUEST_DELAY_MS (requestDelayMs): expected number` instead of `requestDelayMs: expected number, received NaN`. Moved the dynamic "API-key-present → 100ms delay" logic out of inline env plumbing into a post-parse override so the Zod schema stays declarative. Added an `emptyAsUndefined` preprocessor on `apiKey` and `adminEmail` to preserve the empty-string-as-unset semantics the previous implementation provided via `env.VAR || undefined` — without it, `NCBI_ADMIN_EMAIL=` would fail `z.email()` validation instead of being treated as "no admin email configured". No runtime behavior change for existing consumers: same field names, same types, same defaults.
26
+ - **`maintenance` skill synced to v1.3** (`skills/maintenance/SKILL.md`, plus agent-directory copies in `.claude/skills/` and `.agents/skills/`): Rewritten around a two-mode flow (Mode A — full update-investigate-adopt flow, Mode B — post-update review), delegates per-package release-note investigation to the `changelog` skill, and documents the two-phase skill sync (package → project → agent dirs).
27
+ - **In-file consistency in `fetch-articles.tool.ts`**: Replaced a lone `a.affiliations.forEach((aff, i) => ...)` with `for (const [i, aff] of a.affiliations.entries())` to match the `for...of` convention used everywhere else in the same file.
28
+
29
+ ## References
30
+
31
+ - Closes [#29](https://github.com/cyanheads/pubmed-mcp-server/issues/29) — `fetch_fulltext` format() dropped fields from `content[]` that were present in `structuredContent`.
32
+ - Closes [#30](https://github.com/cyanheads/pubmed-mcp-server/issues/30) — `fetch_articles` MeSH `descriptorUi` / `qualifierUi` missing from `content[]`.
33
+ - Closes [#31](https://github.com/cyanheads/pubmed-mcp-server/issues/31) — `search_articles` summaries rendered `pmcUrl` but not the raw `pmcId`.
34
+ - Closes [#32](https://github.com/cyanheads/pubmed-mcp-server/issues/32) — `convert_ids` error rows reused the DOI column for `errmsg`, hiding any other fields.
@@ -0,0 +1,32 @@
1
+ ---
2
+ summary: "Adopts `@cyanheads/mcp-ts-core` 0.5.3, whose new `format-parity` lint rule flagged 20 tool fields that were declared in `output` but never rendered by `format()`."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.4.1 — 2026-04-20
7
+
8
+ Adopts `@cyanheads/mcp-ts-core` 0.5.3, whose new `format-parity` lint rule flagged 20 tool fields that were declared in `output` but never rendered by `format()`. Every flagged field now appears in both surfaces, so `content[]`-reading clients (e.g., Claude Desktop) see the same data as `structuredContent`-reading clients (e.g., Claude Code).
9
+
10
+ ## Fixed
11
+
12
+ - **`pubmed_spell_check` — `hasSuggestion` invisible in `content[]`** (`spell-check.tool.ts`): Neither branch of `format()` included the word "suggestion" as a whole word or the boolean value, so a `content[]`-only client had no way to tell whether a correction was offered. Renamed the label to `**Suggestion:**` and reworded the no-suggestion branch to read `No suggestion — query "<q>" appears correct as written.`, so the key name now matches in either case.
13
+ - **`pubmed_convert_ids` — error rows hid pmid/pmcid/doi** (`convert-ids.tool.ts`): The prior split-table layout (introduced by #32) was invisible to the parity rule because synthetic lint input sets `errmsg` on every record, routing all rows through the errors-only branch that never rendered pmid/pmcid/doi. Collapsed back to a single table with an added `Error` column — keeps columns semantically correct (the root concern of #32) while rendering every declared field on every row.
14
+ - **`pubmed_lookup_citation` — `detail` not rendered for `matched` status** (`lookup-citation.tool.ts`): `detail` was emitted only inside the `ambiguous` and `not_found` branches. Moved the render above the branch so it prints whenever present, regardless of status.
15
+ - **`pubmed_fetch_articles` — author/journal/MeSH fields silently dropped** (`fetch-articles.tool.ts`): `formatAuthor` short-circuited on `collectiveName`, hiding `lastName`, `firstName`, `initials`, `affiliationIndices`, and `orcid` for any collective author. `ji.isoAbbreviation ?? ji.title` showed only one. `ji.eIssn ? … : ji.issn ? …` showed only one. `formatPublicationDate` returned `medlineDate` alone and skipped year/month/day. MeSH `isMajorTopic` rendered as a bare `*` that the permissive matcher couldn't tie back to the `isMajorTopic` key. All six paths now render every present field: author lines show `<name> (<initials>) [aff 0,1] · ORCID <id>`, journals show full title `(<iso>), <date>, <vol>(<iss>), <pages>, ISSN <issn>, eISSN <eissn>`, MeSH major topics render as `(major)`, and affiliations switched to `- [0] <text>` so the 0-indexed `affiliationIndices` values line up with the list they reference.
16
+ - **`pubmed_fetch_fulltext` — author names and reference ids silently dropped** (`fetch-fulltext.tool.ts`): Same `collectiveName` short-circuit problem as `fetch-articles`. Reference lines rendered `label ?? id`, so a reference with both only showed one. Authors now render collective name then individual `givenNames lastName`; reference tags now render both `label` and `id` when present (`[1 gks1195-B1] <citation>`).
17
+
18
+ ## Changed
19
+
20
+ - **Framework bump — `@cyanheads/mcp-ts-core` 0.5.0 → 0.5.3** (patch) (`package.json`, `bun.lock`): 0.5.1 is doc polish and retroactive skill version bumps; 0.5.2 adds the `format-parity` lint rule enforced at startup and via `bun run devcheck`; 0.5.3 rewrites the diagnostic wording around dual-surface parity (some clients forward `structuredContent`, others `content[]`, both must carry the full picture) and ships a new `check-docs-sync.ts` script for newly-scaffolded projects — not auto-adopted here since our `scripts/devcheck.ts` is a standalone copy.
21
+ - **Project skills synced from 0.5.3** (`skills/`, `.agents/skills/`, `.claude/skills/`): `add-tool` v1.4 → v1.6, `api-config` v1.1 → v1.2, `design-mcp-server` v2.3 → v2.4, `field-test` v1.1 → v1.2, `polish-docs-meta` v1.3 → v1.4, `setup` v1.2 → v1.3. Deleted `skills/devcheck/` (removed upstream in 0.5.2 — the skill was a thin restatement of the `devcheck` command already documented in the agent protocol).
22
+
23
+ ## Tests
24
+
25
+ - **Updated eight assertions** in `convert-ids.tool.test.ts`, `spell-check.tool.test.ts`, and `fetch-articles.tool.test.ts` to match the new rendering — author-line format, affiliation list ordering, MeSH `(major)` label, unified convert-ids table, suggestion-branch wording.
26
+ - Full suite: **392 passed** / 4 skipped / 0 regressions. `bun run devcheck` green across all 8 checks including the new `format-parity` rule (0 errors, was 20).
27
+ - **Verified end-to-end against live HTTP server** for all five touched tools (`spell_check`, `convert_ids`, `lookup_citation`, `fetch_articles`, `fetch_fulltext`): both `content[]` and `structuredContent` surfaces carry the same fields for real PubMed data.
28
+
29
+ ## References
30
+
31
+ - Issue [#32](https://github.com/cyanheads/pubmed-mcp-server/issues/32): convert_ids error-column semantics — my fix preserves the column-integrity concern from the original issue but switches from the 2.4.0 split-section layout to a unified table with an explicit `Error` column (the issue's Option B).
32
+ - Upstream: `@cyanheads/mcp-ts-core` 0.5.2 format-parity rule.
@@ -0,0 +1,35 @@
1
+ ---
2
+ summary: "Three feature tracks land together: MCPmed-aligned semantic concept tags on every tool, an HTTP landing page with per-tool view-source links, and a framework bump to `@cyanheads/mcp-ts-core` 0.6.3 that exposes `sourceUrl?` on definitions so the…"
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.5.0 — 2026-04-21
7
+
8
+ Three feature tracks land together: MCPmed-aligned semantic concept tags on every tool, an HTTP landing page with per-tool view-source links, and a framework bump to `@cyanheads/mcp-ts-core` 0.6.3 that exposes `sourceUrl?` on definitions so the auto-derived path convention is overridable without file renames or type casts.
9
+
10
+ ## Added
11
+
12
+ - **Ontology-backed semantic concept tags on every tool** (`_concepts.ts`, all 9 `*.tool.ts` files): Each tool now emits `_meta['io.mcpmed/concepts']` with resolvable Schema.org (`SearchAction`, `ScholarlyArticle`, `CreativeWork`, `DefinedTerm`, `DefinedTermSet`) and EDAM (`operation_2421` Database search, `operation_2422` Data retrieval, `operation_3282` ID mapping, `operation_0335` Data formatting, `topic_0089` Ontology and terminology, `data_1187` PubMed ID, `data_2091` Accession) URIs — implementing the concept-mapping proposal from Flotho et al., *Briefings in Bioinformatics* 2026 (doi:10.1093/bib/bbag076) with real URIs rather than the paper's placeholder strings. Namespace key follows the MCP `_meta` spec (`<reverse-dns>/<name>`); the `conceptMeta()` helper declares the key once so a rename-on-review is a single-file change. Draft MCPmed listing PR at `docs/mcpmed-pr-draft.md`.
13
+ - **HTTP landing page + SEP-1649 Server Card** (`src/index.ts`): `createApp({ landing: { ... } })` wires a server-specific tagline, `repoRoot`, four footer links (PubMed, E-utilities docs, NCBI API key signup, MeSH Browser), and `envExample` surfacing `NCBI_API_KEY` / `NCBI_ADMIN_EMAIL` in the STDIO/Claude CLI connect snippets. The page renders at `/` and the Server Card at `/.well-known/mcp.json` — no new env vars required; Cloudflare pass-through already reaches the container for both paths.
14
+ - **Per-tool `sourceUrl` view-source overrides** (all 9 `*.tool.ts` files): Each tool definition now carries an explicit `sourceUrl` pointing at its actual file path. Without the override, the framework's default `snake_case → kebab-case` filename derivation would produce `pubmed-convert-ids.tool.ts` (prefixed) from the tool name `pubmed_convert_ids`, while our actual file is `convert-ids.tool.ts` (unprefixed, since the directory already namespaces). Landing-page per-tool view-source links now resolve.
15
+
16
+ ## Changed
17
+
18
+ - **Framework bump — `@cyanheads/mcp-ts-core` 0.5.3 → 0.6.3**: 0.5.4 added the `api-linter` skill and a diagnostic breadcrumb on every `LintDiagnostic`; 0.6.0 introduced the landing page, SEP-1649 Server Card, `LandingConfig` export, and directory-based changelog system (opt-in per 0.6.2); 0.6.1 added `envExample` and the tabbed terminal-chrome connect card; 0.6.2 softened the directory-based changelog prescription so runtime-only consumer servers can stay monolithic (closes [cyanheads/mcp-ts-core#41](https://github.com/cyanheads/mcp-ts-core/issues/41)) and clarified `changelog/unreleased.md` as a pristine format reference; 0.6.3 exposed `sourceUrl?: string` on `ToolDefinition` / `ResourceDefinition` / `PromptDefinition` (closes [cyanheads/mcp-ts-core#42](https://github.com/cyanheads/mcp-ts-core/issues/42)).
19
+ - **Test runner — `vitest` 4.1.4 → 4.1.5**: patch bump (bug fixes + experimental istanbul instrumenter option). 392 tests pass on 4.1.5 with no changes required.
20
+ - **`CLAUDE.md` skill table**: added `api-linter` (new in 0.5.4) and `add-app-tool` rows; refreshed `maintenance` description.
21
+ - **Project skills synced**: `add-app-tool` 1.2→1.3, `add-prompt` 1.1→1.2, `add-resource` 1.2→1.3, `add-service` 1.2→1.3, `add-tool` 1.6→1.7, `api-context` 1.0→1.1, `api-services` 1.2→1.3, `api-utils` 2.0→2.1, `design-mcp-server` 2.4→2.5, `maintenance` 1.3→1.4, `polish-docs-meta` 1.4→1.6, `setup` 1.3→1.4. New: `api-linter` v1.0.
22
+
23
+ ## Tests
24
+
25
+ - **Live end-to-end verification** against pubmed-mcp-server running in HTTP mode:
26
+ - Landing page (`GET /`) renders identity, tagline, 4 NCBI links, `envExample` keys in all 3 connect-tab panels (STDIO JSON / Claude CLI `--env` / curl), and per-tool view-source URLs resolving to real repo files.
27
+ - Server Card (`GET /.well-known/mcp.json`) returns correct `server_name`, `server_version`, and all three capability flags `true`.
28
+ - `pubmed_spell_check("alzhimer disese")` → both `content[].text` and `structuredContent` carry the corrected query, `hasSuggestion: true`, and original input.
29
+ - `pubmed_search_articles({query: "CRISPR Cas9", maxResults: 3, summaryCount: 2})` → both surfaces populated with 38,563 hits, PMIDs, per-summary fields (authors/doi/pmid/pubDate/pubmedUrl/source/title), and applied filters.
30
+ - Full suite: **392 passed** / 4 skipped / 0 regressions. `bun run devcheck` green across all 8 checks.
31
+
32
+ ## References
33
+
34
+ - Framework issues closed by upstream: [cyanheads/mcp-ts-core#41](https://github.com/cyanheads/mcp-ts-core/issues/41) (soften directory-based changelog prescription — filed to preserve monolithic `CHANGELOG.md` as a valid choice for runtime-only consumer servers), [cyanheads/mcp-ts-core#42](https://github.com/cyanheads/mcp-ts-core/issues/42) (expose `sourceUrl?` on definitions — unblocks per-tool view-source overrides without file renames or type casts).
35
+ - Upstream: `@cyanheads/mcp-ts-core` 0.6.3 `sourceUrl` export.
@@ -0,0 +1,32 @@
1
+ ---
2
+ summary: "End-to-end cancellation."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.5.1 — 2026-04-22
7
+
8
+ End-to-end cancellation. `ctx.signal` from every tool and resource handler now threads through the NCBI service layer into both `fetch()` and the retry-loop backoff sleep, so client cancellations and the new service-level deadline interrupt the *full* retry chain instead of waiting for the next attempt to complete. Adds a `NCBI_TOTAL_DEADLINE_MS` knob (default `60000`) that bounds worst-case tool latency regardless of `NCBI_MAX_RETRIES × backoff`.
9
+
10
+ ## Added
11
+
12
+ - **Service-level deadline + caller-signal abort propagation** (`src/services/ncbi/ncbi-service.ts`, `src/services/ncbi/api-client.ts`, `src/services/ncbi/types.ts`, `src/config/server-config.ts`, `.env.example`):
13
+ - New `runWithDeadline()` wrapper composes an internal `AbortController` (fires at `totalDeadlineMs`) with the caller's `ctx.signal` via `AbortSignal.any()`. The combined signal is threaded into both `apiClient.makeRequest` (cancels the in-flight `fetch`) and the backoff sleep (cancels pending retries).
14
+ - New `abortableSleep()` replaces the bare `setTimeout` in `withRetry()`'s backoff so retry chains short-circuit on abort instead of finishing the current sleep first.
15
+ - New `NcbiCallOptions { signal? }` exported from `types.ts`. Every public `NcbiService` method (`eSearch`, `eSummary`, `eFetch`, `eLink`, `eSpell`, `eInfo`, `eCitMatch`, `idConvert`) now accepts it as its trailing optional arg.
16
+ - New `NCBI_TOTAL_DEADLINE_MS` env var (range `5000`–`600000`, default `60000`). Surfaced in `.env.example`, `README.md` config table, and the Zod schema in `src/config/server-config.ts`. Deadline expiry throws `McpError(JsonRpcErrorCode.Timeout)` with `{ deadlineMs }` data.
17
+ - `NcbiApiClient.makeExternalRequest` (PMC ID Converter — uses `globalThis.fetch` directly, not `fetchWithTimeout`) now composes the per-request `AbortSignal.timeout` with an optional caller signal via `AbortSignal.any()`. `getRequest` / `postRequest` forward an optional `signal` into `fetchWithTimeout` so service-level cancellation reaches the lower-level fetch.
18
+
19
+ ## Changed
20
+
21
+ - **All 9 tools + database-info resource thread `ctx.signal`** through to the NCBI service (`convert-ids`, `fetch-articles`, `fetch-fulltext`, `find-related`, `format-citations`, `lookup-citation`, `lookup-mesh`, `search-articles`, `database-info.resource`). Single-line wiring per call site — `{ signal: ctx.signal }` is forwarded as the new options arg.
22
+ - **`NCBI_TIMEOUT_MS` description clarified** (`.env.example`, `src/config/server-config.ts`, `README.md`): `Request timeout` → `Per-request HTTP timeout` to disambiguate from the new total-deadline knob.
23
+ - **Framework bump — `@cyanheads/mcp-ts-core` 0.6.3 → 0.6.5** (`package.json`, `bun.lock`): patch bumps with no API surface impact on this server.
24
+
25
+ ## Tests
26
+
27
+ - **`api-client.test.ts` (+97 lines)**: 6 new tests covering `signal` forwarding on GET / POST / no-signal paths, plus three `makeExternalRequest` cases (timeout-only signal, `AbortSignal.any()` composition, pre-aborted external signal short-circuit).
28
+ - **`ncbi-service.test.ts` (+277 lines)**: three new describe blocks:
29
+ - **Retry behavior with signals** (4 tests): forwards `signal` to `apiClient.makeRequest`, throws `Timeout` when deadline fires before first attempt, short-circuits the retry chain when caller signal aborts before invocation or mid-flight.
30
+ - **Real-timer signal wiring during backoff sleep** (4 tests): proves the deadline + caller-signal both cut backoff sleeps short (≤500ms vs the 750–1250ms first-attempt window) for both `eSearch` (`makeRequest` path) and `idConvert` (`makeExternalRequest` path).
31
+ - **Deadline timer cleanup** (3 tests): pins the contract that every request — success, non-retryable error, exhausted retries — clears its deadline timer (`setTimeout` ↔ `clearTimeout` parity).
32
+ - **9 tool-test assertion updates** in `convert-ids`, `fetch-articles`, `fetch-fulltext` (3 sites), `find-related` (3 sites), `search-articles` (4 sites) test files — switch from positional args to `expect.objectContaining({ signal: expect.any(AbortSignal) })` to verify each tool wires `ctx.signal` through to the service.
@@ -0,0 +1,23 @@
1
+ ---
2
+ summary: "Framework patch series bump (`@cyanheads/mcp-ts-core` 0.6.5 → 0.6.8) and documentation refresh."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.5.2 — 2026-04-22
7
+
8
+ Framework patch series bump (`@cyanheads/mcp-ts-core` 0.6.5 → 0.6.8) and documentation refresh. Picks up the new `MCP_PUBLIC_URL` override for TLS-terminating reverse-proxy deployments (0.6.6), template hygiene fixes (0.6.7), and landing-page visual polish plus a new CSS-injection lint rule (0.6.8). No API surface impact on this server.
9
+
10
+ ## Added
11
+
12
+ - **`MCP_PUBLIC_URL` env var documentation** (`.env.example`, `README.md`, `server.json`): Surfaces the 0.6.6 override for deployments behind Cloudflare Tunnel / Caddy / nginx / ALB so the landing page, SEP-1649 Server Card, and RFC 9728 protected-resource metadata emit the public `https://` origin instead of the internal container hostname. Declared on the `streamable-http` package entry in `server.json` so MCP registry clients advertise it alongside `MCP_HTTP_HOST`/`MCP_HTTP_PORT`.
13
+ - **`MCP_HTTP_ENDPOINT_PATH` env var documentation** (`.env.example`, `README.md`): Documents the existing framework knob for the HTTP mount path (default `/mcp`).
14
+
15
+ ## Changed
16
+
17
+ - **Framework bump — `@cyanheads/mcp-ts-core` 0.6.5 → 0.6.8** (`package.json`, `bun.lock`): 0.6.6 adds `MCP_PUBLIC_URL` + `design-mcp-server` skill rework (v2.7 codifies the `{server}_{verb}_{noun}` naming default, documents workflow-safety patterns, and diversifies examples beyond email/notifications); 0.6.7 is template hygiene with no consumer impact; 0.6.8 ships landing-page visual polish (auto-derived `--accent-2` secondary token via `oklch` relative color, animated conic-gradient border beam on the connect card, brighter dark-mode surfaces, accent bar prefix on `h2`s) and a new `landing-theme-accent-format` lint rule that rejects CSS-injection payloads in `landing.theme.accent`. Patch series — no breaking changes.
18
+ - **`.dockerignore` adds `.agents`** (`.dockerignore`): Mirrors the 0.6.7 template fix. Keeps agent scratch directories out of the production image, matching the existing `.claude` exclusion.
19
+ - **Project skills synced from 0.6.6** (`skills/`, `.agents/skills/`, `.claude/skills/`): `add-tool` v1.7 → v1.8, `design-mcp-server` v2.5 → v2.7, `field-test` v1.2 → v1.3, `polish-docs-meta` v1.6 → v1.7.
20
+
21
+ ## Tests
22
+
23
+ - Full suite: **409 passed** / 4 skipped / 0 regressions. `bun run devcheck` green across all 8 checks — the new `landing-theme-accent-format` rule is a no-op here since `src/index.ts` doesn't set `landing.theme.accent`.
@@ -0,0 +1,22 @@
1
+ ---
2
+ summary: "Framework patch bump (`@cyanheads/mcp-ts-core` 0.6.8 → 0.6.10) and agent-protocol polish."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.5.3 — 2026-04-23
7
+
8
+ Framework patch bump (`@cyanheads/mcp-ts-core` 0.6.8 → 0.6.10) and agent-protocol polish. 0.6.9 is an internal landing-page refactor with a new CSP header and per-request render memoization; 0.6.10 renames the `release` skill to `release-and-publish` and expands the `setup` skill to cover everything `init` scaffolds. No library API changes — no code edits required.
9
+
10
+ ## Changed
11
+
12
+ - **Framework bump — `@cyanheads/mcp-ts-core` 0.6.8 → 0.6.10** (`package.json`, `bun.lock`): 0.6.9 splits the 1.7kLOC landing-page monolith into `landing-page/` sub-modules, adds a strict `Content-Security-Policy` header to `GET /`, and memoizes both full and degraded render paths when `transport.publicUrl` is set. 0.6.10 renames the shipping skill to `release-and-publish` (v2.0, now `audience: external`) as a post-wrapup publish workflow that runs the verification gate → push → npm → MCP Registry → GHCR, halting on first failure. `setup` skill bumped 1.4 → 1.5. Patch series — no breaking changes and no API surface impact on this server.
13
+ - **Project skills synced from 0.6.10** (`skills/`, `.agents/skills/`, `.claude/skills/`): new `release-and-publish` v2.0 skill added; `setup` v1.4 → v1.5. Skipped internal-audience skills (`add-export`, `add-provider`). Both agent skill directories refreshed end-to-end.
14
+ - **`CLAUDE.md` agent protocol updates**:
15
+ - Skills table now lists `release-and-publish` so agents can discover it when a release is requested.
16
+ - `## Publishing` section rewritten to direct agents at the `release-and-publish` skill as the primary path; the raw `bun publish` + `docker buildx` commands remain as reference and `mcp-publisher publish` added to cover the MCP Registry leg.
17
+ - Checklist expanded with form-client safety (empty inner values on optional nested objects), `format()` completeness (Claude Code reads `structuredContent`, Claude Desktop reads `content[]` — both must carry the same data), and three NCBI-wrapping items (sparsity review on required/optional fields, uncertainty preservation in normalization/format, sparse-payload test coverage).
18
+ - Zod non-serializable type list in the checklist now enumerates the full set (`z.bigint()`, `z.symbol()`, `z.void()`, `z.map()`, `z.set()`, `z.function()`, `z.nan()` in addition to the existing entries).
19
+
20
+ ## Tests
21
+
22
+ - Full suite: **409 passed** / 4 skipped / 0 regressions. `bun run devcheck` green across all 8 checks — MCP definition lint, Biome, TypeScript, depcheck, audit, outdated all clean.
@@ -0,0 +1,52 @@
1
+ ---
2
+ summary: "Framework minor bump (`@cyanheads/mcp-ts-core` 0.6.17 → 0.7.0)."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.5.5 — 2026-04-24
7
+
8
+ Framework minor bump (`@cyanheads/mcp-ts-core` 0.6.17 → 0.7.0). Issue-cleanup release from upstream with no runtime breaking changes. Picks up the devcheck changelog-sync crash fix for single-file `CHANGELOG.md` consumers (this server), the flattened ZodError message shape with structured `data.issues`, locale-aware digit-group separators in the `format-parity` linter rule, and new GitHub issue-management scaffolding. Adopts the framework template updates into `CLAUDE.md` / `AGENTS.md`, syncs five skill version bumps, and scaffolds `.github/ISSUE_TEMPLATE/` for bug reports and feature requests.
9
+
10
+ ## Added
11
+
12
+ - **`.github/ISSUE_TEMPLATE/`** (`bug_report.yml`, `feature_request.yml`, `config.yml`): Copied from `node_modules/@cyanheads/mcp-ts-core/templates/.github/ISSUE_TEMPLATE/` (0.7.0 scaffolding). Structured bug report with runtime / transport / framework-version fields, a feature request form, and disabled blank-issue creation. Both forms reference secondary labels (`regression`, `performance`, `security`, `breaking-change`) documented inline; assignees line left commented. Create missing labels on the repo once with `gh label create <name>` if you want the sidebar hint to resolve.
13
+ - **`security-pass` skill reference in agent protocol** (`CLAUDE.md`, `AGENTS.md`): Added as item #8 in the "What's Next?" list and to the Skills table. The skill file itself was already adopted in 2.5.4; this wires it into the template's orientation surface per 0.7.0.
14
+
15
+ ## Changed
16
+
17
+ - **Framework bump — `@cyanheads/mcp-ts-core` 0.6.17 → 0.7.0** (`package.json`, `bun.lock`): Issue-cleanup release, no runtime breaking changes.
18
+ - **Flattened ZodError message shape** (upstream #55): `getErrorMessage(err)` now detects `ZodError` and returns `<first-issue.message> at <path> (+N more)` instead of the raw serialized issue array. Resource param validation, tool output validation, and user-thrown `ZodError` now populate `error.data.issues` with the full `ZodIssue[]`; for tools, issues surface via `_meta.error.data.issues` alongside explicit `McpError.data`. Verified wire-transparent — no code paths in this server's `src/` or `tests/` parse `error.message` JSON, so downstream behavior is unchanged (cleaner logs aside).
19
+ - **Locale-aware `format-parity` linter** (upstream #54): Numeric sentinel matching now retries against text with common digit-group separators stripped (comma, period, underscore, apostrophe, right single quote, space variants including narrow no-break U+202F, Arabic thousands U+066C). Covers en-US, de-DE, fr-FR, de-CH formatting.
20
+ - **Devcheck changelog-sync crash fix** (upstream #51): Guard now checks only for the `changelog/` directory; the monolithic `CHANGELOG.md` alone is a supported configuration. This server uses single-file `CHANGELOG.md`, so the step now skips cleanly (⚪ SKIPPED) instead of crashing on `readdirSync` `ENOENT`. We filed this upstream during the 2.5.4 cycle; it's now resolved in the framework directly.
21
+ - **Phase C script sync — `scripts/devcheck.ts`**: Resynced from `@cyanheads/mcp-ts-core/scripts/devcheck.ts` (only framework script whose content hash differed). Picks up the guard described above. Other framework scripts (`build.ts`, `build-changelog.ts`, `check-docs-sync.ts`, `check-skills-sync.ts`, `clean.ts`, `lint-mcp.ts`, `tree.ts`) were already in sync.
22
+ - **Phase A skill sync — five skill version bumps** (`skills/`, `.agents/skills/`, `.claude/skills/`): `api-linter` 1.0 → 1.1 (recursion-rules table for `describe-on-fields`, primitive array elements explicitly skipped, softened "mechanical fix" framing); `maintenance` 1.4 → 1.5 (Step 4 template review defaults to direct application of framework-authored updates; Step 6 splits into two tiers — framework changes default adopt, third-party changes default cost/benefit; Step 8 renames "Needs attention" → "Open decisions"); `release-and-publish` 2.0 → 2.1 (transient-failure retry protocol for network steps 3–6 with short backoff, idempotent-success skip signals for npm / MCP Registry, `docker builder prune -f` before retrying `buildx --push`); `report-issue-framework` 1.2 → 1.3 and `report-issue-local` 1.2 → 1.3 (primary + secondary label restructure, `--assignee "@me"` CLI examples, `gh label create` bootstrap block in the local skill).
23
+ - **Phase B agent skill refresh** (`.claude/skills/`, `.agents/skills/`): All 25 project skills copied end-to-end into both agent-discovery paths, including the five version bumps from Phase A.
24
+ - **`CLAUDE.md` / `AGENTS.md` template sync**: Skill-directory callout now references the maintenance skill's Phase B auto-resync instead of instructing a manual re-copy — matches the v1.5 maintenance flow.
25
+
26
+
27
+
28
+ Framework patch series bump (`@cyanheads/mcp-ts-core` 0.6.10 → 0.6.17) and a code-cohesion pass. Picks up the new recursive `describe-on-fields` linter (0.6.16) and an HTTP transport per-request `McpServer` race fix (0.6.17). Adds the new `security-pass` skill, syncs the Phase C build/check scripts from the package, and refactors two heavy output schemas into named sub-schemas for readability — verified wire-format-transparent. No library API changes, no tool behavior changes.
29
+
30
+ ## Added
31
+
32
+ - **`security-pass` skill** (`skills/security-pass/`, `.agents/skills/security-pass/`): New v1.1 skill from framework 0.6.14 — systematic audit pass covering secrets, input validation, rate limiting, error surface, and dependency hygiene. Available as first-class skill for post-change security review.
33
+ - **Phase C build/check scripts** (`scripts/build-changelog.ts`, `scripts/check-docs-sync.ts`, `scripts/check-skills-sync.ts`): Copied from `@cyanheads/mcp-ts-core` 0.6.16 as part of the new package → project script sync path. `build-changelog.ts` is invoked by the `devcheck` Changelog Sync step with an added `existsSync(CHANGELOG_DIR)` early-exit to handle single-file `CHANGELOG.md` projects (this server does not use a directory-based changelog). Filed upstream as [cyanheads/mcp-ts-core#51](https://github.com/cyanheads/mcp-ts-core/issues/51).
34
+
35
+ ## Changed
36
+
37
+ - **Framework bump — `@cyanheads/mcp-ts-core` 0.6.10 → 0.6.17** (`package.json`, `bun.lock`): seven patch releases rolled up.
38
+ - **0.6.11–0.6.13**: Template + skill polish (internal-audience), no consumer impact.
39
+ - **0.6.14**: Ships the `security-pass` skill (adopted above).
40
+ - **0.6.15**: Landing-page hardening.
41
+ - **0.6.16**: Definition-linter upgrade — `describe-on-fields` now walks nested object properties, array element schemas, and union variants recursively. Flagged 19 missing `.describe()` calls on inner schemas across this server's 9 tools + 1 resource; all added in this release.
42
+ - **0.6.17**: HTTP transport fix — per-request `McpServer` instantiation resolves a session race where concurrent requests on the same session could see cross-wired tool registrations.
43
+ - **Schema refactor — `fetch-articles.tool.ts`, `fetch-fulltext.tool.ts`**: Extracted deeply nested inline `z.object({...})` output schemas into named module-scoped schemas (`AuthorSchema`, `JournalInfoSchema`, `MeshTermSchema`, `GrantSchema`, `ArticleDateSchema`, `FetchedArticleSchema`, `SubsectionSchema`, `SectionSchema`, `ReferenceSchema`, `PublicationDateSchema`, `FulltextArticleSchema`, etc.). Code-organization change only — verified byte-identical JSON Schema output via `toJSONSchema` from `zod/v4/core`, so MCP SDK's `tools/list` emission and the LLM's view of the tool are unchanged.
44
+ - **`describe-on-fields` compliance** (`convert-ids.tool.ts`, `find-related.tool.ts`, `search-articles.tool.ts`, `lookup-mesh.tool.ts`, `lookup-citation.tool.ts`, `format-citations.tool.ts`, `database-info.resource.ts`): Added `.describe()` on 19 previously unannotated array element schemas and nested object properties flagged by the 0.6.16 recursive linter. Tools' surface descriptions unchanged — this fills in the missing per-field hints for nested structures.
45
+ - **Project skills synced from 0.6.17** (`skills/`, `.agents/skills/`): `security-pass` v1.1 added; `field-test` 1.3 → 2.0 (HTTP + JSON-RPC helper, universal battery vs situational categories); content refreshes at same version on `add-tool`, `design-mcp-server`, `maintenance`, `polish-docs-meta`, `release-and-publish`, `report-issue-framework`, `report-issue-local`, `setup`. Both agent skill directories refreshed end-to-end.
46
+ - **Project scripts synced from 0.6.16** (`scripts/devcheck.ts`, `scripts/lint-mcp.ts`, `scripts/tree.ts`): picked up the new Docs Sync + Changelog Sync + Skills Sync steps in `devcheck.ts` and linter-rule updates in `lint-mcp.ts`.
47
+ - **`AGENTS.md` re-synced from `CLAUDE.md`**: the two were out of sync (2.5.0 vs 2.5.3); the mirror is now re-established.
48
+ - **Biome patch bump — `@biomejs/biome` 2.4.12 → 2.4.13** (`package.json`, `bun.lock`): internal fixes only.
49
+
50
+ ## Tests
51
+
52
+ - Full suite: **409 passed** / 4 skipped / 0 regressions. `bun run devcheck` green across all 11 checks (Docs Sync, Changelog Sync, Skills Sync, and MCP definition lint included). Field-tested all 9 tools via real HTTP + JSON-RPC transport — happy path, `structuredContent` ↔ `content[]` parity, and input-validation error messages verified.
@@ -0,0 +1,33 @@
1
+ ---
2
+ summary: "Correctness + ergonomics pass on `pubmed_lookup_citation`."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.5.6 — 2026-04-24
7
+
8
+ Correctness + ergonomics pass on `pubmed_lookup_citation`. Closes [#35](https://github.com/cyanheads/pubmed-mcp-server/issues/35) (silent wrong-PMID on author mismatch), [#40](https://github.com/cyanheads/pubmed-mcp-server/issues/40) (same class of bug for year), and [#25](https://github.com/cyanheads/pubmed-mcp-server/issues/25) (AMBIGUOUS candidate PMIDs only available as a CSV buried in `detail`). The root cause for #35/#40 is that NCBI's ECitMatch weights journal+volume+page as the primary key and tolerates author/year disagreement, so a query like `{ authorName: "husain m", journal: "lancet", volume: "394", firstPage: "121", year: "2019" }` (intent: PIONEER-6, Husain M, NEJM 2019, with a deliberately wrong journal) was resolving to PMID `31189511` (REWIND, Gerstein HC, 2019) and surfacing `Status: Matched` with no warning.
9
+
10
+ ## Fixed
11
+
12
+ - **`pubmed_lookup_citation` verifies queried author AND year against the matched article** (`src/mcp-server/tools/definitions/lookup-citation.tool.ts`). After ECitMatch resolves, collects all matched PMIDs and issues a single batched `eSummary` call to enrich with author strings and `pubDate`. Author check: normalize lowercase, compare surname-to-surname (first whitespace-separated token on each side) — avoids `Smith`/`Smithson` false positives. Year check: compare queried `year` against the first four chars of the standardized `pubDate`. On either mismatch, keeps the PMID in results ("flag, don't drop") and attaches a structured `warnings: [{ code: 'author_mismatch' | 'year_mismatch', message }]` entry so downstream code can key on `code` without parsing `format()` text. Both warnings stack on the same result when both disagree.
13
+
14
+ ## Added
15
+
16
+ - **`matchedFirstAuthor` in `pubmed_lookup_citation` output** (`src/mcp-server/tools/definitions/lookup-citation.tool.ts`). Populated for matched results when ESummary returns an author list. Provides an eyeball signal even for clean matches (the prior `format()` dropped author info entirely). Rendered as `**First Author:** Lastname FI`.
17
+ - **`candidatePmids: string[]` for AMBIGUOUS matches** ([#25](https://github.com/cyanheads/pubmed-mcp-server/issues/25), `src/services/ncbi/ncbi-service.ts`, `src/services/ncbi/types.ts`). Parsed in the service layer from the raw `AMBIGUOUS <csv>` string that ECitMatch returns in `detail`; `detail` is preserved for human readability. Exposes candidates directly on `ECitMatchResult` so programmatic consumers no longer have to regex-extract from the detail field. `format()` renders them as `**Candidate PMIDs:** 33057196, 32076266, 32025019` with a next-step hint to fetch via `pubmed_fetch_articles` to disambiguate manually.
18
+ - **`warnings` array on per-result output and `totalWarnings` on top-level** (`src/mcp-server/tools/definitions/lookup-citation.tool.ts`). Structured `{ code: 'author_mismatch' | 'year_mismatch', message }[]` flagged at the result level; `totalWarnings` rolls up how many matches carry at least one warning. `format()` renders a `**Warnings:** N` header and a `- [code] …` bullet per finding, and swaps the matched-status next-step from "PMID is ready" to "`<code1> + <code2>` detected — confirm this PMID is the intended article before citing" when present.
19
+
20
+ ## Tests
21
+
22
+ - Ten new tests across `tests/mcp-server/tools/definitions/lookup-citation.tool.test.ts` and `tests/services/ncbi/ncbi-service.test.ts`:
23
+ - Clean match surfaces first author, no warning.
24
+ - The PIONEER-6 / REWIND reproducer from #35 is flagged but not dropped.
25
+ - `Smith` vs `Smithson` does not false-positive (surname vs substring).
26
+ - Missing `authorName` skips verification while still surfacing `matchedFirstAuthor`.
27
+ - `eSummary` is skipped when no matches land.
28
+ - Multiple matched PMIDs batch into a single `eSummary` call.
29
+ - Year mismatch is flagged; year match is not.
30
+ - Author + year mismatches stack on the same result.
31
+ - AMBIGUOUS with CSV PMIDs produces `candidatePmids: string[]`; `eSummary` is not called for ambiguous-only batches.
32
+ - `format()` renders candidate PMIDs and the fetch-articles hint; combined mismatch codes appear in the next-step.
33
+ - Suite: **422 passed** / 4 skipped / 0 regressions. `bun run devcheck` green across all 11 checks.
@@ -0,0 +1,32 @@
1
+ ---
2
+ summary: "Closes [#34](https://github.com/cyanheads/pubmed-mcp-server/issues/34) — non-PMC full-text fallback via Unpaywall."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.6.0 — 2026-04-24
7
+
8
+ Closes [#34](https://github.com/cyanheads/pubmed-mcp-server/issues/34) — non-PMC full-text fallback via Unpaywall. `pubmed_fetch_fulltext` previously returned `{ articles: [], unavailable: [...] }` for every article that wasn't in PubMed Central, even when a legal open-access copy existed at the publisher or in an institutional repository. New behavior: when `UNPAYWALL_EMAIL` is configured, articles without a PMCID are routed through [Unpaywall](https://unpaywall.org/) to resolve the DOI to an OA location, the content is fetched (PDF preferred, HTML fallback), and extracted to the LLM-friendly surface (Markdown for HTML via [Defuddle](https://github.com/kepano/defuddle); text for PDF via [unpdf](https://github.com/unjs/unpdf)). Absent `UNPAYWALL_EMAIL`, the tool's legacy behavior is preserved — unavailable results now carry a structured `reason` code so callers can distinguish "fallback disabled" from "no OA copy exists" from "fetch/parse failure."
9
+
10
+ ## Added
11
+
12
+ - **Unpaywall service** (`src/services/unpaywall/`, new module). `UnpaywallService.resolve(doi)` calls Unpaywall's `/v2/{doi}` endpoint, normalizes `doi:`- and `doi.org/`-prefixed inputs, and returns a discriminated resolution `{ kind: 'found', location } | { kind: 'no-oa', reason }`. 404 and 422 from upstream are treated as structured non-OA (not errors). `UnpaywallService.fetchContent(location)` prefers `url_for_pdf` when present and falls back to `url`, detecting content type from response headers to branch into HTML or PDF extraction. Init/accessor pattern (`initUnpaywallService`, `getUnpaywallService`) — no-op when `UNPAYWALL_EMAIL` is unset, so the fallback is opt-in and downstream code can guard on `getUnpaywallService() !== undefined`.
13
+ - **`source: "unpaywall"` output variant on `pubmed_fetch_fulltext`** (`src/mcp-server/tools/definitions/fetch-fulltext.tool.ts`). Output is now a Zod discriminated union on `source`: the existing PMC variant (structured `sections`, `references`, `journal`, etc.) remains unchanged; the new Unpaywall variant returns `{ pmid, doi, title, body, contentFormat, source, sourceUrl, license, version, hostType }` where `contentFormat` is `'html-markdown' | 'pdf-text'` so the LLM can reason about granularity without guessing from body content. `license`, `version` (submitted/accepted/published), and `hostType` (publisher/repository) pass through unaltered from Unpaywall for provenance.
14
+ - **Structured `unavailable.reason` enum** (`src/mcp-server/tools/definitions/fetch-fulltext.tool.ts`). `unavailable[]` entries now carry `reason: 'no-pmc-fallback-disabled' | 'no-doi' | 'no-oa' | 'fetch-failed' | 'parse-failed' | 'service-error'` alongside optional `detail`. Lets downstream agents decide whether to retry (service-error), suggest configuration (no-pmc-fallback-disabled), or surface to the user (no-oa).
15
+ - **Config — `UNPAYWALL_EMAIL` and `UNPAYWALL_TIMEOUT_MS`** (`src/config/server-config.ts`, `.env.example`, `server.json`). Zod-validated; email uses `z.email()` with empty-string-as-undefined preprocessing so blank `.env` entries don't fail validation. Timeout defaults to 20_000 ms, min 1_000, max 120_000. `server.json` declares `UNPAYWALL_EMAIL` as an optional environment variable for both stdio and HTTP packages so registry consumers see it in client config UIs.
16
+ - **New dependencies** (`package.json`): `defuddle@^0.18.1` + `linkedom@^0.18.12` for HTML → Markdown extraction; `unpdf@^1.6.0` for PDF → text. All three are tree-shakable and Worker-compatible.
17
+
18
+ ## Fixed
19
+
20
+ - **`pubmed_fetch_fulltext` now sources DOI from PubMed metadata when the PMC ID Converter omits it** (`src/mcp-server/tools/definitions/fetch-fulltext.tool.ts`). Root cause: NCBI's PMC ID Converter API only returns DOI for articles already indexed in PMC — non-PMC PMIDs come back as `{ pmid, errmsg: "Identifier not found in PMC" }` with no DOI field. Discovered via live field testing: PMID `30298337` has DOI `10.1200/JCO.2018.79.0691` per `pubmed_fetch_articles`, but `pubmed_fetch_fulltext` returned `no-doi` because idConvert said so. Fix: after idConvert, the handler now batch-fetches missing DOIs from `db=pubmed` via `eFetch`, reusing existing `extractDoi`/`extractPmid` helpers over the `PubmedArticle` XML (ELocationID and ArticleIdList scanned for `EIdType='doi'`). Without this, the Unpaywall fallback would have been effectively unreachable for the exact case it's meant to serve.
21
+
22
+ ## Changed
23
+
24
+ - **Issue templates auto-assign to repo owner** (`.github/ISSUE_TEMPLATE/bug_report.yml`, `.github/ISSUE_TEMPLATE/feature_request.yml`). Switched the `assignees:` key from commented placeholder to `["cyanheads"]` so new bug reports and feature requests route to notifications automatically.
25
+
26
+ ## Tests
27
+
28
+ - **`tests/services/unpaywall/unpaywall-service.test.ts`** (new file, 18 tests). Covers `resolve` (found with `best_oa_location`, found via `oa_locations[0]` fallback, `is_oa: false` → no-oa, 404 → no-oa, 422 → no-oa, 500 → service-unavailable, network failure → service-unavailable, invalid DOI normalization), `fetchContent` (PDF preference, HTML fallback after PDF 500, content-type-driven branching, Uint8Array body for PDF vs string body for HTML, redirect-followed `fetchedUrl`), and the init/accessor pattern (`initUnpaywallService` no-op when env unset, constructs with email/timeout when set).
29
+ - **Rewrote Unpaywall tests in `tests/mcp-server/tools/definitions/fetch-fulltext.tool.test.ts`** with a db-aware `mockEFetchBy({ pmc?, pubmedDois? })` helper that dispatches by `params.db` (`pmc` → full JATS body, `pubmed` → PubmedArticleSet with DOI in ArticleIdList). Previous tests stubbed `idConvert` to return DOI directly, which masked the real-world gap where non-PMC PMIDs come back without DOIs. Includes a named regression test (`"sources the DOI from PubMed metadata when ID Converter omits it (regression for field-test)"`) that reproduces the production bug with an idConvert response lacking the DOI field.
30
+ - **Config test** — `UNPAYWALL_EMAIL` and `UNPAYWALL_TIMEOUT_MS` pickup (`tests/config/server-config.test.ts`).
31
+ - **Index test** — `initUnpaywallService` mocked and asserted called in `setup()` (`tests/index.test.ts`).
32
+ - Suite: **445 passed** / 4 skipped / 0 regressions (up from 422). `bun run devcheck` green across all 11 checks.
@@ -0,0 +1,26 @@
1
+ ---
2
+ summary: "Field-test correctness + DX pass."
3
+ breaking: false
4
+ ---
5
+
6
+ # 2.6.1 — 2026-04-24
7
+
8
+ Field-test correctness + DX pass. Closes [#41](https://github.com/cyanheads/pubmed-mcp-server/issues/41) (data-correctness bug — `<sup>`/`<sub>`/`<inf>` content silently dropped from EFetch abstracts), [#42](https://github.com/cyanheads/pubmed-mcp-server/issues/42), [#43](https://github.com/cyanheads/pubmed-mcp-server/issues/43), and [#44](https://github.com/cyanheads/pubmed-mcp-server/issues/44) (DX polish across `find_related`, `convert_ids`, `search_articles`).
9
+
10
+ ## Fixed
11
+
12
+ - **`pubmed_fetch_articles` and `pubmed_format_citations` preserve superscript/subscript content in abstracts** ([#41](https://github.com/cyanheads/pubmed-mcp-server/issues/41), `src/services/ncbi/response-handler.ts`). Root cause: the non-ordered `XMLParser` used for EFetch responses doesn't preserve mixed content — `1.73 m<sup>2</sup>` parses to `{ '#text': '1.73 m', sup: 2 }`, and `extractAbstractText` only reads `#text`, so the superscript digit was silently dropped from titles and abstracts. Discovered via live field testing: PMID 38785209 abstract showed `1.73 mof body-surface area` instead of `1.73 m² of body-surface area`; PMID 38740993 showed `≥40 kg m` instead of `≥40 kg m⁻²`. Fix: pre-flatten `<sup>`/`<sub>`/`<inf>` to Unicode (or `^X`/`_X` ASCII fallback) before fast-xml-parser runs, and strip emphasis tags (`<i>`, `<b>`, `<u>`, `<sc>`) keeping their content. Scoped to the regular parser path; the PMC JATS path already preserves inline markup via `preserveOrder: true` and is left untouched.
13
+
14
+ ## Changed
15
+
16
+ - **`pubmed_find_related` returns a relationship-aware notice when `references` yields zero results for a non-PMC source** ([#42](https://github.com/cyanheads/pubmed-mcp-server/issues/42), `src/mcp-server/tools/definitions/find-related.tool.ts`). NCBI's `pubmed_pubmed_refs` ELink only resolves reference lists for PMC-indexed sources, so a perfectly valid PMID without a PMCID returned an empty payload indistinguishable from "this article cites nothing." On a zero-result `references` query, the handler now does a single ESummary lookup on the source PMID and emits a `notice` field — either "Reference lists require the source article to be indexed in PMC. PMID X has no PMCID …" with recovery steps, or "No reference list found in PMC for PMID X (PMCID Y)." when the source IS in PMC. Added `notice?: string` to the output schema; `format()` renders it as a blockquote in place of the generic "No related articles found." line.
17
+ - **`pubmed_convert_ids` rewrites NCBI's "Identifier not found in PMC" errmsg** ([#43](https://github.com/cyanheads/pubmed-mcp-server/issues/43), `src/mcp-server/tools/definitions/convert-ids.tool.ts`). Upstream wording reads as "this PMID doesn't exist," but the article may still be retrievable via `pubmed_fetch_articles`. The handler now matches `/^identifier not found in pmc$/i` and substitutes `"Not in PMC ID Converter. Article may still exist in PubMed — try pubmed_fetch_articles (PMID → DOI) or pubmed_search_articles."`, preserving the original at debug log level. Other NCBI error messages pass through unchanged.
18
+ - **`pubmed_search_articles` `format()` explains the summary/PMID count split** ([#44](https://github.com/cyanheads/pubmed-mcp-server/issues/44), `src/mcp-server/tools/definitions/search-articles.tool.ts`). When `summaryCount < maxResults` the response correctly carries more PMIDs than summaries, but the rendered output didn't say so. Added a one-line blockquote above the Summaries section: `"Summaries shown for top N of M PMIDs. Increase summaryCount (max 50) to fetch more."` Suppressed when summaries == pmids or when summaries are empty.
19
+
20
+ ## Tests
21
+
22
+ - **Inline markup flattening** (`tests/services/ncbi/response-handler.test.ts`) — 8 new assertions covering `<sup>` digits → Unicode, leading-minus → `⁻²`, `<sub>`/`<inf>` → Unicode, alphabetic fallback to `^X`/`_X`, emphasis-tag stripping, end-to-end AbstractText regression for PMID 38785209, and confirmation that the ordered/PMC parser path is left untouched.
23
+ - **`find_related` references notice** (`tests/mcp-server/tools/definitions/find-related.tool.test.ts`) — 5 new tests covering non-PMC source hint, PMC-source-with-no-refs hint, no-notice for `similar`/`cited_by` paths, graceful degradation when ESummary fails, and `format()` blockquote rendering.
24
+ - **`convert_ids` PMC-not-found rewrite** (`tests/mcp-server/tools/definitions/convert-ids.tool.test.ts`) — 2 new tests confirming the rewrite fires on the matching errmsg and leaves other NCBI errors untouched.
25
+ - **`search_articles` count-split note** (`tests/mcp-server/tools/definitions/search-articles.tool.test.ts`) — 3 new tests covering the partial-summary case, summaries==pmids parity, and empty summaries.
26
+ - Suite: **466 passed** / 4 skipped (up from 449).
@@ -0,0 +1,16 @@
1
+ ---
2
+ summary: "Reclassify NCBI prolog-only XML responses as transient `ServiceUnavailable` so the retry chain recovers, fixing intermittent `pubmed_find_related` (and any eLink) failures caused by upstream TXCLIENT EOFs."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.6.10 — 2026-05-10
8
+
9
+ ## Fixed
10
+
11
+ - **`response-handler.ts`** — NCBI occasionally drops the root element when its upstream backend connection fails mid-response, returning just `<?xml ... ?>` + DOCTYPE with no body (the same TXCLIENT EOF surfaced as an `ERROR` field in JSON retmode). The XMLValidator was rejecting these as malformed and the framework was classifying them as `SerializationError` — a non-retryable code — so the retry chain short-circuited on what is in fact a transient outage. Empty bodies (after stripping prolog + DOCTYPE) now throw `serviceUnavailable({ reason: 'ncbi_unreachable' })` and route through the existing retry/backoff path.
12
+ - **`pubmed_find_related`** — drops the unreachable `if (firstResult?.ERROR)` branch and the corresponding `ERROR?: string` field on `ELinkResultItem`. `response-handler` already throws on `<ERROR>` payloads before the parsed body reaches the tool, so the branch was dead. The empty-LinkSet ESummary disambiguation path (invalid PMID vs. valid-but-no-relations) is unchanged.
13
+
14
+ ## Tests
15
+
16
+ - 539 passed / 4 skipped. `bun run devcheck` clean.
@@ -0,0 +1,24 @@
1
+ ---
2
+ summary: "Adopts `@cyanheads/mcp-ts-core` 0.9.0 (Workers under `nodejs_compat`, `instructions` field, portability lint family). Pins `fast-xml-parser` to `~5.7.3` to avoid the 5.8.0 `XMLValidator` deprecation in favor of an unproven sibling package. Server now ships an `instructions` string."
3
+ breaking: false
4
+ security: false
5
+ ---
6
+
7
+ # 2.6.11 — 2026-05-13
8
+
9
+ ## Added
10
+
11
+ - **`instructions` on `createApp()`** ([mcp-ts-core#91](https://github.com/cyanheads/mcp-ts-core/issues/91)) — server-level orientation surfaced on every `initialize` response. Echoes the same typical-flow guidance carried in the MCP server instructions block (`pubmed_search_articles` → `pubmed_fetch_articles` → `pubmed_fetch_fulltext`, prefer `pubmed_lookup_citation` for known references, `pubmed_lookup_mesh`/`pubmed_spell_check` for query refinement). Spec-compliant clients forward this to the model as session-level system context.
12
+
13
+ ## Changed
14
+
15
+ - **`@cyanheads/mcp-ts-core` ^0.8.20 → ^0.9.0** ([0.9.0 changelog](https://github.com/cyanheads/mcp-ts-core/blob/main/changelog/0.9.x/0.9.0.md)) — Workers boot under `nodejs_compat`, cross-vendor portability lint family (5 rules), definition linting moves to build-time only, RFC 8414 §3 well-known path-suffix mount, SSRF DNS validation in Workers, tenant-id boundary check in `FileSystemProvider`. All schemas already conformed to the new `schema-format-portability` allowlist — no migration needed.
16
+ - **`fast-xml-parser` ^5.7.3 → ~5.7.3 (pinned)** — 5.8.0 deprecates `XMLValidator` in favor of a brand-new sibling package `fast-xml-validator` (0 dependents, published days before the deprecation by the same author). No security fix or feature we need, so we stay on 5.7.x. Documented in `src/services/ncbi/response-handler.ts` and added to `devcheck.config.json` `outdated.allowlist` so the gate stops flagging it. Re-evaluate during a future `maintenance` pass.
17
+ - **Changelog tooling summary cap raised 250 → 350 chars** (mirrors [mcp-ts-core#129](https://github.com/cyanheads/mcp-ts-core/issues/129)) — `SUMMARY_MAX_LENGTH` in `scripts/build-changelog.ts` and `changelog/template.md`.
18
+ - **`.gitignore`** — ignore `.agents/` (the agent-skill-directory mirror populated by the `maintenance` skill).
19
+ - **Skill bumps mirrored from upstream.** `api-linter` 1.2 → 1.3 (adds portability/landing/handler-body/error-contract rule docs), `polish-docs-meta` 1.7 → 1.8, `design-mcp-server` 2.10 → 2.11, `add-tool` 2.8 → 2.9, `tool-defs-analysis` 1.1 → 1.2 (10 → 12 audit categories), `field-test` 2.3 → 2.4, `api-errors` 1.5 → 1.6.
20
+ - **Dev deps.** `@types/node` ^25.6.2 → ^25.7.0, `@vitest/coverage-istanbul` ^4.1.5 → ^4.1.6, `fast-check` ^4.7.0 → ^4.8.0, `vitest` ^4.1.5 → ^4.1.6.
21
+
22
+ ## Tests
23
+
24
+ - 539 passed / 4 skipped. `bun run devcheck` clean.