@djmima/pubmed-mcp 0.0.0-stage → 1.0.0
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/LICENSE +21 -0
- package/README.md +793 -3
- package/dist/auth.d.ts +39 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +97 -0
- package/dist/auth.js.map +1 -0
- package/dist/cli/commands.d.ts +42 -0
- package/dist/cli/commands.d.ts.map +1 -0
- package/dist/cli/commands.js +508 -0
- package/dist/cli/commands.js.map +1 -0
- package/dist/cli/run.d.ts +21 -0
- package/dist/cli/run.d.ts.map +1 -0
- package/dist/cli/run.js +154 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +52 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +32 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +126 -0
- package/dist/index.js.map +1 -0
- package/dist/pubmed/client.d.ts +150 -0
- package/dist/pubmed/client.d.ts.map +1 -0
- package/dist/pubmed/client.js +315 -0
- package/dist/pubmed/client.js.map +1 -0
- package/dist/pubmed/types.d.ts +159 -0
- package/dist/pubmed/types.d.ts.map +1 -0
- package/dist/pubmed/types.js +11 -0
- package/dist/pubmed/types.js.map +1 -0
- package/dist/server.d.ts +18 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +157 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/convertArticleIds.d.ts +25 -0
- package/dist/tools/convertArticleIds.d.ts.map +1 -0
- package/dist/tools/convertArticleIds.js +39 -0
- package/dist/tools/convertArticleIds.js.map +1 -0
- package/dist/tools/findRelatedArticles.d.ts +30 -0
- package/dist/tools/findRelatedArticles.d.ts.map +1 -0
- package/dist/tools/findRelatedArticles.js +77 -0
- package/dist/tools/findRelatedArticles.js.map +1 -0
- package/dist/tools/getArticleMetadata.d.ts +31 -0
- package/dist/tools/getArticleMetadata.d.ts.map +1 -0
- package/dist/tools/getArticleMetadata.js +72 -0
- package/dist/tools/getArticleMetadata.js.map +1 -0
- package/dist/tools/getCopyrightStatus.d.ts +25 -0
- package/dist/tools/getCopyrightStatus.d.ts.map +1 -0
- package/dist/tools/getCopyrightStatus.js +84 -0
- package/dist/tools/getCopyrightStatus.js.map +1 -0
- package/dist/tools/getFullTextArticle.d.ts +21 -0
- package/dist/tools/getFullTextArticle.d.ts.map +1 -0
- package/dist/tools/getFullTextArticle.js +89 -0
- package/dist/tools/getFullTextArticle.js.map +1 -0
- package/dist/tools/lookupArticleByCitation.d.ts +33 -0
- package/dist/tools/lookupArticleByCitation.d.ts.map +1 -0
- package/dist/tools/lookupArticleByCitation.js +96 -0
- package/dist/tools/lookupArticleByCitation.js.map +1 -0
- package/dist/tools/searchArticles.d.ts +49 -0
- package/dist/tools/searchArticles.d.ts.map +1 -0
- package/dist/tools/searchArticles.js +121 -0
- package/dist/tools/searchArticles.js.map +1 -0
- package/dist/utils/xml.d.ts +37 -0
- package/dist/utils/xml.d.ts.map +1 -0
- package/dist/utils/xml.js +515 -0
- package/dist/utils/xml.js.map +1 -0
- package/package.json +72 -4
- package/skills/pubmed-cli/SKILL.md +79 -0
package/dist/cli/run.js
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Argument parsing, dispatch, and output for the pubmed CLI.
|
|
3
|
+
*
|
|
4
|
+
* Kept apart from the entrypoint (src/cli.ts) so tests can run commands with
|
|
5
|
+
* in-memory I/O and a mocked NCBI client.
|
|
6
|
+
*
|
|
7
|
+
* Output contract: the command's JSON goes to stdout (or to the -o file) and
|
|
8
|
+
* nothing else does; messages go to stderr. Exit status is 0 on success, 1
|
|
9
|
+
* when NCBI or a file operation fails, and 2 for invalid usage.
|
|
10
|
+
*/
|
|
11
|
+
import { parseArgs } from 'node:util';
|
|
12
|
+
import { NcbiClient } from '../pubmed/client.js';
|
|
13
|
+
import { COMMANDS, UsageError, errorMessage, } from './commands.js';
|
|
14
|
+
const COMMON_OPTIONS = {
|
|
15
|
+
output: { type: 'string', short: 'o' },
|
|
16
|
+
help: { type: 'boolean', short: 'h' },
|
|
17
|
+
};
|
|
18
|
+
/** Run one CLI invocation and return its exit status. */
|
|
19
|
+
export async function runCli(argv, io, client) {
|
|
20
|
+
const [name, ...args] = argv;
|
|
21
|
+
if (name === undefined) {
|
|
22
|
+
io.stderr(globalHelp());
|
|
23
|
+
return 2;
|
|
24
|
+
}
|
|
25
|
+
if (name === '-h' || name === '--help') {
|
|
26
|
+
io.stdout(globalHelp());
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
if (name === 'help') {
|
|
30
|
+
if (args[0] === undefined) {
|
|
31
|
+
io.stdout(globalHelp());
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
const target = findCommand(args[0]);
|
|
35
|
+
if (!target)
|
|
36
|
+
return unknownCommand(io, args[0]);
|
|
37
|
+
io.stdout(commandHelp(target));
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
if (name.startsWith('-')) {
|
|
41
|
+
io.stderr(`pubmed: options go after the command, e.g. pubmed search <query> ${name} ...\n` +
|
|
42
|
+
'Run "pubmed --help" to list the commands.\n');
|
|
43
|
+
return 2;
|
|
44
|
+
}
|
|
45
|
+
const command = findCommand(name);
|
|
46
|
+
if (!command)
|
|
47
|
+
return unknownCommand(io, name);
|
|
48
|
+
let values;
|
|
49
|
+
let positionals;
|
|
50
|
+
try {
|
|
51
|
+
({ values, positionals } = parseArgs({
|
|
52
|
+
args,
|
|
53
|
+
options: { ...COMMON_OPTIONS, ...command.options },
|
|
54
|
+
allowPositionals: true,
|
|
55
|
+
strict: true,
|
|
56
|
+
}));
|
|
57
|
+
}
|
|
58
|
+
catch (err) {
|
|
59
|
+
// parseArgs appends advice about "--" that does not apply here.
|
|
60
|
+
return usageFailure(io, command, errorMessage(err).split(/\.\s|\n/)[0]);
|
|
61
|
+
}
|
|
62
|
+
if (values.help) {
|
|
63
|
+
io.stdout(commandHelp(command));
|
|
64
|
+
return 0;
|
|
65
|
+
}
|
|
66
|
+
const { output: outFile, help: _help, ...options } = values;
|
|
67
|
+
let stdin;
|
|
68
|
+
const input = {
|
|
69
|
+
values: options,
|
|
70
|
+
positionals,
|
|
71
|
+
// Several "-" arguments share one read of stdin.
|
|
72
|
+
readText: (path) => (path === '-' ? (stdin ??= io.readText('-')) : io.readText(path)),
|
|
73
|
+
};
|
|
74
|
+
try {
|
|
75
|
+
const { output, summary } = await command.execute(input, client ?? new NcbiClient());
|
|
76
|
+
const json = `${JSON.stringify(output)}\n`;
|
|
77
|
+
if (typeof outFile === 'string' && outFile !== '-') {
|
|
78
|
+
await io.writeText(outFile, json);
|
|
79
|
+
// The size tells an agent whether the file is safe to read whole.
|
|
80
|
+
io.stderr(`pubmed ${command.name}: ${summary}; wrote ${formatBytes(Buffer.byteLength(json))} to ${outFile}\n`);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
io.stdout(json);
|
|
84
|
+
}
|
|
85
|
+
return 0;
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
if (err instanceof UsageError)
|
|
89
|
+
return usageFailure(io, command, err.message);
|
|
90
|
+
io.stderr(`pubmed ${command.name}: ${errorMessage(err)}\n`);
|
|
91
|
+
return 1;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
// ─── Help and messages ───────────────────────────────────────────────────────
|
|
95
|
+
function findCommand(name) {
|
|
96
|
+
return COMMANDS.find((c) => c.name === name);
|
|
97
|
+
}
|
|
98
|
+
function unknownCommand(io, name) {
|
|
99
|
+
io.stderr(`pubmed: unknown command ${JSON.stringify(name)}\nRun "pubmed --help" to list the commands.\n`);
|
|
100
|
+
return 2;
|
|
101
|
+
}
|
|
102
|
+
function usageFailure(io, command, message) {
|
|
103
|
+
io.stderr(`pubmed ${command.name}: ${message}\nRun "pubmed ${command.name} --help" for usage.\n`);
|
|
104
|
+
return 2;
|
|
105
|
+
}
|
|
106
|
+
const COMMON_OPTIONS_HELP = `
|
|
107
|
+
Common options:
|
|
108
|
+
-o, --output <file> Write the JSON to <file> instead of stdout ("-" = stdout)
|
|
109
|
+
-h, --help Show this help
|
|
110
|
+
`;
|
|
111
|
+
function commandHelp(command) {
|
|
112
|
+
return command.help + COMMON_OPTIONS_HELP;
|
|
113
|
+
}
|
|
114
|
+
function globalHelp() {
|
|
115
|
+
const nameWidth = Math.max(...COMMANDS.map((c) => c.name.length));
|
|
116
|
+
const descriptionWidth = Math.max(...COMMANDS.map((c) => c.description.length));
|
|
117
|
+
const commandList = COMMANDS.map((c) => ` ${c.name.padEnd(nameWidth)} ${c.description.padEnd(descriptionWidth)} (${c.tool})`).join('\n');
|
|
118
|
+
return `Usage: pubmed <command> [options] [arguments]
|
|
119
|
+
|
|
120
|
+
Query PubMed and PubMed Central through NCBI E-utilities. Each command runs
|
|
121
|
+
the matching PubMed MCP tool and prints the same JSON, so results can be
|
|
122
|
+
saved to a file and filtered with jq instead of being read in full.
|
|
123
|
+
|
|
124
|
+
Commands:
|
|
125
|
+
${commandList}
|
|
126
|
+
|
|
127
|
+
Options:
|
|
128
|
+
-o, --output <file> Write the JSON to <file> instead of stdout
|
|
129
|
+
-h, --help Show help; "pubmed <command> --help" for a command
|
|
130
|
+
|
|
131
|
+
IDs are given as arguments separated by spaces or commas, or read from stdin
|
|
132
|
+
with "-". Long lists are split into batches and sent one at a time.
|
|
133
|
+
|
|
134
|
+
Environment:
|
|
135
|
+
NCBI_API_KEY NCBI API key; raises the rate limit from 3 to 8 requests/s.
|
|
136
|
+
In a source checkout, also read from its .env file.
|
|
137
|
+
|
|
138
|
+
Exit status: 0 on success, 1 when NCBI or a file operation fails, 2 on
|
|
139
|
+
invalid usage.
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
pubmed search "TP53 AND myelodysplastic syndromes[mh]" --max-results 100 -o search.json
|
|
143
|
+
jq -r '.pmids[]' search.json | pubmed metadata - -o meta.json
|
|
144
|
+
jq -c '.articles[] | {pmid: .identifiers.pmid, title}' meta.json
|
|
145
|
+
`;
|
|
146
|
+
}
|
|
147
|
+
function formatBytes(bytes) {
|
|
148
|
+
if (bytes < 1024)
|
|
149
|
+
return `${bytes} bytes`;
|
|
150
|
+
if (bytes < 1024 * 1024)
|
|
151
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
152
|
+
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
|
|
153
|
+
}
|
|
154
|
+
//# sourceMappingURL=run.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/cli/run.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PubMed CLI — entrypoint.
|
|
4
|
+
*
|
|
5
|
+
* Runs the same tool functions as the MCP server and prints their JSON to
|
|
6
|
+
* stdout, or to a file with -o, so large results can be saved and filtered
|
|
7
|
+
* with jq instead of being loaded into an LLM's context. See `pubmed --help`.
|
|
8
|
+
*
|
|
9
|
+
* Environment variables:
|
|
10
|
+
* NCBI_API_KEY — NCBI API key; raises rate limit from 3 → 8 req/s.
|
|
11
|
+
* Falls back to the .env file in the package root, so in a
|
|
12
|
+
* source checkout the key is found whichever directory the
|
|
13
|
+
* command runs in.
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PubMed CLI — entrypoint.
|
|
4
|
+
*
|
|
5
|
+
* Runs the same tool functions as the MCP server and prints their JSON to
|
|
6
|
+
* stdout, or to a file with -o, so large results can be saved and filtered
|
|
7
|
+
* with jq instead of being loaded into an LLM's context. See `pubmed --help`.
|
|
8
|
+
*
|
|
9
|
+
* Environment variables:
|
|
10
|
+
* NCBI_API_KEY — NCBI API key; raises rate limit from 3 → 8 req/s.
|
|
11
|
+
* Falls back to the .env file in the package root, so in a
|
|
12
|
+
* source checkout the key is found whichever directory the
|
|
13
|
+
* command runs in.
|
|
14
|
+
*/
|
|
15
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
16
|
+
import path from 'node:path';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { config as loadDotenv } from 'dotenv';
|
|
19
|
+
import { runCli } from './cli/run.js';
|
|
20
|
+
// quiet: dotenv >=17 logs to stdout by default, which would corrupt the JSON
|
|
21
|
+
// output. Variables already set in the environment take precedence.
|
|
22
|
+
loadDotenv({
|
|
23
|
+
path: path.join(path.dirname(fileURLToPath(import.meta.url)), '..', '.env'),
|
|
24
|
+
quiet: true,
|
|
25
|
+
});
|
|
26
|
+
// A reader that stops early (e.g. `pubmed ... | head`) is not an error.
|
|
27
|
+
process.stdout.on('error', (err) => {
|
|
28
|
+
if (err.code !== 'EPIPE')
|
|
29
|
+
process.stderr.write(`pubmed: ${err.message}\n`);
|
|
30
|
+
process.exit(err.code === 'EPIPE' ? 0 : 1);
|
|
31
|
+
});
|
|
32
|
+
async function readStdin() {
|
|
33
|
+
const chunks = [];
|
|
34
|
+
for await (const chunk of process.stdin) {
|
|
35
|
+
chunks.push(chunk);
|
|
36
|
+
}
|
|
37
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
38
|
+
}
|
|
39
|
+
runCli(process.argv.slice(2), {
|
|
40
|
+
stdout: (text) => process.stdout.write(text),
|
|
41
|
+
stderr: (text) => process.stderr.write(text),
|
|
42
|
+
readText: (file) => (file === '-' ? readStdin() : readFile(file, 'utf8')),
|
|
43
|
+
writeText: (file, text) => writeFile(file, text, 'utf8'),
|
|
44
|
+
}).then(
|
|
45
|
+
// Set exitCode rather than calling process.exit() so stdout can drain.
|
|
46
|
+
(code) => {
|
|
47
|
+
process.exitCode = code;
|
|
48
|
+
}, (err) => {
|
|
49
|
+
process.stderr.write(`pubmed: ${String(err)}\n`);
|
|
50
|
+
process.exitCode = 1;
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,MAAM,IAAI,UAAU,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAC;AAEtC,6EAA6E;AAC7E,oEAAoE;AACpE,UAAU,CAAC;IACT,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC;IAC3E,KAAK,EAAE,IAAI;CACZ,CAAC,CAAC;AAEH,wEAAwE;AACxE,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAA0B,EAAE,EAAE;IACxD,IAAI,GAAG,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;IAC3E,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AAC7C,CAAC,CAAC,CAAC;AAEH,KAAK,UAAU,SAAS;IACtB,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,IAAI,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QACxC,MAAM,CAAC,IAAI,CAAC,KAAe,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAChD,CAAC;AAED,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;IAC5B,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;IAC5C,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC;IAC5C,QAAQ,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,KAAK,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACzE,SAAS,EAAE,CAAC,IAAI,EAAE,IAAI,EAAE,EAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC;CACzD,CAAC,CAAC,IAAI;AACL,uEAAuE;AACvE,CAAC,IAAI,EAAE,EAAE;IACP,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC;AAC1B,CAAC,EACD,CAAC,GAAG,EAAE,EAAE;IACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,WAAW,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACjD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC,CACF,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PubMed MCP Server — entrypoint.
|
|
4
|
+
*
|
|
5
|
+
* Transport is selected at startup based on environment:
|
|
6
|
+
*
|
|
7
|
+
* STDIO mode (default):
|
|
8
|
+
* Used by local MCP clients that launch the server as a subprocess
|
|
9
|
+
* (Claude Desktop, Claude Code, Codex).
|
|
10
|
+
* Start with: node dist/index.js
|
|
11
|
+
*
|
|
12
|
+
* HTTP mode (Cloud Run / remote deployment):
|
|
13
|
+
* Activated when the PORT environment variable is set.
|
|
14
|
+
* Starts an Express server that handles Streamable HTTP transport at /mcp.
|
|
15
|
+
* Used by remote clients: Claude custom connectors and the Anthropic API
|
|
16
|
+
* MCP connector, which can only reach servers over HTTP.
|
|
17
|
+
* /mcp requires an OAuth 2.0 Bearer token (see auth.ts); /health and
|
|
18
|
+
* /.well-known/oauth-protected-resource are public.
|
|
19
|
+
* Cloud Run injects PORT=8080 automatically.
|
|
20
|
+
* Start with: PORT=8080 node dist/index.js (plus the OAuth variables)
|
|
21
|
+
*
|
|
22
|
+
* Environment variables:
|
|
23
|
+
* NCBI_API_KEY — NCBI API key; raises rate limit from 3 → 8 req/s
|
|
24
|
+
* Register free at https://www.ncbi.nlm.nih.gov/account/
|
|
25
|
+
* PORT — When set, enables HTTP mode on the specified port.
|
|
26
|
+
* OAUTH_ISSUER, OAUTH_JWKS_URL, MCP_RESOURCE_URL
|
|
27
|
+
* — Required in HTTP mode; startup fails without them.
|
|
28
|
+
* ALLOWED_SUBJECTS — Optional allowlist of token subjects (HTTP mode).
|
|
29
|
+
* See auth.ts for all four.
|
|
30
|
+
*/
|
|
31
|
+
export {};
|
|
32
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PubMed MCP Server — entrypoint.
|
|
4
|
+
*
|
|
5
|
+
* Transport is selected at startup based on environment:
|
|
6
|
+
*
|
|
7
|
+
* STDIO mode (default):
|
|
8
|
+
* Used by local MCP clients that launch the server as a subprocess
|
|
9
|
+
* (Claude Desktop, Claude Code, Codex).
|
|
10
|
+
* Start with: node dist/index.js
|
|
11
|
+
*
|
|
12
|
+
* HTTP mode (Cloud Run / remote deployment):
|
|
13
|
+
* Activated when the PORT environment variable is set.
|
|
14
|
+
* Starts an Express server that handles Streamable HTTP transport at /mcp.
|
|
15
|
+
* Used by remote clients: Claude custom connectors and the Anthropic API
|
|
16
|
+
* MCP connector, which can only reach servers over HTTP.
|
|
17
|
+
* /mcp requires an OAuth 2.0 Bearer token (see auth.ts); /health and
|
|
18
|
+
* /.well-known/oauth-protected-resource are public.
|
|
19
|
+
* Cloud Run injects PORT=8080 automatically.
|
|
20
|
+
* Start with: PORT=8080 node dist/index.js (plus the OAuth variables)
|
|
21
|
+
*
|
|
22
|
+
* Environment variables:
|
|
23
|
+
* NCBI_API_KEY — NCBI API key; raises rate limit from 3 → 8 req/s
|
|
24
|
+
* Register free at https://www.ncbi.nlm.nih.gov/account/
|
|
25
|
+
* PORT — When set, enables HTTP mode on the specified port.
|
|
26
|
+
* OAUTH_ISSUER, OAUTH_JWKS_URL, MCP_RESOURCE_URL
|
|
27
|
+
* — Required in HTTP mode; startup fails without them.
|
|
28
|
+
* ALLOWED_SUBJECTS — Optional allowlist of token subjects (HTTP mode).
|
|
29
|
+
* See auth.ts for all four.
|
|
30
|
+
*/
|
|
31
|
+
// Load .env file if present. Silently skipped in production where env vars
|
|
32
|
+
// are injected by the runtime (Cloud Run, Docker --env, etc.).
|
|
33
|
+
// quiet: dotenv >=17 logs to stdout by default, which corrupts the stdio
|
|
34
|
+
// JSON-RPC stream.
|
|
35
|
+
import { config as loadDotenv } from 'dotenv';
|
|
36
|
+
loadDotenv({ quiet: true });
|
|
37
|
+
import { createServer } from './server.js';
|
|
38
|
+
import { NcbiClient } from './pubmed/client.js';
|
|
39
|
+
async function main() {
|
|
40
|
+
const port = process.env.PORT ? parseInt(process.env.PORT, 10) : undefined;
|
|
41
|
+
if (port !== undefined) {
|
|
42
|
+
await startHttpServer(port);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
await startStdioServer();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// ── HTTP mode (Cloud Run) ──────────────────────────────────────────────────
|
|
49
|
+
// One NCBI client for the whole process. HTTP mode creates an MCP server per
|
|
50
|
+
// request, and passing them all this client makes its rate limiter span
|
|
51
|
+
// concurrent requests instead of restarting with each one. Must be created
|
|
52
|
+
// after loadDotenv() so that it reads NCBI_API_KEY from .env.
|
|
53
|
+
const sharedNcbiClient = new NcbiClient();
|
|
54
|
+
async function startHttpServer(port) {
|
|
55
|
+
const { default: express } = await import('express');
|
|
56
|
+
const { StreamableHTTPServerTransport } = await import('@modelcontextprotocol/sdk/server/streamableHttp.js');
|
|
57
|
+
const { protectedResourceMetadata, requireAuth } = await import('./auth.js');
|
|
58
|
+
const app = express();
|
|
59
|
+
app.use(express.json());
|
|
60
|
+
// OAuth 2.0 Protected Resource Metadata (RFC 9728) — public so the client can
|
|
61
|
+
// discover the authorization server. No token required.
|
|
62
|
+
app.get('/.well-known/oauth-protected-resource', protectedResourceMetadata);
|
|
63
|
+
// Return JSON (not HTML) for malformed request bodies.
|
|
64
|
+
app.use((err, _req, res, next) => {
|
|
65
|
+
if (err.type === 'entity.parse.failed') {
|
|
66
|
+
res.status(400).json({ error: 'Invalid JSON body' });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
next(err);
|
|
70
|
+
});
|
|
71
|
+
// MCP Streamable HTTP — handles both POST (client→server) and GET (SSE stream).
|
|
72
|
+
// SDK v1.x: stateless transports cannot be reused across requests.
|
|
73
|
+
// A new transport and server instance must be created for every request.
|
|
74
|
+
app.all('/mcp', requireAuth, (req, res) => {
|
|
75
|
+
const transport = new StreamableHTTPServerTransport({
|
|
76
|
+
sessionIdGenerator: undefined, // stateless mode
|
|
77
|
+
});
|
|
78
|
+
const mcpServer = createServer(sharedNcbiClient);
|
|
79
|
+
mcpServer
|
|
80
|
+
.connect(transport)
|
|
81
|
+
.then(() => transport.handleRequest(req, res, req.body))
|
|
82
|
+
.catch((err) => {
|
|
83
|
+
process.stderr.write(`[http] handleRequest error: ${String(err)}\n`);
|
|
84
|
+
if (!res.headersSent) {
|
|
85
|
+
res.status(500).json({ error: 'Internal server error' });
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
// Health check endpoint — used by Cloud Run readiness/liveness probes.
|
|
90
|
+
// No auth required so Cloud Run can always reach it.
|
|
91
|
+
app.get('/health', (_req, res) => {
|
|
92
|
+
res.send('ok');
|
|
93
|
+
});
|
|
94
|
+
const httpServer = app.listen(port, () => {
|
|
95
|
+
process.stderr.write(`PubMed MCP server running on HTTP port ${port}\n`);
|
|
96
|
+
process.stderr.write(` MCP endpoint : http://localhost:${port}/mcp\n`);
|
|
97
|
+
process.stderr.write(` Health check : http://localhost:${port}/health\n`);
|
|
98
|
+
process.stderr.write(' Auth : OAuth 2.0 (WorkOS AuthKit) — Bearer required on /mcp\n');
|
|
99
|
+
});
|
|
100
|
+
const shutdown = () => {
|
|
101
|
+
process.stderr.write('Shutting down PubMed MCP server\n');
|
|
102
|
+
httpServer.close(() => process.exit(0));
|
|
103
|
+
};
|
|
104
|
+
process.on('SIGINT', shutdown);
|
|
105
|
+
process.on('SIGTERM', shutdown);
|
|
106
|
+
}
|
|
107
|
+
// ── Stdio mode (local / Claude Desktop) ───────────────────────────────────
|
|
108
|
+
async function startStdioServer() {
|
|
109
|
+
const { StdioServerTransport } = await import('@modelcontextprotocol/sdk/server/stdio.js');
|
|
110
|
+
const server = createServer();
|
|
111
|
+
const transport = new StdioServerTransport();
|
|
112
|
+
await server.connect(transport);
|
|
113
|
+
process.stderr.write('PubMed MCP server running on stdio\n');
|
|
114
|
+
const shutdown = () => {
|
|
115
|
+
process.stderr.write('Shutting down PubMed MCP server\n');
|
|
116
|
+
process.exit(0);
|
|
117
|
+
};
|
|
118
|
+
process.on('SIGINT', shutdown);
|
|
119
|
+
process.on('SIGTERM', shutdown);
|
|
120
|
+
}
|
|
121
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
122
|
+
main().catch((err) => {
|
|
123
|
+
process.stderr.write(`Fatal error: ${String(err)}\n`);
|
|
124
|
+
process.exit(1);
|
|
125
|
+
});
|
|
126
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* NCBI E-utilities HTTP client.
|
|
3
|
+
* Handles rate limiting, API key injection, and request construction.
|
|
4
|
+
*
|
|
5
|
+
* Base URL: https://eutils.ncbi.nlm.nih.gov/entrez/eutils/
|
|
6
|
+
* Rate limits:
|
|
7
|
+
* - Without API key: 3 requests/sec
|
|
8
|
+
* - With API key: 10 requests/sec
|
|
9
|
+
* NCBI counts requests by arrival time, so we send at 8 req/s with a key to
|
|
10
|
+
* absorb network jitter, and retry briefly on HTTP 429.
|
|
11
|
+
*/
|
|
12
|
+
export declare class NcbiNetworkError extends Error {
|
|
13
|
+
readonly cause?: unknown | undefined;
|
|
14
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
15
|
+
}
|
|
16
|
+
export declare class NcbiApiError extends Error {
|
|
17
|
+
readonly statusCode?: number | undefined;
|
|
18
|
+
constructor(message: string, statusCode?: number | undefined);
|
|
19
|
+
}
|
|
20
|
+
export declare class NcbiParseError extends Error {
|
|
21
|
+
readonly cause?: unknown | undefined;
|
|
22
|
+
constructor(message: string, cause?: unknown | undefined);
|
|
23
|
+
}
|
|
24
|
+
export declare class NcbiClient {
|
|
25
|
+
private readonly apiKey;
|
|
26
|
+
private readonly rateLimiter;
|
|
27
|
+
constructor(apiKey?: string);
|
|
28
|
+
private commonParams;
|
|
29
|
+
/**
|
|
30
|
+
* Throttle, send, and retry on HTTP 429. Every NCBI call goes through here
|
|
31
|
+
* so each attempt (including retries) takes its own rate-limiter slot.
|
|
32
|
+
*/
|
|
33
|
+
private request;
|
|
34
|
+
/**
|
|
35
|
+
* ESearch — search PubMed for article IDs.
|
|
36
|
+
* Returns the raw JSON response from NCBI.
|
|
37
|
+
*/
|
|
38
|
+
esearch(params: {
|
|
39
|
+
term: string;
|
|
40
|
+
retmax?: number;
|
|
41
|
+
retstart?: number;
|
|
42
|
+
sort?: string;
|
|
43
|
+
field?: string;
|
|
44
|
+
datetype?: string;
|
|
45
|
+
mindate?: string;
|
|
46
|
+
maxdate?: string;
|
|
47
|
+
}): Promise<EsearchResult>;
|
|
48
|
+
/**
|
|
49
|
+
* EFetch — retrieve full records from PubMed or PMC.
|
|
50
|
+
* Returns raw XML text.
|
|
51
|
+
*/
|
|
52
|
+
efetch(params: {
|
|
53
|
+
db: string;
|
|
54
|
+
id: string | string[];
|
|
55
|
+
retmode?: string;
|
|
56
|
+
rettype?: string;
|
|
57
|
+
}): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* ELink — find related articles or cross-database links.
|
|
60
|
+
* Accepts multiple IDs and returns the raw JSON response.
|
|
61
|
+
*/
|
|
62
|
+
elink(params: {
|
|
63
|
+
dbfrom: string;
|
|
64
|
+
id: string[];
|
|
65
|
+
linkname: string;
|
|
66
|
+
cmd?: string;
|
|
67
|
+
}): Promise<ElinkResponse>;
|
|
68
|
+
/**
|
|
69
|
+
* ECitmatch — look up PubMed IDs from structured citation data.
|
|
70
|
+
* Returns raw text with pipe-delimited fields.
|
|
71
|
+
*/
|
|
72
|
+
ecitmatch(citations: CitationInput[]): Promise<string>;
|
|
73
|
+
/**
|
|
74
|
+
* PMC ID Converter — convert between PMID, PMCID, and DOI.
|
|
75
|
+
*/
|
|
76
|
+
idConvert(params: {
|
|
77
|
+
ids: string[];
|
|
78
|
+
idtype?: string;
|
|
79
|
+
}): Promise<IdConvResponse>;
|
|
80
|
+
}
|
|
81
|
+
export interface EsearchResult {
|
|
82
|
+
count: string;
|
|
83
|
+
retmax: string;
|
|
84
|
+
retstart: string;
|
|
85
|
+
idlist: string[];
|
|
86
|
+
translationset: unknown[];
|
|
87
|
+
querytranslation: string;
|
|
88
|
+
errorlist?: {
|
|
89
|
+
phrasesnotfound?: string[];
|
|
90
|
+
fieldsnotfound?: string[];
|
|
91
|
+
};
|
|
92
|
+
warninglist?: {
|
|
93
|
+
phrasesignored?: string[];
|
|
94
|
+
quotedphrasesnotfound?: string[];
|
|
95
|
+
outputmessages?: string[];
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
export interface ElinkResponse {
|
|
99
|
+
header?: {
|
|
100
|
+
type: string;
|
|
101
|
+
version: string;
|
|
102
|
+
};
|
|
103
|
+
linksets: ElinkLinkset[];
|
|
104
|
+
}
|
|
105
|
+
export interface ElinkLinkset {
|
|
106
|
+
dbfrom: string;
|
|
107
|
+
ids: string[];
|
|
108
|
+
linksetdbs?: ElinkLinksetDb[];
|
|
109
|
+
idurllist?: unknown[];
|
|
110
|
+
}
|
|
111
|
+
export interface ElinkLinksetDb {
|
|
112
|
+
dbto: string;
|
|
113
|
+
linkname: string;
|
|
114
|
+
links: Array<{
|
|
115
|
+
id: string;
|
|
116
|
+
score?: string;
|
|
117
|
+
} | string>;
|
|
118
|
+
}
|
|
119
|
+
export interface CitationInput {
|
|
120
|
+
journal: string;
|
|
121
|
+
year: number;
|
|
122
|
+
volume: string;
|
|
123
|
+
first_page: string;
|
|
124
|
+
author: string;
|
|
125
|
+
key: string;
|
|
126
|
+
}
|
|
127
|
+
export interface IdConvResponse {
|
|
128
|
+
status: string;
|
|
129
|
+
'response-date': string;
|
|
130
|
+
request: {
|
|
131
|
+
warnings: string[];
|
|
132
|
+
format: string;
|
|
133
|
+
idtype: string;
|
|
134
|
+
ids: string[];
|
|
135
|
+
email: string;
|
|
136
|
+
tool: string;
|
|
137
|
+
echo: string;
|
|
138
|
+
versions: string;
|
|
139
|
+
showaiid: string;
|
|
140
|
+
};
|
|
141
|
+
records: Array<{
|
|
142
|
+
pmid?: string;
|
|
143
|
+
pmcid?: string;
|
|
144
|
+
doi?: string;
|
|
145
|
+
'requested-id': string;
|
|
146
|
+
errmsg?: string;
|
|
147
|
+
[key: string]: string | undefined;
|
|
148
|
+
}>;
|
|
149
|
+
}
|
|
150
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/pubmed/client.ts"],"names":[],"mappings":"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"}
|