@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.
- package/README.md +183 -116
- package/dist/config/index.d.ts +8 -20
- package/dist/config/index.js +16 -45
- package/dist/index.js +77 -29
- package/dist/mcp-server/server.d.ts +3 -2
- package/dist/mcp-server/server.js +16 -14
- package/dist/mcp-server/tools/fetchPubMedContent/logic.js +2 -1
- package/dist/mcp-server/tools/generatePubMedChart/logic.d.ts +3 -3
- package/dist/mcp-server/tools/generatePubMedChart/logic.js +21 -10
- package/dist/mcp-server/tools/generatePubMedChart/registration.js +2 -2
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.js +2 -1
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.js +2 -1
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/inputSchema.js +1 -1
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/planOrchestrator.js +21 -10
- package/dist/mcp-server/tools/searchPubMedArticles/logic.js +2 -1
- package/dist/mcp-server/transports/authentication/authContext.d.ts +33 -0
- package/dist/mcp-server/transports/authentication/authContext.js +24 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.d.ts +21 -15
- package/dist/mcp-server/transports/authentication/authMiddleware.js +51 -69
- package/dist/mcp-server/transports/authentication/authUtils.d.ts +18 -0
- package/dist/mcp-server/transports/authentication/authUtils.js +45 -0
- package/dist/mcp-server/transports/authentication/oauthMiddleware.d.ts +24 -0
- package/dist/mcp-server/transports/authentication/oauthMiddleware.js +109 -0
- package/dist/mcp-server/transports/authentication/types.d.ts +17 -0
- package/dist/mcp-server/transports/authentication/types.js +5 -0
- package/dist/mcp-server/transports/httpTransport.d.ts +5 -4
- package/dist/mcp-server/transports/httpTransport.js +177 -143
- package/dist/services/NCBI/ncbiCoreApiClient.js +0 -5
- package/dist/services/NCBI/ncbiRequestQueueManager.js +2 -4
- package/dist/services/NCBI/ncbiResponseHandler.js +0 -3
- package/dist/services/NCBI/ncbiService.d.ts +1 -1
- package/dist/services/NCBI/ncbiService.js +11 -4
- package/dist/utils/internal/logger.js +10 -30
- package/package.json +27 -13
- package/dist/services/index.d.ts +0 -7
- package/dist/services/index.js +0 -7
- package/dist/services/llm-providers/index.d.ts +0 -7
- package/dist/services/llm-providers/index.js +0 -7
- package/dist/services/llm-providers/llmFactory.d.ts +0 -69
- package/dist/services/llm-providers/llmFactory.js +0 -132
- package/dist/services/llm-providers/openRouter/index.d.ts +0 -6
- package/dist/services/llm-providers/openRouter/index.js +0 -7
- package/dist/services/llm-providers/openRouter/openRouterProvider.d.ts +0 -99
- 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',
|
|
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
|
-
|
|
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";
|
|
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
|
-
|
|
74
|
-
await registerGeneratePubMedChartTool(server); // Added new tool registration
|
|
76
|
+
await registerGeneratePubMedChartTool(server);
|
|
75
77
|
await registerGetPubMedArticleConnectionsTool(server);
|
|
76
|
-
await registerPubMedResearchAgentTool(server);
|
|
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`
|
|
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
|
|
113
|
-
await startHttpTransport(createMcpServerInstance, context);
|
|
114
|
-
return;
|
|
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
|
|
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',
|
|
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 {
|
|
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<["
|
|
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: "
|
|
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?: "
|
|
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(["
|
|
34
|
-
.default("
|
|
35
|
-
.describe("Specifies the output format for the chart. Currently, only '
|
|
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 !== "
|
|
87
|
-
const unsupportedFormatError = new McpError(BaseErrorCode.VALIDATION_ERROR, `Unsupported output format: ${input.outputFormat}. Currently, only '
|
|
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: "
|
|
207
|
+
renderer: "canvas", // Explicitly set renderer to 'canvas'
|
|
207
208
|
});
|
|
208
|
-
const svgString = await view.toSVG();
|
|
209
|
-
|
|
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: [
|
|
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 (
|
|
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
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
39
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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 {
|
|
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
|
-
*
|
|
11
|
-
*
|
|
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 {
|
|
17
|
-
import {
|
|
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
|
-
*
|
|
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
|
|
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">)>;
|