@cyanheads/git-mcp-server 2.0.2 → 2.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +45 -85
  2. package/dist/config/index.js +16 -18
  3. package/dist/index.js +80 -30
  4. package/dist/mcp-server/server.js +247 -523
  5. package/dist/mcp-server/tools/gitAdd/logic.js +9 -6
  6. package/dist/mcp-server/tools/gitAdd/registration.js +7 -4
  7. package/dist/mcp-server/tools/gitBranch/logic.js +23 -12
  8. package/dist/mcp-server/tools/gitBranch/registration.js +8 -5
  9. package/dist/mcp-server/tools/gitCheckout/logic.js +92 -44
  10. package/dist/mcp-server/tools/gitCheckout/registration.js +8 -5
  11. package/dist/mcp-server/tools/gitCherryPick/logic.js +10 -7
  12. package/dist/mcp-server/tools/gitCherryPick/registration.js +8 -5
  13. package/dist/mcp-server/tools/gitClean/logic.js +9 -6
  14. package/dist/mcp-server/tools/gitClean/registration.js +8 -5
  15. package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +3 -2
  16. package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +7 -4
  17. package/dist/mcp-server/tools/gitClone/logic.js +8 -5
  18. package/dist/mcp-server/tools/gitClone/registration.js +7 -4
  19. package/dist/mcp-server/tools/gitCommit/logic.js +98 -20
  20. package/dist/mcp-server/tools/gitCommit/registration.js +22 -15
  21. package/dist/mcp-server/tools/gitDiff/logic.js +9 -6
  22. package/dist/mcp-server/tools/gitDiff/registration.js +8 -5
  23. package/dist/mcp-server/tools/gitFetch/logic.js +10 -7
  24. package/dist/mcp-server/tools/gitFetch/registration.js +8 -5
  25. package/dist/mcp-server/tools/gitInit/index.js +2 -2
  26. package/dist/mcp-server/tools/gitInit/logic.js +9 -6
  27. package/dist/mcp-server/tools/gitInit/registration.js +66 -12
  28. package/dist/mcp-server/tools/gitLog/logic.js +53 -16
  29. package/dist/mcp-server/tools/gitLog/registration.js +8 -5
  30. package/dist/mcp-server/tools/gitMerge/logic.js +9 -6
  31. package/dist/mcp-server/tools/gitMerge/registration.js +8 -5
  32. package/dist/mcp-server/tools/gitPull/logic.js +11 -8
  33. package/dist/mcp-server/tools/gitPull/registration.js +7 -4
  34. package/dist/mcp-server/tools/gitPush/logic.js +12 -9
  35. package/dist/mcp-server/tools/gitPush/registration.js +7 -4
  36. package/dist/mcp-server/tools/gitRebase/logic.js +9 -6
  37. package/dist/mcp-server/tools/gitRebase/registration.js +8 -5
  38. package/dist/mcp-server/tools/gitRemote/logic.js +4 -5
  39. package/dist/mcp-server/tools/gitRemote/registration.js +2 -4
  40. package/dist/mcp-server/tools/gitReset/logic.js +5 -6
  41. package/dist/mcp-server/tools/gitReset/registration.js +2 -4
  42. package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +5 -6
  43. package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +22 -13
  44. package/dist/mcp-server/tools/gitShow/logic.js +5 -6
  45. package/dist/mcp-server/tools/gitShow/registration.js +3 -5
  46. package/dist/mcp-server/tools/gitStash/logic.js +5 -6
  47. package/dist/mcp-server/tools/gitStash/registration.js +3 -5
  48. package/dist/mcp-server/tools/gitStatus/logic.js +5 -6
  49. package/dist/mcp-server/tools/gitStatus/registration.js +2 -4
  50. package/dist/mcp-server/tools/gitTag/logic.js +3 -4
  51. package/dist/mcp-server/tools/gitTag/registration.js +2 -4
  52. package/dist/mcp-server/transports/authentication/authMiddleware.js +145 -0
  53. package/dist/mcp-server/transports/httpTransport.js +432 -0
  54. package/dist/mcp-server/transports/stdioTransport.js +87 -0
  55. package/dist/types-global/errors.js +2 -2
  56. package/dist/utils/index.js +12 -11
  57. package/dist/utils/{errorHandler.js → internal/errorHandler.js} +18 -8
  58. package/dist/utils/internal/index.js +3 -0
  59. package/dist/utils/internal/logger.js +254 -0
  60. package/dist/utils/{requestContext.js → internal/requestContext.js} +2 -3
  61. package/dist/utils/metrics/index.js +1 -0
  62. package/dist/utils/{tokenCounter.js → metrics/tokenCounter.js} +3 -3
  63. package/dist/utils/parsing/dateParser.js +62 -0
  64. package/dist/utils/parsing/index.js +2 -0
  65. package/dist/utils/{jsonParser.js → parsing/jsonParser.js} +3 -2
  66. package/dist/utils/{idGenerator.js → security/idGenerator.js} +4 -5
  67. package/dist/utils/security/index.js +3 -0
  68. package/dist/utils/{rateLimiter.js → security/rateLimiter.js} +7 -10
  69. package/dist/utils/{sanitization.js → security/sanitization.js} +4 -3
  70. package/package.json +12 -9
  71. package/dist/types-global/mcp.js +0 -59
  72. package/dist/types-global/tool.js +0 -1
  73. package/dist/utils/logger.js +0 -266
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Handles the setup and connection for the Stdio MCP transport.
3
+ * Implements the MCP Specification 2025-03-26 for stdio transport.
4
+ * This transport communicates directly over standard input (stdin) and
5
+ * standard output (stdout), typically used when the MCP server is launched
6
+ * as a child process by a host application.
7
+ *
8
+ * Specification Reference:
9
+ * https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/transports.mdx#stdio
10
+ *
11
+ * --- Authentication Note ---
12
+ * As per the MCP Authorization Specification (2025-03-26, Section 1.2),
13
+ * STDIO transports SHOULD NOT implement HTTP-based authentication flows.
14
+ * Authorization is typically handled implicitly by the host application
15
+ * controlling the server process. This implementation follows that guideline.
16
+ *
17
+ * @see {@link https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/authorization.mdx | MCP Authorization Specification}
18
+ */
19
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
20
+ // Import core utilities: ErrorHandler for centralized error management and logger for logging.
21
+ import { ErrorHandler, logger } from '../../utils/index.js';
22
+ // --- Stdio Session State ---
23
+ // Since stdio typically involves a single, persistent connection managed by a parent,
24
+ // we manage a single working directory state for the entire process.
25
+ let currentWorkingDirectory = undefined; // Initialize as undefined
26
+ /**
27
+ * Gets the current working directory set for the stdio session.
28
+ * @returns {string | undefined} The current working directory path or undefined if not set.
29
+ */
30
+ export function getStdioWorkingDirectory() {
31
+ return currentWorkingDirectory;
32
+ }
33
+ /**
34
+ * Sets the working directory for the stdio session.
35
+ * @param {string} dir - The new working directory path.
36
+ */
37
+ export function setStdioWorkingDirectory(dir) {
38
+ currentWorkingDirectory = dir;
39
+ logger.info(`Stdio working directory set to: ${dir}`, { operation: 'setStdioWorkingDirectory' });
40
+ }
41
+ /**
42
+ * Connects a given McpServer instance to the Stdio transport. (Asynchronous)
43
+ * Initializes the SDK's StdioServerTransport, which handles reading newline-delimited
44
+ * JSON-RPC messages from process.stdin and writing corresponding messages to process.stdout,
45
+ * adhering to the MCP stdio transport specification.
46
+ *
47
+ * MCP Spec Points Covered by SDK's StdioServerTransport:
48
+ * - Reads JSON-RPC messages (requests, notifications, responses, batches) from stdin.
49
+ * - Writes JSON-RPC messages to stdout.
50
+ * - Handles newline delimiters and ensures no embedded newlines in output messages.
51
+ * - Ensures only valid MCP messages are written to stdout.
52
+ *
53
+ * Note: Logging via the `logger` utility MAY result in output to stderr, which is
54
+ * permitted by the spec for logging purposes.
55
+ *
56
+ * @param {McpServer} server - The McpServer instance containing the core logic (tools, resources).
57
+ * @param {Record<string, any>} context - Logging context for correlation.
58
+ * @returns {Promise<void>} A promise that resolves when the connection is successfully established.
59
+ * @throws {Error} Throws an error if the connection fails during setup (e.g., issues connecting server to transport).
60
+ */
61
+ export async function connectStdioTransport(server, context) {
62
+ // Add a specific operation name to the context for better log filtering.
63
+ const operationContext = { ...context, operation: 'connectStdioTransport', transportType: 'Stdio' };
64
+ logger.debug('Attempting to connect stdio transport...', operationContext);
65
+ try {
66
+ logger.debug('Creating StdioServerTransport instance...', operationContext);
67
+ // Instantiate the transport provided by the SDK for standard I/O communication.
68
+ // This class encapsulates the logic for reading from stdin and writing to stdout
69
+ // according to the MCP stdio spec.
70
+ const transport = new StdioServerTransport();
71
+ logger.debug('Connecting McpServer instance to StdioServerTransport...', operationContext);
72
+ // Establish the link between the server's core logic and the transport layer.
73
+ // This internally starts the necessary listeners on process.stdin.
74
+ await server.connect(transport);
75
+ // Log successful connection. The server is now ready to process messages via stdio.
76
+ logger.info('MCP Server connected and listening via stdio transport.', operationContext);
77
+ // Use console.log for prominent startup message visibility when run directly.
78
+ console.log(`\n🚀 MCP Server running in STDIO mode.\n (MCP Spec: 2025-03-26 Stdio Transport)\n`);
79
+ }
80
+ catch (err) {
81
+ // Catch and handle any critical errors during the transport connection setup.
82
+ // Mark as critical because the server cannot function without a connected transport.
83
+ ErrorHandler.handleError(err, { ...operationContext, critical: true });
84
+ // Rethrow the error to signal the failure to the calling code (e.g., the main server startup).
85
+ throw err;
86
+ }
87
+ }
@@ -15,6 +15,8 @@ export var BaseErrorCode;
15
15
  BaseErrorCode["CONFLICT"] = "CONFLICT";
16
16
  /** The request failed due to invalid input parameters or data. */
17
17
  BaseErrorCode["VALIDATION_ERROR"] = "VALIDATION_ERROR";
18
+ /** An error occurred while parsing input data (e.g., date string, JSON). */
19
+ BaseErrorCode["PARSING_ERROR"] = "PARSING_ERROR";
18
20
  /** The request was rejected because the client has exceeded rate limits. */
19
21
  BaseErrorCode["RATE_LIMITED"] = "RATE_LIMITED";
20
22
  /** The request timed out before a response could be generated. */
@@ -27,8 +29,6 @@ export var BaseErrorCode;
27
29
  BaseErrorCode["UNKNOWN_ERROR"] = "UNKNOWN_ERROR";
28
30
  /** An error occurred during the loading or validation of configuration data. */
29
31
  BaseErrorCode["CONFIGURATION_ERROR"] = "CONFIGURATION_ERROR";
30
- /** An error occurred related to network connectivity (e.g., DNS resolution, connection refused). */
31
- BaseErrorCode["NETWORK_ERROR"] = "NETWORK_ERROR";
32
32
  })(BaseErrorCode || (BaseErrorCode = {}));
33
33
  /**
34
34
  * Custom error class for MCP-specific errors.
@@ -1,11 +1,12 @@
1
- // Re-export all utilities using wildcard exports for simplicity
2
- export * from './requestContext.js';
3
- export * from './errorHandler.js';
4
- export * from './idGenerator.js';
5
- export * from './logger.js';
6
- export * from './rateLimiter.js';
7
- export * from './sanitization.js';
8
- export * from './tokenCounter.js';
9
- export * from './jsonParser.js';
10
- // No need for explicit named imports/exports or default export
11
- // when using wildcard exports for a simple barrel file.
1
+ // Re-export all utilities from their categorized subdirectories
2
+ export * from './internal/index.js';
3
+ export * from './parsing/index.js';
4
+ export * from './security/index.js';
5
+ export * from './metrics/index.js';
6
+ // It's good practice to have index.ts files in each subdirectory
7
+ // that export the contents of that directory.
8
+ // Assuming those will be created or already exist.
9
+ // If not, this might need adjustment to export specific files, e.g.:
10
+ // export * from './internal/errorHandler.js';
11
+ // export * from './internal/logger.js';
12
+ // ... etc.
@@ -1,6 +1,6 @@
1
- import { BaseErrorCode, McpError } from '../types-global/errors.js';
1
+ import { BaseErrorCode, McpError } from '../../types-global/errors.js'; // Corrected path
2
2
  import { logger } from './logger.js';
3
- import { sanitizeInputForLogging } from './sanitization.js'; // Updated import
3
+ import { sanitizeInputForLogging } from '../index.js'; // Import from main barrel file
4
4
  /**
5
5
  * Simple mapper that maps error types to error codes
6
6
  */
@@ -115,7 +115,9 @@ export class ErrorHandler {
115
115
  if (error instanceof McpError) {
116
116
  // Add any additional context
117
117
  if (context && Object.keys(context).length > 0) {
118
- error.details = { ...error.details, ...context };
118
+ // Ensure details is an object before spreading
119
+ const existingDetails = typeof error.details === 'object' && error.details !== null ? error.details : {};
120
+ error.details = { ...existingDetails, ...context };
119
121
  }
120
122
  // Log the error with sanitized input
121
123
  logger.error(`Error ${operation}: ${error.message}`, {
@@ -129,13 +131,14 @@ export class ErrorHandler {
129
131
  if (rethrow) {
130
132
  throw error;
131
133
  }
134
+ // Ensure the function returns an Error type
132
135
  return error;
133
136
  }
134
137
  // Sanitize input for logging
135
138
  const sanitizedInput = input ? sanitizeInputForLogging(input) : undefined;
136
139
  // Log the error with consistent format
137
140
  logger.error(`Error ${operation}`, {
138
- error: error instanceof Error ? error.message : String(error),
141
+ error: getErrorMessage(error), // Use helper function
139
142
  errorType: getErrorName(error),
140
143
  input: sanitizedInput,
141
144
  requestId: context?.requestId,
@@ -148,16 +151,22 @@ export class ErrorHandler {
148
151
  ErrorHandler.determineErrorCode(error) ||
149
152
  BaseErrorCode.INTERNAL_ERROR;
150
153
  // Transform to appropriate error type
151
- const transformedError = options.errorMapper
152
- ? options.errorMapper(error)
153
- : new McpError(errorCode, `Error ${operation}: ${error instanceof Error ? error.message : 'Unknown error'}`, {
154
+ let transformedError;
155
+ if (options.errorMapper) {
156
+ transformedError = options.errorMapper(error);
157
+ }
158
+ else {
159
+ transformedError = new McpError(errorCode, `Error ${operation}: ${getErrorMessage(error)}`, // Use helper function
160
+ {
154
161
  originalError: getErrorName(error),
155
162
  ...context
156
163
  });
164
+ }
157
165
  // Rethrow if requested
158
166
  if (rethrow) {
159
167
  throw transformedError;
160
168
  }
169
+ // Ensure the function returns an Error type
161
170
  return transformedError;
162
171
  }
163
172
  /**
@@ -204,7 +213,8 @@ export class ErrorHandler {
204
213
  return {
205
214
  code: error.code,
206
215
  message: error.message,
207
- details: error.details || {}
216
+ // Ensure details is an object
217
+ details: typeof error.details === 'object' && error.details !== null ? error.details : {}
208
218
  };
209
219
  }
210
220
  if (error instanceof Error) {
@@ -0,0 +1,3 @@
1
+ export * from './errorHandler.js';
2
+ export * from './logger.js';
3
+ export * from './requestContext.js';
@@ -0,0 +1,254 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import { fileURLToPath } from 'url';
4
+ import winston from 'winston';
5
+ import { config } from '../../config/index.js';
6
+ // Define the numeric severity for comparison (lower is more severe)
7
+ const mcpLevelSeverity = {
8
+ emerg: 0, alert: 1, crit: 2, error: 3, warning: 4, notice: 5, info: 6, debug: 7
9
+ };
10
+ // Map MCP levels to Winston's core levels for file logging
11
+ const mcpToWinstonLevel = {
12
+ debug: 'debug',
13
+ info: 'info',
14
+ notice: 'info', // Map notice to info for file logging
15
+ warning: 'warn',
16
+ error: 'error',
17
+ crit: 'error', // Map critical levels to error for file logging
18
+ alert: 'error',
19
+ emerg: 'error',
20
+ };
21
+ // Resolve __dirname for ESM
22
+ const __filename = fileURLToPath(import.meta.url);
23
+ const __dirname = path.dirname(__filename);
24
+ // Calculate project root robustly (works from src/ or dist/)
25
+ const isRunningFromDist = __dirname.includes(path.sep + 'dist' + path.sep);
26
+ const levelsToGoUp = isRunningFromDist ? 3 : 2;
27
+ const pathSegments = Array(levelsToGoUp).fill('..');
28
+ const projectRoot = path.resolve(__dirname, ...pathSegments);
29
+ const logsDir = path.join(projectRoot, 'logs');
30
+ // Security: ensure logsDir is within projectRoot
31
+ const resolvedLogsDir = path.resolve(logsDir);
32
+ const isLogsDirSafe = resolvedLogsDir === projectRoot || resolvedLogsDir.startsWith(projectRoot + path.sep);
33
+ if (!isLogsDirSafe) {
34
+ // Use console.error here as logger might not be initialized or safe
35
+ console.error(`FATAL: logs directory "${resolvedLogsDir}" is outside project root "${projectRoot}". File logging disabled.`);
36
+ }
37
+ /**
38
+ * Singleton Logger wrapping Winston, adapted for MCP.
39
+ * Logs to files and optionally sends MCP notifications/message.
40
+ */
41
+ class Logger {
42
+ static instance;
43
+ winstonLogger;
44
+ initialized = false;
45
+ mcpNotificationSender;
46
+ currentMcpLevel = 'info'; // Default MCP level
47
+ currentWinstonLevel = 'info'; // Default Winston level
48
+ constructor() { }
49
+ /**
50
+ * Initialize Winston logger for file transport. Must be called once at app start.
51
+ * Console transport is added conditionally.
52
+ * @param level Initial minimum level to log ('info' default).
53
+ */
54
+ async initialize(level = 'info') {
55
+ if (this.initialized) {
56
+ console.warn('Logger already initialized.');
57
+ return;
58
+ }
59
+ this.currentMcpLevel = level;
60
+ this.currentWinstonLevel = mcpToWinstonLevel[level];
61
+ // Ensure logs directory exists
62
+ if (isLogsDirSafe) {
63
+ try {
64
+ if (!fs.existsSync(resolvedLogsDir)) {
65
+ fs.mkdirSync(resolvedLogsDir, { recursive: true });
66
+ console.log(`Created logs directory: ${resolvedLogsDir}`);
67
+ }
68
+ }
69
+ catch (err) {
70
+ console.error(`Error creating logs directory at ${resolvedLogsDir}: ${err.message}. File logging disabled.`);
71
+ }
72
+ }
73
+ // Common format for files
74
+ const fileFormat = winston.format.combine(winston.format.timestamp(), winston.format.errors({ stack: true }), winston.format.json());
75
+ const transports = [];
76
+ // Add file transports only if the directory is safe
77
+ if (isLogsDirSafe) {
78
+ transports.push(new winston.transports.File({ filename: path.join(resolvedLogsDir, 'error.log'), level: 'error', format: fileFormat }), new winston.transports.File({ filename: path.join(resolvedLogsDir, 'warn.log'), level: 'warn', format: fileFormat }), new winston.transports.File({ filename: path.join(resolvedLogsDir, 'info.log'), level: 'info', format: fileFormat }), new winston.transports.File({ filename: path.join(resolvedLogsDir, 'debug.log'), level: 'debug', format: fileFormat }), new winston.transports.File({ filename: path.join(resolvedLogsDir, 'combined.log'), format: fileFormat }));
79
+ }
80
+ else {
81
+ console.warn("File logging disabled due to unsafe logs directory path.");
82
+ }
83
+ // Conditionally add Console transport only if:
84
+ // 1. MCP level is 'debug'
85
+ // 2. stdout is a TTY (interactive terminal, not piped)
86
+ if (this.currentMcpLevel === 'debug' && process.stdout.isTTY) {
87
+ const consoleFormat = winston.format.combine(winston.format.colorize(), winston.format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }), winston.format.printf(({ timestamp, level, message, ...meta }) => {
88
+ let metaString = '';
89
+ const metaCopy = { ...meta };
90
+ if (metaCopy.error && typeof metaCopy.error === 'object') {
91
+ const errorObj = metaCopy.error;
92
+ if (errorObj.message)
93
+ metaString += `\n Error: ${errorObj.message}`;
94
+ if (errorObj.stack)
95
+ metaString += `\n Stack: ${String(errorObj.stack).split('\n').map((l) => ` ${l}`).join('\n')}`;
96
+ delete metaCopy.error;
97
+ }
98
+ if (Object.keys(metaCopy).length > 0) {
99
+ try {
100
+ const remainingMetaJson = JSON.stringify(metaCopy, null, 2);
101
+ if (remainingMetaJson !== '{}')
102
+ metaString += `\n Meta: ${remainingMetaJson}`;
103
+ }
104
+ catch (stringifyError) {
105
+ metaString += `\n Meta: [Error stringifying metadata: ${stringifyError.message}]`;
106
+ }
107
+ }
108
+ return `${timestamp} ${level}: ${message}${metaString}`;
109
+ }));
110
+ transports.push(new winston.transports.Console({
111
+ level: 'debug',
112
+ format: consoleFormat,
113
+ }));
114
+ console.log(`Console logging enabled at level: debug (stdout is TTY)`);
115
+ }
116
+ else if (this.currentMcpLevel === 'debug' && !process.stdout.isTTY) {
117
+ console.log(`Console logging skipped: Level is debug, but stdout is not a TTY (likely stdio transport).`);
118
+ }
119
+ // Create logger with the initial Winston level and configured transports
120
+ this.winstonLogger = winston.createLogger({
121
+ level: this.currentWinstonLevel,
122
+ transports,
123
+ exitOnError: false
124
+ });
125
+ this.initialized = true;
126
+ await Promise.resolve(); // Yield to event loop
127
+ this.info(`Logger initialized. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${process.stdout.isTTY && this.currentMcpLevel === 'debug' ? 'enabled' : 'disabled'}`);
128
+ }
129
+ /**
130
+ * Sets the function used to send MCP 'notifications/message'.
131
+ */
132
+ setMcpNotificationSender(sender) {
133
+ this.mcpNotificationSender = sender;
134
+ const status = sender ? 'enabled' : 'disabled';
135
+ this.info(`MCP notification sending ${status}.`);
136
+ }
137
+ /**
138
+ * Dynamically sets the minimum logging level.
139
+ */
140
+ setLevel(newLevel) {
141
+ if (!this.ensureInitialized()) {
142
+ console.error("Cannot set level: Logger not initialized.");
143
+ return;
144
+ }
145
+ if (!(newLevel in mcpLevelSeverity)) {
146
+ this.warning(`Invalid MCP log level provided: ${newLevel}. Level not changed.`);
147
+ return;
148
+ }
149
+ const oldLevel = this.currentMcpLevel;
150
+ this.currentMcpLevel = newLevel;
151
+ this.currentWinstonLevel = mcpToWinstonLevel[newLevel];
152
+ this.winstonLogger.level = this.currentWinstonLevel;
153
+ // Add or remove console transport based on the new level and TTY status
154
+ const consoleTransport = this.winstonLogger.transports.find(t => t instanceof winston.transports.Console);
155
+ const shouldHaveConsole = newLevel === 'debug' && process.stdout.isTTY;
156
+ if (shouldHaveConsole && !consoleTransport) {
157
+ // Add console transport
158
+ const consoleFormat = winston.format.combine( /* ... same format as in initialize ... */); // TODO: Extract format to avoid duplication
159
+ this.winstonLogger.add(new winston.transports.Console({ level: 'debug', format: consoleFormat }));
160
+ this.info('Console logging dynamically enabled.');
161
+ }
162
+ else if (!shouldHaveConsole && consoleTransport) {
163
+ // Remove console transport
164
+ this.winstonLogger.remove(consoleTransport);
165
+ this.info('Console logging dynamically disabled.');
166
+ }
167
+ if (oldLevel !== newLevel) {
168
+ this.info(`Log level changed. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}. Console logging: ${shouldHaveConsole ? 'enabled' : 'disabled'}`);
169
+ }
170
+ }
171
+ /** Get singleton instance. */
172
+ static getInstance() {
173
+ if (!Logger.instance) {
174
+ Logger.instance = new Logger();
175
+ }
176
+ return Logger.instance;
177
+ }
178
+ /** Ensures the logger has been initialized. */
179
+ ensureInitialized() {
180
+ if (!this.initialized || !this.winstonLogger) {
181
+ console.warn('Logger not initialized; message dropped.');
182
+ return false;
183
+ }
184
+ return true;
185
+ }
186
+ /** Centralized log processing */
187
+ log(level, msg, context, error) {
188
+ if (!this.ensureInitialized())
189
+ return;
190
+ if (mcpLevelSeverity[level] > mcpLevelSeverity[this.currentMcpLevel]) {
191
+ return;
192
+ }
193
+ const logData = { ...context };
194
+ const winstonLevel = mcpToWinstonLevel[level];
195
+ if (error) {
196
+ this.winstonLogger.log(winstonLevel, msg, { ...logData, error: error });
197
+ }
198
+ else {
199
+ this.winstonLogger.log(winstonLevel, msg, logData);
200
+ }
201
+ if (this.mcpNotificationSender) {
202
+ const mcpDataPayload = { message: msg };
203
+ if (context)
204
+ mcpDataPayload.context = context;
205
+ if (error) {
206
+ mcpDataPayload.error = { message: error.message };
207
+ if (this.currentMcpLevel === 'debug' && error.stack) {
208
+ mcpDataPayload.error.stack = error.stack.substring(0, 500);
209
+ }
210
+ }
211
+ try {
212
+ this.mcpNotificationSender(level, mcpDataPayload, config.mcpServerName);
213
+ }
214
+ catch (sendError) {
215
+ this.winstonLogger.error("Failed to send MCP log notification", {
216
+ originalLevel: level,
217
+ originalMessage: msg,
218
+ sendError: sendError instanceof Error ? sendError.message : String(sendError),
219
+ mcpPayload: mcpDataPayload
220
+ });
221
+ }
222
+ }
223
+ }
224
+ // --- Public Logging Methods ---
225
+ debug(msg, context) { this.log('debug', msg, context); }
226
+ info(msg, context) { this.log('info', msg, context); }
227
+ notice(msg, context) { this.log('notice', msg, context); }
228
+ warning(msg, context) { this.log('warning', msg, context); }
229
+ error(msg, err, context) {
230
+ const errorObj = err instanceof Error ? err : undefined;
231
+ const combinedContext = err instanceof Error ? context : { ...(err || {}), ...(context || {}) };
232
+ this.log('error', msg, combinedContext, errorObj);
233
+ }
234
+ crit(msg, err, context) {
235
+ const errorObj = err instanceof Error ? err : undefined;
236
+ const combinedContext = err instanceof Error ? context : { ...(err || {}), ...(context || {}) };
237
+ this.log('crit', msg, combinedContext, errorObj);
238
+ }
239
+ alert(msg, err, context) {
240
+ const errorObj = err instanceof Error ? err : undefined;
241
+ const combinedContext = err instanceof Error ? context : { ...(err || {}), ...(context || {}) };
242
+ this.log('alert', msg, combinedContext, errorObj);
243
+ }
244
+ emerg(msg, err, context) {
245
+ const errorObj = err instanceof Error ? err : undefined;
246
+ const combinedContext = err instanceof Error ? context : { ...(err || {}), ...(context || {}) };
247
+ this.log('emerg', msg, combinedContext, errorObj);
248
+ }
249
+ fatal(msg, context, error) {
250
+ this.log('emerg', msg, context, error);
251
+ }
252
+ }
253
+ // Export singleton instance
254
+ export const logger = Logger.getInstance();
@@ -1,5 +1,6 @@
1
1
  import { logger } from './logger.js';
2
- import { generateUUID } from './idGenerator.js'; // Import generateUUID
2
+ // Import utils from the main barrel file (generateUUID from ../security/idGenerator.js)
3
+ import { generateUUID } from '../index.js';
3
4
  // Direct instance for request context utilities
4
5
  const requestContextServiceInstance = {
5
6
  config: {},
@@ -39,8 +40,6 @@ const requestContextServiceInstance = {
39
40
  },
40
41
  // generateSecureRandomString function removed as it was unused and redundant
41
42
  };
42
- // Initialize logger message
43
- logger.debug('RequestContext service initialized');
44
43
  // Export the instance directly
45
44
  export const requestContextService = requestContextServiceInstance;
46
45
  // Removed delegate functions and default export for simplicity.
@@ -0,0 +1 @@
1
+ export * from './tokenCounter.js';
@@ -1,7 +1,7 @@
1
1
  import { encoding_for_model } from 'tiktoken';
2
- import { BaseErrorCode } from '../types-global/errors.js'; // Import BaseErrorCode and McpError
3
- import { ErrorHandler } from './errorHandler.js'; // Import ErrorHandler
4
- import { logger } from './logger.js';
2
+ import { BaseErrorCode } from '../../types-global/errors.js';
3
+ // Import utils from the main barrel file (ErrorHandler, logger, RequestContext from ../internal/*)
4
+ import { ErrorHandler, logger } from '../index.js';
5
5
  // Define the model used specifically for token counting
6
6
  const TOKENIZATION_MODEL = 'gpt-4o'; // Note this is strictly for token counting, not the model used for inference
7
7
  /**
@@ -0,0 +1,62 @@
1
+ import * as chrono from 'chrono-node';
2
+ // Import utils from the main barrel file (logger, ErrorHandler, RequestContext from ../internal/*)
3
+ import { logger, ErrorHandler } from '../index.js';
4
+ import { BaseErrorCode } from '../../types-global/errors.js'; // Corrected path
5
+ /**
6
+ * Parses a natural language date string into a Date object.
7
+ *
8
+ * @param text The natural language date string (e.g., "tomorrow", "in 5 days", "2024-01-15").
9
+ * @param context The request context for logging and error tracking.
10
+ * @param refDate Optional reference date for parsing relative dates. Defaults to now.
11
+ * @returns A Date object representing the parsed date, or null if parsing fails.
12
+ * @throws McpError if parsing fails unexpectedly.
13
+ */
14
+ async function parseDateString(text, context, refDate) {
15
+ const operation = 'parseDateString';
16
+ const logContext = { ...context, operation, inputText: text, refDate };
17
+ logger.debug(`Attempting to parse date string: "${text}"`, logContext);
18
+ return await ErrorHandler.tryCatch(async () => {
19
+ const parsedDate = chrono.parseDate(text, refDate, { forwardDate: true });
20
+ if (parsedDate) {
21
+ logger.debug(`Successfully parsed "${text}" to ${parsedDate.toISOString()}`, logContext);
22
+ return parsedDate;
23
+ }
24
+ else {
25
+ logger.warning(`Failed to parse date string: "${text}"`, logContext);
26
+ return null;
27
+ }
28
+ }, {
29
+ operation,
30
+ context: logContext,
31
+ input: { text, refDate },
32
+ errorCode: BaseErrorCode.PARSING_ERROR,
33
+ });
34
+ }
35
+ /**
36
+ * Parses a natural language date string and returns detailed parsing results.
37
+ *
38
+ * @param text The natural language date string.
39
+ * @param context The request context for logging and error tracking.
40
+ * @param refDate Optional reference date for parsing relative dates. Defaults to now.
41
+ * @returns An array of chrono.ParsedResult objects, or an empty array if parsing fails.
42
+ * @throws McpError if parsing fails unexpectedly.
43
+ */
44
+ async function parseDateStringDetailed(text, context, refDate) {
45
+ const operation = 'parseDateStringDetailed';
46
+ const logContext = { ...context, operation, inputText: text, refDate };
47
+ logger.debug(`Attempting detailed parse of date string: "${text}"`, logContext);
48
+ return await ErrorHandler.tryCatch(async () => {
49
+ const results = chrono.parse(text, refDate, { forwardDate: true });
50
+ logger.debug(`Detailed parse of "${text}" resulted in ${results.length} result(s)`, logContext);
51
+ return results;
52
+ }, {
53
+ operation,
54
+ context: logContext,
55
+ input: { text, refDate },
56
+ errorCode: BaseErrorCode.PARSING_ERROR,
57
+ });
58
+ }
59
+ export const dateParser = {
60
+ parse: parseDateStringDetailed,
61
+ parseDate: parseDateString,
62
+ };
@@ -0,0 +1,2 @@
1
+ export * from './jsonParser.js';
2
+ export * from './dateParser.js';
@@ -1,6 +1,7 @@
1
1
  import { parse as parsePartialJson, Allow as PartialJsonAllow } from 'partial-json';
2
- import { BaseErrorCode, McpError } from '../types-global/errors.js';
3
- import { logger } from './logger.js'; // Import logger
2
+ import { BaseErrorCode, McpError } from '../../types-global/errors.js';
3
+ // Import utils from the main barrel file (logger, RequestContext from ../internal/*)
4
+ import { logger } from '../index.js';
4
5
  /**
5
6
  * Enum mirroring partial-json's Allow constants for specifying
6
7
  * what types of partial JSON structures are permissible during parsing.
@@ -1,6 +1,5 @@
1
- import { randomBytes } from 'crypto';
2
- import { BaseErrorCode, McpError } from '../types-global/errors.js';
3
- import { logger } from './logger.js';
1
+ import { randomBytes, randomUUID as cryptoRandomUUID } from 'crypto'; // Import cryptoRandomUUID
2
+ import { BaseErrorCode, McpError } from '../../types-global/errors.js'; // Corrected path
4
3
  /**
5
4
  * Generic ID Generator class for creating and managing unique identifiers
6
5
  */
@@ -34,7 +33,7 @@ export class IdGenerator {
34
33
  acc[prefix.toLowerCase()] = type;
35
34
  return acc;
36
35
  }, {});
37
- logger.debug('Entity prefixes updated', { entityPrefixes: this.entityPrefixes });
36
+ // Removed logger call from setEntityPrefixes to prevent logging before initialization
38
37
  }
39
38
  /**
40
39
  * Get all registered entity prefixes
@@ -144,5 +143,5 @@ export class IdGenerator {
144
143
  export const idGenerator = new IdGenerator();
145
144
  // For standalone use as a UUID generator
146
145
  export const generateUUID = () => {
147
- return crypto.randomUUID();
146
+ return cryptoRandomUUID(); // Use imported cryptoRandomUUID
148
147
  };
@@ -0,0 +1,3 @@
1
+ export * from './sanitization.js';
2
+ export * from './rateLimiter.js';
3
+ export * from './idGenerator.js';
@@ -1,5 +1,7 @@
1
- import { BaseErrorCode, McpError } from '../types-global/errors.js';
2
- import { logger } from './logger.js';
1
+ import { BaseErrorCode, McpError } from '../../types-global/errors.js';
2
+ // Import config and utils
3
+ import { environment } from '../../config/index.js'; // Import environment from config
4
+ import { logger } from '../index.js';
3
5
  /**
4
6
  * Generic rate limiter that can be used across the application
5
7
  */
@@ -26,12 +28,7 @@ export class RateLimiter {
26
28
  this.config = { ...RateLimiter.DEFAULT_CONFIG, ...config };
27
29
  this.limits = new Map();
28
30
  this.startCleanupTimer();
29
- // Log initialization
30
- logger.debug('RateLimiter initialized', {
31
- windowMs: this.config.windowMs,
32
- maxRequests: this.config.maxRequests,
33
- cleanupInterval: this.config.cleanupInterval
34
- });
31
+ // Removed logger call from constructor to prevent logging before initialization
35
32
  }
36
33
  /**
37
34
  * Start the cleanup timer to periodically remove expired entries
@@ -102,8 +99,8 @@ export class RateLimiter {
102
99
  * @throws {McpError} If rate limit is exceeded
103
100
  */
104
101
  check(key, context) {
105
- // Skip in development if configured
106
- if (this.config.skipInDevelopment && process.env.NODE_ENV === 'development') {
102
+ // Skip in development if configured, using the validated environment from config
103
+ if (this.config.skipInDevelopment && environment === 'development') {
107
104
  return;
108
105
  }
109
106
  // Generate key using custom generator if provided