@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/auth.d.ts
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 resource-server enforcement for HTTP mode.
|
|
3
|
+
*
|
|
4
|
+
* The MCP server acts as an OAuth 2.0 *resource server* (RFC 9728 /
|
|
5
|
+
* MCP authorization spec 2025-06-18). A managed authorization server
|
|
6
|
+
* (WorkOS AuthKit) runs Dynamic Client Registration (so claude.ai can register
|
|
7
|
+
* itself) and issues signed JWT access tokens. This module:
|
|
8
|
+
*
|
|
9
|
+
* 1. Advertises the authorization server via Protected Resource Metadata at
|
|
10
|
+
* /.well-known/oauth-protected-resource
|
|
11
|
+
* 2. Validates the Bearer JWT on every /mcp request against the provider's
|
|
12
|
+
* JWKS (signature, issuer, audience, expiry).
|
|
13
|
+
* 3. Restricts access to an allowlist of token subjects — since DCR lets
|
|
14
|
+
* anyone register a client, this is what makes the server *yours only*.
|
|
15
|
+
* With the allowlist empty, any valid token is accepted.
|
|
16
|
+
*
|
|
17
|
+
* Required environment variables (copy issuer/jwks from the provider's
|
|
18
|
+
* https://<your-authkit-domain>/.well-known/oauth-authorization-server ):
|
|
19
|
+
* OAUTH_ISSUER — `issuer` from that document
|
|
20
|
+
* OAUTH_JWKS_URL — `jwks_uri` from that document
|
|
21
|
+
* MCP_RESOURCE_URL — public URL of this server's MCP endpoint, exactly as
|
|
22
|
+
* clients connect to it (e.g.
|
|
23
|
+
* https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp);
|
|
24
|
+
* used as the expected token `aud` and as the resource
|
|
25
|
+
* identifier
|
|
26
|
+
*
|
|
27
|
+
* Optional:
|
|
28
|
+
* ALLOWED_SUBJECTS — comma-separated token `sub` values (user IDs) permitted
|
|
29
|
+
* to connect. When empty, nothing is logged, so to find
|
|
30
|
+
* your own id deploy with a placeholder value first:
|
|
31
|
+
* rejected subjects are logged as
|
|
32
|
+
* "[auth] forbidden subject: <sub>". Then set the real
|
|
33
|
+
* value with `gcloud run services update --update-env-vars`.
|
|
34
|
+
*/
|
|
35
|
+
import type { Request, Response, NextFunction } from 'express';
|
|
36
|
+
/** Tells the client which authorization server to use for this resource. */
|
|
37
|
+
export declare function protectedResourceMetadata(_req: Request, res: Response): void;
|
|
38
|
+
export declare function requireAuth(req: Request, res: Response, next: NextFunction): Promise<void>;
|
|
39
|
+
//# sourceMappingURL=auth.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAmB/D,4EAA4E;AAC5E,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,GAAG,IAAI,CAM5E;AAID,wBAAsB,WAAW,CAC/B,GAAG,EAAE,OAAO,EACZ,GAAG,EAAE,QAAQ,EACb,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
|
package/dist/auth.js
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OAuth 2.0 resource-server enforcement for HTTP mode.
|
|
3
|
+
*
|
|
4
|
+
* The MCP server acts as an OAuth 2.0 *resource server* (RFC 9728 /
|
|
5
|
+
* MCP authorization spec 2025-06-18). A managed authorization server
|
|
6
|
+
* (WorkOS AuthKit) runs Dynamic Client Registration (so claude.ai can register
|
|
7
|
+
* itself) and issues signed JWT access tokens. This module:
|
|
8
|
+
*
|
|
9
|
+
* 1. Advertises the authorization server via Protected Resource Metadata at
|
|
10
|
+
* /.well-known/oauth-protected-resource
|
|
11
|
+
* 2. Validates the Bearer JWT on every /mcp request against the provider's
|
|
12
|
+
* JWKS (signature, issuer, audience, expiry).
|
|
13
|
+
* 3. Restricts access to an allowlist of token subjects — since DCR lets
|
|
14
|
+
* anyone register a client, this is what makes the server *yours only*.
|
|
15
|
+
* With the allowlist empty, any valid token is accepted.
|
|
16
|
+
*
|
|
17
|
+
* Required environment variables (copy issuer/jwks from the provider's
|
|
18
|
+
* https://<your-authkit-domain>/.well-known/oauth-authorization-server ):
|
|
19
|
+
* OAUTH_ISSUER — `issuer` from that document
|
|
20
|
+
* OAUTH_JWKS_URL — `jwks_uri` from that document
|
|
21
|
+
* MCP_RESOURCE_URL — public URL of this server's MCP endpoint, exactly as
|
|
22
|
+
* clients connect to it (e.g.
|
|
23
|
+
* https://pubmed-mcp-PROJECT_NUMBER.asia-northeast1.run.app/mcp);
|
|
24
|
+
* used as the expected token `aud` and as the resource
|
|
25
|
+
* identifier
|
|
26
|
+
*
|
|
27
|
+
* Optional:
|
|
28
|
+
* ALLOWED_SUBJECTS — comma-separated token `sub` values (user IDs) permitted
|
|
29
|
+
* to connect. When empty, nothing is logged, so to find
|
|
30
|
+
* your own id deploy with a placeholder value first:
|
|
31
|
+
* rejected subjects are logged as
|
|
32
|
+
* "[auth] forbidden subject: <sub>". Then set the real
|
|
33
|
+
* value with `gcloud run services update --update-env-vars`.
|
|
34
|
+
*/
|
|
35
|
+
import { createRemoteJWKSet, jwtVerify } from 'jose';
|
|
36
|
+
const ISSUER = requireEnv('OAUTH_ISSUER');
|
|
37
|
+
const JWKS_URL = requireEnv('OAUTH_JWKS_URL');
|
|
38
|
+
const RESOURCE = requireEnv('MCP_RESOURCE_URL');
|
|
39
|
+
const ALLOWED_SUBJECTS = (process.env.ALLOWED_SUBJECTS ?? '')
|
|
40
|
+
.split(',')
|
|
41
|
+
.map((s) => s.trim())
|
|
42
|
+
.filter(Boolean);
|
|
43
|
+
// Metadata lives at the well-known path on the same origin as the resource.
|
|
44
|
+
const RESOURCE_METADATA_URL = `${originOf(RESOURCE)}/.well-known/oauth-protected-resource`;
|
|
45
|
+
const jwks = createRemoteJWKSet(new URL(JWKS_URL));
|
|
46
|
+
// ── Protected Resource Metadata (RFC 9728) ──────────────────────────────────
|
|
47
|
+
/** Tells the client which authorization server to use for this resource. */
|
|
48
|
+
export function protectedResourceMetadata(_req, res) {
|
|
49
|
+
res.json({
|
|
50
|
+
resource: RESOURCE,
|
|
51
|
+
authorization_servers: [ISSUER],
|
|
52
|
+
bearer_methods_supported: ['header'],
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
// ── Bearer token enforcement ────────────────────────────────────────────────
|
|
56
|
+
export async function requireAuth(req, res, next) {
|
|
57
|
+
const header = req.headers.authorization;
|
|
58
|
+
if (!header?.startsWith('Bearer ')) {
|
|
59
|
+
unauthorized(res, 'missing_token');
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
const token = header.slice('Bearer '.length).trim();
|
|
63
|
+
try {
|
|
64
|
+
const { payload } = await jwtVerify(token, jwks, {
|
|
65
|
+
issuer: ISSUER,
|
|
66
|
+
audience: RESOURCE,
|
|
67
|
+
});
|
|
68
|
+
if (ALLOWED_SUBJECTS.length > 0 && !ALLOWED_SUBJECTS.includes(payload.sub ?? '')) {
|
|
69
|
+
// Valid token, but not you. Log the subject so you can allowlist yourself.
|
|
70
|
+
process.stderr.write(`[auth] forbidden subject: ${payload.sub ?? '(none)'}\n`);
|
|
71
|
+
res.status(403).json({ error: 'forbidden' });
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
next();
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
process.stderr.write(`[auth] token rejected: ${String(err)}\n`);
|
|
78
|
+
unauthorized(res, 'invalid_token');
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
82
|
+
/** 401 with the RFC 9728 pointer so the client can discover how to authorize. */
|
|
83
|
+
function unauthorized(res, error) {
|
|
84
|
+
res.setHeader('WWW-Authenticate', `Bearer resource_metadata="${RESOURCE_METADATA_URL}", error="${error}"`);
|
|
85
|
+
res.status(401).json({ error });
|
|
86
|
+
}
|
|
87
|
+
function originOf(url) {
|
|
88
|
+
return new URL(url).origin;
|
|
89
|
+
}
|
|
90
|
+
function requireEnv(name) {
|
|
91
|
+
const value = process.env[name];
|
|
92
|
+
if (!value) {
|
|
93
|
+
throw new Error(`Missing required environment variable: ${name}`);
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=auth.js.map
|
package/dist/auth.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../src/auth.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Command table for the pubmed CLI.
|
|
3
|
+
*
|
|
4
|
+
* Each command runs one MCP tool: it turns command-line input into the tool's
|
|
5
|
+
* parameters, validates them with the tool's zod schema (as the MCP server
|
|
6
|
+
* does), and calls the same tool function, so the JSON output is the same.
|
|
7
|
+
* Lists longer than a tool's per-request limit are split into batches that
|
|
8
|
+
* run one after another through the shared client and its rate limiter; the
|
|
9
|
+
* batch results are merged into one response of the same shape.
|
|
10
|
+
*/
|
|
11
|
+
import type { NcbiClient } from '../pubmed/client.js';
|
|
12
|
+
/** Invalid command-line input. The CLI exits with status 2 instead of 1. */
|
|
13
|
+
export declare class UsageError extends Error {
|
|
14
|
+
constructor(message: string);
|
|
15
|
+
}
|
|
16
|
+
export interface CommandInput {
|
|
17
|
+
/** Option values by long name (e.g. "max-results"); unset options are undefined. */
|
|
18
|
+
values: Record<string, string | undefined>;
|
|
19
|
+
positionals: string[];
|
|
20
|
+
/** Read a file, or stdin when path is "-". */
|
|
21
|
+
readText(path: string): Promise<string>;
|
|
22
|
+
}
|
|
23
|
+
export interface CommandResult {
|
|
24
|
+
output: unknown;
|
|
25
|
+
/** Short description of the output, e.g. "20 articles". */
|
|
26
|
+
summary: string;
|
|
27
|
+
}
|
|
28
|
+
export interface Command {
|
|
29
|
+
name: string;
|
|
30
|
+
/** The MCP tool this command runs. */
|
|
31
|
+
tool: string;
|
|
32
|
+
/** One line for the command list in `pubmed --help`. */
|
|
33
|
+
description: string;
|
|
34
|
+
help: string;
|
|
35
|
+
options: Record<string, {
|
|
36
|
+
type: 'string';
|
|
37
|
+
}>;
|
|
38
|
+
execute(input: CommandInput, client: NcbiClient): Promise<CommandResult>;
|
|
39
|
+
}
|
|
40
|
+
export declare const COMMANDS: Command[];
|
|
41
|
+
export declare function errorMessage(err: unknown): string;
|
|
42
|
+
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/cli/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAGH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAgCtD,4EAA4E;AAC5E,qBAAa,UAAW,SAAQ,KAAK;gBACvB,OAAO,EAAE,MAAM;CAI5B;AAED,MAAM,WAAW,YAAY;IAC3B,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IAC3C,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,8CAA8C;IAC9C,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CACzC;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,OAAO,CAAC;IAChB,2DAA2D;IAC3D,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,IAAI,EAAE,QAAQ,CAAA;KAAE,CAAC,CAAC;IAC5C,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,MAAM,EAAE,UAAU,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;CAC1E;AAmbD,eAAO,MAAM,QAAQ,EAAE,OAAO,EAQ7B,CAAC;AAsGF,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAEjD"}
|