@cyanheads/pubmed-mcp-server 1.0.12
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/LICENSE +201 -0
- package/README.md +174 -0
- package/dist/config/index.d.ts +91 -0
- package/dist/config/index.js +324 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +190 -0
- package/dist/mcp-server/resources/echoResource/echoResourceLogic.d.ts +79 -0
- package/dist/mcp-server/resources/echoResource/echoResourceLogic.js +82 -0
- package/dist/mcp-server/resources/echoResource/index.d.ts +13 -0
- package/dist/mcp-server/resources/echoResource/index.js +13 -0
- package/dist/mcp-server/resources/echoResource/registration.d.ts +30 -0
- package/dist/mcp-server/resources/echoResource/registration.js +168 -0
- package/dist/mcp-server/server.d.ts +28 -0
- package/dist/mcp-server/server.js +159 -0
- package/dist/mcp-server/tools/fetchPubMedContent/index.d.ts +6 -0
- package/dist/mcp-server/tools/fetchPubMedContent/index.js +6 -0
- package/dist/mcp-server/tools/fetchPubMedContent/logic.d.ts +63 -0
- package/dist/mcp-server/tools/fetchPubMedContent/logic.js +518 -0
- package/dist/mcp-server/tools/fetchPubMedContent/registration.d.ts +10 -0
- package/dist/mcp-server/tools/fetchPubMedContent/registration.js +37 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/index.d.ts +6 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/index.js +6 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.d.ts +9 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/citationFormatter.js +288 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.d.ts +9 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/elinkHandler.js +208 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.d.ts +15 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/index.js +82 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/types.d.ts +27 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic/types.js +5 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic.d.ts +6 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/logic.js +6 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/registration.d.ts +36 -0
- package/dist/mcp-server/tools/getPubMedArticleConnections/registration.js +76 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/index.d.ts +6 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/index.js +6 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/index.d.ts +7 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/index.js +9 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/inputSchema.d.ts +111 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/inputSchema.js +149 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/outputTypes.d.ts +106 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/outputTypes.js +7 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/planOrchestrator.d.ts +10 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic/planOrchestrator.js +251 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic.d.ts +12 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/logic.js +55 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/registration.d.ts +10 -0
- package/dist/mcp-server/tools/pubmedResearchAgent/registration.js +36 -0
- package/dist/mcp-server/tools/searchPubMedArticles/index.d.ts +6 -0
- package/dist/mcp-server/tools/searchPubMedArticles/index.js +6 -0
- package/dist/mcp-server/tools/searchPubMedArticles/logic.d.ts +61 -0
- package/dist/mcp-server/tools/searchPubMedArticles/logic.js +229 -0
- package/dist/mcp-server/tools/searchPubMedArticles/registration.d.ts +10 -0
- package/dist/mcp-server/tools/searchPubMedArticles/registration.js +38 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.d.ts +29 -0
- package/dist/mcp-server/transports/authentication/authMiddleware.js +174 -0
- package/dist/mcp-server/transports/httpTransport.d.ts +23 -0
- package/dist/mcp-server/transports/httpTransport.js +463 -0
- package/dist/mcp-server/transports/stdioTransport.d.ts +42 -0
- package/dist/mcp-server/transports/stdioTransport.js +63 -0
- package/dist/services/NCBI/ncbiConstants.d.ts +22 -0
- package/dist/services/NCBI/ncbiConstants.js +5 -0
- package/dist/services/NCBI/ncbiCoreApiClient.d.ts +24 -0
- package/dist/services/NCBI/ncbiCoreApiClient.js +118 -0
- package/dist/services/NCBI/ncbiRequestQueueManager.d.ts +36 -0
- package/dist/services/NCBI/ncbiRequestQueueManager.js +96 -0
- package/dist/services/NCBI/ncbiResponseHandler.d.ts +22 -0
- package/dist/services/NCBI/ncbiResponseHandler.js +192 -0
- package/dist/services/NCBI/ncbiService.d.ts +24 -0
- package/dist/services/NCBI/ncbiService.js +57 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.js +7 -0
- package/dist/services/llm-providers/index.d.ts +7 -0
- package/dist/services/llm-providers/index.js +7 -0
- package/dist/services/llm-providers/llmFactory.d.ts +69 -0
- package/dist/services/llm-providers/llmFactory.js +132 -0
- package/dist/services/llm-providers/openRouter/index.d.ts +6 -0
- package/dist/services/llm-providers/openRouter/index.js +7 -0
- package/dist/services/llm-providers/openRouter/openRouterProvider.d.ts +99 -0
- package/dist/services/llm-providers/openRouter/openRouterProvider.js +329 -0
- package/dist/types-global/errors.d.ts +119 -0
- package/dist/types-global/errors.js +119 -0
- package/dist/types-global/pubmedXml.d.ts +350 -0
- package/dist/types-global/pubmedXml.js +7 -0
- package/dist/utils/index.d.ts +10 -0
- package/dist/utils/index.js +18 -0
- package/dist/utils/internal/errorHandler.d.ts +176 -0
- package/dist/utils/internal/errorHandler.js +335 -0
- package/dist/utils/internal/index.d.ts +9 -0
- package/dist/utils/internal/index.js +9 -0
- package/dist/utils/internal/logger.d.ts +141 -0
- package/dist/utils/internal/logger.js +426 -0
- package/dist/utils/internal/requestContext.d.ts +83 -0
- package/dist/utils/internal/requestContext.js +72 -0
- package/dist/utils/metrics/index.d.ts +7 -0
- package/dist/utils/metrics/index.js +7 -0
- package/dist/utils/metrics/tokenCounter.d.ts +35 -0
- package/dist/utils/metrics/tokenCounter.js +110 -0
- package/dist/utils/parsing/dateParser.d.ts +73 -0
- package/dist/utils/parsing/dateParser.js +107 -0
- package/dist/utils/parsing/index.d.ts +8 -0
- package/dist/utils/parsing/index.js +8 -0
- package/dist/utils/parsing/jsonParser.d.ts +82 -0
- package/dist/utils/parsing/jsonParser.js +126 -0
- package/dist/utils/parsing/ncbi-parsing/eSummaryResultParser.d.ts +32 -0
- package/dist/utils/parsing/ncbi-parsing/eSummaryResultParser.js +295 -0
- package/dist/utils/parsing/ncbi-parsing/index.d.ts +8 -0
- package/dist/utils/parsing/ncbi-parsing/index.js +8 -0
- package/dist/utils/parsing/ncbi-parsing/pubmedArticleStructureParser.d.ts +70 -0
- package/dist/utils/parsing/ncbi-parsing/pubmedArticleStructureParser.js +229 -0
- package/dist/utils/parsing/ncbi-parsing/xmlGenericHelpers.d.ts +31 -0
- package/dist/utils/parsing/ncbi-parsing/xmlGenericHelpers.js +76 -0
- package/dist/utils/security/idGenerator.d.ts +134 -0
- package/dist/utils/security/idGenerator.js +198 -0
- package/dist/utils/security/index.d.ts +9 -0
- package/dist/utils/security/index.js +9 -0
- package/dist/utils/security/rateLimiter.d.ts +108 -0
- package/dist/utils/security/rateLimiter.js +179 -0
- package/dist/utils/security/sanitization.d.ts +173 -0
- package/dist/utils/security/sanitization.js +439 -0
- package/package.json +97 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Provides a singleton Logger class that wraps Winston for file logging
|
|
3
|
+
* and supports sending MCP (Model Context Protocol) `notifications/message`.
|
|
4
|
+
* It handles different log levels compliant with RFC 5424 and MCP specifications.
|
|
5
|
+
* @module src/utils/internal/logger
|
|
6
|
+
*/
|
|
7
|
+
import fs from "fs";
|
|
8
|
+
import path from "path";
|
|
9
|
+
import winston from "winston";
|
|
10
|
+
import { config } from "../../config/index.js";
|
|
11
|
+
/**
|
|
12
|
+
* Numeric severity mapping for MCP log levels (lower is more severe).
|
|
13
|
+
* @private
|
|
14
|
+
*/
|
|
15
|
+
const mcpLevelSeverity = {
|
|
16
|
+
emerg: 0,
|
|
17
|
+
alert: 1,
|
|
18
|
+
crit: 2,
|
|
19
|
+
error: 3,
|
|
20
|
+
warning: 4,
|
|
21
|
+
notice: 5,
|
|
22
|
+
info: 6,
|
|
23
|
+
debug: 7,
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Maps MCP log levels to Winston's core levels for file logging.
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
const mcpToWinstonLevel = {
|
|
30
|
+
debug: "debug",
|
|
31
|
+
info: "info",
|
|
32
|
+
notice: "info",
|
|
33
|
+
warning: "warn",
|
|
34
|
+
error: "error",
|
|
35
|
+
crit: "error",
|
|
36
|
+
alert: "error",
|
|
37
|
+
emerg: "error",
|
|
38
|
+
};
|
|
39
|
+
// The logsPath from config is already resolved and validated by src/config/index.ts
|
|
40
|
+
const resolvedLogsDir = config.logsPath;
|
|
41
|
+
const isLogsDirSafe = !!resolvedLogsDir; // If logsPath is set, it's considered safe by config logic.
|
|
42
|
+
/**
|
|
43
|
+
* Creates the Winston console log format.
|
|
44
|
+
* @returns The Winston log format for console output.
|
|
45
|
+
* @private
|
|
46
|
+
*/
|
|
47
|
+
function createWinstonConsoleFormat() {
|
|
48
|
+
return winston.format.combine(winston.format.colorize(), winston.format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), winston.format.printf(({ timestamp, level, message, ...meta }) => {
|
|
49
|
+
let metaString = "";
|
|
50
|
+
const metaCopy = { ...meta };
|
|
51
|
+
if (metaCopy.error && typeof metaCopy.error === "object") {
|
|
52
|
+
const errorObj = metaCopy.error;
|
|
53
|
+
if (errorObj.message)
|
|
54
|
+
metaString += `\n Error: ${errorObj.message}`;
|
|
55
|
+
if (errorObj.stack)
|
|
56
|
+
metaString += `\n Stack: ${String(errorObj.stack)
|
|
57
|
+
.split("\n")
|
|
58
|
+
.map((l) => ` ${l}`)
|
|
59
|
+
.join("\n")}`;
|
|
60
|
+
delete metaCopy.error;
|
|
61
|
+
}
|
|
62
|
+
if (Object.keys(metaCopy).length > 0) {
|
|
63
|
+
try {
|
|
64
|
+
const remainingMetaJson = JSON.stringify(metaCopy, null, 2);
|
|
65
|
+
if (remainingMetaJson !== "{}")
|
|
66
|
+
metaString += `\n Meta: ${remainingMetaJson}`;
|
|
67
|
+
}
|
|
68
|
+
catch (stringifyError) {
|
|
69
|
+
const errorMessage = stringifyError instanceof Error
|
|
70
|
+
? stringifyError.message
|
|
71
|
+
: String(stringifyError);
|
|
72
|
+
metaString += `\n Meta: [Error stringifying metadata: ${errorMessage}]`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return `${timestamp} ${level}: ${message}${metaString}`;
|
|
76
|
+
}));
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Singleton Logger class that wraps Winston for robust logging.
|
|
80
|
+
* Supports file logging, conditional console logging, and MCP notifications.
|
|
81
|
+
*/
|
|
82
|
+
export class Logger {
|
|
83
|
+
/** @private */
|
|
84
|
+
constructor() {
|
|
85
|
+
this.initialized = false;
|
|
86
|
+
this.currentMcpLevel = "info";
|
|
87
|
+
this.currentWinstonLevel = "info";
|
|
88
|
+
this.MCP_NOTIFICATION_STACK_TRACE_MAX_LENGTH = 1024;
|
|
89
|
+
this.LOG_FILE_MAX_SIZE = 5 * 1024 * 1024; // 5MB
|
|
90
|
+
this.LOG_MAX_FILES = 5;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Initializes the Winston logger instance.
|
|
94
|
+
* Should be called once at application startup.
|
|
95
|
+
* @param level - The initial minimum MCP log level.
|
|
96
|
+
*/
|
|
97
|
+
async initialize(level = "info") {
|
|
98
|
+
if (this.initialized) {
|
|
99
|
+
this.warning("Logger already initialized.", {
|
|
100
|
+
loggerSetup: true,
|
|
101
|
+
requestId: "logger-init",
|
|
102
|
+
timestamp: new Date().toISOString(),
|
|
103
|
+
});
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
this.currentMcpLevel = level;
|
|
107
|
+
this.currentWinstonLevel = mcpToWinstonLevel[level];
|
|
108
|
+
let logsDirCreatedMessage = null; // This message is now informational as creation is handled by config
|
|
109
|
+
if (isLogsDirSafe) {
|
|
110
|
+
// Directory creation is handled by config/index.ts ensureDirectory.
|
|
111
|
+
// We can log if it was newly created by checking if it existed before config ran,
|
|
112
|
+
// but that's complex. For now, we assume config handled it.
|
|
113
|
+
// If resolvedLogsDir is set, config ensures it exists.
|
|
114
|
+
if (!fs.existsSync(resolvedLogsDir)) {
|
|
115
|
+
// This case should ideally not be hit if config.logsPath is correctly set up and validated.
|
|
116
|
+
// However, if it somehow occurs (e.g. dir deleted after config init but before logger init),
|
|
117
|
+
// we attempt to create it.
|
|
118
|
+
try {
|
|
119
|
+
await fs.promises.mkdir(resolvedLogsDir, { recursive: true });
|
|
120
|
+
logsDirCreatedMessage = `Re-created logs directory (should have been created by config): ${resolvedLogsDir}`;
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (process.stdout.isTTY) {
|
|
124
|
+
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
125
|
+
console.error(`Error creating logs directory at ${resolvedLogsDir}: ${errorMessage}. File logging disabled.`);
|
|
126
|
+
}
|
|
127
|
+
throw err; // Critical if logs dir cannot be ensured
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const fileFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json());
|
|
132
|
+
const transports = [];
|
|
133
|
+
const fileTransportOptions = {
|
|
134
|
+
format: fileFormat,
|
|
135
|
+
maxsize: this.LOG_FILE_MAX_SIZE,
|
|
136
|
+
maxFiles: this.LOG_MAX_FILES,
|
|
137
|
+
tailable: true,
|
|
138
|
+
};
|
|
139
|
+
if (isLogsDirSafe) {
|
|
140
|
+
transports.push(new winston.transports.File({
|
|
141
|
+
filename: path.join(resolvedLogsDir, "error.log"),
|
|
142
|
+
level: "error",
|
|
143
|
+
...fileTransportOptions,
|
|
144
|
+
}), new winston.transports.File({
|
|
145
|
+
filename: path.join(resolvedLogsDir, "warn.log"),
|
|
146
|
+
level: "warn",
|
|
147
|
+
...fileTransportOptions,
|
|
148
|
+
}), new winston.transports.File({
|
|
149
|
+
filename: path.join(resolvedLogsDir, "info.log"),
|
|
150
|
+
level: "info",
|
|
151
|
+
...fileTransportOptions,
|
|
152
|
+
}), new winston.transports.File({
|
|
153
|
+
filename: path.join(resolvedLogsDir, "debug.log"),
|
|
154
|
+
level: "debug",
|
|
155
|
+
...fileTransportOptions,
|
|
156
|
+
}), new winston.transports.File({
|
|
157
|
+
filename: path.join(resolvedLogsDir, "combined.log"),
|
|
158
|
+
...fileTransportOptions,
|
|
159
|
+
}));
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
if (process.stdout.isTTY) {
|
|
163
|
+
console.warn("File logging disabled as logsPath is not configured or invalid.");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
this.winstonLogger = winston.createLogger({
|
|
167
|
+
level: this.currentWinstonLevel,
|
|
168
|
+
transports,
|
|
169
|
+
exitOnError: false,
|
|
170
|
+
});
|
|
171
|
+
// Configure console transport after Winston logger is created
|
|
172
|
+
const consoleStatus = this._configureConsoleTransport();
|
|
173
|
+
const initialContext = {
|
|
174
|
+
loggerSetup: true,
|
|
175
|
+
requestId: "logger-init-deferred",
|
|
176
|
+
timestamp: new Date().toISOString(),
|
|
177
|
+
};
|
|
178
|
+
if (logsDirCreatedMessage) {
|
|
179
|
+
// Log if we had to re-create it
|
|
180
|
+
this.info(logsDirCreatedMessage, initialContext);
|
|
181
|
+
}
|
|
182
|
+
if (consoleStatus.message) {
|
|
183
|
+
this.info(consoleStatus.message, initialContext);
|
|
184
|
+
}
|
|
185
|
+
this.initialized = true;
|
|
186
|
+
this.info(`Logger initialized. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${consoleStatus.enabled ? "enabled" : "disabled"}`, {
|
|
187
|
+
loggerSetup: true,
|
|
188
|
+
requestId: "logger-post-init",
|
|
189
|
+
timestamp: new Date().toISOString(),
|
|
190
|
+
logsPathUsed: resolvedLogsDir,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Sets the function used to send MCP 'notifications/message'.
|
|
195
|
+
* @param sender - The function to call for sending notifications, or undefined to disable.
|
|
196
|
+
*/
|
|
197
|
+
setMcpNotificationSender(sender) {
|
|
198
|
+
this.mcpNotificationSender = sender;
|
|
199
|
+
const status = sender ? "enabled" : "disabled";
|
|
200
|
+
this.info(`MCP notification sending ${status}.`, {
|
|
201
|
+
loggerSetup: true,
|
|
202
|
+
requestId: "logger-set-sender",
|
|
203
|
+
timestamp: new Date().toISOString(),
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Dynamically sets the minimum logging level.
|
|
208
|
+
* @param newLevel - The new minimum MCP log level to set.
|
|
209
|
+
*/
|
|
210
|
+
setLevel(newLevel) {
|
|
211
|
+
const setLevelContext = {
|
|
212
|
+
loggerSetup: true,
|
|
213
|
+
requestId: "logger-set-level",
|
|
214
|
+
timestamp: new Date().toISOString(),
|
|
215
|
+
};
|
|
216
|
+
if (!this.ensureInitialized()) {
|
|
217
|
+
if (process.stdout.isTTY) {
|
|
218
|
+
console.error("Cannot set level: Logger not initialized.");
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (!(newLevel in mcpLevelSeverity)) {
|
|
223
|
+
this.warning(`Invalid MCP log level provided: ${newLevel}. Level not changed.`, setLevelContext);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
const oldLevel = this.currentMcpLevel;
|
|
227
|
+
this.currentMcpLevel = newLevel;
|
|
228
|
+
this.currentWinstonLevel = mcpToWinstonLevel[newLevel];
|
|
229
|
+
if (this.winstonLogger) {
|
|
230
|
+
// Ensure winstonLogger is defined
|
|
231
|
+
this.winstonLogger.level = this.currentWinstonLevel;
|
|
232
|
+
}
|
|
233
|
+
const consoleStatus = this._configureConsoleTransport();
|
|
234
|
+
if (oldLevel !== newLevel) {
|
|
235
|
+
this.info(`Log level changed. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${consoleStatus.enabled ? "enabled" : "disabled"}`, setLevelContext);
|
|
236
|
+
if (consoleStatus.message &&
|
|
237
|
+
consoleStatus.message !== "Console logging status unchanged.") {
|
|
238
|
+
this.info(consoleStatus.message, setLevelContext);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Configures the console transport based on the current log level and TTY status.
|
|
244
|
+
* Adds or removes the console transport as needed.
|
|
245
|
+
* @returns {{ enabled: boolean, message: string | null }} Status of console logging.
|
|
246
|
+
* @private
|
|
247
|
+
*/
|
|
248
|
+
_configureConsoleTransport() {
|
|
249
|
+
if (!this.winstonLogger) {
|
|
250
|
+
return {
|
|
251
|
+
enabled: false,
|
|
252
|
+
message: "Cannot configure console: Winston logger not initialized.",
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
const consoleTransport = this.winstonLogger.transports.find((t) => t instanceof winston.transports.Console);
|
|
256
|
+
const shouldHaveConsole = this.currentMcpLevel === "debug" && process.stdout.isTTY;
|
|
257
|
+
let message = null;
|
|
258
|
+
if (shouldHaveConsole && !consoleTransport) {
|
|
259
|
+
const consoleFormat = createWinstonConsoleFormat();
|
|
260
|
+
this.winstonLogger.add(new winston.transports.Console({
|
|
261
|
+
level: "debug", // Console always logs debug if enabled
|
|
262
|
+
format: consoleFormat,
|
|
263
|
+
}));
|
|
264
|
+
message = "Console logging enabled (level: debug, stdout is TTY).";
|
|
265
|
+
}
|
|
266
|
+
else if (!shouldHaveConsole && consoleTransport) {
|
|
267
|
+
this.winstonLogger.remove(consoleTransport);
|
|
268
|
+
message = "Console logging disabled (level not debug or stdout not TTY).";
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
message = "Console logging status unchanged.";
|
|
272
|
+
}
|
|
273
|
+
return { enabled: shouldHaveConsole, message };
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Gets the singleton instance of the Logger.
|
|
277
|
+
* @returns The singleton Logger instance.
|
|
278
|
+
*/
|
|
279
|
+
static getInstance() {
|
|
280
|
+
if (!Logger.instance) {
|
|
281
|
+
Logger.instance = new Logger();
|
|
282
|
+
}
|
|
283
|
+
return Logger.instance;
|
|
284
|
+
}
|
|
285
|
+
/**
|
|
286
|
+
* Ensures the logger has been initialized.
|
|
287
|
+
* @returns True if initialized, false otherwise.
|
|
288
|
+
* @private
|
|
289
|
+
*/
|
|
290
|
+
ensureInitialized() {
|
|
291
|
+
if (!this.initialized || !this.winstonLogger) {
|
|
292
|
+
if (process.stdout.isTTY) {
|
|
293
|
+
console.warn("Logger not initialized; message dropped.");
|
|
294
|
+
}
|
|
295
|
+
return false;
|
|
296
|
+
}
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* Centralized log processing method.
|
|
301
|
+
* @param level - The MCP severity level of the message.
|
|
302
|
+
* @param msg - The main log message.
|
|
303
|
+
* @param context - Optional request context for the log.
|
|
304
|
+
* @param error - Optional error object associated with the log.
|
|
305
|
+
* @private
|
|
306
|
+
*/
|
|
307
|
+
log(level, msg, context, error) {
|
|
308
|
+
if (!this.ensureInitialized())
|
|
309
|
+
return;
|
|
310
|
+
if (mcpLevelSeverity[level] > mcpLevelSeverity[this.currentMcpLevel]) {
|
|
311
|
+
return; // Do not log if message level is less severe than currentMcpLevel
|
|
312
|
+
}
|
|
313
|
+
const logData = { ...context };
|
|
314
|
+
const winstonLevel = mcpToWinstonLevel[level];
|
|
315
|
+
if (error) {
|
|
316
|
+
this.winstonLogger.log(winstonLevel, msg, { ...logData, error });
|
|
317
|
+
}
|
|
318
|
+
else {
|
|
319
|
+
this.winstonLogger.log(winstonLevel, msg, logData);
|
|
320
|
+
}
|
|
321
|
+
if (this.mcpNotificationSender) {
|
|
322
|
+
const mcpDataPayload = { message: msg };
|
|
323
|
+
if (context && Object.keys(context).length > 0)
|
|
324
|
+
mcpDataPayload.context = context;
|
|
325
|
+
if (error) {
|
|
326
|
+
mcpDataPayload.error = { message: error.message };
|
|
327
|
+
// Include stack trace in debug mode for MCP notifications, truncated for brevity
|
|
328
|
+
if (this.currentMcpLevel === "debug" && error.stack) {
|
|
329
|
+
mcpDataPayload.error.stack = error.stack.substring(0, this.MCP_NOTIFICATION_STACK_TRACE_MAX_LENGTH);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
try {
|
|
333
|
+
const serverName = config?.mcpServerName ?? "MCP_SERVER_NAME_NOT_CONFIGURED";
|
|
334
|
+
this.mcpNotificationSender(level, mcpDataPayload, serverName);
|
|
335
|
+
}
|
|
336
|
+
catch (sendError) {
|
|
337
|
+
const errorMessage = sendError instanceof Error ? sendError.message : String(sendError);
|
|
338
|
+
const internalErrorContext = {
|
|
339
|
+
requestId: context?.requestId || "logger-internal-error",
|
|
340
|
+
timestamp: new Date().toISOString(),
|
|
341
|
+
originalLevel: level,
|
|
342
|
+
originalMessage: msg,
|
|
343
|
+
sendError: errorMessage,
|
|
344
|
+
mcpPayload: JSON.stringify(mcpDataPayload).substring(0, 500), // Log a preview
|
|
345
|
+
};
|
|
346
|
+
this.winstonLogger.error("Failed to send MCP log notification", internalErrorContext);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
/** Logs a message at the 'debug' level. */
|
|
351
|
+
debug(msg, context) {
|
|
352
|
+
this.log("debug", msg, context);
|
|
353
|
+
}
|
|
354
|
+
/** Logs a message at the 'info' level. */
|
|
355
|
+
info(msg, context) {
|
|
356
|
+
this.log("info", msg, context);
|
|
357
|
+
}
|
|
358
|
+
/** Logs a message at the 'notice' level. */
|
|
359
|
+
notice(msg, context) {
|
|
360
|
+
this.log("notice", msg, context);
|
|
361
|
+
}
|
|
362
|
+
/** Logs a message at the 'warning' level. */
|
|
363
|
+
warning(msg, context) {
|
|
364
|
+
this.log("warning", msg, context);
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Logs a message at the 'error' level.
|
|
368
|
+
* @param msg - The main log message.
|
|
369
|
+
* @param err - Optional. Error object or RequestContext.
|
|
370
|
+
* @param context - Optional. RequestContext if `err` is an Error.
|
|
371
|
+
*/
|
|
372
|
+
error(msg, err, context) {
|
|
373
|
+
const errorObj = err instanceof Error ? err : undefined;
|
|
374
|
+
const actualContext = err instanceof Error ? context : err;
|
|
375
|
+
this.log("error", msg, actualContext, errorObj);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Logs a message at the 'crit' (critical) level.
|
|
379
|
+
* @param msg - The main log message.
|
|
380
|
+
* @param err - Optional. Error object or RequestContext.
|
|
381
|
+
* @param context - Optional. RequestContext if `err` is an Error.
|
|
382
|
+
*/
|
|
383
|
+
crit(msg, err, context) {
|
|
384
|
+
const errorObj = err instanceof Error ? err : undefined;
|
|
385
|
+
const actualContext = err instanceof Error ? context : err;
|
|
386
|
+
this.log("crit", msg, actualContext, errorObj);
|
|
387
|
+
}
|
|
388
|
+
/**
|
|
389
|
+
* Logs a message at the 'alert' level.
|
|
390
|
+
* @param msg - The main log message.
|
|
391
|
+
* @param err - Optional. Error object or RequestContext.
|
|
392
|
+
* @param context - Optional. RequestContext if `err` is an Error.
|
|
393
|
+
*/
|
|
394
|
+
alert(msg, err, context) {
|
|
395
|
+
const errorObj = err instanceof Error ? err : undefined;
|
|
396
|
+
const actualContext = err instanceof Error ? context : err;
|
|
397
|
+
this.log("alert", msg, actualContext, errorObj);
|
|
398
|
+
}
|
|
399
|
+
/**
|
|
400
|
+
* Logs a message at the 'emerg' (emergency) level.
|
|
401
|
+
* @param msg - The main log message.
|
|
402
|
+
* @param err - Optional. Error object or RequestContext.
|
|
403
|
+
* @param context - Optional. RequestContext if `err` is an Error.
|
|
404
|
+
*/
|
|
405
|
+
emerg(msg, err, context) {
|
|
406
|
+
const errorObj = err instanceof Error ? err : undefined;
|
|
407
|
+
const actualContext = err instanceof Error ? context : err;
|
|
408
|
+
this.log("emerg", msg, actualContext, errorObj);
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Logs a message at the 'emerg' (emergency) level, typically for fatal errors.
|
|
412
|
+
* @param msg - The main log message.
|
|
413
|
+
* @param err - Optional. Error object or RequestContext.
|
|
414
|
+
* @param context - Optional. RequestContext if `err` is an Error.
|
|
415
|
+
*/
|
|
416
|
+
fatal(msg, err, context) {
|
|
417
|
+
const errorObj = err instanceof Error ? err : undefined;
|
|
418
|
+
const actualContext = err instanceof Error ? context : err;
|
|
419
|
+
this.log("emerg", msg, actualContext, errorObj);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* The singleton instance of the Logger.
|
|
424
|
+
* Use this instance for all logging operations.
|
|
425
|
+
*/
|
|
426
|
+
export const logger = Logger.getInstance();
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Utilities for creating and managing request contexts.
|
|
3
|
+
* A request context is an object carrying a unique ID, timestamp, and other
|
|
4
|
+
* relevant data for logging, tracing, and processing. It also defines
|
|
5
|
+
* configuration and operational context structures.
|
|
6
|
+
* @module src/utils/internal/requestContext
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Defines the core structure for context information associated with a request or operation.
|
|
10
|
+
* This is fundamental for logging, tracing, and passing operational data.
|
|
11
|
+
*/
|
|
12
|
+
export interface RequestContext {
|
|
13
|
+
/**
|
|
14
|
+
* Unique ID for the context instance.
|
|
15
|
+
* Used for log correlation and request tracing.
|
|
16
|
+
*/
|
|
17
|
+
requestId: string;
|
|
18
|
+
/**
|
|
19
|
+
* ISO 8601 timestamp indicating when the context was created.
|
|
20
|
+
*/
|
|
21
|
+
timestamp: string;
|
|
22
|
+
/**
|
|
23
|
+
* Allows arbitrary key-value pairs for specific context needs.
|
|
24
|
+
* Using `unknown` promotes type-safe access.
|
|
25
|
+
* Consumers must type-check/assert when accessing extended properties.
|
|
26
|
+
*/
|
|
27
|
+
[key: string]: unknown;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Configuration for the {@link requestContextService}.
|
|
31
|
+
* Allows for future extensibility of service-wide settings.
|
|
32
|
+
*/
|
|
33
|
+
export interface ContextConfig {
|
|
34
|
+
/** Custom configuration properties. Allows for arbitrary key-value pairs. */
|
|
35
|
+
[key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Represents a broader context for a specific operation or task.
|
|
39
|
+
* It can optionally include a base {@link RequestContext} and other custom properties
|
|
40
|
+
* relevant to the operation.
|
|
41
|
+
*/
|
|
42
|
+
export interface OperationContext {
|
|
43
|
+
/** Optional base request context data, adhering to the `RequestContext` structure. */
|
|
44
|
+
requestContext?: RequestContext;
|
|
45
|
+
/** Allows for additional, custom properties specific to the operation. */
|
|
46
|
+
[key: string]: unknown;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Primary export for request context functionalities.
|
|
50
|
+
* This service provides methods to create and manage {@link RequestContext} instances,
|
|
51
|
+
* which are essential for logging, tracing, and correlating operations.
|
|
52
|
+
*/
|
|
53
|
+
export declare const requestContextService: {
|
|
54
|
+
/**
|
|
55
|
+
* Internal configuration store for the service.
|
|
56
|
+
*/
|
|
57
|
+
config: ContextConfig;
|
|
58
|
+
/**
|
|
59
|
+
* Configures the request context service with new settings.
|
|
60
|
+
* Merges the provided partial configuration with existing settings.
|
|
61
|
+
*
|
|
62
|
+
* @param config - A partial `ContextConfig` object containing settings to update or add.
|
|
63
|
+
* @returns A shallow copy of the newly updated configuration.
|
|
64
|
+
*/
|
|
65
|
+
configure(config: Partial<ContextConfig>): ContextConfig;
|
|
66
|
+
/**
|
|
67
|
+
* Retrieves a shallow copy of the current service configuration.
|
|
68
|
+
* This prevents direct mutation of the internal configuration state.
|
|
69
|
+
*
|
|
70
|
+
* @returns A shallow copy of the current `ContextConfig`.
|
|
71
|
+
*/
|
|
72
|
+
getConfig(): ContextConfig;
|
|
73
|
+
/**
|
|
74
|
+
* Creates a new {@link RequestContext} instance.
|
|
75
|
+
* Each context is assigned a unique `requestId` (UUID) and a current `timestamp` (ISO 8601).
|
|
76
|
+
* Additional custom properties can be merged into the context.
|
|
77
|
+
*
|
|
78
|
+
* @param additionalContext - An optional record of key-value pairs to be
|
|
79
|
+
* included in the created request context.
|
|
80
|
+
* @returns A new `RequestContext` object.
|
|
81
|
+
*/
|
|
82
|
+
createRequestContext(additionalContext?: Record<string, unknown>): RequestContext;
|
|
83
|
+
};
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Utilities for creating and managing request contexts.
|
|
3
|
+
* A request context is an object carrying a unique ID, timestamp, and other
|
|
4
|
+
* relevant data for logging, tracing, and processing. It also defines
|
|
5
|
+
* configuration and operational context structures.
|
|
6
|
+
* @module src/utils/internal/requestContext
|
|
7
|
+
*/
|
|
8
|
+
import { generateUUID } from "../index.js";
|
|
9
|
+
import { logger } from "./logger.js";
|
|
10
|
+
/**
|
|
11
|
+
* Singleton-like service object for managing request context operations.
|
|
12
|
+
* @private
|
|
13
|
+
*/
|
|
14
|
+
const requestContextServiceInstance = {
|
|
15
|
+
/**
|
|
16
|
+
* Internal configuration store for the service.
|
|
17
|
+
*/
|
|
18
|
+
config: {},
|
|
19
|
+
/**
|
|
20
|
+
* Configures the request context service with new settings.
|
|
21
|
+
* Merges the provided partial configuration with existing settings.
|
|
22
|
+
*
|
|
23
|
+
* @param config - A partial `ContextConfig` object containing settings to update or add.
|
|
24
|
+
* @returns A shallow copy of the newly updated configuration.
|
|
25
|
+
*/
|
|
26
|
+
configure(config) {
|
|
27
|
+
this.config = {
|
|
28
|
+
...this.config,
|
|
29
|
+
...config,
|
|
30
|
+
};
|
|
31
|
+
const logContext = this.createRequestContext({
|
|
32
|
+
operation: "RequestContextService.configure",
|
|
33
|
+
newConfigState: { ...this.config },
|
|
34
|
+
});
|
|
35
|
+
logger.debug("RequestContextService configuration updated", logContext);
|
|
36
|
+
return { ...this.config };
|
|
37
|
+
},
|
|
38
|
+
/**
|
|
39
|
+
* Retrieves a shallow copy of the current service configuration.
|
|
40
|
+
* This prevents direct mutation of the internal configuration state.
|
|
41
|
+
*
|
|
42
|
+
* @returns A shallow copy of the current `ContextConfig`.
|
|
43
|
+
*/
|
|
44
|
+
getConfig() {
|
|
45
|
+
return { ...this.config };
|
|
46
|
+
},
|
|
47
|
+
/**
|
|
48
|
+
* Creates a new {@link RequestContext} instance.
|
|
49
|
+
* Each context is assigned a unique `requestId` (UUID) and a current `timestamp` (ISO 8601).
|
|
50
|
+
* Additional custom properties can be merged into the context.
|
|
51
|
+
*
|
|
52
|
+
* @param additionalContext - An optional record of key-value pairs to be
|
|
53
|
+
* included in the created request context.
|
|
54
|
+
* @returns A new `RequestContext` object.
|
|
55
|
+
*/
|
|
56
|
+
createRequestContext(additionalContext = {}) {
|
|
57
|
+
const requestId = generateUUID();
|
|
58
|
+
const timestamp = new Date().toISOString();
|
|
59
|
+
const context = {
|
|
60
|
+
requestId,
|
|
61
|
+
timestamp,
|
|
62
|
+
...additionalContext,
|
|
63
|
+
};
|
|
64
|
+
return context;
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Primary export for request context functionalities.
|
|
69
|
+
* This service provides methods to create and manage {@link RequestContext} instances,
|
|
70
|
+
* which are essential for logging, tracing, and correlating operations.
|
|
71
|
+
*/
|
|
72
|
+
export const requestContextService = requestContextServiceInstance;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Provides utility functions for counting tokens in text and chat messages
|
|
3
|
+
* using the `tiktoken` library, specifically configured for 'gpt-4o' tokenization.
|
|
4
|
+
* These functions are essential for managing token limits and estimating costs
|
|
5
|
+
* when interacting with language models.
|
|
6
|
+
* @module src/utils/metrics/tokenCounter
|
|
7
|
+
*/
|
|
8
|
+
import { ChatCompletionMessageParam } from "openai/resources/chat/completions";
|
|
9
|
+
import { RequestContext } from "../index.js";
|
|
10
|
+
/**
|
|
11
|
+
* Calculates the number of tokens for a given text string using the
|
|
12
|
+
* tokenizer specified by `TOKENIZATION_MODEL`.
|
|
13
|
+
* Wraps tokenization in `ErrorHandler.tryCatch` for robust error management.
|
|
14
|
+
*
|
|
15
|
+
* @param text - The input text to tokenize.
|
|
16
|
+
* @param context - Optional request context for logging and error handling.
|
|
17
|
+
* @returns A promise that resolves with the number of tokens in the text.
|
|
18
|
+
* @throws {McpError} If tokenization fails.
|
|
19
|
+
*/
|
|
20
|
+
export declare function countTokens(text: string, context?: RequestContext): Promise<number>;
|
|
21
|
+
/**
|
|
22
|
+
* Calculates the estimated number of tokens for an array of chat messages.
|
|
23
|
+
* Uses the tokenizer specified by `TOKENIZATION_MODEL` and accounts for
|
|
24
|
+
* special tokens and message overhead according to OpenAI's guidelines.
|
|
25
|
+
*
|
|
26
|
+
* For multi-part content, only text parts are currently tokenized.
|
|
27
|
+
*
|
|
28
|
+
* Reference: {@link https://github.com/openai/openai-cookbook/blob/main/examples/How_to_count_tokens_with_tiktoken.ipynb}
|
|
29
|
+
*
|
|
30
|
+
* @param messages - An array of chat messages.
|
|
31
|
+
* @param context - Optional request context for logging and error handling.
|
|
32
|
+
* @returns A promise that resolves with the estimated total number of tokens.
|
|
33
|
+
* @throws {McpError} If tokenization fails.
|
|
34
|
+
*/
|
|
35
|
+
export declare function countChatTokens(messages: ReadonlyArray<ChatCompletionMessageParam>, context?: RequestContext): Promise<number>;
|