@djmima/pubmed-mcp 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +803 -2
  4. package/dist/auth.d.ts +39 -0
  5. package/dist/auth.d.ts.map +1 -0
  6. package/dist/auth.js +97 -0
  7. package/dist/auth.js.map +1 -0
  8. package/dist/cli/commands.d.ts +42 -0
  9. package/dist/cli/commands.d.ts.map +1 -0
  10. package/dist/cli/commands.js +508 -0
  11. package/dist/cli/commands.js.map +1 -0
  12. package/dist/cli/run.d.ts +21 -0
  13. package/dist/cli/run.d.ts.map +1 -0
  14. package/dist/cli/run.js +154 -0
  15. package/dist/cli/run.js.map +1 -0
  16. package/dist/cli.d.ts +16 -0
  17. package/dist/cli.d.ts.map +1 -0
  18. package/dist/cli.js +52 -0
  19. package/dist/cli.js.map +1 -0
  20. package/dist/index.d.ts +32 -0
  21. package/dist/index.d.ts.map +1 -0
  22. package/dist/index.js +126 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/pubmed/client.d.ts +150 -0
  25. package/dist/pubmed/client.d.ts.map +1 -0
  26. package/dist/pubmed/client.js +319 -0
  27. package/dist/pubmed/client.js.map +1 -0
  28. package/dist/pubmed/types.d.ts +159 -0
  29. package/dist/pubmed/types.d.ts.map +1 -0
  30. package/dist/pubmed/types.js +11 -0
  31. package/dist/pubmed/types.js.map +1 -0
  32. package/dist/server.d.ts +18 -0
  33. package/dist/server.d.ts.map +1 -0
  34. package/dist/server.js +157 -0
  35. package/dist/server.js.map +1 -0
  36. package/dist/tools/convertArticleIds.d.ts +25 -0
  37. package/dist/tools/convertArticleIds.d.ts.map +1 -0
  38. package/dist/tools/convertArticleIds.js +39 -0
  39. package/dist/tools/convertArticleIds.js.map +1 -0
  40. package/dist/tools/findRelatedArticles.d.ts +30 -0
  41. package/dist/tools/findRelatedArticles.d.ts.map +1 -0
  42. package/dist/tools/findRelatedArticles.js +77 -0
  43. package/dist/tools/findRelatedArticles.js.map +1 -0
  44. package/dist/tools/getArticleMetadata.d.ts +31 -0
  45. package/dist/tools/getArticleMetadata.d.ts.map +1 -0
  46. package/dist/tools/getArticleMetadata.js +72 -0
  47. package/dist/tools/getArticleMetadata.js.map +1 -0
  48. package/dist/tools/getCopyrightStatus.d.ts +25 -0
  49. package/dist/tools/getCopyrightStatus.d.ts.map +1 -0
  50. package/dist/tools/getCopyrightStatus.js +84 -0
  51. package/dist/tools/getCopyrightStatus.js.map +1 -0
  52. package/dist/tools/getFullTextArticle.d.ts +21 -0
  53. package/dist/tools/getFullTextArticle.d.ts.map +1 -0
  54. package/dist/tools/getFullTextArticle.js +89 -0
  55. package/dist/tools/getFullTextArticle.js.map +1 -0
  56. package/dist/tools/lookupArticleByCitation.d.ts +33 -0
  57. package/dist/tools/lookupArticleByCitation.d.ts.map +1 -0
  58. package/dist/tools/lookupArticleByCitation.js +96 -0
  59. package/dist/tools/lookupArticleByCitation.js.map +1 -0
  60. package/dist/tools/searchArticles.d.ts +49 -0
  61. package/dist/tools/searchArticles.d.ts.map +1 -0
  62. package/dist/tools/searchArticles.js +121 -0
  63. package/dist/tools/searchArticles.js.map +1 -0
  64. package/dist/utils/xml.d.ts +37 -0
  65. package/dist/utils/xml.d.ts.map +1 -0
  66. package/dist/utils/xml.js +516 -0
  67. package/dist/utils/xml.js.map +1 -0
  68. package/package.json +73 -4
  69. 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
@@ -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"}