@cyanheads/pubmed-mcp-server 1.1.4 → 1.2.2

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 (47) hide show
  1. package/README.md +10 -12
  2. package/dist/mcp-server/server.d.ts +0 -7
  3. package/dist/mcp-server/server.js +17 -53
  4. package/dist/mcp-server/tools/fetchPubMedContent/logic.d.ts +6 -2
  5. package/dist/mcp-server/tools/fetchPubMedContent/logic.js +94 -202
  6. package/dist/mcp-server/tools/fetchPubMedContent/registration.js +43 -7
  7. package/dist/mcp-server/tools/generatePubMedChart/logic.d.ts +9 -28
  8. package/dist/mcp-server/tools/generatePubMedChart/logic.js +133 -192
  9. package/dist/mcp-server/tools/generatePubMedChart/registration.js +57 -13
  10. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.d.ts +1 -1
  11. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.d.ts +1 -1
  12. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.d.ts +27 -4
  13. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.js +50 -45
  14. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/types.d.ts +1 -1
  15. package/dist/mcp-server/tools/getPubMedArticleConnections/registration.d.ts +0 -24
  16. package/dist/mcp-server/tools/getPubMedArticleConnections/registration.js +51 -45
  17. package/dist/mcp-server/tools/pubmedResearchAgent/logic.d.ts +2 -4
  18. package/dist/mcp-server/tools/pubmedResearchAgent/logic.js +7 -41
  19. package/dist/mcp-server/tools/pubmedResearchAgent/registration.js +49 -8
  20. package/dist/mcp-server/tools/searchPubMedArticles/logic.d.ts +12 -10
  21. package/dist/mcp-server/tools/searchPubMedArticles/logic.js +68 -126
  22. package/dist/mcp-server/tools/searchPubMedArticles/registration.js +46 -6
  23. package/dist/mcp-server/transports/{authentication → auth/core}/authContext.d.ts +2 -2
  24. package/dist/mcp-server/transports/{authentication → auth/core}/authContext.js +1 -1
  25. package/dist/mcp-server/transports/{authentication/types.d.ts → auth/core/authTypes.d.ts} +1 -1
  26. package/dist/mcp-server/transports/{authentication/types.js → auth/core/authTypes.js} +1 -1
  27. package/dist/mcp-server/transports/{authentication → auth/core}/authUtils.d.ts +1 -1
  28. package/dist/mcp-server/transports/{authentication → auth/core}/authUtils.js +3 -3
  29. package/dist/mcp-server/transports/auth/index.d.ts +10 -0
  30. package/dist/mcp-server/transports/auth/index.js +9 -0
  31. package/dist/mcp-server/transports/{authentication/authMiddleware.d.ts → auth/strategies/jwt/jwtMiddleware.d.ts} +4 -12
  32. package/dist/mcp-server/transports/{authentication/authMiddleware.js → auth/strategies/jwt/jwtMiddleware.js} +36 -43
  33. package/dist/mcp-server/transports/{authentication → auth/strategies/oauth}/oauthMiddleware.d.ts +2 -6
  34. package/dist/mcp-server/transports/{authentication → auth/strategies/oauth}/oauthMiddleware.js +33 -18
  35. package/dist/mcp-server/transports/httpErrorHandler.d.ts +26 -0
  36. package/dist/mcp-server/transports/httpErrorHandler.js +73 -0
  37. package/dist/mcp-server/transports/httpTransport.d.ts +11 -14
  38. package/dist/mcp-server/transports/httpTransport.js +91 -389
  39. package/package.json +13 -21
  40. package/dist/mcp-server/resources/echoResource/echoResourceLogic.d.ts +0 -79
  41. package/dist/mcp-server/resources/echoResource/echoResourceLogic.js +0 -82
  42. package/dist/mcp-server/resources/echoResource/index.d.ts +0 -13
  43. package/dist/mcp-server/resources/echoResource/index.js +0 -13
  44. package/dist/mcp-server/resources/echoResource/registration.d.ts +0 -30
  45. package/dist/mcp-server/resources/echoResource/registration.js +0 -168
  46. package/dist/mcp-server/tools/getPubMedArticleConnections/logic.d.ts +0 -6
  47. package/dist/mcp-server/tools/getPubMedArticleConnections/logic.js +0 -6
@@ -5,14 +5,14 @@
5
5
  * On success, it populates an AuthInfo object and stores it in an AsyncLocalStorage
6
6
  * context for use in downstream handlers.
7
7
  *
8
- * @module src/mcp-server/transports/authentication/oauthMiddleware
8
+ * @module src/mcp-server/transports/auth/strategies/oauth/oauthMiddleware
9
9
  */
10
10
  import { createRemoteJWKSet, jwtVerify } from "jose";
11
- import { config } from "../../../config/index.js";
12
- import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
13
- import { ErrorHandler } from "../../../utils/internal/errorHandler.js";
14
- import { logger, requestContextService } from "../../../utils/index.js";
15
- import { authContext } from "./authContext.js";
11
+ import { config } from "../../../../../config/index.js";
12
+ import { BaseErrorCode, McpError } from "../../../../../types-global/errors.js";
13
+ import { logger, requestContextService } from "../../../../../utils/index.js";
14
+ import { ErrorHandler } from "../../../../../utils/internal/errorHandler.js";
15
+ import { authContext } from "../../core/authContext.js";
16
16
  // --- Startup Validation ---
17
17
  // Ensures that necessary OAuth configuration is present when the mode is 'oauth'.
18
18
  if (config.mcpAuthMode === "oauth") {
@@ -57,6 +57,10 @@ if (config.mcpAuthMode === "oauth" && config.oauthIssuerUrl) {
57
57
  * @param next - The function to call to proceed to the next middleware.
58
58
  */
59
59
  export async function oauthMiddleware(c, next) {
60
+ // If OAuth is not the configured auth mode, skip this middleware.
61
+ if (config.mcpAuthMode !== "oauth") {
62
+ return await next();
63
+ }
60
64
  const context = requestContextService.createRequestContext({
61
65
  operation: "oauthMiddleware",
62
66
  httpMethod: c.req.method,
@@ -64,13 +68,12 @@ export async function oauthMiddleware(c, next) {
64
68
  });
65
69
  if (!jwks) {
66
70
  // This should not happen if startup validation is correct, but it's a safeguard.
67
- const error = new McpError(BaseErrorCode.CONFIGURATION_ERROR, "OAuth middleware is active, but JWKS client is not initialized.", context);
68
- ErrorHandler.handleError(error, { operation: "oauthMiddleware", context });
69
- return c.json({ error: "Server configuration error." }, 500);
71
+ // This should not happen if startup validation is correct, but it's a safeguard.
72
+ throw new McpError(BaseErrorCode.CONFIGURATION_ERROR, "OAuth middleware is active, but JWKS client is not initialized.", context);
70
73
  }
71
74
  const authHeader = c.req.header("Authorization");
72
75
  if (!authHeader || !authHeader.startsWith("Bearer ")) {
73
- return c.json({ error: "Unauthorized: Missing or invalid token format." }, 401);
76
+ throw new McpError(BaseErrorCode.UNAUTHORIZED, "Missing or invalid token format.");
74
77
  }
75
78
  const token = authHeader.substring(7);
76
79
  try {
@@ -80,10 +83,14 @@ export async function oauthMiddleware(c, next) {
80
83
  });
81
84
  // The 'scope' claim is typically a space-delimited string in OAuth 2.1.
82
85
  const scopes = typeof payload.scope === "string" ? payload.scope.split(" ") : [];
86
+ if (scopes.length === 0) {
87
+ logger.warning("Authentication failed: Token contains no scopes, but scopes are required.", { ...context, jwtPayloadKeys: Object.keys(payload) });
88
+ throw new McpError(BaseErrorCode.UNAUTHORIZED, "Token must contain valid, non-empty scopes.");
89
+ }
83
90
  const clientId = typeof payload.client_id === "string" ? payload.client_id : undefined;
84
91
  if (!clientId) {
85
92
  logger.warning("Authentication failed: OAuth token 'client_id' claim is missing or not a string.", { ...context, jwtPayloadKeys: Object.keys(payload) });
86
- return c.json({ error: "Unauthorized: Invalid token, missing client identifier." }, 401);
93
+ throw new McpError(BaseErrorCode.UNAUTHORIZED, "Invalid token, missing client identifier.");
87
94
  }
88
95
  const authInfo = {
89
96
  token,
@@ -97,13 +104,21 @@ export async function oauthMiddleware(c, next) {
97
104
  await authContext.run({ authInfo }, next);
98
105
  }
99
106
  catch (error) {
100
- logger.warning("OAuth token validation failed", {
101
- ...context,
102
- errorName: error.name,
103
- errorMessage: error.message,
107
+ if (error instanceof Error && error.name === "JWTExpired") {
108
+ logger.warning("Authentication failed: OAuth token expired.", context);
109
+ throw new McpError(BaseErrorCode.UNAUTHORIZED, "Token expired.");
110
+ }
111
+ const handledError = ErrorHandler.handleError(error, {
112
+ operation: "oauthMiddleware",
113
+ context,
114
+ rethrow: false, // We will throw a new McpError below
104
115
  });
105
- // The `jose` library provides specific error codes like 'ERR_JWT_EXPIRED' or 'ERR_JWS_INVALID'
106
- const message = `Unauthorized: ${error.message || "Invalid token"}`;
107
- return c.json({ error: message }, 401);
116
+ // Ensure we always throw an McpError for consistency
117
+ if (handledError instanceof McpError) {
118
+ throw handledError;
119
+ }
120
+ else {
121
+ throw new McpError(BaseErrorCode.UNAUTHORIZED, `Unauthorized: ${handledError.message || "Invalid token"}`, { originalError: handledError.name });
122
+ }
108
123
  }
109
124
  }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * @fileoverview Centralized error handler for the Hono HTTP transport.
3
+ * This middleware intercepts errors that occur during request processing,
4
+ * standardizes them using the application's ErrorHandler utility, and
5
+ * formats them into a consistent JSON-RPC error response.
6
+ * @module src/mcp-server/transports/httpErrorHandler
7
+ */
8
+ import { Context } from "hono";
9
+ import { BaseErrorCode } from "../../types-global/errors.js";
10
+ /**
11
+ * A centralized error handling middleware for Hono.
12
+ * This function is registered with `app.onError()` and will catch any errors
13
+ * thrown from preceding middleware or route handlers.
14
+ *
15
+ * @param err - The error that was thrown.
16
+ * @param c - The Hono context object for the request.
17
+ * @returns A Response object containing the formatted JSON-RPC error.
18
+ */
19
+ export declare const httpErrorHandler: (err: Error, c: Context) => Promise<Response & import("hono").TypedResponse<{
20
+ jsonrpc: string;
21
+ error: {
22
+ code: number | BaseErrorCode;
23
+ message: string;
24
+ };
25
+ id: string | number | null;
26
+ }, import("hono/utils/http-status").ContentfulStatusCode, "json">>;
@@ -0,0 +1,73 @@
1
+ /**
2
+ * @fileoverview Centralized error handler for the Hono HTTP transport.
3
+ * This middleware intercepts errors that occur during request processing,
4
+ * standardizes them using the application's ErrorHandler utility, and
5
+ * formats them into a consistent JSON-RPC error response.
6
+ * @module src/mcp-server/transports/httpErrorHandler
7
+ */
8
+ import { BaseErrorCode, McpError } from "../../types-global/errors.js";
9
+ import { ErrorHandler, requestContextService } from "../../utils/index.js";
10
+ /**
11
+ * A centralized error handling middleware for Hono.
12
+ * This function is registered with `app.onError()` and will catch any errors
13
+ * thrown from preceding middleware or route handlers.
14
+ *
15
+ * @param err - The error that was thrown.
16
+ * @param c - The Hono context object for the request.
17
+ * @returns A Response object containing the formatted JSON-RPC error.
18
+ */
19
+ export const httpErrorHandler = async (err, c) => {
20
+ const context = requestContextService.createRequestContext({
21
+ operation: "httpErrorHandler",
22
+ path: c.req.path,
23
+ method: c.req.method,
24
+ });
25
+ const handledError = ErrorHandler.handleError(err, {
26
+ operation: "httpTransport",
27
+ context,
28
+ });
29
+ let status = 500;
30
+ if (handledError instanceof McpError) {
31
+ switch (handledError.code) {
32
+ case BaseErrorCode.NOT_FOUND:
33
+ status = 404;
34
+ break;
35
+ case BaseErrorCode.UNAUTHORIZED:
36
+ status = 401;
37
+ break;
38
+ case BaseErrorCode.FORBIDDEN:
39
+ status = 403;
40
+ break;
41
+ case BaseErrorCode.VALIDATION_ERROR:
42
+ status = 400;
43
+ break;
44
+ case BaseErrorCode.CONFLICT:
45
+ status = 409;
46
+ break;
47
+ case BaseErrorCode.RATE_LIMITED:
48
+ status = 429;
49
+ break;
50
+ default:
51
+ status = 500;
52
+ }
53
+ }
54
+ // Attempt to get the request ID from the body, but don't fail if it's not there or unreadable.
55
+ let requestId = null;
56
+ try {
57
+ const body = await c.req.json();
58
+ requestId = body?.id || null;
59
+ }
60
+ catch {
61
+ // Ignore parsing errors, requestId will remain null
62
+ }
63
+ const errorCode = handledError instanceof McpError ? handledError.code : -32603;
64
+ c.status(status);
65
+ return c.json({
66
+ jsonrpc: "2.0",
67
+ error: {
68
+ code: errorCode,
69
+ message: handledError.message,
70
+ },
71
+ id: requestId,
72
+ });
73
+ };
@@ -1,10 +1,15 @@
1
1
  /**
2
- * @fileoverview Handles the setup and management of the Streamable HTTP MCP transport using Hono.
3
- * Implements the MCP Specification 2025-03-26 for Streamable HTTP.
4
- * This includes creating a Hono server, configuring middleware (CORS, Authentication),
5
- * defining request routing for the single MCP endpoint (POST/GET/DELETE),
6
- * managing server-side sessions, handling Server-Sent Events (SSE) for streaming,
7
- * and binding to a network port with retry logic for port conflicts.
2
+ * @fileoverview Configures and starts the Streamable HTTP MCP transport using Hono.
3
+ * This module integrates the `@modelcontextprotocol/sdk`'s `StreamableHTTPServerTransport`
4
+ * into a Hono web server. Its responsibilities include:
5
+ * - Creating a Hono server instance.
6
+ * - Applying and configuring middleware for CORS, rate limiting, and authentication (JWT/OAuth).
7
+ * - Defining the routes (`/mcp` endpoint for POST, GET, DELETE) to handle the MCP lifecycle.
8
+ * - Orchestrating session management by mapping session IDs to SDK transport instances.
9
+ * - Implementing port-binding logic with automatic retry on conflicts.
10
+ *
11
+ * The underlying implementation of the MCP Streamable HTTP specification, including
12
+ * Server-Sent Events (SSE) for streaming, is handled by the SDK's transport class.
8
13
  *
9
14
  * Specification Reference:
10
15
  * https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/transports.mdx#streamable-http
@@ -13,12 +18,4 @@
13
18
  import { ServerType } from "@hono/node-server";
14
19
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
15
20
  import { RequestContext } from "../../utils/index.js";
16
- /**
17
- * Sets up and starts the Streamable HTTP transport layer for the MCP server.
18
- *
19
- * @param createServerInstanceFn - An asynchronous factory function that returns a new `McpServer` instance.
20
- * @param parentContext - Logging context from the main server startup process.
21
- * @returns A promise that resolves with the Node.js `http.Server` instance when the HTTP server is successfully listening.
22
- * @throws {Error} If the server fails to start after all port retries.
23
- */
24
21
  export declare function startHttpTransport(createServerInstanceFn: () => Promise<McpServer>, parentContext: RequestContext): Promise<ServerType>;