@cyanheads/pubmed-mcp-server 1.0.14 → 1.1.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 (44) hide show
  1. package/README.md +183 -116
  2. package/dist/config/index.d.ts +8 -20
  3. package/dist/config/index.js +16 -45
  4. package/dist/index.js +77 -29
  5. package/dist/mcp-server/server.d.ts +3 -2
  6. package/dist/mcp-server/server.js +16 -14
  7. package/dist/mcp-server/tools/fetchPubMedContent/logic.js +2 -1
  8. package/dist/mcp-server/tools/generatePubMedChart/logic.d.ts +3 -3
  9. package/dist/mcp-server/tools/generatePubMedChart/logic.js +21 -10
  10. package/dist/mcp-server/tools/generatePubMedChart/registration.js +2 -2
  11. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.js +2 -1
  12. package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.js +2 -1
  13. package/dist/mcp-server/tools/pubmedResearchAgent/logic/inputSchema.js +1 -1
  14. package/dist/mcp-server/tools/pubmedResearchAgent/logic/planOrchestrator.js +21 -10
  15. package/dist/mcp-server/tools/searchPubMedArticles/logic.js +2 -1
  16. package/dist/mcp-server/transports/authentication/authContext.d.ts +33 -0
  17. package/dist/mcp-server/transports/authentication/authContext.js +24 -0
  18. package/dist/mcp-server/transports/authentication/authMiddleware.d.ts +21 -15
  19. package/dist/mcp-server/transports/authentication/authMiddleware.js +51 -69
  20. package/dist/mcp-server/transports/authentication/authUtils.d.ts +18 -0
  21. package/dist/mcp-server/transports/authentication/authUtils.js +45 -0
  22. package/dist/mcp-server/transports/authentication/oauthMiddleware.d.ts +24 -0
  23. package/dist/mcp-server/transports/authentication/oauthMiddleware.js +109 -0
  24. package/dist/mcp-server/transports/authentication/types.d.ts +17 -0
  25. package/dist/mcp-server/transports/authentication/types.js +5 -0
  26. package/dist/mcp-server/transports/httpTransport.d.ts +5 -4
  27. package/dist/mcp-server/transports/httpTransport.js +177 -143
  28. package/dist/services/NCBI/ncbiCoreApiClient.js +0 -5
  29. package/dist/services/NCBI/ncbiRequestQueueManager.js +2 -4
  30. package/dist/services/NCBI/ncbiResponseHandler.js +0 -3
  31. package/dist/services/NCBI/ncbiService.d.ts +1 -1
  32. package/dist/services/NCBI/ncbiService.js +11 -4
  33. package/dist/utils/internal/logger.js +10 -30
  34. package/package.json +27 -13
  35. package/dist/services/index.d.ts +0 -7
  36. package/dist/services/index.js +0 -7
  37. package/dist/services/llm-providers/index.d.ts +0 -7
  38. package/dist/services/llm-providers/index.js +0 -7
  39. package/dist/services/llm-providers/llmFactory.d.ts +0 -69
  40. package/dist/services/llm-providers/llmFactory.js +0 -132
  41. package/dist/services/llm-providers/openRouter/index.d.ts +0 -6
  42. package/dist/services/llm-providers/openRouter/index.js +0 -7
  43. package/dist/services/llm-providers/openRouter/openRouterProvider.d.ts +0 -99
  44. package/dist/services/llm-providers/openRouter/openRouterProvider.js +0 -329
@@ -13,6 +13,7 @@
13
13
  * - Transports: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/transports.mdx
14
14
  * @module src/mcp-server/server
15
15
  */
16
+ import { ServerType } from "@hono/node-server";
16
17
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
18
  /**
18
19
  * Main application entry point. Initializes and starts the MCP server.
@@ -22,7 +23,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
22
23
  * - Manages server startup, leading to a server ready for MCP messages.
23
24
  * - Handles critical startup failures, ensuring appropriate process exit.
24
25
  *
25
- * @returns For 'stdio', resolves with `McpServer`. For 'http', runs indefinitely.
26
+ * @returns For 'stdio', resolves with `McpServer`. For 'http', resolves with `http.Server`.
26
27
  * Rejects on critical failure, leading to process exit.
27
28
  */
28
- export declare function initializeAndStartServer(): Promise<void | McpServer>;
29
+ export declare function initializeAndStartServer(): Promise<void | McpServer | ServerType>;
@@ -14,14 +14,12 @@
14
14
  * @module src/mcp-server/server
15
15
  */
16
16
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
- import { config } from "../config/index.js";
17
+ import { config, environment } from "../config/index.js";
18
18
  import { ErrorHandler, logger, requestContextService } from "../utils/index.js";
19
- // import { registerEchoResource } from "./resources/echoResource/index.js"; // To be removed after resource implementations.
20
19
  import { registerFetchPubMedContentTool } from "./tools/fetchPubMedContent/index.js";
21
- // Removed: import { registerFetchImageTestTool } from "./tools/imageTest/index.js";
22
- import { registerGeneratePubMedChartTool } from "./tools/generatePubMedChart/index.js"; // Added import
20
+ import { registerGeneratePubMedChartTool } from "./tools/generatePubMedChart/index.js";
23
21
  import { registerGetPubMedArticleConnectionsTool } from "./tools/getPubMedArticleConnections/index.js";
24
- import { registerPubMedResearchAgentTool } from "./tools/pubmedResearchAgent/index.js"; // Added import
22
+ import { registerPubMedResearchAgentTool } from "./tools/pubmedResearchAgent/index.js";
25
23
  import { registerSearchPubMedArticlesTool } from "./tools/searchPubMedArticles/index.js";
26
24
  import { startHttpTransport } from "./transports/httpTransport.js";
27
25
  import { connectStdioTransport } from "./transports/stdioTransport.js";
@@ -47,6 +45,11 @@ async function createMcpServerInstance() {
47
45
  operation: "createMcpServerInstance",
48
46
  });
49
47
  logger.info("Initializing MCP server instance", context);
48
+ requestContextService.configure({
49
+ appName: config.mcpServerName,
50
+ appVersion: config.mcpServerVersion,
51
+ environment,
52
+ });
50
53
  logger.debug("Instantiating McpServer with capabilities", {
51
54
  ...context,
52
55
  serverInfo: {
@@ -70,10 +73,9 @@ async function createMcpServerInstance() {
70
73
  logger.debug("Registering resources and tools...", context);
71
74
  // IMPORTANT: Keep tool registrations in alphabetical order. Do not remove this comment.
72
75
  await registerFetchPubMedContentTool(server);
73
- // Removed: await registerFetchImageTestTool(server);
74
- await registerGeneratePubMedChartTool(server); // Added new tool registration
76
+ await registerGeneratePubMedChartTool(server);
75
77
  await registerGetPubMedArticleConnectionsTool(server);
76
- await registerPubMedResearchAgentTool(server); // Added new tool registration
78
+ await registerPubMedResearchAgentTool(server);
77
79
  await registerSearchPubMedArticlesTool(server);
78
80
  // Add other tool/resource registrations here
79
81
  logger.info("Resources and tools registered successfully", context);
@@ -96,7 +98,7 @@ async function createMcpServerInstance() {
96
98
  * - Transport Connection: Calls dedicated functions for chosen transport.
97
99
  * - Server Instance Lifecycle: Single instance for 'stdio', per-session for 'http'.
98
100
  *
99
- * @returns Resolves with `McpServer` for 'stdio', or `void` for 'http'.
101
+ * @returns Resolves with `McpServer` for 'stdio', `http.Server` for 'http', or `void` if http transport manages its own lifecycle without returning a server.
100
102
  * @throws {Error} If transport type is unsupported or setup fails.
101
103
  * @private
102
104
  */
@@ -109,16 +111,16 @@ async function startTransport() {
109
111
  logger.info(`Starting transport: ${transportType}`, context);
110
112
  if (transportType === "http") {
111
113
  logger.debug("Delegating to startHttpTransport...", context);
112
- // For HTTP, startHttpTransport manages its own lifecycle and server instances per session.
113
- await startHttpTransport(createMcpServerInstance, context);
114
- return; // HTTP server runs indefinitely, no single server instance returned here.
114
+ // For HTTP, startHttpTransport now returns the http.Server instance.
115
+ const httpServerInstance = await startHttpTransport(createMcpServerInstance, context);
116
+ return httpServerInstance;
115
117
  }
116
118
  if (transportType === "stdio") {
117
119
  logger.debug("Creating single McpServer instance for stdio transport...", context);
118
120
  const server = await createMcpServerInstance();
119
121
  logger.debug("Delegating to connectStdioTransport...", context);
120
122
  await connectStdioTransport(server, context);
121
- return server; // Return the single server instance for stdio.
123
+ return server; // Return the single McpServer instance for stdio.
122
124
  }
123
125
  // Should not be reached if config validation is effective.
124
126
  logger.fatal(`Unsupported transport type configured: ${transportType}`, context);
@@ -132,7 +134,7 @@ async function startTransport() {
132
134
  * - Manages server startup, leading to a server ready for MCP messages.
133
135
  * - Handles critical startup failures, ensuring appropriate process exit.
134
136
  *
135
- * @returns For 'stdio', resolves with `McpServer`. For 'http', runs indefinitely.
137
+ * @returns For 'stdio', resolves with `McpServer`. For 'http', resolves with `http.Server`.
136
138
  * Rejects on critical failure, leading to process exit.
137
139
  */
138
140
  export async function initializeAndStartServer() {
@@ -6,7 +6,7 @@
6
6
  * @module src/mcp-server/tools/fetchPubMedContent/logic
7
7
  */
8
8
  import { z } from "zod";
9
- import { ncbiService } from "../../../services/NCBI/ncbiService.js";
9
+ import { getNcbiService } from "../../../services/NCBI/ncbiService.js";
10
10
  import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
11
11
  import { logger, requestContextService, sanitizeInputForLogging, } from "../../../utils/index.js";
12
12
  import { ensureArray, extractAbstractText, extractArticleDates, extractAuthors, extractDoi, extractGrants, extractJournalInfo, extractKeywords, extractMeshTerms, extractPmid, extractPublicationTypes, getText, } from "../../../utils/parsing/ncbi-parsing/index.js";
@@ -292,6 +292,7 @@ export async function fetchPubMedContentLogic(input, parentRequestContext) {
292
292
  isError: true,
293
293
  };
294
294
  }
295
+ const ncbiService = getNcbiService();
295
296
  const toolLogicContext = requestContextService.createRequestContext({
296
297
  parentRequestId: parentRequestContext.requestId,
297
298
  operation: "fetchPubMedContentLogic",
@@ -12,7 +12,7 @@ export declare const GeneratePubMedChartInputSchema: z.ZodObject<{
12
12
  width: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
13
13
  height: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
14
14
  dataValues: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodAny>, "many">;
15
- outputFormat: z.ZodDefault<z.ZodEnum<["svg"]>>;
15
+ outputFormat: z.ZodDefault<z.ZodEnum<["png"]>>;
16
16
  xField: z.ZodString;
17
17
  yField: z.ZodString;
18
18
  xFieldType: z.ZodOptional<z.ZodEnum<["nominal", "ordinal", "quantitative", "temporal"]>>;
@@ -26,7 +26,7 @@ export declare const GeneratePubMedChartInputSchema: z.ZodObject<{
26
26
  }, "strip", z.ZodTypeAny, {
27
27
  width: number;
28
28
  height: number;
29
- outputFormat: "svg";
29
+ outputFormat: "png";
30
30
  chartType: "bar" | "line" | "scatter";
31
31
  dataValues: Record<string, any>[];
32
32
  xField: string;
@@ -48,7 +48,7 @@ export declare const GeneratePubMedChartInputSchema: z.ZodObject<{
48
48
  title?: string | undefined;
49
49
  width?: number | undefined;
50
50
  height?: number | undefined;
51
- outputFormat?: "svg" | undefined;
51
+ outputFormat?: "png" | undefined;
52
52
  xFieldType?: "nominal" | "ordinal" | "quantitative" | "temporal" | undefined;
53
53
  yFieldType?: "nominal" | "ordinal" | "quantitative" | "temporal" | undefined;
54
54
  colorField?: string | undefined;
@@ -30,9 +30,9 @@ export const GeneratePubMedChartInputSchema = z.object({
30
30
  .min(1)
31
31
  .describe("Required. An array of data objects used to plot the chart. Each object represents a data point or bar, structured as key-value pairs (e.g., [{ 'year': '2020', 'articles': 150 }, { 'year': '2021', 'articles': 180 }]). Must contain at least one data object."),
32
32
  outputFormat: z
33
- .enum(["svg"])
34
- .default("svg")
35
- .describe("Specifies the output format for the chart. Currently, only 'svg' (Scalable Vector Graphics) is supported and is the default."),
33
+ .enum(["png"]) // Changed from svg to png
34
+ .default("png") // Changed default to png
35
+ .describe("Specifies the output format for the chart. Currently, only 'png' (Portable Network Graphics) is supported and is the default."),
36
36
  xField: z
37
37
  .string()
38
38
  .describe("Required. The name of the field in `dataValues` to be used for the X-axis (horizontal). This field determines the categories or values along the bottom of the chart (e.g., 'year', 'geneName', 'publicationCount')."),
@@ -83,8 +83,9 @@ export async function generatePubMedChartLogic(input, parentRequestContext) {
83
83
  input: sanitizeInputForLogging(input),
84
84
  });
85
85
  logger.info(`Executing 'generate_pubmed_chart'. Chart type: ${input.chartType}, Output format: ${input.outputFormat}`, operationContext);
86
- if (input.outputFormat !== "svg") {
87
- const unsupportedFormatError = new McpError(BaseErrorCode.VALIDATION_ERROR, `Unsupported output format: ${input.outputFormat}. Currently, only 'svg' is supported.`, { requestedFormat: input.outputFormat });
86
+ if (input.outputFormat !== "png") { // Changed from svg to png
87
+ const unsupportedFormatError = new McpError(BaseErrorCode.VALIDATION_ERROR, `Unsupported output format: ${input.outputFormat}. Currently, only 'png' is supported.`, // Changed message
88
+ { requestedFormat: input.outputFormat });
88
89
  logger.warning(unsupportedFormatError.message, operationContext);
89
90
  return {
90
91
  content: [
@@ -203,14 +204,24 @@ export async function generatePubMedChartLogic(input, parentRequestContext) {
203
204
  }
204
205
  const compiledVegaSpec = vegaLite.compile(vegaLiteSpec).spec;
205
206
  const view = new vega.View(vega.parse(compiledVegaSpec), {
206
- renderer: "none",
207
+ renderer: "canvas", // Explicitly set renderer to 'canvas'
207
208
  });
208
- const svgString = await view.toSVG();
209
- const imageBuffer = Buffer.from(svgString, "utf-8");
209
+ // const svgString = await view.toSVG(); // Old SVG method
210
+ // New PNG method
211
+ // Initialize the view to ensure canvas is ready
212
+ await view.runAsync(); // Initialize and run the view
213
+ const canvas = await view.toCanvas(); // Render to canvas
214
+ // Cast to 'any' to access toBuffer, assuming it's a Node Canvas instance at runtime
215
+ const imageBuffer = await canvas.toBuffer("image/png"); // Get PNG buffer from canvas
210
216
  const base64Data = imageBuffer.toString("base64");
211
- const dataUriSvg = `data:image/svg+xml;base64,${base64Data}`;
212
217
  return {
213
- content: [{ type: "text", text: dataUriSvg }],
218
+ content: [
219
+ {
220
+ type: "image",
221
+ data: base64Data,
222
+ mimeType: "image/png", // Changed MIME type to image/png
223
+ },
224
+ ],
214
225
  isError: false,
215
226
  };
216
227
  }
@@ -5,11 +5,11 @@ export function registerGeneratePubMedChartTool(server) {
5
5
  const operation = "registerGeneratePubMedChartTool";
6
6
  const regContext = requestContextService.createRequestContext({ operation });
7
7
  try {
8
- server.tool("generate_pubmed_chart", "Generates a customizable chart (SVG) from structured data. " +
8
+ server.tool("generate_pubmed_chart", "Generates a customizable chart (PNG) from structured data. " +
9
9
  "Supports 'bar', 'line', and 'scatter' plots. " +
10
10
  "Requires data values and field mappings for axes. " +
11
11
  "Optional parameters allow for titles, dimensions, and color/size/series encoding. " +
12
- "Internally uses Vega-Lite to produce an SVG image.", GeneratePubMedChartInputSchema.shape, async (validatedInput, mcpProvidedContext) => {
12
+ "Internally uses Vega-Lite and a canvas renderer to produce a Base64-encoded PNG image.", GeneratePubMedChartInputSchema.shape, async (validatedInput, mcpProvidedContext) => {
13
13
  const handlerRequestContext = requestContextService.createRequestContext({
14
14
  parentRequestId: regContext.requestId,
15
15
  operation: "generatePubMedChartToolHandler",
@@ -3,7 +3,7 @@
3
3
  * Fetches article details using EFetch and formats them into various citation styles.
4
4
  * @module src/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter
5
5
  */
6
- import { ncbiService } from "../../../../services/NCBI/ncbiService.js";
6
+ import { getNcbiService } from "../../../../services/NCBI/ncbiService.js";
7
7
  import { logger, requestContextService, } from "../../../../utils/index.js";
8
8
  import { extractAuthors, extractDoi, extractJournalInfo, extractPmid, getText, } from "../../../../utils/parsing/ncbi-parsing/index.js";
9
9
  // Main handler for citation formats
@@ -17,6 +17,7 @@ export async function handleCitationFormats(input, outputData, context) {
17
17
  const eFetchBaseUrl = "https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi";
18
18
  const searchParamsString = new URLSearchParams(eFetchParams).toString();
19
19
  outputData.eUtilityUrl = `${eFetchBaseUrl}?${searchParamsString}`;
20
+ const ncbiService = getNcbiService();
20
21
  const eFetchResult = await ncbiService.eFetch(eFetchParams, context);
21
22
  if (!eFetchResult?.PubmedArticleSet?.PubmedArticle?.[0]) {
22
23
  outputData.message =
@@ -3,7 +3,7 @@
3
3
  * for the getPubMedArticleConnections tool.
4
4
  * @module src/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler
5
5
  */
6
- import { ncbiService } from "../../../../services/NCBI/ncbiService.js";
6
+ import { getNcbiService } from "../../../../services/NCBI/ncbiService.js";
7
7
  import { logger } from "../../../../utils/index.js";
8
8
  import { extractBriefSummaries } from "../../../../utils/parsing/ncbi-parsing/index.js";
9
9
  import { ensureArray } from "../../../../utils/parsing/ncbi-parsing/xmlGenericHelpers.js"; // Added import
@@ -33,6 +33,7 @@ export async function handleELinkRelationships(input, outputData, context) {
33
33
  const tempUrl = new URL("https://dummy.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi");
34
34
  Object.keys(eLinkParams).forEach((key) => tempUrl.searchParams.append(key, String(eLinkParams[key])));
35
35
  outputData.eUtilityUrl = `https://eutils.ncbi.nlm.nih.gov/entrez/eutils/elink.fcgi?${tempUrl.search.substring(1)}`;
36
+ const ncbiService = getNcbiService();
36
37
  const eLinkResult = await ncbiService.eLink(eLinkParams, context);
37
38
  // Log the full eLinkResult for debugging
38
39
  logger.debug("Raw eLinkResult from ncbiService:", {
@@ -141,7 +141,7 @@ export const PubMedResearchAgentInputSchema = z.object({
141
141
  .optional()
142
142
  .describe("Ethical considerations, IRB/IACUC approval plans, data privacy, RCR training."),
143
143
  // Meta-parameter from previous iterations, still useful
144
- include_detailed_prompts_for_agent: z // Renamed from include_edge_cases_and_challenges_in_plan
144
+ include_detailed_prompts_for_agent: z
145
145
  .boolean()
146
146
  .optional()
147
147
  .default(false) // Default to false, meaning the tool primarily structures the detailed input.
@@ -27,39 +27,50 @@ function G(notes, includePrompts) {
27
27
  function allPropertiesUndefined(obj) {
28
28
  return Object.values(obj).every((value) => value === undefined);
29
29
  }
30
- // Helper function to recursively remove keys with empty object values
30
+ // Helper function to recursively remove keys with empty object or empty array values
31
31
  function removeEmptyObjectsRecursively(obj) {
32
32
  // Base cases for recursion
33
33
  if (typeof obj !== "object" || obj === null) {
34
34
  return obj; // Not an object or array, return as is
35
35
  }
36
36
  if (Array.isArray(obj)) {
37
- // If it's an array, recurse on each element
38
- // And filter out any elements that become empty objects after recursion
39
- return obj.map(removeEmptyObjectsRecursively).filter((item) => {
37
+ // If it's an array, recurse on each element and filter out empty objects/arrays
38
+ const newArr = obj
39
+ .map(removeEmptyObjectsRecursively)
40
+ .filter((item) => {
41
+ if (item === null || item === undefined)
42
+ return false;
43
+ if (Array.isArray(item) && item.length === 0)
44
+ return false; // Filter out empty arrays
40
45
  if (typeof item === "object" &&
41
- item !== null &&
42
46
  !Array.isArray(item) &&
43
47
  Object.keys(item).length === 0) {
44
- return false; // Filter out empty objects from arrays
48
+ return false; // Filter out empty objects
45
49
  }
46
50
  return true;
47
51
  });
52
+ return newArr;
48
53
  }
49
54
  // If it's an object, create a new object with non-empty properties
50
55
  const newObj = {};
51
56
  for (const key in obj) {
52
57
  if (Object.prototype.hasOwnProperty.call(obj, key)) {
53
58
  const value = removeEmptyObjectsRecursively(obj[key]);
54
- // Check if the recursed value is an empty object
59
+ // Skip null or undefined values
60
+ if (value === null || value === undefined) {
61
+ continue;
62
+ }
63
+ // Skip empty arrays
64
+ if (Array.isArray(value) && value.length === 0) {
65
+ continue;
66
+ }
67
+ // Skip empty objects
55
68
  if (typeof value === "object" &&
56
- value !== null &&
57
69
  !Array.isArray(value) &&
58
70
  Object.keys(value).length === 0) {
59
- // It's an empty object, so we don't add this key-value pair to newObj
60
71
  continue;
61
72
  }
62
- // If value is not an empty object (or not an object at all), add it
73
+ // If value is not empty, add it
63
74
  newObj[key] = value;
64
75
  }
65
76
  }
@@ -5,7 +5,7 @@
5
5
  * @module src/mcp-server/tools/searchPubMedArticles/logic
6
6
  */
7
7
  import { z } from "zod";
8
- import { ncbiService } from "../../../services/NCBI/ncbiService.js";
8
+ import { getNcbiService } from "../../../services/NCBI/ncbiService.js";
9
9
  import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
10
10
  import { logger, requestContextService, sanitizeInputForLogging, } from "../../../utils/index.js";
11
11
  import { extractBriefSummaries } from "../../../utils/parsing/ncbi-parsing/index.js";
@@ -70,6 +70,7 @@ export const SearchPubMedArticlesInputSchema = z.object({
70
70
  * @returns A promise resolving to a CallToolResult.
71
71
  */
72
72
  export async function searchPubMedArticlesLogic(input, parentRequestContext) {
73
+ const ncbiService = getNcbiService();
73
74
  const toolLogicContext = requestContextService.createRequestContext({
74
75
  parentRequestId: parentRequestContext.requestId,
75
76
  operation: "searchPubMedArticlesLogic",
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @fileoverview Defines the AsyncLocalStorage context for authentication information.
3
+ * This module provides a mechanism to store and retrieve authentication details
4
+ * (like scopes and client ID) across asynchronous operations, making it available
5
+ * from the middleware layer down to the tool and resource handlers without
6
+ * drilling props.
7
+ *
8
+ * @module src/mcp-server/transports/authentication/authContext
9
+ */
10
+ import { AsyncLocalStorage } from "async_hooks";
11
+ import type { AuthInfo } from "./types.js";
12
+ /**
13
+ * Defines the structure of the store used within the AsyncLocalStorage.
14
+ * It holds the authentication information for the current request context.
15
+ */
16
+ interface AuthStore {
17
+ authInfo: AuthInfo;
18
+ }
19
+ /**
20
+ * An instance of AsyncLocalStorage to hold the authentication context (`AuthStore`).
21
+ * This allows `authInfo` to be accessible throughout the async call chain of a request
22
+ * after being set in the authentication middleware.
23
+ *
24
+ * @example
25
+ * // In middleware:
26
+ * await authContext.run({ authInfo }, next);
27
+ *
28
+ * // In a deeper handler:
29
+ * const store = authContext.getStore();
30
+ * const scopes = store?.authInfo.scopes;
31
+ */
32
+ export declare const authContext: AsyncLocalStorage<AuthStore>;
33
+ export {};
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @fileoverview Defines the AsyncLocalStorage context for authentication information.
3
+ * This module provides a mechanism to store and retrieve authentication details
4
+ * (like scopes and client ID) across asynchronous operations, making it available
5
+ * from the middleware layer down to the tool and resource handlers without
6
+ * drilling props.
7
+ *
8
+ * @module src/mcp-server/transports/authentication/authContext
9
+ */
10
+ import { AsyncLocalStorage } from "async_hooks";
11
+ /**
12
+ * An instance of AsyncLocalStorage to hold the authentication context (`AuthStore`).
13
+ * This allows `authInfo` to be accessible throughout the async call chain of a request
14
+ * after being set in the authentication middleware.
15
+ *
16
+ * @example
17
+ * // In middleware:
18
+ * await authContext.run({ authInfo }, next);
19
+ *
20
+ * // In a deeper handler:
21
+ * const store = authContext.getStore();
22
+ * const scopes = store?.authInfo.scopes;
23
+ */
24
+ export const authContext = new AsyncLocalStorage();
@@ -1,5 +1,5 @@
1
1
  /**
2
- * @fileoverview MCP Authentication Middleware for Bearer Token Validation (JWT).
2
+ * @fileoverview MCP Authentication Middleware for Bearer Token Validation (JWT) for Hono.
3
3
  *
4
4
  * This middleware validates JSON Web Tokens (JWT) passed via the 'Authorization' header
5
5
  * using the 'Bearer' scheme (e.g., "Authorization: Bearer <your_token>").
@@ -7,23 +7,29 @@
7
7
  * in the configuration (`config.mcpAuthSecretKey`).
8
8
  *
9
9
  * If the token is valid, an object conforming to the MCP SDK's `AuthInfo` type
10
- * (expected to contain `token`, `clientId`, and `scopes`) is attached to `req.auth`.
11
- * If the token is missing, invalid, or expired, it sends an HTTP 401 Unauthorized response.
10
+ * is attached to `c.env.incoming.auth`. This direct attachment to the raw Node.js
11
+ * request object is for compatibility with the underlying SDK transport, which is
12
+ * not Hono-context-aware.
13
+ * If the token is missing, invalid, or expired, it returns an HTTP 401 Unauthorized response.
12
14
  *
13
15
  * @see {@link https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/authorization.mdx | MCP Authorization Specification}
14
16
  * @module src/mcp-server/transports/authentication/authMiddleware
15
17
  */
16
- import { NextFunction, Request, Response } from "express";
17
- import { AuthInfo } from "@modelcontextprotocol/sdk/server/auth/types.js";
18
- declare global {
19
- namespace Express {
20
- interface Request {
21
- /** Authentication information derived from the JWT, conforming to MCP SDK's AuthInfo. */
22
- auth?: AuthInfo;
23
- }
24
- }
25
- }
18
+ import { HttpBindings } from "@hono/node-server";
19
+ import { Context, Next } from "hono";
26
20
  /**
27
- * Express middleware for verifying JWT Bearer token authentication.
21
+ * Validates the presence of the MCP_AUTH_SECRET_KEY at startup.
22
+ * This should be called once when the application is initializing.
28
23
  */
29
- export declare function mcpAuthMiddleware(req: Request, res: Response, next: NextFunction): void;
24
+ export declare function initializeAuthMiddleware(): void;
25
+ /**
26
+ * Hono middleware for verifying JWT Bearer token authentication.
27
+ * It attaches authentication info to `c.env.incoming.auth` for SDK compatibility with the node server.
28
+ */
29
+ export declare function mcpAuthMiddleware(c: Context<{
30
+ Bindings: HttpBindings;
31
+ }>, next: Next): Promise<void | (Response & import("hono").TypedResponse<{
32
+ error: string;
33
+ }, 500, "json">) | (Response & import("hono").TypedResponse<{
34
+ error: string;
35
+ }, 401, "json">)>;