@cyanheads/pubmed-mcp-server 2.10.11 → 2.10.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +19 -18
- package/CLAUDE.md +19 -18
- package/README.md +87 -119
- package/dist/config/server-config.d.ts +4 -4
- package/dist/config/server-config.d.ts.map +1 -1
- package/dist/config/server-config.js +9 -24
- package/dist/config/server-config.js.map +1 -1
- package/dist/mcp-server/resources/definitions/database-info.resource.js +6 -6
- package/dist/mcp-server/resources/definitions/database-info.resource.js.map +1 -1
- package/dist/services/ncbi/formatting/citation-formatter.d.ts +13 -0
- package/dist/services/ncbi/formatting/citation-formatter.d.ts.map +1 -1
- package/dist/services/ncbi/formatting/citation-formatter.js +35 -11
- package/dist/services/ncbi/formatting/citation-formatter.js.map +1 -1
- package/dist/services/ncbi/ncbi-service.d.ts.map +1 -1
- package/dist/services/ncbi/ncbi-service.js +7 -4
- package/dist/services/ncbi/ncbi-service.js.map +1 -1
- package/dist/services/ncbi/parsing/article-parser.d.ts.map +1 -1
- package/dist/services/ncbi/parsing/article-parser.js +14 -22
- package/dist/services/ncbi/parsing/article-parser.js.map +1 -1
- package/dist/services/ncbi/parsing/esummary-parser.d.ts +11 -1
- package/dist/services/ncbi/parsing/esummary-parser.d.ts.map +1 -1
- package/dist/services/ncbi/parsing/esummary-parser.js +16 -7
- package/dist/services/ncbi/parsing/esummary-parser.js.map +1 -1
- package/dist/services/ncbi/parsing/pmc-article-parser.d.ts.map +1 -1
- package/dist/services/ncbi/parsing/pmc-article-parser.js +26 -3
- package/dist/services/ncbi/parsing/pmc-article-parser.js.map +1 -1
- package/dist/services/ncbi/parsing/pmc-xml-helpers.d.ts +19 -0
- package/dist/services/ncbi/parsing/pmc-xml-helpers.d.ts.map +1 -1
- package/dist/services/ncbi/parsing/pmc-xml-helpers.js +81 -2
- package/dist/services/ncbi/parsing/pmc-xml-helpers.js.map +1 -1
- package/dist/services/ncbi/parsing/xml-helpers.d.ts +29 -3
- package/dist/services/ncbi/parsing/xml-helpers.d.ts.map +1 -1
- package/dist/services/ncbi/parsing/xml-helpers.js +46 -0
- package/dist/services/ncbi/parsing/xml-helpers.js.map +1 -1
- package/dist/services/ncbi/response-handler.d.ts +9 -0
- package/dist/services/ncbi/response-handler.d.ts.map +1 -1
- package/dist/services/ncbi/response-handler.js +91 -32
- package/dist/services/ncbi/response-handler.js.map +1 -1
- package/package.json +9 -7
- package/server.json +3 -3
package/AGENTS.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Agent Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** @cyanheads/pubmed-mcp-server
|
|
4
|
-
**Version:** 2.10.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.
|
|
6
|
-
**Engines:** Bun ≥1.
|
|
4
|
+
**Version:** 2.10.13
|
|
5
|
+
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.0`
|
|
6
|
+
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
|
|
8
8
|
> **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
|
|
9
9
|
|
|
@@ -111,12 +111,10 @@ export const databaseInfoResource = resource('pubmed://database/info', {
|
|
|
111
111
|
import { z } from '@cyanheads/mcp-ts-core';
|
|
112
112
|
import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
|
|
113
113
|
|
|
114
|
-
const emptyAsUndefined = (v: unknown) => (v === '' ? undefined : v);
|
|
115
|
-
|
|
116
114
|
const ServerConfigSchema = z.object({
|
|
117
|
-
apiKey: z.
|
|
115
|
+
apiKey: z.string().optional().describe('NCBI API key'),
|
|
118
116
|
toolIdentifier: z.string().default('pubmed-mcp-server').describe('NCBI tool identifier'),
|
|
119
|
-
adminEmail: z.
|
|
117
|
+
adminEmail: z.email().optional().describe('Admin contact email'),
|
|
120
118
|
requestDelayMs: z.coerce.number().min(50).max(5000).default(334).describe('Request delay in ms'),
|
|
121
119
|
maxRetries: z.coerce.number().min(0).max(10).default(6).describe('Max retry attempts'),
|
|
122
120
|
timeoutMs: z.coerce.number().min(1000).max(120000).default(30000).describe('Request timeout in ms'),
|
|
@@ -138,6 +136,8 @@ export function getServerConfig(): z.infer<typeof ServerConfigSchema> {
|
|
|
138
136
|
|
|
139
137
|
`parseEnvConfig` maps Zod schema paths → env var names so validation errors name the actual variable (`NCBI_REQUEST_DELAY_MS` must be a number) rather than the internal path (`requestDelayMs: expected number`).
|
|
140
138
|
|
|
139
|
+
An empty value and a whole-value `${…}` placeholder (what an MCPB or plugin host forwards when a user leaves an option blank) read as unset: an optional field stays `undefined`, a defaulted field takes its default. No per-field `z.preprocess` guard is needed.
|
|
140
|
+
|
|
141
141
|
For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.
|
|
142
142
|
|
|
143
143
|
### Server identity and instructions
|
|
@@ -267,9 +267,9 @@ src/
|
|
|
267
267
|
|
|
268
268
|
## Skills
|
|
269
269
|
|
|
270
|
-
Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool.
|
|
270
|
+
Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it. Keep `skills/` free for skills meant for those agents.
|
|
271
271
|
|
|
272
|
-
**Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent).
|
|
272
|
+
**Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
|
|
273
273
|
|
|
274
274
|
Available skills:
|
|
275
275
|
|
|
@@ -284,7 +284,7 @@ Available skills:
|
|
|
284
284
|
| `add-service` | Scaffold a new service integration |
|
|
285
285
|
| `add-test` | Scaffold test file for a tool, resource, or service |
|
|
286
286
|
| `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
|
|
287
|
-
| `tool-defs-analysis` | Read-only audit of definition language
|
|
287
|
+
| `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
|
|
288
288
|
| `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
|
|
289
289
|
| `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
|
|
290
290
|
| `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
|
|
@@ -309,7 +309,7 @@ Available skills:
|
|
|
309
309
|
| `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
|
|
310
310
|
| `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
|
|
311
311
|
|
|
312
|
-
**Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
|
|
312
|
+
**Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `framework-skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
|
|
313
313
|
|
|
314
314
|
When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
|
|
315
315
|
|
|
@@ -323,7 +323,8 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
323
323
|
| `bun run rebuild` | Clean + build |
|
|
324
324
|
| `bun run clean` | Remove build artifacts |
|
|
325
325
|
| `bun run devcheck` | Lint + format + typecheck + security + packaging alignment |
|
|
326
|
-
| `bun run audit:
|
|
326
|
+
| `bun run audit:fix` | `bun audit fix` — upgrade vulnerable packages to the lowest safe version within existing ranges (`--dry-run` previews, `--latest` rewrites ranges). First response when `devcheck` flags a transitive advisory; then `bun update <name>`, then `bun dedupe` |
|
|
327
|
+
| `bun run audit:refresh` | Delete `bun.lock` and reinstall. Last resort after `audit:fix`, `bun update <name>`, and `bun dedupe` — re-resolves every ranged dep (the framework pin included) and rewrites the lockfile as `lockfileVersion: 2` |
|
|
327
328
|
| `bun run tree` | Generate directory structure doc |
|
|
328
329
|
| `bun run format` | Auto-fix formatting (safe fixes only) |
|
|
329
330
|
| `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
|
|
@@ -332,7 +333,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
332
333
|
| `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
|
|
333
334
|
| `bun run lint:mcp` | Validate MCP definitions against spec |
|
|
334
335
|
| `bun run lint:packaging` | Validate env var alignment between `manifest.json` and `server.json` (skipped cleanly when `manifest.json` is absent) |
|
|
335
|
-
| `bun run list-skills` | List skills in `skills/` with name + description |
|
|
336
|
+
| `bun run list-skills` | List skills in `framework-skills/` with name + description |
|
|
336
337
|
| `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
|
|
337
338
|
| `bun run release:github` | Create GitHub Release from an annotated tag — enforces `v<VERSION>: <subject>` title, attaches `.mcpb` bundle |
|
|
338
339
|
| `bun run start:stdio` | Production mode (stdio) |
|
|
@@ -342,9 +343,9 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
342
343
|
|
|
343
344
|
## Bundling
|
|
344
345
|
|
|
345
|
-
`bun run bundle` produces `dist/pubmed-mcp-server.mcpb` for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. MCPB is stdio-only — HTTP and Docker deployments are unaffected. The `release-and-publish` skill attaches the bundle to the GitHub Release at a stable `releases/latest/download/pubmed-mcp-server.mcpb` URL that powers the README install badge.
|
|
346
|
+
`bun run bundle` produces `dist/pubmed-mcp-server.mcpb` for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. MCPB is stdio-only — HTTP and Docker deployments are unaffected. The `release-and-publish` skill attaches the bundle to the GitHub Release at a stable `releases/latest/download/pubmed-mcp-server.mcpb` URL that powers the README install badge.
|
|
346
347
|
|
|
347
|
-
**Adding an env var requires both files**: `server.json` stdio `environmentVariables[]` (registry discovery) and `manifest.json`
|
|
348
|
+
**Adding an env var requires both files**: `server.json` stdio `environmentVariables[]` (registry discovery) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `bun run lint:packaging` (run by `devcheck`) verifies the env var names match, that every `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.X}"` (the host substitutes nothing else — `"${X}"` reaches the server as that literal string), and that an optional string option carries `"default": ""`. A user-supplied variable also goes into the plugin manifests — `.claude-plugin/plugin.json` `userConfig` + `env`, `.codex-plugin/mcp.json` `env_vars` (see Checklist).
|
|
348
349
|
|
|
349
350
|
---
|
|
350
351
|
|
|
@@ -407,7 +408,7 @@ import { getNcbiService } from '@/services/ncbi/ncbi-service.js';
|
|
|
407
408
|
- [ ] NCBI wrapping: tests include at least one sparse payload case with omitted upstream fields
|
|
408
409
|
- [ ] Registered in `createApp()` arrays (directly or via barrel exports)
|
|
409
410
|
- [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
|
|
410
|
-
- [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` =
|
|
411
|
-
- [ ] `.codex-plugin/mcp.json` updated — server name key
|
|
412
|
-
- [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry
|
|
411
|
+
- [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name (never the npm scope — `lint:packaging` enforces this); `interface.shortDescription` from `package.json` description
|
|
412
|
+
- [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable (API key, contact email, instance URL) is listed in `env_vars` so Codex forwards it from the user's environment. Never write `"KEY": ""` into `env` — an empty value replaces the user's exported key and is read as unset
|
|
413
|
+
- [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `author`, `repository`, `license`, `keywords` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name. Every user-supplied variable is declared under `userConfig` (`type`, `title`, `description`; `sensitive: true` for keys and tokens; `required: true` or `default: ""`) and referenced from `env` as `"KEY": "${user_config.<option>}"` — mirror the `user_config` block in `manifest.json`. Never write `"KEY": ""` into `env`
|
|
413
414
|
- [ ] `bun run devcheck` passes
|
package/CLAUDE.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Agent Protocol
|
|
2
2
|
|
|
3
3
|
**Server:** @cyanheads/pubmed-mcp-server
|
|
4
|
-
**Version:** 2.10.
|
|
5
|
-
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.
|
|
6
|
-
**Engines:** Bun ≥1.
|
|
4
|
+
**Version:** 2.10.13
|
|
5
|
+
**Framework:** [@cyanheads/mcp-ts-core](https://www.npmjs.com/package/@cyanheads/mcp-ts-core) `^0.13.0`
|
|
6
|
+
**Engines:** Bun ≥1.4.0, Node ≥24.0.0
|
|
7
7
|
|
|
8
8
|
> **Read the framework docs first:** `node_modules/@cyanheads/mcp-ts-core/CLAUDE.md` contains the full API reference — builders, Context, error codes, exports, patterns. This file covers server-specific conventions only.
|
|
9
9
|
|
|
@@ -111,12 +111,10 @@ export const databaseInfoResource = resource('pubmed://database/info', {
|
|
|
111
111
|
import { z } from '@cyanheads/mcp-ts-core';
|
|
112
112
|
import { parseEnvConfig } from '@cyanheads/mcp-ts-core/config';
|
|
113
113
|
|
|
114
|
-
const emptyAsUndefined = (v: unknown) => (v === '' ? undefined : v);
|
|
115
|
-
|
|
116
114
|
const ServerConfigSchema = z.object({
|
|
117
|
-
apiKey: z.
|
|
115
|
+
apiKey: z.string().optional().describe('NCBI API key'),
|
|
118
116
|
toolIdentifier: z.string().default('pubmed-mcp-server').describe('NCBI tool identifier'),
|
|
119
|
-
adminEmail: z.
|
|
117
|
+
adminEmail: z.email().optional().describe('Admin contact email'),
|
|
120
118
|
requestDelayMs: z.coerce.number().min(50).max(5000).default(334).describe('Request delay in ms'),
|
|
121
119
|
maxRetries: z.coerce.number().min(0).max(10).default(6).describe('Max retry attempts'),
|
|
122
120
|
timeoutMs: z.coerce.number().min(1000).max(120000).default(30000).describe('Request timeout in ms'),
|
|
@@ -138,6 +136,8 @@ export function getServerConfig(): z.infer<typeof ServerConfigSchema> {
|
|
|
138
136
|
|
|
139
137
|
`parseEnvConfig` maps Zod schema paths → env var names so validation errors name the actual variable (`NCBI_REQUEST_DELAY_MS` must be a number) rather than the internal path (`requestDelayMs: expected number`).
|
|
140
138
|
|
|
139
|
+
An empty value and a whole-value `${…}` placeholder (what an MCPB or plugin host forwards when a user leaves an option blank) read as unset: an optional field stays `undefined`, a defaulted field takes its default. No per-field `z.preprocess` guard is needed.
|
|
140
|
+
|
|
141
141
|
For env booleans use `z.stringbool()`, never `z.coerce.boolean()` — `Boolean("false")` is `true`, so a coerced flag can't be disabled through the environment. `z.stringbool()` parses `true/false/1/0/yes/no/on/off` and rejects anything else, so `=false` actually disables.
|
|
142
142
|
|
|
143
143
|
### Server identity and instructions
|
|
@@ -267,9 +267,9 @@ src/
|
|
|
267
267
|
|
|
268
268
|
## Skills
|
|
269
269
|
|
|
270
|
-
Skills are modular instructions in `skills/` at the project root. Read them directly when a task matches — e.g., `skills/add-tool/SKILL.md` when adding a tool.
|
|
270
|
+
Skills are modular instructions in `framework-skills/` at the project root. Read them directly when a task matches — e.g., `framework-skills/add-tool/SKILL.md` when adding a tool. `bun run list-skills` prints the full registry. The directory is deliberately not `skills/`: Claude Code and Codex auto-load a plugin's root `skills/`, so a server that ships `.claude-plugin/` or `.codex-plugin/` would hand these development skills to every agent that installs it. Keep `skills/` free for skills meant for those agents.
|
|
271
271
|
|
|
272
|
-
**Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent).
|
|
272
|
+
**Agent skill directory:** Copy skills into the directory your agent discovers (Claude Code: `.claude/skills/`, others: equivalent). Skills then load as context without referencing `framework-skills/` paths. After framework updates, run the `maintenance` skill — Phase B re-syncs the agent directory.
|
|
273
273
|
|
|
274
274
|
Available skills:
|
|
275
275
|
|
|
@@ -284,7 +284,7 @@ Available skills:
|
|
|
284
284
|
| `add-service` | Scaffold a new service integration |
|
|
285
285
|
| `add-test` | Scaffold test file for a tool, resource, or service |
|
|
286
286
|
| `field-test` | Exercise tools/resources/prompts with real inputs, verify behavior, report issues |
|
|
287
|
-
| `tool-defs-analysis` | Read-only audit of definition language
|
|
287
|
+
| `tool-defs-analysis` | Read-only audit of MCP definition language across the surface — voice, leaks, defaults, recovery hints, output descriptions |
|
|
288
288
|
| `security-pass` | Audit server for MCP-flavored security gaps: output injection, scope blast radius, input sinks, tenant isolation |
|
|
289
289
|
| `code-simplifier` | Post-session cleanup against `git diff` — modernize syntax, consolidate duplication, align with the codebase |
|
|
290
290
|
| `polish-docs-meta` | Finalize docs, README, metadata, and agent protocol for shipping |
|
|
@@ -309,7 +309,7 @@ Available skills:
|
|
|
309
309
|
| `report-issue-framework` | File a bug or feature request against `@cyanheads/mcp-ts-core` via `gh` CLI |
|
|
310
310
|
| `report-issue-local` | File a bug or feature request against this server's own repo via `gh` CLI |
|
|
311
311
|
|
|
312
|
-
**Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
|
|
312
|
+
**Chaining skills into pipelines.** When the user wants a multi-phase effort — build this server out, QA-and-fix the surface, update-and-ship — *and you can spawn sub-agents*, `framework-skills/orchestrations/SKILL.md` sequences the task skills above into a gated pipeline with verification at each step. Read it to drive the run. Optional: skip it if you can't orchestrate sub-agents, and ignore it entirely if you were *spawned* as one — you've already been scoped to a single phase.
|
|
313
313
|
|
|
314
314
|
When you complete a skill's checklist, check the boxes and add a completion timestamp at the end (e.g., `Completed: 2026-03-11`).
|
|
315
315
|
|
|
@@ -323,7 +323,8 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
323
323
|
| `bun run rebuild` | Clean + build |
|
|
324
324
|
| `bun run clean` | Remove build artifacts |
|
|
325
325
|
| `bun run devcheck` | Lint + format + typecheck + security + packaging alignment |
|
|
326
|
-
| `bun run audit:
|
|
326
|
+
| `bun run audit:fix` | `bun audit fix` — upgrade vulnerable packages to the lowest safe version within existing ranges (`--dry-run` previews, `--latest` rewrites ranges). First response when `devcheck` flags a transitive advisory; then `bun update <name>`, then `bun dedupe` |
|
|
327
|
+
| `bun run audit:refresh` | Delete `bun.lock` and reinstall. Last resort after `audit:fix`, `bun update <name>`, and `bun dedupe` — re-resolves every ranged dep (the framework pin included) and rewrites the lockfile as `lockfileVersion: 2` |
|
|
327
328
|
| `bun run tree` | Generate directory structure doc |
|
|
328
329
|
| `bun run format` | Auto-fix formatting (safe fixes only) |
|
|
329
330
|
| `bun run format:unsafe` | Also apply Biome's unsafe autofixes — review the diff; they can change behavior |
|
|
@@ -332,7 +333,7 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
332
333
|
| `bun run changelog:check` | Verify `CHANGELOG.md` is in sync (used by devcheck) |
|
|
333
334
|
| `bun run lint:mcp` | Validate MCP definitions against spec |
|
|
334
335
|
| `bun run lint:packaging` | Validate env var alignment between `manifest.json` and `server.json` (skipped cleanly when `manifest.json` is absent) |
|
|
335
|
-
| `bun run list-skills` | List skills in `skills/` with name + description |
|
|
336
|
+
| `bun run list-skills` | List skills in `framework-skills/` with name + description |
|
|
336
337
|
| `bun run bundle` | Build, pack, and clean a `.mcpb` for one-click Claude Desktop install |
|
|
337
338
|
| `bun run release:github` | Create GitHub Release from an annotated tag — enforces `v<VERSION>: <subject>` title, attaches `.mcpb` bundle |
|
|
338
339
|
| `bun run start:stdio` | Production mode (stdio) |
|
|
@@ -342,9 +343,9 @@ When you complete a skill's checklist, check the boxes and add a completion time
|
|
|
342
343
|
|
|
343
344
|
## Bundling
|
|
344
345
|
|
|
345
|
-
`bun run bundle` produces `dist/pubmed-mcp-server.mcpb` for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. MCPB is stdio-only — HTTP and Docker deployments are unaffected. The `release-and-publish` skill attaches the bundle to the GitHub Release at a stable `releases/latest/download/pubmed-mcp-server.mcpb` URL that powers the README install badge.
|
|
346
|
+
`bun run bundle` produces `dist/pubmed-mcp-server.mcpb` for one-click install in Claude Desktop. The pack step is followed by `scripts/clean-mcpb.ts`, which prunes dev dependencies (`mcpb clean`) and strips two classes of `node_modules/**` content that root-anchored `.mcpbignore` patterns cannot reach: dependency-shipped agent docs (`framework-skills/`, `skills/`, `.claude/`, `.agents/`, `SKILL.md`) and platform-specific native bindings, which would otherwise lock the bundle to the platform it was packed on. MCPB is stdio-only — HTTP and Docker deployments are unaffected. The `release-and-publish` skill attaches the bundle to the GitHub Release at a stable `releases/latest/download/pubmed-mcp-server.mcpb` URL that powers the README install badge.
|
|
346
347
|
|
|
347
|
-
**Adding an env var requires both files**: `server.json` stdio `environmentVariables[]` (registry discovery) and `manifest.json`
|
|
348
|
+
**Adding an env var requires both files**: `server.json` stdio `environmentVariables[]` (registry discovery) and `manifest.json` (bundle install UX, `mcp_config.env` + `user_config`). `bun run lint:packaging` (run by `devcheck`) verifies the env var names match, that every `user_config` option is wired into `mcp_config.env` as `"X": "${user_config.X}"` (the host substitutes nothing else — `"${X}"` reaches the server as that literal string), and that an optional string option carries `"default": ""`. A user-supplied variable also goes into the plugin manifests — `.claude-plugin/plugin.json` `userConfig` + `env`, `.codex-plugin/mcp.json` `env_vars` (see Checklist).
|
|
348
349
|
|
|
349
350
|
---
|
|
350
351
|
|
|
@@ -407,7 +408,7 @@ import { getNcbiService } from '@/services/ncbi/ncbi-service.js';
|
|
|
407
408
|
- [ ] NCBI wrapping: tests include at least one sparse payload case with omitted upstream fields
|
|
408
409
|
- [ ] Registered in `createApp()` arrays (directly or via barrel exports)
|
|
409
410
|
- [ ] Tests use `createMockContext()` from `@cyanheads/mcp-ts-core/testing`
|
|
410
|
-
- [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` =
|
|
411
|
-
- [ ] `.codex-plugin/mcp.json` updated — server name key
|
|
412
|
-
- [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; inline `mcpServers` entry
|
|
411
|
+
- [ ] `.codex-plugin/plugin.json` populated — `name`, `version`, `description`, `repository`, `license` from `package.json`; `interface.displayName` = the unscoped repo name (never the npm scope — `lint:packaging` enforces this); `interface.shortDescription` from `package.json` description
|
|
412
|
+
- [ ] `.codex-plugin/mcp.json` updated — server name key is the unscoped repo name; every user-supplied variable (API key, contact email, instance URL) is listed in `env_vars` so Codex forwards it from the user's environment. Never write `"KEY": ""` into `env` — an empty value replaces the user's exported key and is read as unset
|
|
413
|
+
- [ ] `.claude-plugin/plugin.json` populated — `name`, `version`, `description`, `author`, `repository`, `license`, `keywords` from `package.json`; inline `mcpServers` entry keyed by the unscoped repo name. Every user-supplied variable is declared under `userConfig` (`type`, `title`, `description`; `sensitive: true` for keys and tokens; `required: true` or `default: ""`) and referenced from `env` as `"KEY": "${user_config.<option>}"` — mirror the `user_config` block in `manifest.json`. Never write `"KEY": ""` into `env`
|
|
413
414
|
- [ ] `bun run devcheck` passes
|
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
[](./CHANGELOG.md) [](./LICENSE) [](https://github.com/users/cyanheads/packages/container/package/pubmed-mcp-server) [](https://modelcontextprotocol.io/) [](https://www.npmjs.com/package/@cyanheads/pubmed-mcp-server) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
13
13
|
|
|
14
14
|
</div>
|
|
15
15
|
|
|
@@ -29,9 +29,11 @@
|
|
|
29
29
|
|
|
30
30
|
---
|
|
31
31
|
|
|
32
|
-
##
|
|
32
|
+
## Overview
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
An MCP server over NCBI's E-utilities, PubMed Central, and Europe PMC. Search the biomedical literature, fetch metadata and full text, resolve identifiers and partial citations, format references, and ground queries in MeSH vocabulary. Runs as a stdio process, a local Streamable HTTP server, or the public hosted endpoint above.
|
|
35
|
+
|
|
36
|
+
### Tools
|
|
35
37
|
|
|
36
38
|
| Tool | Description |
|
|
37
39
|
|:---|:---|
|
|
@@ -42,173 +44,139 @@
|
|
|
42
44
|
| `pubmed_fetch_fulltext` | Fetch full-text articles via a chain: NCBI PMC EFetch → Europe PMC `fullTextXML` → Unpaywall. Accepts PMIDs, PMCIDs, or DOIs. |
|
|
43
45
|
| `pubmed_format_citations` | Generate formatted citations in APA 7th, MLA 9th, BibTeX, RIS, or Vancouver (ICMJE/NLM) |
|
|
44
46
|
| `pubmed_find_related` | Find similar articles, citing articles, or references for a given PMID |
|
|
45
|
-
| `pubmed_spell_check` | Spell-check biomedical
|
|
46
|
-
| `pubmed_lookup_mesh` | Search
|
|
47
|
+
| `pubmed_spell_check` | Spell-check a biomedical query via NCBI ESpell — returns the corrected query and whether a suggestion was found |
|
|
48
|
+
| `pubmed_lookup_mesh` | Search MeSH by heading — tree numbers, scope notes, entry terms — for building controlled-vocabulary queries |
|
|
47
49
|
| `pubmed_lookup_citation` | Resolve partial bibliographic references to PubMed IDs via ECitMatch |
|
|
48
50
|
| `pubmed_convert_ids` | Convert between DOI, PMID, and PMCID using the PMC ID Converter API |
|
|
49
51
|
|
|
50
|
-
###
|
|
52
|
+
### Resources
|
|
51
53
|
|
|
52
|
-
|
|
54
|
+
| Resource | Description |
|
|
55
|
+
|:---|:---|
|
|
56
|
+
| `pubmed://database/info` | PubMed database metadata via EInfo (field list, record count, last update) |
|
|
53
57
|
|
|
54
|
-
|
|
55
|
-
- Field-specific filters: author, journal, MeSH terms, language, species
|
|
56
|
-
- Common filters: has abstract, free full text
|
|
57
|
-
- Date range filtering by publication, modification, or Entrez date
|
|
58
|
-
- Publication type filtering (Review, Clinical Trial, Meta-Analysis, etc.)
|
|
59
|
-
- Sort by relevance, publication date, author, or journal
|
|
60
|
-
- Pagination via offset for paging through large result sets
|
|
61
|
-
- Optional brief summaries for top N results via ESummary
|
|
62
|
-
- NCBI Bookshelf results carry their own venue — `bookTitle`, `publisherName`, and `docType` (`chapter`, `book`, or `citation`) — because PubMed leaves `source` empty on them; the book's editors are reported in `editors`, apart from the chapter's own authors
|
|
63
|
-
- Returns the original query plus the fully applied PubMed query and normalized filter metadata
|
|
58
|
+
### Prompts
|
|
64
59
|
|
|
65
|
-
|
|
60
|
+
| Prompt | Description |
|
|
61
|
+
|:---|:---|
|
|
62
|
+
| `research_plan` | Generate a structured 4-phase biomedical research plan outline |
|
|
66
63
|
|
|
67
|
-
|
|
64
|
+
## Capability reference
|
|
68
65
|
|
|
69
|
-
|
|
66
|
+
### `pubmed_search_articles` <sub>tool</sub>
|
|
70
67
|
|
|
71
|
-
-
|
|
72
|
-
-
|
|
73
|
-
-
|
|
74
|
-
-
|
|
75
|
-
-
|
|
76
|
-
- NCBI Bookshelf chapters and whole books are returned as first-class records, not reported unavailable: `recordType` (`journal-article`, `book-chapter`, `book`) tells them apart, and a `book` object carries the book title, publisher, place, dates, medium, edition, series, ISBNs, book DOI, editors, and Bookshelf accession. `journalInfo` is absent on those records — a book title is never reported as a journal
|
|
77
|
-
- Journals that assign article numbers instead of page ranges often carry no pagination at all; the number is reported as `journalInfo.elocationId` with its `journalInfo.elocationIdType` (`pii`), never merged into `journalInfo.pages` and never confused with the DOI
|
|
78
|
-
- Opt-in whole-response ceiling: `maxResponseCharacters` keeps complete article records in response order until the next one would cross it, then defers the rest whole and lists their PMIDs in `deferred.ids`. Re-call with those PMIDs to resume exactly where the response stopped — no article is split, skipped, or duplicated. Each article is measured as the JSON record it is returned as, so a ceiling under the first article returns zero articles, the full deferred list, and the size to clear
|
|
68
|
+
- Full PubMed boolean and field-tag syntax, plus structured filters: author, journal, MeSH terms, language, species, publication type, has-abstract, free-full-text
|
|
69
|
+
- Date ranges by publication, modification, or Entrez date; sort by relevance, date, author, or journal; offset pagination
|
|
70
|
+
- Optional brief summaries for the top N results via ESummary
|
|
71
|
+
- NCBI Bookshelf hits carry `bookTitle`, `publisherName`, `docType`, and `editors` in place of the empty `source`
|
|
72
|
+
- Echoes the original query, the fully applied PubMed query, and normalized filter metadata
|
|
79
73
|
|
|
80
74
|
---
|
|
81
75
|
|
|
82
|
-
### `
|
|
83
|
-
|
|
84
|
-
Fetch full-text articles via a three-stage chain: NCBI PMC EFetch → Europe PMC `fullTextXML` → Unpaywall.
|
|
85
|
-
|
|
86
|
-
- Accepts exactly one of `pmcids` (direct PMC IDs), `pmids` (PubMed IDs, auto-resolved), or `dois` (auto-resolved to PMC via the ID Converter; preprints and EPMC-only OA fall through to Europe PMC / Unpaywall). One identifier per element in every branch — a DOI carrying a comma or whitespace is rejected at the schema
|
|
87
|
-
- NCBI PMC and Europe PMC both return structured JATS; output records origin via `viaSource: "pmc" | "europepmc" | "unpaywall"`
|
|
88
|
-
- Europe PMC layer (enabled by default; disable with `EUROPEPMC_ENABLED=false`) 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 preprints (PPR), patents (PAT), and Agricola (AGR) are reachable via `pubmed_europepmc_search` for metadata but have no full text via this chain.
|
|
89
|
-
- Unpaywall layer (enabled by setting `UNPAYWALL_EMAIL`) resolves DOIs to legal OA copies; extracts HTML landing pages to Markdown via Defuddle or PDFs to text via unpdf
|
|
90
|
-
- Discriminated output contract — `source: "pmc"` (structured sections, regardless of whether it came from PMC or EPMC) or `source: "unpaywall"` (best-effort body + `contentFormat`: `html-markdown` or `pdf-text`)
|
|
91
|
-
- Structured unavailable reasons (`not-found`, `no-pmc-fallback-disabled`, `no-epmc-fulltext`, `no-body`, `no-doi`, `doi-lookup-failed`, `no-oa`, `fetch-failed`, `parse-failed`, `service-error`) so callers can retry or explain to users without parsing text. `no-doi` and `doi-lookup-failed` are the settled and unsettled halves of the same gap: the first means the DOI lookup ran and the record has none, the second that the lookup itself errored, so a DOI may well exist and the request is worth retrying
|
|
92
|
-
- An `unavailable` entry also carries `unqueriedTiers` when the chain skipped a tier this deployment has not configured and that tier could have served the id — the search was incomplete, and a deployment with those tiers configured may still resolve it
|
|
93
|
-
- Each `unavailable` entry carries `idType` (`pmid` / `pmcid` / `doi`) and `triedTiers` — per-tier outcomes (`not-attempted`, `miss`, `no-fulltext`, `service-error`, …) in execution order, so callers can see which stage failed and why
|
|
94
|
-
- Section filtering by title (case-insensitive substring match at any nesting depth, e.g. `["methods", "results"]`) and configurable max sections apply to PMC output. A section that matches directly is returned whole; one kept only because a nested subsection matched keeps its heading as a breadcrumb with its own text cleared
|
|
95
|
-
- Tables are returned as structured cells (`tables[]` on each PMC article — rows, caption, label, footnotes, and the enclosing section, named for back-matter and appendix tables as well as body ones), covering `<floats-group>`, `<back>` and appendix deposits alongside body tables. `colspan` and `rowspan` are expanded to one entry per grid column, so a value stays under the header it belongs to on both output surfaces; a cell spanning several columns or rows repeats across the cells it covers. A deposit with no readable markup comes back labelled with an `unextractableReason` rather than silently missing. Turn them off with `includeTables: false`
|
|
96
|
-
- Figures and supplementary material come back as structured entries (`assets[]` on each PMC article — `assetType`, label, caption, the enclosing section, and the `<graphic>`/`<media>` pointer exactly as deposited, which is a name inside the PMC deposit rather than a fetchable URL), covering `<floats-group>`, `<back>` and appendix placements alongside body ones. Each one lifted out of the body leaves a `[Figure: <label>]` / `[Supplementary: <label>]` marker at its position, so reading order survives the lift. Turn them off with `includeAssets: false`, which removes the markers with them. Prose-shaped blocks — lists, definition lists, block quotes, boxed text, preformatted blocks, displayed formulae — render into the section text at their document position instead, and no block is ever concatenated into a neighbouring sentence
|
|
97
|
-
- Character budgets keep context size predictable: `maxCharacters` caps body text per article (PMC sections and subsections, inline blocks included, plus table content — cell, caption, label and footnote text, not the Markdown grid rendered around it — and asset label, caption and pointer text; or the Unpaywall body), `maxCharactersPerSection` caps a single PMC section, and `overflowMode` picks between `truncate` (fill sections in document order) and `outline` (split the budget evenly so every heading survives with an excerpt). Sections are served first, then tables, then assets, each spending what is left in document order until one does not fit; that entry and the rest are dropped whole rather than cut mid-row or returned with a shortened caption, and named in `truncation.articles[].omittedTableNames` / `omittedAssetNames`. Budgets run after the semantic filters, and a `truncation` object reports per-article and per-section character counts whenever anything was shortened
|
|
98
|
-
- `maxResponseCharacters` bounds the whole response instead of each body: every field of a returned record counts (abstract, references, metadata, body), one ledger across PMC-, Europe PMC-, and Unpaywall-served articles. Articles past the ceiling are deferred whole, with their ids — in the branch they were requested under — in `deferred.ids` for a follow-up call
|
|
99
|
-
- Up to 10 articles per request
|
|
76
|
+
### `pubmed_fetch_articles` <sub>tool</sub>
|
|
100
77
|
|
|
101
|
-
|
|
78
|
+
- Up to 200 PMIDs per call (POST for batches of 100 or more)
|
|
79
|
+
- Title, abstract, authors with deduplicated affiliations, journal info, DOI, PubMed/PMC links; optional MeSH terms, grants, and publication types
|
|
80
|
+
- Tolerant of PubMed's inconsistent XML — structured abstracts, missing fields, varying date formats
|
|
81
|
+
- Bookshelf chapters and books are first-class: `recordType` (`journal-article` / `book-chapter` / `book`) plus a `book` object (title, publisher, editors, ISBNs, Bookshelf accession); `journalInfo` is absent on them
|
|
82
|
+
- Article-number journals report `journalInfo.elocationId` + `elocationIdType` rather than a page range
|
|
83
|
+
- Opt-in `maxResponseCharacters` keeps whole records in order until the ceiling, then defers the rest to `deferred.ids` for a follow-up call
|
|
102
84
|
|
|
103
|
-
|
|
85
|
+
---
|
|
104
86
|
|
|
105
|
-
|
|
87
|
+
### `pubmed_fetch_fulltext` <sub>tool</sub>
|
|
106
88
|
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
-
|
|
111
|
-
-
|
|
112
|
-
-
|
|
89
|
+
- Exactly one of `pmcids`, `pmids`, or `dois` (one id per element), up to 10 per request
|
|
90
|
+
- Three-tier chain: NCBI PMC EFetch → Europe PMC `fullTextXML` (`EUROPEPMC_ENABLED`, default on) → Unpaywall (needs `UNPAYWALL_EMAIL`); `viaSource` names which tier served each article
|
|
91
|
+
- Preprints, patents, and Agricola records have metadata via `pubmed_europepmc_search` but no full text through this chain — Europe PMC's `fullTextXML` is PMC-keyed
|
|
92
|
+
- `source: "pmc"` returns structured sections plus `tables[]` (cells, caption, label, footnotes) and `assets[]` (figures and supplementary material, with `[Figure: <label>]` markers left in the body); `source: "unpaywall"` returns a best-effort body with `contentFormat` (`html-markdown` / `pdf-text`)
|
|
93
|
+
- Unavailable entries carry a typed `reason` (`not-found`, `no-doi`, `doi-lookup-failed`, `no-oa`, `service-error`, …), `idType`, `triedTiers` (per-tier outcome in execution order), and `unqueriedTiers` when an unconfigured tier could have served the id
|
|
94
|
+
- Filters and budgets: `sections` (case-insensitive title match), `maxSections`, `includeTables`, `includeAssets`, `maxCharacters`, `maxCharactersPerSection`, `overflowMode` (`truncate` / `outline`), and `maxResponseCharacters`, which defers whole articles past the ceiling to `deferred.ids`; a `truncation` object reports what was shortened or omitted
|
|
113
95
|
|
|
114
96
|
---
|
|
115
97
|
|
|
116
|
-
### `
|
|
117
|
-
|
|
118
|
-
Fetch complete Europe PMC records by `source` + `epmcId`, the detail counterpart to `pubmed_europepmc_search`.
|
|
98
|
+
### `pubmed_europepmc_search` <sub>tool</sub>
|
|
119
99
|
|
|
120
|
-
-
|
|
121
|
-
-
|
|
122
|
-
-
|
|
123
|
-
-
|
|
124
|
-
- Disabled when `EUROPEPMC_ENABLED=false`; tool is not registered in that case
|
|
100
|
+
- Reaches records PubMed can't: preprints (`PPR`), patents (`PAT`), Agricola (`AGR`), alongside `MED` and `PMC`; default `sources` is `["MED", "PMC", "PPR"]`
|
|
101
|
+
- Cursor pagination via `cursorMark` — `*` for the first page, then `nextCursorMark`
|
|
102
|
+
- Hits carry `source` plus `pmid` / `pmcId` / `doi` when known; `abstractSnippet` is capped at 400 characters, with `abstractTruncated` flagging the cut
|
|
103
|
+
- Not registered when `EUROPEPMC_ENABLED=false`
|
|
125
104
|
|
|
126
105
|
---
|
|
127
106
|
|
|
128
|
-
### `
|
|
129
|
-
|
|
130
|
-
Generate formatted citations for articles.
|
|
107
|
+
### `pubmed_europepmc_fetch` <sub>tool</sub>
|
|
131
108
|
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
-
|
|
135
|
-
- Request multiple styles per article in a single call
|
|
136
|
-
- Hand-rolled formatters — zero external dependencies, fully Workers-compatible
|
|
137
|
-
- Up to 50 articles per request
|
|
138
|
-
- Reports formatted counts and unavailable PMIDs for partial-result handling
|
|
109
|
+
- Full records with the untruncated plain-text abstract, addressed by `source` + `epmcId` — the only identifier preprint, patent, and Agricola records reliably carry
|
|
110
|
+
- Up to 25 per call in one Europe PMC request; unresolved ids come back in `notFound` rather than failing the batch
|
|
111
|
+
- Not registered when `EUROPEPMC_ENABLED=false`
|
|
139
112
|
|
|
140
113
|
---
|
|
141
114
|
|
|
142
|
-
### `
|
|
115
|
+
### `pubmed_format_citations` <sub>tool</sub>
|
|
143
116
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
-
|
|
147
|
-
-
|
|
148
|
-
- Results returned in NCBI's relevance order
|
|
149
|
-
- Falls back to Europe PMC, then OpenAlex, when NCBI cannot answer; the response names which provider served it. A request no provider can answer fails with a typed `all_providers_failed` error instead of an empty result
|
|
117
|
+
- APA 7th, MLA 9th, BibTeX, RIS, Vancouver (ICMJE/NLM); several styles per article in one call, up to 50 articles
|
|
118
|
+
- Bookshelf chapters and books cite in each style's edited-book form; articles without a page range cite by electronic locator in each style's convention
|
|
119
|
+
- Hand-rolled formatters — zero dependencies, Workers-compatible
|
|
120
|
+
- Reports formatted counts and unavailable PMIDs
|
|
150
121
|
|
|
151
122
|
---
|
|
152
123
|
|
|
153
|
-
### `
|
|
154
|
-
|
|
155
|
-
Spell-check a biomedical query using NCBI's ESpell.
|
|
124
|
+
### `pubmed_find_related` <sub>tool</sub>
|
|
156
125
|
|
|
157
|
-
-
|
|
158
|
-
-
|
|
126
|
+
- `similar`, `cited_by`, or `references` for a PMID, in NCBI relevance order, enriched with title, authors, date, and source (or Bookshelf book title and publisher)
|
|
127
|
+
- Falls back to Europe PMC, then OpenAlex, when NCBI can't answer; the response names the provider. Fails with a typed `all_providers_failed` error rather than an empty result
|
|
159
128
|
|
|
160
129
|
---
|
|
161
130
|
|
|
162
|
-
### `
|
|
131
|
+
### `pubmed_spell_check` <sub>tool</sub>
|
|
163
132
|
|
|
164
|
-
|
|
133
|
+
- Runs a query through NCBI ESpell and returns `original`, `corrected`, and `hasSuggestion`
|
|
134
|
+
- A blank or whitespace-only query is rejected rather than sent upstream
|
|
165
135
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
### `pubmed_lookup_mesh` <sub>tool</sub>
|
|
139
|
+
|
|
140
|
+
- Looks up MeSH descriptors by name or free-text term, pinning the exact-heading match to the top of the first page
|
|
141
|
+
- Records carry `meshId` (DescriptorUI), `entrezUid`, and, with `includeDetails` (default on), tree numbers, scope notes, and entry terms
|
|
142
|
+
- `maxResults` up to 50 with offset pagination via `nextOffset`; `totalCount` reports the upstream match count
|
|
169
143
|
|
|
170
144
|
---
|
|
171
145
|
|
|
172
|
-
### `pubmed_lookup_citation`
|
|
146
|
+
### `pubmed_lookup_citation` <sub>tool</sub>
|
|
173
147
|
|
|
174
|
-
|
|
148
|
+
- Match on journal, year, volume, first page, and/or author — at least one field, more fields for better precision; up to 25 per call
|
|
149
|
+
- Pipes and line breaks are rejected at the schema (ECitMatch's wire format is pipe-delimited); the free-form `key` label is exempt
|
|
150
|
+
- Explicit `matched`, `not_found`, and `ambiguous` statuses with recovery detail
|
|
151
|
+
|
|
152
|
+
---
|
|
175
153
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
-
|
|
179
|
-
-
|
|
180
|
-
-
|
|
181
|
-
- Returns explicit `matched`, `not_found`, and `ambiguous` statuses with recovery detail
|
|
154
|
+
### `pubmed_convert_ids` <sub>tool</sub>
|
|
155
|
+
|
|
156
|
+
- Up to 50 DOIs, PMIDs, or PMCIDs per call, all one type; only PMC-indexed articles resolve
|
|
157
|
+
- One id per element — a packed `"23193287,37952131"` is rejected rather than expanded
|
|
158
|
+
- Per-id success/error rows; a partial batch never fails as a whole
|
|
182
159
|
|
|
183
160
|
---
|
|
184
161
|
|
|
185
|
-
### `
|
|
162
|
+
### `pubmed://database/info` <sub>resource</sub>
|
|
186
163
|
|
|
187
|
-
|
|
164
|
+
- Live EInfo call for the `pubmed` database, returned as `application/json`
|
|
165
|
+
- `dbName`, `description`, `count`, `lastUpdate`, and `fields[]` — each field's short `name` (the tag usable in `pubmed_search_articles` queries), `fullName`, and `description`
|
|
166
|
+
- No parameters
|
|
188
167
|
|
|
189
|
-
|
|
190
|
-
- Accepts DOIs, PMIDs, or PMCIDs (all IDs must be the same type)
|
|
191
|
-
- One identifier per array element, checked against `idType` before the request — a packed value like `"23193287,37952131"` is rejected rather than expanded into extra records, since a comma is the converter's list delimiter in any encoding
|
|
192
|
-
- Only resolves articles indexed in PubMed Central
|
|
193
|
-
- Per-ID success/error reporting — partial batches return resolved mappings alongside structured errors for unresolvable IDs, not a batch-level failure
|
|
168
|
+
---
|
|
194
169
|
|
|
195
|
-
|
|
170
|
+
### `research_plan` <sub>prompt</sub>
|
|
196
171
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
172
|
+
- Arguments: `title`, `goal`, `keywords` (comma-separated) required; `organism` and `includeAgentPrompts` (`"true"` / `"false"`) optional
|
|
173
|
+
- Returns two messages: an assistant framing message (biomedical research planning assistant, grounds recommendations in the PubMed tools when available) and a user message carrying the plan
|
|
174
|
+
- The plan walks four phases — Conception & Planning, Data Collection & Processing, Analysis & Interpretation, Dissemination — with sub-steps under each
|
|
175
|
+
- `includeAgentPrompts: "true"` adds an agent-guidance block under each sub-step, several of which point at `pubmed_search_articles` and `pubmed_lookup_mesh`
|
|
201
176
|
|
|
202
177
|
## Features
|
|
203
178
|
|
|
204
|
-
Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core):
|
|
205
|
-
|
|
206
|
-
- Declarative tool definitions — single file per tool, framework handles registration and validation
|
|
207
|
-
- Unified error handling across all tools
|
|
208
|
-
- Pluggable auth (`none`, `jwt`, `oauth`)
|
|
209
|
-
- Swappable storage backends: `in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`
|
|
210
|
-
- Structured logging with optional OpenTelemetry tracing
|
|
211
|
-
- Runs locally (stdio/HTTP) or on Cloudflare Workers from the same codebase
|
|
179
|
+
Built on [`@cyanheads/mcp-ts-core`](https://github.com/cyanheads/mcp-ts-core): stdio and Streamable HTTP transports (Cloudflare Workers from the same codebase), pluggable auth (`none` / `jwt` / `oauth`), swappable storage (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2/D1`), structured logging with optional OpenTelemetry tracing.
|
|
212
180
|
|
|
213
181
|
PubMed-specific:
|
|
214
182
|
|
|
@@ -403,7 +371,7 @@ See [`CLAUDE.md`](./CLAUDE.md) for development guidelines and architectural rule
|
|
|
403
371
|
|
|
404
372
|
## Contributing
|
|
405
373
|
|
|
406
|
-
Issues
|
|
374
|
+
Issues are welcome. Run checks and tests before submitting:
|
|
407
375
|
|
|
408
376
|
```sh
|
|
409
377
|
bun run devcheck
|
|
@@ -6,18 +6,18 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { z } from '@cyanheads/mcp-ts-core';
|
|
8
8
|
declare const ServerConfigSchema: z.ZodObject<{
|
|
9
|
-
apiKey: z.
|
|
9
|
+
apiKey: z.ZodOptional<z.ZodString>;
|
|
10
10
|
toolIdentifier: z.ZodDefault<z.ZodString>;
|
|
11
|
-
adminEmail: z.
|
|
11
|
+
adminEmail: z.ZodOptional<z.ZodEmail>;
|
|
12
12
|
requestDelayMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
13
13
|
maxConcurrent: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
14
14
|
maxRetries: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
15
15
|
timeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
16
16
|
totalDeadlineMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
17
|
-
unpaywallEmail: z.
|
|
17
|
+
unpaywallEmail: z.ZodOptional<z.ZodEmail>;
|
|
18
18
|
unpaywallTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
19
19
|
europepmcEnabled: z.ZodDefault<z.ZodCodec<z.ZodString, z.ZodBoolean>>;
|
|
20
|
-
europepmcEmail: z.
|
|
20
|
+
europepmcEmail: z.ZodOptional<z.ZodEmail>;
|
|
21
21
|
europepmcRequestDelayMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
22
22
|
europepmcMaxRetries: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
23
23
|
europepmcTimeoutMs: z.ZodDefault<z.ZodCoercedNumber<unknown>>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"server-config.d.ts","sourceRoot":"","sources":["../../src/config/server-config.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,wBAAwB,CAAC;AAG3C,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;iBA8DtB,CAAC;AAEH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAI9D,wBAAgB,eAAe,IAAI,YAAY,CA+B9C"}
|