@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.
- package/README.md +45 -85
- package/dist/config/index.js +16 -18
- package/dist/index.js +80 -30
- package/dist/mcp-server/server.js +247 -523
- package/dist/mcp-server/tools/gitAdd/logic.js +9 -6
- package/dist/mcp-server/tools/gitAdd/registration.js +7 -4
- package/dist/mcp-server/tools/gitBranch/logic.js +23 -12
- package/dist/mcp-server/tools/gitBranch/registration.js +8 -5
- package/dist/mcp-server/tools/gitCheckout/logic.js +92 -44
- package/dist/mcp-server/tools/gitCheckout/registration.js +8 -5
- package/dist/mcp-server/tools/gitCherryPick/logic.js +10 -7
- package/dist/mcp-server/tools/gitCherryPick/registration.js +8 -5
- package/dist/mcp-server/tools/gitClean/logic.js +9 -6
- package/dist/mcp-server/tools/gitClean/registration.js +8 -5
- package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +3 -2
- package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +7 -4
- package/dist/mcp-server/tools/gitClone/logic.js +8 -5
- package/dist/mcp-server/tools/gitClone/registration.js +7 -4
- package/dist/mcp-server/tools/gitCommit/logic.js +98 -20
- package/dist/mcp-server/tools/gitCommit/registration.js +22 -15
- package/dist/mcp-server/tools/gitDiff/logic.js +9 -6
- package/dist/mcp-server/tools/gitDiff/registration.js +8 -5
- package/dist/mcp-server/tools/gitFetch/logic.js +10 -7
- package/dist/mcp-server/tools/gitFetch/registration.js +8 -5
- package/dist/mcp-server/tools/gitInit/index.js +2 -2
- package/dist/mcp-server/tools/gitInit/logic.js +9 -6
- package/dist/mcp-server/tools/gitInit/registration.js +66 -12
- package/dist/mcp-server/tools/gitLog/logic.js +53 -16
- package/dist/mcp-server/tools/gitLog/registration.js +8 -5
- package/dist/mcp-server/tools/gitMerge/logic.js +9 -6
- package/dist/mcp-server/tools/gitMerge/registration.js +8 -5
- package/dist/mcp-server/tools/gitPull/logic.js +11 -8
- package/dist/mcp-server/tools/gitPull/registration.js +7 -4
- package/dist/mcp-server/tools/gitPush/logic.js +12 -9
- package/dist/mcp-server/tools/gitPush/registration.js +7 -4
- package/dist/mcp-server/tools/gitRebase/logic.js +9 -6
- package/dist/mcp-server/tools/gitRebase/registration.js +8 -5
- package/dist/mcp-server/tools/gitRemote/logic.js +4 -5
- package/dist/mcp-server/tools/gitRemote/registration.js +2 -4
- package/dist/mcp-server/tools/gitReset/logic.js +5 -6
- package/dist/mcp-server/tools/gitReset/registration.js +2 -4
- package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +5 -6
- package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +22 -13
- package/dist/mcp-server/tools/gitShow/logic.js +5 -6
- package/dist/mcp-server/tools/gitShow/registration.js +3 -5
- package/dist/mcp-server/tools/gitStash/logic.js +5 -6
- package/dist/mcp-server/tools/gitStash/registration.js +3 -5
- package/dist/mcp-server/tools/gitStatus/logic.js +5 -6
- package/dist/mcp-server/tools/gitStatus/registration.js +2 -4
- package/dist/mcp-server/tools/gitTag/logic.js +3 -4
- package/dist/mcp-server/tools/gitTag/registration.js +2 -4
- package/dist/mcp-server/transports/authentication/authMiddleware.js +145 -0
- package/dist/mcp-server/transports/httpTransport.js +432 -0
- package/dist/mcp-server/transports/stdioTransport.js +87 -0
- package/dist/types-global/errors.js +2 -2
- package/dist/utils/index.js +12 -11
- package/dist/utils/{errorHandler.js → internal/errorHandler.js} +18 -8
- package/dist/utils/internal/index.js +3 -0
- package/dist/utils/internal/logger.js +254 -0
- package/dist/utils/{requestContext.js → internal/requestContext.js} +2 -3
- package/dist/utils/metrics/index.js +1 -0
- package/dist/utils/{tokenCounter.js → metrics/tokenCounter.js} +3 -3
- package/dist/utils/parsing/dateParser.js +62 -0
- package/dist/utils/parsing/index.js +2 -0
- package/dist/utils/{jsonParser.js → parsing/jsonParser.js} +3 -2
- package/dist/utils/{idGenerator.js → security/idGenerator.js} +4 -5
- package/dist/utils/security/index.js +3 -0
- package/dist/utils/{rateLimiter.js → security/rateLimiter.js} +7 -10
- package/dist/utils/{sanitization.js → security/sanitization.js} +4 -3
- package/package.json +12 -9
- package/dist/types-global/mcp.js +0 -59
- package/dist/types-global/tool.js +0 -1
- 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.
|
package/dist/utils/index.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
// Re-export all utilities
|
|
2
|
-
export * from './
|
|
3
|
-
export * from './
|
|
4
|
-
export * from './
|
|
5
|
-
export * from './
|
|
6
|
-
|
|
7
|
-
export
|
|
8
|
-
|
|
9
|
-
export
|
|
10
|
-
//
|
|
11
|
-
//
|
|
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 '
|
|
1
|
+
import { BaseErrorCode, McpError } from '../../types-global/errors.js'; // Corrected path
|
|
2
2
|
import { logger } from './logger.js';
|
|
3
|
-
import { sanitizeInputForLogging } from '
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
-
|
|
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,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
|
-
|
|
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 '
|
|
3
|
-
|
|
4
|
-
import { logger } from '
|
|
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
|
+
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { parse as parsePartialJson, Allow as PartialJsonAllow } from 'partial-json';
|
|
2
|
-
import { BaseErrorCode, McpError } from '
|
|
3
|
-
|
|
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 '
|
|
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
|
|
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
|
|
146
|
+
return cryptoRandomUUID(); // Use imported cryptoRandomUUID
|
|
148
147
|
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import { BaseErrorCode, McpError } from '
|
|
2
|
-
|
|
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
|
-
//
|
|
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 &&
|
|
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
|