@cyanheads/pubmed-mcp-server 1.0.16 → 1.1.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.
- package/dist/config/index.d.ts +12 -22
- package/dist/config/index.js +29 -51
- 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.js +2 -1
- 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 +19 -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 +52 -74
- package/package.json +21 -9
- 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
package/dist/config/index.d.ts
CHANGED
|
@@ -25,8 +25,10 @@ export declare const config: {
|
|
|
25
25
|
mcpServerVersion: string;
|
|
26
26
|
/** Logging level. From `MCP_LOG_LEVEL` env var. Default: "debug". */
|
|
27
27
|
logLevel: string;
|
|
28
|
-
/**
|
|
29
|
-
|
|
28
|
+
/** Defines the logging output mode ('file' or 'stdout'). From `LOG_OUTPUT_MODE`. */
|
|
29
|
+
logOutputMode: "file" | "stdout";
|
|
30
|
+
/** Absolute path to the logs directory (if logOutputMode is 'file'). From `LOGS_DIR`. */
|
|
31
|
+
logsPath: string | null;
|
|
30
32
|
/** Runtime environment. From `NODE_ENV` env var. Default: "development". */
|
|
31
33
|
environment: string;
|
|
32
34
|
/** MCP transport type ('stdio' or 'http'). From `MCP_TRANSPORT_TYPE` env var. Default: "stdio". */
|
|
@@ -39,26 +41,14 @@ export declare const config: {
|
|
|
39
41
|
mcpAllowedOrigins: string[] | undefined;
|
|
40
42
|
/** Auth secret key (JWTs, http transport). From `MCP_AUTH_SECRET_KEY`. CRITICAL. */
|
|
41
43
|
mcpAuthSecretKey: string | undefined;
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
|
|
50
|
-
/** Default LLM temperature. From `LLM_DEFAULT_TEMPERATURE`. */
|
|
51
|
-
llmDefaultTemperature: number | undefined;
|
|
52
|
-
/** Default LLM top_p. From `LLM_DEFAULT_TOP_P`. */
|
|
53
|
-
llmDefaultTopP: number | undefined;
|
|
54
|
-
/** Default LLM max tokens. From `LLM_DEFAULT_MAX_TOKENS`. */
|
|
55
|
-
llmDefaultMaxTokens: number | undefined;
|
|
56
|
-
/** Default LLM top_k. From `LLM_DEFAULT_TOP_K`. */
|
|
57
|
-
llmDefaultTopK: number | undefined;
|
|
58
|
-
/** Default LLM min_p. From `LLM_DEFAULT_MIN_P`. */
|
|
59
|
-
llmDefaultMinP: number | undefined;
|
|
60
|
-
/** Gemini API Key. From `GEMINI_API_KEY`. */
|
|
61
|
-
geminiApiKey: string | undefined;
|
|
44
|
+
/** Auth mode ('jwt' or 'oauth'). From `MCP_AUTH_MODE`. */
|
|
45
|
+
mcpAuthMode: "jwt" | "oauth";
|
|
46
|
+
/** OAuth Issuer URL. From `OAUTH_ISSUER_URL`. */
|
|
47
|
+
oauthIssuerUrl: string | undefined;
|
|
48
|
+
/** OAuth Audience. From `OAUTH_AUDIENCE`. */
|
|
49
|
+
oauthAudience: string | undefined;
|
|
50
|
+
/** OAuth JWKS URI. From `OAUTH_JWKS_URI`. */
|
|
51
|
+
oauthJwksUri: string | undefined;
|
|
62
52
|
/** NCBI API Key. From `NCBI_API_KEY`. */
|
|
63
53
|
ncbiApiKey: string | undefined;
|
|
64
54
|
/** NCBI Tool Identifier. From `NCBI_TOOL_IDENTIFIER`. Defaults to server name/version. */
|
package/dist/config/index.js
CHANGED
|
@@ -80,6 +80,8 @@ const EnvSchema = z.object({
|
|
|
80
80
|
MCP_LOG_LEVEL: z.string().default("debug"),
|
|
81
81
|
/** Directory for log files. Defaults to "logs" in project root. */
|
|
82
82
|
LOGS_DIR: z.string().default(path.join(projectRoot, "logs")),
|
|
83
|
+
/** Defines the logging output mode. "file" for logs in LOGS_DIR, "stdout" for console logging. */
|
|
84
|
+
LOG_OUTPUT_MODE: z.enum(["file", "stdout"]).default("file"),
|
|
83
85
|
/** Runtime environment (e.g., "development", "production"). Default: "development". */
|
|
84
86
|
NODE_ENV: z.string().default("development"),
|
|
85
87
|
/** MCP communication transport ("stdio" or "http"). Default: "stdio". */
|
|
@@ -95,31 +97,14 @@ const EnvSchema = z.object({
|
|
|
95
97
|
.string()
|
|
96
98
|
.min(32, "MCP_AUTH_SECRET_KEY must be at least 32 characters long for security reasons.")
|
|
97
99
|
.optional(),
|
|
98
|
-
/**
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
OPENROUTER_API_KEY: z.string().optional(),
|
|
107
|
-
/** Default LLM model. Default: "google/gemini-2.5-flash-preview:thinking". */
|
|
108
|
-
LLM_DEFAULT_MODEL: z
|
|
109
|
-
.string()
|
|
110
|
-
.default("google/gemini-2.5-flash-preview-05-20"),
|
|
111
|
-
/** Optional. Default LLM temperature (0.0-2.0). */
|
|
112
|
-
LLM_DEFAULT_TEMPERATURE: z.coerce.number().min(0).max(2).optional(),
|
|
113
|
-
/** Optional. Default LLM top_p (0.0-1.0). */
|
|
114
|
-
LLM_DEFAULT_TOP_P: z.coerce.number().min(0).max(1).optional(),
|
|
115
|
-
/** Optional. Default LLM max tokens (positive integer). */
|
|
116
|
-
LLM_DEFAULT_MAX_TOKENS: z.coerce.number().int().positive().optional(),
|
|
117
|
-
/** Optional. Default LLM top_k (non-negative integer). */
|
|
118
|
-
LLM_DEFAULT_TOP_K: z.coerce.number().int().nonnegative().optional(),
|
|
119
|
-
/** Optional. Default LLM min_p (0.0-1.0). */
|
|
120
|
-
LLM_DEFAULT_MIN_P: z.coerce.number().min(0).max(1).optional(),
|
|
121
|
-
/** Optional. API key for Google Gemini services. */
|
|
122
|
-
GEMINI_API_KEY: z.string().optional(),
|
|
100
|
+
/** Authentication mode ('jwt' or 'oauth'). Default: 'jwt'. */
|
|
101
|
+
MCP_AUTH_MODE: z.enum(["jwt", "oauth"]).default("jwt"),
|
|
102
|
+
/** OAuth: The expected issuer of the JWT. */
|
|
103
|
+
OAUTH_ISSUER_URL: z.string().url().optional(),
|
|
104
|
+
/** OAuth: The expected audience of the JWT. */
|
|
105
|
+
OAUTH_AUDIENCE: z.string().optional(),
|
|
106
|
+
/** OAuth: The URI of the JWKS endpoint. */
|
|
107
|
+
OAUTH_JWKS_URI: z.string().url().optional(),
|
|
123
108
|
/** Optional. OAuth provider authorization endpoint URL. */
|
|
124
109
|
OAUTH_PROXY_AUTHORIZATION_URL: z
|
|
125
110
|
.string()
|
|
@@ -226,12 +211,15 @@ const ensureDirectory = (dirPath, rootDir, dirName) => {
|
|
|
226
211
|
};
|
|
227
212
|
// --- End Directory Ensurance Function ---
|
|
228
213
|
// --- Logs Directory Handling ---
|
|
229
|
-
|
|
230
|
-
if (
|
|
231
|
-
|
|
232
|
-
|
|
214
|
+
let validatedLogsPath = null;
|
|
215
|
+
if (env.LOG_OUTPUT_MODE === "file") {
|
|
216
|
+
validatedLogsPath = ensureDirectory(env.LOGS_DIR, projectRoot, "logs");
|
|
217
|
+
if (!validatedLogsPath) {
|
|
218
|
+
if (process.stdout.isTTY) {
|
|
219
|
+
console.error("FATAL: Log mode is 'file' but logs directory is invalid or could not be created. Please check LOGS_DIR, permissions, and path. Exiting.");
|
|
220
|
+
}
|
|
221
|
+
process.exit(1); // Exit if file logging is configured but directory is not usable
|
|
233
222
|
}
|
|
234
|
-
process.exit(1); // Exit if logs directory is not usable
|
|
235
223
|
}
|
|
236
224
|
// --- End Logs Directory Handling ---
|
|
237
225
|
/**
|
|
@@ -245,7 +233,9 @@ export const config = {
|
|
|
245
233
|
mcpServerVersion: env.MCP_SERVER_VERSION || pkg.version,
|
|
246
234
|
/** Logging level. From `MCP_LOG_LEVEL` env var. Default: "debug". */
|
|
247
235
|
logLevel: env.MCP_LOG_LEVEL,
|
|
248
|
-
/**
|
|
236
|
+
/** Defines the logging output mode ('file' or 'stdout'). From `LOG_OUTPUT_MODE`. */
|
|
237
|
+
logOutputMode: env.LOG_OUTPUT_MODE,
|
|
238
|
+
/** Absolute path to the logs directory (if logOutputMode is 'file'). From `LOGS_DIR`. */
|
|
249
239
|
logsPath: validatedLogsPath,
|
|
250
240
|
/** Runtime environment. From `NODE_ENV` env var. Default: "development". */
|
|
251
241
|
environment: env.NODE_ENV,
|
|
@@ -261,26 +251,14 @@ export const config = {
|
|
|
261
251
|
.filter(Boolean),
|
|
262
252
|
/** Auth secret key (JWTs, http transport). From `MCP_AUTH_SECRET_KEY`. CRITICAL. */
|
|
263
253
|
mcpAuthSecretKey: env.MCP_AUTH_SECRET_KEY,
|
|
264
|
-
/**
|
|
265
|
-
|
|
266
|
-
/**
|
|
267
|
-
|
|
268
|
-
/**
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
|
|
272
|
-
/** Default LLM temperature. From `LLM_DEFAULT_TEMPERATURE`. */
|
|
273
|
-
llmDefaultTemperature: env.LLM_DEFAULT_TEMPERATURE,
|
|
274
|
-
/** Default LLM top_p. From `LLM_DEFAULT_TOP_P`. */
|
|
275
|
-
llmDefaultTopP: env.LLM_DEFAULT_TOP_P,
|
|
276
|
-
/** Default LLM max tokens. From `LLM_DEFAULT_MAX_TOKENS`. */
|
|
277
|
-
llmDefaultMaxTokens: env.LLM_DEFAULT_MAX_TOKENS,
|
|
278
|
-
/** Default LLM top_k. From `LLM_DEFAULT_TOP_K`. */
|
|
279
|
-
llmDefaultTopK: env.LLM_DEFAULT_TOP_K,
|
|
280
|
-
/** Default LLM min_p. From `LLM_DEFAULT_MIN_P`. */
|
|
281
|
-
llmDefaultMinP: env.LLM_DEFAULT_MIN_P,
|
|
282
|
-
/** Gemini API Key. From `GEMINI_API_KEY`. */
|
|
283
|
-
geminiApiKey: env.GEMINI_API_KEY,
|
|
254
|
+
/** Auth mode ('jwt' or 'oauth'). From `MCP_AUTH_MODE`. */
|
|
255
|
+
mcpAuthMode: env.MCP_AUTH_MODE,
|
|
256
|
+
/** OAuth Issuer URL. From `OAUTH_ISSUER_URL`. */
|
|
257
|
+
oauthIssuerUrl: env.OAUTH_ISSUER_URL,
|
|
258
|
+
/** OAuth Audience. From `OAUTH_AUDIENCE`. */
|
|
259
|
+
oauthAudience: env.OAUTH_AUDIENCE,
|
|
260
|
+
/** OAuth JWKS URI. From `OAUTH_JWKS_URI`. */
|
|
261
|
+
oauthJwksUri: env.OAUTH_JWKS_URI,
|
|
284
262
|
// NCBI Configuration
|
|
285
263
|
/** NCBI API Key. From `NCBI_API_KEY`. */
|
|
286
264
|
ncbiApiKey: env.NCBI_API_KEY,
|
package/dist/index.js
CHANGED
|
@@ -22,16 +22,21 @@
|
|
|
22
22
|
* @module src/index
|
|
23
23
|
*/
|
|
24
24
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
25
|
+
import http from "http"; // Import http module
|
|
25
26
|
import { config, environment } from "./config/index.js";
|
|
26
27
|
import { initializeAndStartServer } from "./mcp-server/server.js";
|
|
27
28
|
import { requestContextService } from "./utils/index.js";
|
|
28
29
|
import { logger } from "./utils/internal/logger.js";
|
|
29
30
|
/**
|
|
30
31
|
* Holds the main MCP server instance, primarily for STDIO transport.
|
|
31
|
-
* For HTTP transport, server instances are typically managed per session.
|
|
32
32
|
* @private
|
|
33
33
|
*/
|
|
34
|
-
let
|
|
34
|
+
let mcpStdioServer;
|
|
35
|
+
/**
|
|
36
|
+
* Holds the Node.js HTTP server instance if HTTP transport is used.
|
|
37
|
+
* @private
|
|
38
|
+
*/
|
|
39
|
+
let actualHttpServer;
|
|
35
40
|
/**
|
|
36
41
|
* Gracefully shuts down the main MCP server and associated resources.
|
|
37
42
|
* Called on process termination signals or critical unhandled errors.
|
|
@@ -46,25 +51,70 @@ const shutdown = async (signal) => {
|
|
|
46
51
|
triggerEvent: signal,
|
|
47
52
|
});
|
|
48
53
|
logger.info(`Received ${signal}. Initiating graceful shutdown...`, shutdownContext);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
let mcpClosed = false;
|
|
55
|
+
let httpClosed = false;
|
|
56
|
+
let closeError = null;
|
|
57
|
+
const checkAndExit = () => {
|
|
58
|
+
if (closeError) {
|
|
59
|
+
logger.error("Critical error encountered during shutdown process.", {
|
|
60
|
+
...shutdownContext,
|
|
61
|
+
errorMessage: closeError.message,
|
|
62
|
+
errorStack: closeError.stack,
|
|
63
|
+
});
|
|
64
|
+
process.exit(1);
|
|
54
65
|
}
|
|
55
|
-
else {
|
|
56
|
-
logger.
|
|
66
|
+
else if (mcpClosed && httpClosed) {
|
|
67
|
+
logger.info("Graceful shutdown completed successfully. Exiting.", shutdownContext);
|
|
68
|
+
process.exit(0);
|
|
57
69
|
}
|
|
58
|
-
|
|
59
|
-
|
|
70
|
+
};
|
|
71
|
+
if (mcpStdioServer) {
|
|
72
|
+
logger.info("Attempting to close main MCP server (STDIO)...", shutdownContext);
|
|
73
|
+
mcpStdioServer
|
|
74
|
+
.close()
|
|
75
|
+
.then(() => {
|
|
76
|
+
logger.info("Main MCP server (STDIO) closed successfully.", shutdownContext);
|
|
77
|
+
mcpClosed = true;
|
|
78
|
+
checkAndExit();
|
|
79
|
+
})
|
|
80
|
+
.catch((err) => {
|
|
81
|
+
logger.error("Error closing MCP server (STDIO).", {
|
|
82
|
+
...shutdownContext,
|
|
83
|
+
error: err,
|
|
84
|
+
});
|
|
85
|
+
mcpClosed = true; // Consider it closed even on error to allow exit
|
|
86
|
+
if (!closeError)
|
|
87
|
+
closeError = err;
|
|
88
|
+
checkAndExit();
|
|
89
|
+
});
|
|
60
90
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
91
|
+
else {
|
|
92
|
+
mcpClosed = true; // No STDIO McpServer to close
|
|
93
|
+
}
|
|
94
|
+
if (actualHttpServer) {
|
|
95
|
+
logger.info("Attempting to close HTTP server...", shutdownContext);
|
|
96
|
+
actualHttpServer.close((err) => {
|
|
97
|
+
if (err) {
|
|
98
|
+
logger.error("Error closing HTTP server.", {
|
|
99
|
+
...shutdownContext,
|
|
100
|
+
error: err,
|
|
101
|
+
});
|
|
102
|
+
if (!closeError)
|
|
103
|
+
closeError = err;
|
|
104
|
+
}
|
|
105
|
+
else {
|
|
106
|
+
logger.info("HTTP server closed successfully.", shutdownContext);
|
|
107
|
+
}
|
|
108
|
+
httpClosed = true;
|
|
109
|
+
checkAndExit();
|
|
66
110
|
});
|
|
67
|
-
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
httpClosed = true; // No HTTP server to close
|
|
114
|
+
}
|
|
115
|
+
// Initial check in case no servers needed closing
|
|
116
|
+
if (mcpClosed && httpClosed) {
|
|
117
|
+
checkAndExit();
|
|
68
118
|
}
|
|
69
119
|
};
|
|
70
120
|
/**
|
|
@@ -100,13 +150,6 @@ const start = async () => {
|
|
|
100
150
|
}
|
|
101
151
|
await logger.initialize(validatedMcpLogLevel);
|
|
102
152
|
logger.info(`Logger has been initialized by start(). Effective MCP logging level set to: ${validatedMcpLogLevel}.`);
|
|
103
|
-
// Configure RequestContextService once globally
|
|
104
|
-
requestContextService.configure({
|
|
105
|
-
appName: config.mcpServerName,
|
|
106
|
-
appVersion: config.mcpServerVersion,
|
|
107
|
-
environment,
|
|
108
|
-
});
|
|
109
|
-
logger.debug("RequestContextService configured with app name, version, and environment.");
|
|
110
153
|
const transportType = config.mcpTransportType;
|
|
111
154
|
const startupContext = requestContextService.createRequestContext({
|
|
112
155
|
operation: `ServerStartupSequence_${transportType}`,
|
|
@@ -129,14 +172,19 @@ const start = async () => {
|
|
|
129
172
|
logger.info(`Starting ${config.mcpServerName} (Version: ${config.mcpServerVersion}, Transport: ${transportType}, Env: ${environment})...`, startupContext);
|
|
130
173
|
try {
|
|
131
174
|
logger.debug("Calling initializeAndStartServer to set up MCP transport...", startupContext);
|
|
132
|
-
const
|
|
133
|
-
if (transportType === "stdio" &&
|
|
134
|
-
|
|
135
|
-
server = potentialServerInstance;
|
|
175
|
+
const serverInstance = await initializeAndStartServer();
|
|
176
|
+
if (transportType === "stdio" && serverInstance instanceof McpServer) {
|
|
177
|
+
mcpStdioServer = serverInstance;
|
|
136
178
|
logger.info("STDIO McpServer instance stored globally for shutdown.", startupContext);
|
|
137
179
|
}
|
|
180
|
+
else if (transportType === "http" &&
|
|
181
|
+
serverInstance instanceof http.Server) {
|
|
182
|
+
actualHttpServer = serverInstance;
|
|
183
|
+
logger.info("HTTP transport initialized, http.Server instance stored globally for shutdown.", startupContext);
|
|
184
|
+
}
|
|
138
185
|
else if (transportType === "http") {
|
|
139
|
-
|
|
186
|
+
// This case should ideally not be reached if initializeAndStartServer correctly returns http.Server
|
|
187
|
+
logger.warning("HTTP transport initialized, but no http.Server instance was returned to index.ts. Shutdown might be incomplete.", startupContext);
|
|
140
188
|
}
|
|
141
189
|
logger.info(`${config.mcpServerName} is now running and ready to accept connections via ${transportType} transport.`, {
|
|
142
190
|
...startupContext,
|
|
@@ -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",
|
|
@@ -83,7 +83,8 @@ 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 !== "png") {
|
|
86
|
+
if (input.outputFormat !== "png") {
|
|
87
|
+
// Changed from svg to png
|
|
87
88
|
const unsupportedFormatError = new McpError(BaseErrorCode.VALIDATION_ERROR, `Unsupported output format: ${input.outputFormat}. Currently, only 'png' is supported.`, // Changed message
|
|
88
89
|
{ requestedFormat: input.outputFormat });
|
|
89
90
|
logger.warning(unsupportedFormatError.message, operationContext);
|
|
@@ -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,48 @@ 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.map(removeEmptyObjectsRecursively).filter((item) => {
|
|
39
|
+
if (item === null || item === undefined)
|
|
40
|
+
return false;
|
|
41
|
+
if (Array.isArray(item) && item.length === 0)
|
|
42
|
+
return false; // Filter out empty arrays
|
|
40
43
|
if (typeof item === "object" &&
|
|
41
|
-
item !== null &&
|
|
42
44
|
!Array.isArray(item) &&
|
|
43
45
|
Object.keys(item).length === 0) {
|
|
44
|
-
return false; // Filter out empty objects
|
|
46
|
+
return false; // Filter out empty objects
|
|
45
47
|
}
|
|
46
48
|
return true;
|
|
47
49
|
});
|
|
50
|
+
return newArr;
|
|
48
51
|
}
|
|
49
52
|
// If it's an object, create a new object with non-empty properties
|
|
50
53
|
const newObj = {};
|
|
51
54
|
for (const key in obj) {
|
|
52
55
|
if (Object.prototype.hasOwnProperty.call(obj, key)) {
|
|
53
56
|
const value = removeEmptyObjectsRecursively(obj[key]);
|
|
54
|
-
//
|
|
57
|
+
// Skip null or undefined values
|
|
58
|
+
if (value === null || value === undefined) {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
// Skip empty arrays
|
|
62
|
+
if (Array.isArray(value) && value.length === 0) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
// Skip empty objects
|
|
55
66
|
if (typeof value === "object" &&
|
|
56
|
-
value !== null &&
|
|
57
67
|
!Array.isArray(value) &&
|
|
58
68
|
Object.keys(value).length === 0) {
|
|
59
|
-
// It's an empty object, so we don't add this key-value pair to newObj
|
|
60
69
|
continue;
|
|
61
70
|
}
|
|
62
|
-
// If value is not
|
|
71
|
+
// If value is not empty, add it
|
|
63
72
|
newObj[key] = value;
|
|
64
73
|
}
|
|
65
74
|
}
|
|
@@ -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 {};
|