@cyanheads/git-mcp-server 2.0.1 → 2.0.3
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 +55 -89
- package/{build → dist}/config/index.js +16 -18
- package/{build → dist}/index.js +80 -30
- package/dist/mcp-server/server.js +296 -0
- package/{build → dist}/mcp-server/tools/gitAdd/logic.js +9 -6
- package/{build → dist}/mcp-server/tools/gitAdd/registration.js +7 -4
- package/{build → dist}/mcp-server/tools/gitBranch/logic.js +23 -12
- package/{build → dist}/mcp-server/tools/gitBranch/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitCheckout/logic.js +92 -44
- package/{build → dist}/mcp-server/tools/gitCheckout/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitCherryPick/logic.js +10 -7
- package/{build → dist}/mcp-server/tools/gitCherryPick/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitClean/logic.js +9 -6
- package/{build → dist}/mcp-server/tools/gitClean/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitClearWorkingDir/logic.js +3 -2
- package/{build → dist}/mcp-server/tools/gitClearWorkingDir/registration.js +7 -4
- package/{build → dist}/mcp-server/tools/gitClone/logic.js +8 -5
- package/{build → dist}/mcp-server/tools/gitClone/registration.js +7 -4
- package/dist/mcp-server/tools/gitCommit/logic.js +207 -0
- package/{build → dist}/mcp-server/tools/gitCommit/registration.js +22 -15
- package/{build → dist}/mcp-server/tools/gitDiff/logic.js +9 -6
- package/{build → dist}/mcp-server/tools/gitDiff/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitFetch/logic.js +10 -7
- package/{build → dist}/mcp-server/tools/gitFetch/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitInit/index.js +2 -2
- package/{build → dist}/mcp-server/tools/gitInit/logic.js +9 -6
- package/dist/mcp-server/tools/gitInit/registration.js +98 -0
- package/{build → dist}/mcp-server/tools/gitLog/logic.js +53 -16
- package/{build → dist}/mcp-server/tools/gitLog/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitMerge/logic.js +9 -6
- package/{build → dist}/mcp-server/tools/gitMerge/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitPull/logic.js +11 -8
- package/{build → dist}/mcp-server/tools/gitPull/registration.js +7 -4
- package/{build → dist}/mcp-server/tools/gitPush/logic.js +12 -9
- package/{build → dist}/mcp-server/tools/gitPush/registration.js +7 -4
- package/{build → dist}/mcp-server/tools/gitRebase/logic.js +9 -6
- package/{build → dist}/mcp-server/tools/gitRebase/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitRemote/logic.js +4 -5
- package/{build → dist}/mcp-server/tools/gitRemote/registration.js +2 -4
- package/{build → dist}/mcp-server/tools/gitReset/logic.js +5 -6
- package/{build → dist}/mcp-server/tools/gitReset/registration.js +2 -4
- package/{build → dist}/mcp-server/tools/gitSetWorkingDir/logic.js +5 -6
- package/{build → dist}/mcp-server/tools/gitSetWorkingDir/registration.js +22 -13
- package/{build → dist}/mcp-server/tools/gitShow/logic.js +5 -6
- package/{build → dist}/mcp-server/tools/gitShow/registration.js +3 -5
- package/{build → dist}/mcp-server/tools/gitStash/logic.js +5 -6
- package/{build → dist}/mcp-server/tools/gitStash/registration.js +3 -5
- package/{build → dist}/mcp-server/tools/gitStatus/logic.js +5 -6
- package/{build → dist}/mcp-server/tools/gitStatus/registration.js +2 -4
- package/{build → dist}/mcp-server/tools/gitTag/logic.js +3 -4
- package/{build → 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/{build → dist}/types-global/errors.js +2 -2
- package/dist/utils/index.js +12 -0
- package/{build/utils → dist/utils/internal}/errorHandler.js +18 -8
- package/dist/utils/internal/index.js +3 -0
- package/dist/utils/internal/logger.js +254 -0
- package/{build/utils → dist/utils/internal}/requestContext.js +2 -3
- package/dist/utils/metrics/index.js +1 -0
- package/{build/utils → dist/utils/metrics}/tokenCounter.js +3 -3
- package/dist/utils/parsing/dateParser.js +62 -0
- package/dist/utils/parsing/index.js +2 -0
- package/{build/utils → dist/utils/parsing}/jsonParser.js +3 -2
- package/{build/utils → dist/utils/security}/idGenerator.js +4 -5
- package/dist/utils/security/index.js +3 -0
- package/{build/utils → dist/utils/security}/rateLimiter.js +7 -10
- package/{build/utils → dist/utils/security}/sanitization.js +4 -3
- package/package.json +20 -16
- package/build/mcp-server/server.js +0 -572
- package/build/mcp-server/tools/gitCommit/logic.js +0 -129
- package/build/mcp-server/tools/gitInit/registration.js +0 -44
- package/build/types-global/mcp.js +0 -59
- package/build/types-global/tool.js +0 -1
- package/build/utils/index.js +0 -11
- package/build/utils/logger.js +0 -266
- /package/{build → dist}/mcp-server/tools/gitAdd/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitBranch/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitCheckout/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitCherryPick/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitClean/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitClearWorkingDir/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitClone/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitCommit/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitDiff/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitFetch/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitLog/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitMerge/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitPull/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitPush/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitRebase/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitRemote/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitReset/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitSetWorkingDir/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitShow/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitStash/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitStatus/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitTag/index.js +0 -0
|
@@ -1,129 +0,0 @@
|
|
|
1
|
-
import { exec } from 'child_process';
|
|
2
|
-
import { promisify } from 'util';
|
|
3
|
-
import { z } from 'zod';
|
|
4
|
-
import { BaseErrorCode, McpError } from '../../../types-global/errors.js';
|
|
5
|
-
import { logger } from '../../../utils/logger.js';
|
|
6
|
-
import { sanitization } from '../../../utils/sanitization.js';
|
|
7
|
-
const execAsync = promisify(exec);
|
|
8
|
-
// Define the input schema for the git_commit tool using Zod
|
|
9
|
-
export const GitCommitInputSchema = z.object({
|
|
10
|
-
path: z.string().min(1).optional().default('.').describe("Path to the Git repository. Defaults to the session's working directory if set via `git_set_working_dir`, otherwise defaults to the server's current working directory (`.`)."),
|
|
11
|
-
message: z.string().min(1).describe('Commit message. Follow Conventional Commits format: `type(scope): subject`. Example: `feat(api): add user signup endpoint`'),
|
|
12
|
-
author: z.object({
|
|
13
|
-
name: z.string().describe('Author name for the commit'),
|
|
14
|
-
email: z.string().email().describe('Author email for the commit'),
|
|
15
|
-
}).optional().describe('Overrides the commit author information (name and email). Use only when necessary (e.g., applying external patches).'),
|
|
16
|
-
allowEmpty: z.boolean().default(false).describe('Allow creating empty commits'),
|
|
17
|
-
amend: z.boolean().default(false).describe('Amend the previous commit instead of creating a new one'),
|
|
18
|
-
});
|
|
19
|
-
/**
|
|
20
|
-
* Executes the 'git commit' command and returns structured JSON output.
|
|
21
|
-
*
|
|
22
|
-
* @param {GitCommitInput} input - The validated input object.
|
|
23
|
-
* @param {RequestContext} context - The request context for logging and error handling.
|
|
24
|
-
* @returns {Promise<GitCommitResult>} A promise that resolves with the structured commit result.
|
|
25
|
-
* @throws {McpError} Throws an McpError if path resolution or validation fails, or if the git command fails unexpectedly.
|
|
26
|
-
*/
|
|
27
|
-
export async function commitGitChanges(input, context // Add getter to context
|
|
28
|
-
) {
|
|
29
|
-
const operation = 'commitGitChanges';
|
|
30
|
-
logger.debug(`Executing ${operation}`, { ...context, input });
|
|
31
|
-
let targetPath;
|
|
32
|
-
try {
|
|
33
|
-
// Resolve the target path
|
|
34
|
-
if (input.path && input.path !== '.') {
|
|
35
|
-
targetPath = input.path;
|
|
36
|
-
logger.debug(`Using provided path: ${targetPath}`, { ...context, operation });
|
|
37
|
-
}
|
|
38
|
-
else {
|
|
39
|
-
const workingDir = context.getWorkingDirectory();
|
|
40
|
-
if (!workingDir) {
|
|
41
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, "No path provided and no working directory set for the session.", { context, operation });
|
|
42
|
-
}
|
|
43
|
-
targetPath = workingDir;
|
|
44
|
-
logger.debug(`Using session working directory: ${targetPath}`, { ...context, operation, sessionId: context.sessionId });
|
|
45
|
-
}
|
|
46
|
-
// Sanitize the resolved path
|
|
47
|
-
const sanitizedPath = sanitization.sanitizePath(targetPath);
|
|
48
|
-
logger.debug('Sanitized path', { ...context, operation, sanitizedPath });
|
|
49
|
-
targetPath = sanitizedPath; // Use the sanitized path going forward
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
logger.error('Path resolution or sanitization failed', { ...context, operation, error });
|
|
53
|
-
if (error instanceof McpError) {
|
|
54
|
-
throw error;
|
|
55
|
-
}
|
|
56
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
57
|
-
}
|
|
58
|
-
try {
|
|
59
|
-
// Construct the git commit command using the resolved targetPath
|
|
60
|
-
let command = `git -C "${targetPath}" commit -m "${input.message.replace(/"/g, '\\"')}"`; // Escape double quotes
|
|
61
|
-
if (input.allowEmpty) {
|
|
62
|
-
command += ' --allow-empty';
|
|
63
|
-
}
|
|
64
|
-
if (input.amend) {
|
|
65
|
-
command += ' --amend --no-edit';
|
|
66
|
-
}
|
|
67
|
-
if (input.author) {
|
|
68
|
-
// Ensure author details are properly escaped if needed, though exec usually handles this
|
|
69
|
-
command = `git -C "${targetPath}" -c user.name="${input.author.name.replace(/"/g, '\\"')}" -c user.email="${input.author.email.replace(/"/g, '\\"')}" commit -m "${input.message.replace(/"/g, '\\"')}"`;
|
|
70
|
-
if (input.allowEmpty)
|
|
71
|
-
command += ' --allow-empty';
|
|
72
|
-
if (input.amend)
|
|
73
|
-
command += ' --amend --no-edit';
|
|
74
|
-
}
|
|
75
|
-
logger.debug(`Executing command: ${command}`, { ...context, operation });
|
|
76
|
-
const { stdout, stderr } = await execAsync(command);
|
|
77
|
-
// Check stderr first for common non-error messages
|
|
78
|
-
if (stderr) {
|
|
79
|
-
if (stderr.includes('nothing to commit, working tree clean') || stderr.includes('no changes added to commit')) {
|
|
80
|
-
const msg = stderr.includes('nothing to commit') ? 'Nothing to commit, working tree clean.' : 'No changes added to commit.';
|
|
81
|
-
logger.info(msg, { ...context, operation, path: targetPath });
|
|
82
|
-
// Use statusMessage
|
|
83
|
-
return { success: true, statusMessage: msg, nothingToCommit: true };
|
|
84
|
-
}
|
|
85
|
-
// Log other stderr as warning but continue, as commit might still succeed
|
|
86
|
-
logger.warning(`Git commit command produced stderr`, { ...context, operation, stderr });
|
|
87
|
-
}
|
|
88
|
-
// Extract commit hash (more robustly)
|
|
89
|
-
let commitHash = undefined;
|
|
90
|
-
const hashMatch = stdout.match(/([a-f0-9]{7,40})/); // Look for typical short or long hash
|
|
91
|
-
if (hashMatch) {
|
|
92
|
-
commitHash = hashMatch[1];
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
// Fallback parsing if needed, or rely on success message
|
|
96
|
-
logger.warning('Could not parse commit hash from stdout', { ...context, operation, stdout });
|
|
97
|
-
}
|
|
98
|
-
// Use statusMessage
|
|
99
|
-
const statusMsg = commitHash
|
|
100
|
-
? `Commit successful: ${commitHash}`
|
|
101
|
-
: `Commit successful (stdout: ${stdout.trim()})`;
|
|
102
|
-
logger.info(`${operation} executed successfully`, { ...context, operation, path: targetPath, commitHash });
|
|
103
|
-
return {
|
|
104
|
-
success: true,
|
|
105
|
-
statusMessage: statusMsg,
|
|
106
|
-
commitHash: commitHash
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
logger.error(`Failed to execute git commit command`, { ...context, operation, path: targetPath, error: error.message, stderr: error.stderr });
|
|
111
|
-
const errorMessage = error.stderr || error.message || '';
|
|
112
|
-
// Handle specific error cases first
|
|
113
|
-
if (errorMessage.toLowerCase().includes('not a git repository')) {
|
|
114
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`, { context, operation, originalError: error });
|
|
115
|
-
}
|
|
116
|
-
if (errorMessage.includes('nothing to commit') || errorMessage.includes('no changes added to commit')) {
|
|
117
|
-
// This might happen if git exits with error despite these messages
|
|
118
|
-
const msg = errorMessage.includes('nothing to commit') ? 'Nothing to commit, working tree clean.' : 'No changes added to commit.';
|
|
119
|
-
logger.info(msg + ' (caught as error)', { ...context, operation, path: targetPath, errorMessage });
|
|
120
|
-
// Return success=false but indicate the reason using statusMessage
|
|
121
|
-
return { success: false, statusMessage: msg, nothingToCommit: true };
|
|
122
|
-
}
|
|
123
|
-
if (errorMessage.includes('conflicts')) {
|
|
124
|
-
throw new McpError(BaseErrorCode.CONFLICT, `Commit failed due to unresolved conflicts in ${targetPath}`, { context, operation, originalError: error });
|
|
125
|
-
}
|
|
126
|
-
// Generic internal error for other failures
|
|
127
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to commit changes for path: ${targetPath}. Error: ${errorMessage}`, { context, operation, originalError: error });
|
|
128
|
-
}
|
|
129
|
-
}
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { ErrorHandler } from '../../../utils/errorHandler.js';
|
|
2
|
-
import { logger } from '../../../utils/logger.js';
|
|
3
|
-
import { requestContextService } from '../../../utils/requestContext.js';
|
|
4
|
-
import { GitInitInputSchema, gitInitLogic } from './logic.js';
|
|
5
|
-
import { BaseErrorCode } from '../../../types-global/errors.js';
|
|
6
|
-
const TOOL_NAME = 'git_init';
|
|
7
|
-
const TOOL_DESCRIPTION = 'Initializes a new Git repository at the specified absolute path. Can optionally set the initial branch name and create a bare repository.';
|
|
8
|
-
/**
|
|
9
|
-
* Registers the git_init tool with the MCP server.
|
|
10
|
-
*
|
|
11
|
-
* @param {McpServer} server - The McpServer instance to register the tool with.
|
|
12
|
-
* @returns {Promise<void>}
|
|
13
|
-
* @throws {Error} If registration fails.
|
|
14
|
-
*/
|
|
15
|
-
export const registerGitInitTool = async (server) => {
|
|
16
|
-
const operation = 'registerGitInitTool';
|
|
17
|
-
const context = requestContextService.createRequestContext({ operation });
|
|
18
|
-
await ErrorHandler.tryCatch(async () => {
|
|
19
|
-
server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitInitInputSchema.shape, // Provide the Zod schema shape
|
|
20
|
-
async (validatedArgs, callContext) => {
|
|
21
|
-
const toolOperation = 'tool:git_init';
|
|
22
|
-
const requestContext = requestContextService.createRequestContext({ operation: toolOperation, parentContext: callContext });
|
|
23
|
-
logger.info(`Executing tool: ${TOOL_NAME}`, requestContext);
|
|
24
|
-
return await ErrorHandler.tryCatch(async () => {
|
|
25
|
-
// Call the core logic function
|
|
26
|
-
const initResult = await gitInitLogic(validatedArgs, requestContext);
|
|
27
|
-
// Format the result as a JSON string within TextContent
|
|
28
|
-
const resultContent = {
|
|
29
|
-
type: 'text',
|
|
30
|
-
text: JSON.stringify(initResult, null, 2), // Pretty-print JSON
|
|
31
|
-
contentType: 'application/json',
|
|
32
|
-
};
|
|
33
|
-
logger.info(`Tool ${TOOL_NAME} executed successfully, returning JSON`, requestContext);
|
|
34
|
-
return { content: [resultContent] };
|
|
35
|
-
}, {
|
|
36
|
-
operation: toolOperation,
|
|
37
|
-
context: requestContext,
|
|
38
|
-
input: validatedArgs,
|
|
39
|
-
errorCode: BaseErrorCode.INTERNAL_ERROR, // Default if unexpected error occurs
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
logger.info(`Tool registered: ${TOOL_NAME}`, context);
|
|
43
|
-
}, { operation, context, critical: true }); // Mark registration as critical
|
|
44
|
-
};
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// Type definitions for the MCP (Message Control Protocol) protocol
|
|
2
|
-
// and standard JSON-RPC 2.0 structures
|
|
3
|
-
// Standard JSON-RPC 2.0 Error Codes
|
|
4
|
-
export var JsonRpcErrorCode;
|
|
5
|
-
(function (JsonRpcErrorCode) {
|
|
6
|
-
JsonRpcErrorCode[JsonRpcErrorCode["PARSE_ERROR"] = -32700] = "PARSE_ERROR";
|
|
7
|
-
JsonRpcErrorCode[JsonRpcErrorCode["INVALID_REQUEST"] = -32600] = "INVALID_REQUEST";
|
|
8
|
-
JsonRpcErrorCode[JsonRpcErrorCode["METHOD_NOT_FOUND"] = -32601] = "METHOD_NOT_FOUND";
|
|
9
|
-
JsonRpcErrorCode[JsonRpcErrorCode["INVALID_PARAMS"] = -32602] = "INVALID_PARAMS";
|
|
10
|
-
JsonRpcErrorCode[JsonRpcErrorCode["INTERNAL_ERROR"] = -32603] = "INTERNAL_ERROR";
|
|
11
|
-
// -32000 to -32099 are reserved for implementation-defined server-errors.
|
|
12
|
-
JsonRpcErrorCode[JsonRpcErrorCode["SERVER_ERROR_START"] = -32000] = "SERVER_ERROR_START";
|
|
13
|
-
JsonRpcErrorCode[JsonRpcErrorCode["SERVER_ERROR_END"] = -32099] = "SERVER_ERROR_END";
|
|
14
|
-
})(JsonRpcErrorCode || (JsonRpcErrorCode = {}));
|
|
15
|
-
// ==================================
|
|
16
|
-
// Helper Functions (Updated for JSON-RPC context)
|
|
17
|
-
// ==================================
|
|
18
|
-
/**
|
|
19
|
-
* Creates a JSON-RPC 2.0 Success Response containing an McpToolResult.
|
|
20
|
-
*/
|
|
21
|
-
export const createJsonRpcToolSuccessResponse = (id, text) => ({
|
|
22
|
-
jsonrpc: "2.0",
|
|
23
|
-
result: {
|
|
24
|
-
content: [{ type: "text", text }]
|
|
25
|
-
},
|
|
26
|
-
id
|
|
27
|
-
});
|
|
28
|
-
/**
|
|
29
|
-
* Creates a JSON-RPC 2.0 Error Response.
|
|
30
|
-
*/
|
|
31
|
-
export const createJsonRpcErrorResponse = (id, code, message, data) => ({
|
|
32
|
-
jsonrpc: "2.0",
|
|
33
|
-
error: {
|
|
34
|
-
code,
|
|
35
|
-
message,
|
|
36
|
-
...(data !== undefined && { data }) // Include data only if provided
|
|
37
|
-
},
|
|
38
|
-
id
|
|
39
|
-
});
|
|
40
|
-
/**
|
|
41
|
-
* Creates a JSON-RPC 2.0 Success Response containing an McpResourceResult.
|
|
42
|
-
*/
|
|
43
|
-
export const createJsonRpcResourceSuccessResponse = (id, uri, text, mimeType) => ({
|
|
44
|
-
jsonrpc: "2.0",
|
|
45
|
-
result: {
|
|
46
|
-
contents: [{ uri, text, mimeType }]
|
|
47
|
-
},
|
|
48
|
-
id
|
|
49
|
-
});
|
|
50
|
-
// Note: PromptResponse helper might need adjustment depending on how prompts fit into JSON-RPC
|
|
51
|
-
export const createPromptResponse = (text, role = "assistant") => ({
|
|
52
|
-
messages: [{
|
|
53
|
-
role,
|
|
54
|
-
content: {
|
|
55
|
-
type: "text",
|
|
56
|
-
text
|
|
57
|
-
}
|
|
58
|
-
}]
|
|
59
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/build/utils/index.js
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
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.
|
package/build/utils/logger.js
DELETED
|
@@ -1,266 +0,0 @@
|
|
|
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'; // Import config for logger name
|
|
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
|
-
// Project root assumed two levels above utils/
|
|
25
|
-
const projectRoot = path.resolve(__dirname, '..', '..');
|
|
26
|
-
const logsDir = path.join(projectRoot, 'logs');
|
|
27
|
-
// Security: ensure logsDir is within projectRoot
|
|
28
|
-
const resolvedLogsDir = path.resolve(logsDir);
|
|
29
|
-
const isLogsDirSafe = resolvedLogsDir === projectRoot || resolvedLogsDir.startsWith(projectRoot + path.sep);
|
|
30
|
-
if (!isLogsDirSafe) {
|
|
31
|
-
// Use console.error here as logger might not be initialized or safe
|
|
32
|
-
console.error(`FATAL: logs directory "${resolvedLogsDir}" is outside project root "${projectRoot}". File logging disabled.`);
|
|
33
|
-
}
|
|
34
|
-
/**
|
|
35
|
-
* Singleton Logger wrapping Winston, adapted for MCP.
|
|
36
|
-
* Logs to files and optionally sends MCP notifications/message.
|
|
37
|
-
*/
|
|
38
|
-
class Logger {
|
|
39
|
-
static instance;
|
|
40
|
-
winstonLogger;
|
|
41
|
-
initialized = false;
|
|
42
|
-
mcpNotificationSender;
|
|
43
|
-
currentMcpLevel = 'info'; // Default MCP level
|
|
44
|
-
currentWinstonLevel = 'info'; // Default Winston level
|
|
45
|
-
constructor() { }
|
|
46
|
-
/**
|
|
47
|
-
* Initialize Winston logger for file transport. Must be called once at app start.
|
|
48
|
-
* Console transport is removed.
|
|
49
|
-
* @param level Initial minimum level to log ('info' default).
|
|
50
|
-
*/
|
|
51
|
-
initialize(level = 'info') {
|
|
52
|
-
if (this.initialized) {
|
|
53
|
-
// Use console.warn as logger might be re-initializing
|
|
54
|
-
console.warn('Logger already initialized.');
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
this.currentMcpLevel = level;
|
|
58
|
-
this.currentWinstonLevel = mcpToWinstonLevel[level];
|
|
59
|
-
// Ensure logs directory exists
|
|
60
|
-
if (isLogsDirSafe) {
|
|
61
|
-
try {
|
|
62
|
-
if (!fs.existsSync(resolvedLogsDir)) {
|
|
63
|
-
fs.mkdirSync(resolvedLogsDir, { recursive: true });
|
|
64
|
-
// Use console.log as logger isn't fully ready
|
|
65
|
-
console.log(`Created logs directory: ${resolvedLogsDir}`);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
catch (err) {
|
|
69
|
-
// Use console.error as logger isn't fully ready
|
|
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 }),
|
|
75
|
-
// Use JSON format for file logs for easier parsing
|
|
76
|
-
winston.format.json());
|
|
77
|
-
const transports = [];
|
|
78
|
-
// Add file transports only if the directory is safe
|
|
79
|
-
if (isLogsDirSafe) {
|
|
80
|
-
transports.push(
|
|
81
|
-
// Log levels equal to or more severe than the specified level
|
|
82
|
-
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 }),
|
|
83
|
-
// Combined log captures everything based on the main logger level
|
|
84
|
-
new winston.transports.File({ filename: path.join(resolvedLogsDir, 'combined.log'), format: fileFormat }));
|
|
85
|
-
}
|
|
86
|
-
else {
|
|
87
|
-
// Use console.warn as logger isn't fully ready
|
|
88
|
-
console.warn("File logging disabled due to unsafe logs directory path.");
|
|
89
|
-
}
|
|
90
|
-
// Create logger with the initial Winston level and file transports
|
|
91
|
-
this.winstonLogger = winston.createLogger({
|
|
92
|
-
level: this.currentWinstonLevel, // Set Winston level for file logging
|
|
93
|
-
transports,
|
|
94
|
-
exitOnError: false
|
|
95
|
-
});
|
|
96
|
-
this.initialized = true;
|
|
97
|
-
// Log initialization message using the logger itself (will go to file)
|
|
98
|
-
this.info(`Logger initialized. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}`);
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Sets the function used to send MCP 'notifications/message'.
|
|
102
|
-
* This should be called by the server logic once an MCP connection
|
|
103
|
-
* supporting logging is established.
|
|
104
|
-
* @param sender The function to call for sending notifications.
|
|
105
|
-
*/
|
|
106
|
-
setMcpNotificationSender(sender) {
|
|
107
|
-
this.mcpNotificationSender = sender;
|
|
108
|
-
const status = sender ? 'enabled' : 'disabled';
|
|
109
|
-
this.info(`MCP notification sending ${status}.`);
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Dynamically sets the minimum logging level for both file logging and MCP notifications.
|
|
113
|
-
* @param newLevel The new minimum MCP log level.
|
|
114
|
-
*/
|
|
115
|
-
setLevel(newLevel) {
|
|
116
|
-
if (!this.ensureInitialized()) {
|
|
117
|
-
// Use console.error as logger state is uncertain
|
|
118
|
-
console.error("Cannot set level: Logger not initialized.");
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
// Validate the level
|
|
122
|
-
if (!(newLevel in mcpLevelSeverity)) {
|
|
123
|
-
this.warning(`Invalid MCP log level provided: ${newLevel}. Level not changed.`);
|
|
124
|
-
return;
|
|
125
|
-
}
|
|
126
|
-
this.currentMcpLevel = newLevel;
|
|
127
|
-
this.currentWinstonLevel = mcpToWinstonLevel[newLevel];
|
|
128
|
-
this.winstonLogger.level = this.currentWinstonLevel; // Update Winston level for files
|
|
129
|
-
this.info(`Log level set. File logging level: ${this.currentWinstonLevel}. MCP logging level: ${this.currentMcpLevel}`);
|
|
130
|
-
}
|
|
131
|
-
/** Get singleton instance. */
|
|
132
|
-
static getInstance() {
|
|
133
|
-
if (!Logger.instance) {
|
|
134
|
-
Logger.instance = new Logger();
|
|
135
|
-
}
|
|
136
|
-
return Logger.instance;
|
|
137
|
-
}
|
|
138
|
-
/** Ensures the logger has been initialized. */
|
|
139
|
-
ensureInitialized() {
|
|
140
|
-
if (!this.initialized || !this.winstonLogger) {
|
|
141
|
-
// Use console.warn as this indicates a programming error (calling log before init)
|
|
142
|
-
console.warn('Logger not initialized; message dropped.');
|
|
143
|
-
return false;
|
|
144
|
-
}
|
|
145
|
-
return true;
|
|
146
|
-
}
|
|
147
|
-
/** Centralized log processing */
|
|
148
|
-
log(level, msg, context, error) {
|
|
149
|
-
if (!this.ensureInitialized())
|
|
150
|
-
return;
|
|
151
|
-
// Check if message level is severe enough for current setting
|
|
152
|
-
if (mcpLevelSeverity[level] > mcpLevelSeverity[this.currentMcpLevel]) {
|
|
153
|
-
return; // Skip logging if level is less severe than current setting
|
|
154
|
-
}
|
|
155
|
-
const logData = { ...context }; // Copy context
|
|
156
|
-
const winstonLevel = mcpToWinstonLevel[level];
|
|
157
|
-
// Log to Winston (files)
|
|
158
|
-
if (error) {
|
|
159
|
-
// Include error details for Winston file log
|
|
160
|
-
logData.error = { message: error.message, stack: error.stack };
|
|
161
|
-
this.winstonLogger.log(winstonLevel, msg, logData);
|
|
162
|
-
}
|
|
163
|
-
else {
|
|
164
|
-
this.winstonLogger.log(winstonLevel, msg, logData);
|
|
165
|
-
}
|
|
166
|
-
// Send MCP notification if sender is configured
|
|
167
|
-
if (this.mcpNotificationSender) {
|
|
168
|
-
// Prepare data for MCP: combine message and context/error info
|
|
169
|
-
const mcpDataPayload = { message: msg };
|
|
170
|
-
if (context) {
|
|
171
|
-
mcpDataPayload.context = context;
|
|
172
|
-
}
|
|
173
|
-
if (error) {
|
|
174
|
-
// Include simplified error info for MCP notification
|
|
175
|
-
mcpDataPayload.error = { message: error.message };
|
|
176
|
-
// Optionally include stack in debug mode? Be cautious about size.
|
|
177
|
-
if (this.currentMcpLevel === 'debug' && error.stack) {
|
|
178
|
-
mcpDataPayload.error.stack = error.stack.substring(0, 500); // Limit stack trace size
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
try {
|
|
182
|
-
this.mcpNotificationSender(level, mcpDataPayload, config.mcpServerName);
|
|
183
|
-
}
|
|
184
|
-
catch (sendError) {
|
|
185
|
-
// Log failure to send MCP notification to file log
|
|
186
|
-
this.winstonLogger.error("Failed to send MCP log notification", {
|
|
187
|
-
originalLevel: level,
|
|
188
|
-
originalMessage: msg,
|
|
189
|
-
sendError: sendError instanceof Error ? sendError.message : String(sendError),
|
|
190
|
-
mcpPayload: mcpDataPayload // Log what we tried to send
|
|
191
|
-
});
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
// --- Public Logging Methods ---
|
|
196
|
-
/** Log debug message (level 7) */
|
|
197
|
-
debug(msg, context) {
|
|
198
|
-
this.log('debug', msg, context);
|
|
199
|
-
}
|
|
200
|
-
/** Log info message (level 6) */
|
|
201
|
-
info(msg, context) {
|
|
202
|
-
this.log('info', msg, context);
|
|
203
|
-
}
|
|
204
|
-
/** Log notice message (level 5) */
|
|
205
|
-
notice(msg, context) {
|
|
206
|
-
this.log('notice', msg, context);
|
|
207
|
-
}
|
|
208
|
-
/** Log warning message (level 4) */
|
|
209
|
-
warning(msg, context) {
|
|
210
|
-
this.log('warning', msg, context);
|
|
211
|
-
}
|
|
212
|
-
/** Log error message (level 3) */
|
|
213
|
-
error(msg, err, context) {
|
|
214
|
-
if (err instanceof Error) {
|
|
215
|
-
this.log('error', msg, context, err);
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
// If err is not an Error object, treat it as additional context
|
|
219
|
-
const combinedContext = { ...(err || {}), ...(context || {}) };
|
|
220
|
-
this.log('error', msg, combinedContext);
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
/** Log critical message (level 2) */
|
|
224
|
-
crit(msg, err, context) {
|
|
225
|
-
if (err instanceof Error) {
|
|
226
|
-
this.log('crit', msg, context, err);
|
|
227
|
-
}
|
|
228
|
-
else {
|
|
229
|
-
const combinedContext = { ...(err || {}), ...(context || {}) };
|
|
230
|
-
this.log('crit', msg, combinedContext);
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
/** Log alert message (level 1) */
|
|
234
|
-
alert(msg, err, context) {
|
|
235
|
-
if (err instanceof Error) {
|
|
236
|
-
this.log('alert', msg, context, err);
|
|
237
|
-
}
|
|
238
|
-
else {
|
|
239
|
-
const combinedContext = { ...(err || {}), ...(context || {}) };
|
|
240
|
-
this.log('alert', msg, combinedContext);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
/** Log emergency message (level 0) */
|
|
244
|
-
emerg(msg, err, context) {
|
|
245
|
-
if (err instanceof Error) {
|
|
246
|
-
this.log('emerg', msg, context, err);
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
const combinedContext = { ...(err || {}), ...(context || {}) };
|
|
250
|
-
this.log('emerg', msg, combinedContext);
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
/** Log fatal message (alias for emergency, ensures process exit) */
|
|
254
|
-
fatal(msg, context, error) {
|
|
255
|
-
this.log('emerg', msg, context, error);
|
|
256
|
-
// Optionally add logic here to ensure process termination after logging fatal error
|
|
257
|
-
// Be careful with async operations here if you intend immediate exit.
|
|
258
|
-
// process.exit(1); // Consider if this is appropriate for your application's shutdown logic
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
// Export singleton instance
|
|
262
|
-
export const logger = Logger.getInstance();
|
|
263
|
-
// Initialize logger on import (can be configured later via setLevel/setMcpNotificationSender)
|
|
264
|
-
// Read initial level from env var or default to 'info'
|
|
265
|
-
const initialLogLevel = process.env.MCP_LOG_LEVEL || 'info';
|
|
266
|
-
logger.initialize(initialLogLevel);
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|