@cyanheads/pubmed-mcp-server 1.1.1 → 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
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". */
|
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". */
|
|
@@ -209,12 +211,15 @@ const ensureDirectory = (dirPath, rootDir, dirName) => {
|
|
|
209
211
|
};
|
|
210
212
|
// --- End Directory Ensurance Function ---
|
|
211
213
|
// --- Logs Directory Handling ---
|
|
212
|
-
|
|
213
|
-
if (
|
|
214
|
-
|
|
215
|
-
|
|
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
|
|
216
222
|
}
|
|
217
|
-
process.exit(1); // Exit if logs directory is not usable
|
|
218
223
|
}
|
|
219
224
|
// --- End Logs Directory Handling ---
|
|
220
225
|
/**
|
|
@@ -228,7 +233,9 @@ export const config = {
|
|
|
228
233
|
mcpServerVersion: env.MCP_SERVER_VERSION || pkg.version,
|
|
229
234
|
/** Logging level. From `MCP_LOG_LEVEL` env var. Default: "debug". */
|
|
230
235
|
logLevel: env.MCP_LOG_LEVEL,
|
|
231
|
-
/**
|
|
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`. */
|
|
232
239
|
logsPath: validatedLogsPath,
|
|
233
240
|
/** Runtime environment. From `NODE_ENV` env var. Default: "development". */
|
|
234
241
|
environment: env.NODE_ENV,
|
|
@@ -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);
|
|
@@ -35,9 +35,7 @@ function removeEmptyObjectsRecursively(obj) {
|
|
|
35
35
|
}
|
|
36
36
|
if (Array.isArray(obj)) {
|
|
37
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) => {
|
|
38
|
+
const newArr = obj.map(removeEmptyObjectsRecursively).filter((item) => {
|
|
41
39
|
if (item === null || item === undefined)
|
|
42
40
|
return false;
|
|
43
41
|
if (Array.isArray(item) && item.length === 0)
|
|
@@ -35,11 +35,8 @@ const mcpToWinstonLevel = {
|
|
|
35
35
|
alert: "error",
|
|
36
36
|
emerg: "error",
|
|
37
37
|
};
|
|
38
|
-
// The logsPath from config is already resolved and validated by src/config/index.ts
|
|
39
|
-
const resolvedLogsDir = config.logsPath;
|
|
40
|
-
const isLogsDirSafe = !!resolvedLogsDir; // If logsPath is set, it's considered safe by config logic.
|
|
41
38
|
/**
|
|
42
|
-
* Creates the Winston console log format.
|
|
39
|
+
* Creates the Winston console log format for interactive TTY sessions.
|
|
43
40
|
* @returns The Winston log format for console output.
|
|
44
41
|
* @private
|
|
45
42
|
*/
|
|
@@ -103,47 +100,45 @@ export class Logger {
|
|
|
103
100
|
});
|
|
104
101
|
return;
|
|
105
102
|
}
|
|
106
|
-
// Set initialized to true at the beginning of the initialization process.
|
|
107
103
|
this.initialized = true;
|
|
108
104
|
this.currentMcpLevel = level;
|
|
109
105
|
this.currentWinstonLevel = mcpToWinstonLevel[level];
|
|
110
|
-
// The logs directory (config.logsPath / resolvedLogsDir) is expected to be created and validated
|
|
111
|
-
// by the configuration module (src/config/index.ts) before logger initialization.
|
|
112
|
-
// If isLogsDirSafe is true, we assume resolvedLogsDir exists and is usable.
|
|
113
|
-
// No redundant directory creation logic here.
|
|
114
|
-
const fileFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json());
|
|
115
106
|
const transports = [];
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
maxFiles: this.LOG_MAX_FILES,
|
|
120
|
-
tailable: true,
|
|
121
|
-
};
|
|
122
|
-
if (isLogsDirSafe) {
|
|
123
|
-
transports.push(new winston.transports.File({
|
|
124
|
-
filename: path.join(resolvedLogsDir, "error.log"),
|
|
125
|
-
level: "error",
|
|
126
|
-
...fileTransportOptions,
|
|
127
|
-
}), new winston.transports.File({
|
|
128
|
-
filename: path.join(resolvedLogsDir, "warn.log"),
|
|
129
|
-
level: "warn",
|
|
130
|
-
...fileTransportOptions,
|
|
131
|
-
}), new winston.transports.File({
|
|
132
|
-
filename: path.join(resolvedLogsDir, "info.log"),
|
|
133
|
-
level: "info",
|
|
134
|
-
...fileTransportOptions,
|
|
135
|
-
}), new winston.transports.File({
|
|
136
|
-
filename: path.join(resolvedLogsDir, "debug.log"),
|
|
137
|
-
level: "debug",
|
|
138
|
-
...fileTransportOptions,
|
|
139
|
-
}), new winston.transports.File({
|
|
140
|
-
filename: path.join(resolvedLogsDir, "combined.log"),
|
|
141
|
-
...fileTransportOptions,
|
|
107
|
+
if (config.logOutputMode === "stdout") {
|
|
108
|
+
transports.push(new winston.transports.Console({
|
|
109
|
+
format: winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json()),
|
|
142
110
|
}));
|
|
143
111
|
}
|
|
144
112
|
else {
|
|
145
|
-
|
|
146
|
-
|
|
113
|
+
const resolvedLogsDir = config.logsPath;
|
|
114
|
+
if (resolvedLogsDir) {
|
|
115
|
+
const fileFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json());
|
|
116
|
+
const fileTransportOptions = {
|
|
117
|
+
format: fileFormat,
|
|
118
|
+
maxsize: this.LOG_FILE_MAX_SIZE,
|
|
119
|
+
maxFiles: this.LOG_MAX_FILES,
|
|
120
|
+
tailable: true,
|
|
121
|
+
};
|
|
122
|
+
transports.push(new winston.transports.File({
|
|
123
|
+
filename: path.join(resolvedLogsDir, "error.log"),
|
|
124
|
+
level: "error",
|
|
125
|
+
...fileTransportOptions,
|
|
126
|
+
}), new winston.transports.File({
|
|
127
|
+
filename: path.join(resolvedLogsDir, "warn.log"),
|
|
128
|
+
level: "warn",
|
|
129
|
+
...fileTransportOptions,
|
|
130
|
+
}), new winston.transports.File({
|
|
131
|
+
filename: path.join(resolvedLogsDir, "info.log"),
|
|
132
|
+
level: "info",
|
|
133
|
+
...fileTransportOptions,
|
|
134
|
+
}), new winston.transports.File({
|
|
135
|
+
filename: path.join(resolvedLogsDir, "debug.log"),
|
|
136
|
+
level: "debug",
|
|
137
|
+
...fileTransportOptions,
|
|
138
|
+
}), new winston.transports.File({
|
|
139
|
+
filename: path.join(resolvedLogsDir, "combined.log"),
|
|
140
|
+
...fileTransportOptions,
|
|
141
|
+
}));
|
|
147
142
|
}
|
|
148
143
|
}
|
|
149
144
|
this.winstonLogger = winston.createLogger({
|
|
@@ -151,23 +146,20 @@ export class Logger {
|
|
|
151
146
|
transports,
|
|
152
147
|
exitOnError: false,
|
|
153
148
|
});
|
|
154
|
-
// Configure console transport after Winston logger is created
|
|
155
149
|
const consoleStatus = this._configureConsoleTransport();
|
|
156
150
|
const initialContext = {
|
|
157
151
|
loggerSetup: true,
|
|
158
152
|
requestId: "logger-init-deferred",
|
|
159
153
|
timestamp: new Date().toISOString(),
|
|
160
154
|
};
|
|
161
|
-
// Removed logging of logsDirCreatedMessage as it's no longer set
|
|
162
155
|
if (consoleStatus.message) {
|
|
163
156
|
this.info(consoleStatus.message, initialContext);
|
|
164
157
|
}
|
|
165
|
-
this.initialized
|
|
166
|
-
this.info(`Logger initialized. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${consoleStatus.enabled ? "enabled" : "disabled"}`, {
|
|
158
|
+
this.info(`Logger initialized. Mode: ${config.logOutputMode}. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}.`, {
|
|
167
159
|
loggerSetup: true,
|
|
168
160
|
requestId: "logger-post-init",
|
|
169
161
|
timestamp: new Date().toISOString(),
|
|
170
|
-
logsPathUsed:
|
|
162
|
+
logsPathUsed: config.logsPath,
|
|
171
163
|
});
|
|
172
164
|
}
|
|
173
165
|
/**
|
|
@@ -207,7 +199,6 @@ export class Logger {
|
|
|
207
199
|
this.currentMcpLevel = newLevel;
|
|
208
200
|
this.currentWinstonLevel = mcpToWinstonLevel[newLevel];
|
|
209
201
|
if (this.winstonLogger) {
|
|
210
|
-
// Ensure winstonLogger is defined
|
|
211
202
|
this.winstonLogger.level = this.currentWinstonLevel;
|
|
212
203
|
}
|
|
213
204
|
const consoleStatus = this._configureConsoleTransport();
|
|
@@ -226,6 +217,12 @@ export class Logger {
|
|
|
226
217
|
* @private
|
|
227
218
|
*/
|
|
228
219
|
_configureConsoleTransport() {
|
|
220
|
+
if (config.logOutputMode === "stdout") {
|
|
221
|
+
return {
|
|
222
|
+
enabled: true,
|
|
223
|
+
message: "Stdout logging is enabled by configuration.",
|
|
224
|
+
};
|
|
225
|
+
}
|
|
229
226
|
if (!this.winstonLogger) {
|
|
230
227
|
return {
|
|
231
228
|
enabled: false,
|
|
@@ -238,17 +235,19 @@ export class Logger {
|
|
|
238
235
|
if (shouldHaveConsole && !consoleTransport) {
|
|
239
236
|
const consoleFormat = createWinstonConsoleFormat();
|
|
240
237
|
this.winstonLogger.add(new winston.transports.Console({
|
|
241
|
-
level: "debug",
|
|
238
|
+
level: "debug",
|
|
242
239
|
format: consoleFormat,
|
|
243
240
|
}));
|
|
244
|
-
message =
|
|
241
|
+
message =
|
|
242
|
+
"Interactive console logging enabled (level: debug, stdout is TTY).";
|
|
245
243
|
}
|
|
246
244
|
else if (!shouldHaveConsole && consoleTransport) {
|
|
247
245
|
this.winstonLogger.remove(consoleTransport);
|
|
248
|
-
message =
|
|
246
|
+
message =
|
|
247
|
+
"Interactive console logging disabled (level not debug or stdout not TTY).";
|
|
249
248
|
}
|
|
250
249
|
else {
|
|
251
|
-
message = "
|
|
250
|
+
message = "Interactive console logging status unchanged.";
|
|
252
251
|
}
|
|
253
252
|
return { enabled: shouldHaveConsole, message };
|
|
254
253
|
}
|
|
@@ -288,7 +287,7 @@ export class Logger {
|
|
|
288
287
|
if (!this.ensureInitialized())
|
|
289
288
|
return;
|
|
290
289
|
if (mcpLevelSeverity[level] > mcpLevelSeverity[this.currentMcpLevel]) {
|
|
291
|
-
return;
|
|
290
|
+
return;
|
|
292
291
|
}
|
|
293
292
|
const logData = { ...context };
|
|
294
293
|
const winstonLevel = mcpToWinstonLevel[level];
|
|
@@ -304,7 +303,6 @@ export class Logger {
|
|
|
304
303
|
mcpDataPayload.context = context;
|
|
305
304
|
if (error) {
|
|
306
305
|
mcpDataPayload.error = { message: error.message };
|
|
307
|
-
// Include stack trace in debug mode for MCP notifications, truncated for brevity
|
|
308
306
|
if (this.currentMcpLevel === "debug" && error.stack) {
|
|
309
307
|
mcpDataPayload.error.stack = error.stack.substring(0, this.MCP_NOTIFICATION_STACK_TRACE_MAX_LENGTH);
|
|
310
308
|
}
|
|
@@ -321,7 +319,7 @@ export class Logger {
|
|
|
321
319
|
originalLevel: level,
|
|
322
320
|
originalMessage: msg,
|
|
323
321
|
sendError: errorMessage,
|
|
324
|
-
mcpPayload: JSON.stringify(mcpDataPayload).substring(0, 500),
|
|
322
|
+
mcpPayload: JSON.stringify(mcpDataPayload).substring(0, 500),
|
|
325
323
|
};
|
|
326
324
|
this.winstonLogger.error("Failed to send MCP log notification", internalErrorContext);
|
|
327
325
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/pubmed-mcp-server",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "A Model Context Protocol (MCP) server enabling AI agents to intelligently search, retrieve, and analyze biomedical literature from PubMed via NCBI E-utilities. Built on the mcp-ts-template for robust, production-ready performance.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -29,8 +29,7 @@
|
|
|
29
29
|
"tree": "ts-node --esm scripts/tree.ts",
|
|
30
30
|
"fetch-spec": "ts-node --esm scripts/fetch-openapi-spec.ts",
|
|
31
31
|
"format": "prettier --write \"**/*.{ts,js,json,md,html,css}\"",
|
|
32
|
-
"inspector": "mcp-inspector --config mcp.json --server pubmed-mcp-server"
|
|
33
|
-
"start:client-cli": "node dist/mcp-client/cli/mcp-client-cli.js"
|
|
32
|
+
"inspector": "mcp-inspector --config mcp.json --server pubmed-mcp-server"
|
|
34
33
|
},
|
|
35
34
|
"dependencies": {
|
|
36
35
|
"@hono/node-server": "^1.14.4",
|