@cyanheads/git-mcp-server 2.2.0 → 2.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp-server/tools/gitAdd/logic.js +26 -45
- package/dist/mcp-server/tools/gitAdd/registration.js +6 -5
- package/dist/mcp-server/tools/gitBranch/logic.js +34 -51
- package/dist/mcp-server/tools/gitBranch/registration.js +7 -7
- package/dist/mcp-server/tools/gitCheckout/logic.js +15 -37
- package/dist/mcp-server/tools/gitCheckout/registration.js +7 -7
- package/dist/mcp-server/tools/gitCherryPick/logic.js +9 -28
- package/dist/mcp-server/tools/gitCherryPick/registration.js +7 -7
- package/dist/mcp-server/tools/gitClean/logic.js +9 -19
- package/dist/mcp-server/tools/gitClean/registration.js +7 -7
- package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +4 -11
- package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +7 -7
- package/dist/mcp-server/tools/gitClone/logic.js +10 -29
- package/dist/mcp-server/tools/gitClone/registration.js +7 -7
- package/dist/mcp-server/tools/gitCommit/logic.js +22 -52
- package/dist/mcp-server/tools/gitCommit/registration.js +7 -7
- package/dist/mcp-server/tools/gitDiff/logic.js +21 -37
- package/dist/mcp-server/tools/gitDiff/registration.js +7 -7
- package/dist/mcp-server/tools/gitFetch/logic.js +4 -20
- package/dist/mcp-server/tools/gitFetch/registration.js +7 -7
- package/dist/mcp-server/tools/gitInit/logic.js +10 -26
- package/dist/mcp-server/tools/gitInit/registration.js +7 -7
- package/dist/mcp-server/tools/gitLog/logic.js +19 -32
- package/dist/mcp-server/tools/gitLog/registration.js +7 -7
- package/dist/mcp-server/tools/gitMerge/logic.js +9 -28
- package/dist/mcp-server/tools/gitMerge/registration.js +7 -7
- package/dist/mcp-server/tools/gitPull/logic.js +4 -23
- package/dist/mcp-server/tools/gitPull/registration.js +7 -7
- package/dist/mcp-server/tools/gitPush/logic.js +9 -28
- package/dist/mcp-server/tools/gitPush/registration.js +7 -7
- package/dist/mcp-server/tools/gitRebase/logic.js +9 -28
- package/dist/mcp-server/tools/gitRebase/registration.js +7 -7
- package/dist/mcp-server/tools/gitRemote/logic.js +22 -38
- package/dist/mcp-server/tools/gitRemote/registration.js +7 -7
- package/dist/mcp-server/tools/gitReset/logic.js +5 -21
- package/dist/mcp-server/tools/gitReset/registration.js +7 -7
- package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +8 -25
- package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +7 -7
- package/dist/mcp-server/tools/gitShow/logic.js +3 -19
- package/dist/mcp-server/tools/gitShow/registration.js +7 -7
- package/dist/mcp-server/tools/gitStash/logic.js +14 -30
- package/dist/mcp-server/tools/gitStash/registration.js +7 -7
- package/dist/mcp-server/tools/gitStatus/logic.js +3 -13
- package/dist/mcp-server/tools/gitStatus/registration.js +7 -7
- package/dist/mcp-server/tools/gitTag/logic.js +6 -25
- package/dist/mcp-server/tools/gitTag/registration.js +7 -7
- package/dist/mcp-server/tools/gitWorktree/logic.js +5 -21
- package/dist/mcp-server/tools/gitWorktree/registration.js +7 -7
- package/dist/mcp-server/tools/gitWrapupInstructions/logic.js +5 -7
- package/dist/mcp-server/tools/gitWrapupInstructions/registration.js +8 -8
- package/package.json +5 -5
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
1
2
|
import { execFile } from "child_process";
|
|
2
3
|
import { promisify } from "util";
|
|
3
4
|
import { z } from "zod";
|
|
4
5
|
import { logger, sanitization } from "../../../utils/index.js";
|
|
5
|
-
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
6
6
|
const execFileAsync = promisify(execFile);
|
|
7
7
|
export const GitAddInputSchema = z.object({
|
|
8
8
|
path: z
|
|
@@ -33,52 +33,33 @@ export async function addGitFiles(params, context) {
|
|
|
33
33
|
if (filesToStage.length === 0) {
|
|
34
34
|
filesToStage.push(".");
|
|
35
35
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
logger.warning(`Git add command produced stderr`, {
|
|
45
|
-
...context,
|
|
46
|
-
operation,
|
|
47
|
-
stderr,
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
const filesAddedDesc = Array.isArray(filesToStage)
|
|
51
|
-
? filesToStage.join(", ")
|
|
52
|
-
: filesToStage;
|
|
53
|
-
const successMessage = `Successfully staged: ${filesAddedDesc}`;
|
|
54
|
-
logger.info(successMessage, {
|
|
55
|
-
...context,
|
|
56
|
-
operation,
|
|
57
|
-
path: targetPath,
|
|
58
|
-
files: filesToStage,
|
|
59
|
-
});
|
|
60
|
-
const reminder = "Remember to write clear, concise commit messages using the Conventional Commits format (e.g., 'feat(scope): subject').";
|
|
61
|
-
return {
|
|
62
|
-
success: true,
|
|
63
|
-
statusMessage: `${successMessage}. ${reminder}`,
|
|
64
|
-
filesStaged: filesToStage,
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
logger.error(`Failed to execute git add command`, {
|
|
36
|
+
const args = ["-C", targetPath, "add", "--", ...filesToStage.map(file => file.startsWith("-") ? `./${file}` : file)];
|
|
37
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
38
|
+
...context,
|
|
39
|
+
operation,
|
|
40
|
+
});
|
|
41
|
+
const { stderr } = await execFileAsync("git", args);
|
|
42
|
+
if (stderr) {
|
|
43
|
+
logger.warning(`Git add command produced stderr`, {
|
|
69
44
|
...context,
|
|
70
45
|
operation,
|
|
71
|
-
|
|
72
|
-
error: error.message,
|
|
73
|
-
stderr: error.stderr,
|
|
46
|
+
stderr,
|
|
74
47
|
});
|
|
75
|
-
const errorMessage = error.stderr || error.message || "";
|
|
76
|
-
if (errorMessage.toLowerCase().includes("not a git repository")) {
|
|
77
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`, { context, operation, originalError: error });
|
|
78
|
-
}
|
|
79
|
-
if (errorMessage.toLowerCase().includes("did not match any files")) {
|
|
80
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Specified files/patterns did not match any files in ${targetPath}: ${filesToStage.join(", ")}`, { context, operation, originalError: error, filesStaged: filesToStage });
|
|
81
|
-
}
|
|
82
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to stage files for path: ${targetPath}. Error: ${errorMessage}`, { context, operation, originalError: error, filesStaged: filesToStage });
|
|
83
48
|
}
|
|
49
|
+
const filesAddedDesc = Array.isArray(filesToStage)
|
|
50
|
+
? filesToStage.join(", ")
|
|
51
|
+
: filesToStage;
|
|
52
|
+
const successMessage = `Successfully staged: ${filesAddedDesc}`;
|
|
53
|
+
logger.info(successMessage, {
|
|
54
|
+
...context,
|
|
55
|
+
operation,
|
|
56
|
+
path: targetPath,
|
|
57
|
+
files: filesToStage,
|
|
58
|
+
});
|
|
59
|
+
const reminder = "Remember to write clear, concise commit messages using the Conventional Commits format (e.g., 'feat(scope): subject').";
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
statusMessage: `${successMessage}. ${reminder}`,
|
|
63
|
+
filesStaged: filesToStage,
|
|
64
|
+
};
|
|
84
65
|
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ErrorHandler, logger, requestContextService } from "../../../utils/index.js";
|
|
2
|
-
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
3
2
|
import { addGitFiles, GitAddInputSchema, GitAddOutputSchema, } from "./logic.js";
|
|
4
3
|
const TOOL_NAME = "git_add";
|
|
5
4
|
const TOOL_DESCRIPTION = "Stages changes in the Git repository for the next commit by adding file contents to the index (staging area). Can stage specific files/patterns or all changes (default: '.'). Returns the result as a JSON object.";
|
|
@@ -39,17 +38,19 @@ export const registerGitAddTool = async (server, getWorkingDirectory, getSession
|
|
|
39
38
|
};
|
|
40
39
|
}
|
|
41
40
|
catch (error) {
|
|
42
|
-
const
|
|
41
|
+
const mcpError = ErrorHandler.handleError(error, {
|
|
43
42
|
operation: "gitAddToolHandler",
|
|
44
43
|
context: logicContext,
|
|
45
44
|
input: validatedArgs,
|
|
46
45
|
});
|
|
47
|
-
const mcpError = handledError instanceof McpError
|
|
48
|
-
? handledError
|
|
49
|
-
: new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred while staging files.", { originalErrorName: handledError.name });
|
|
50
46
|
return {
|
|
51
47
|
isError: true,
|
|
52
48
|
content: [{ type: "text", text: `Error: ${mcpError.message}` }],
|
|
49
|
+
structuredContent: {
|
|
50
|
+
code: mcpError.code,
|
|
51
|
+
message: mcpError.message,
|
|
52
|
+
details: mcpError.details,
|
|
53
|
+
},
|
|
53
54
|
};
|
|
54
55
|
}
|
|
55
56
|
});
|
|
@@ -84,63 +84,46 @@ export async function gitBranchLogic(params, context) {
|
|
|
84
84
|
args.push("branch", "--show-current");
|
|
85
85
|
break;
|
|
86
86
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
const name = parts[0];
|
|
100
|
-
return {
|
|
101
|
-
name: isRemote ? name.split("/").slice(2).join("/") : name,
|
|
102
|
-
isCurrent,
|
|
103
|
-
isRemote,
|
|
104
|
-
commitHash: parts[1],
|
|
105
|
-
commitSubject: parts.slice(2).join(" "),
|
|
106
|
-
};
|
|
107
|
-
});
|
|
108
|
-
return {
|
|
109
|
-
success: true,
|
|
110
|
-
mode: params.mode,
|
|
111
|
-
message: `Found ${branches.length} branches.`,
|
|
112
|
-
branches,
|
|
113
|
-
currentBranch: branches.find(b => b.isCurrent)?.name || null,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
if (params.mode === "show-current") {
|
|
117
|
-
const currentBranchName = stdout.trim() || null;
|
|
87
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
|
|
88
|
+
const { stdout, stderr } = await execFileAsync("git", args.filter(Boolean));
|
|
89
|
+
if (stderr && !stderr.includes("HEAD detached")) {
|
|
90
|
+
logger.warning(`Git branch command produced stderr`, { ...context, operation, stderr });
|
|
91
|
+
}
|
|
92
|
+
if (params.mode === "list") {
|
|
93
|
+
const branches = stdout.trim().split("\n").filter(Boolean).map(line => {
|
|
94
|
+
const isCurrent = line.startsWith("* ");
|
|
95
|
+
const trimmedLine = line.replace(/^\*?\s+/, "");
|
|
96
|
+
const isRemote = trimmedLine.startsWith("remotes/");
|
|
97
|
+
const parts = trimmedLine.split(/\s+/);
|
|
98
|
+
const name = parts[0];
|
|
118
99
|
return {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
100
|
+
name: isRemote ? name.split("/").slice(2).join("/") : name,
|
|
101
|
+
isCurrent,
|
|
102
|
+
isRemote,
|
|
103
|
+
commitHash: parts[1],
|
|
104
|
+
commitSubject: parts.slice(2).join(" "),
|
|
123
105
|
};
|
|
124
|
-
}
|
|
106
|
+
});
|
|
125
107
|
return {
|
|
126
108
|
success: true,
|
|
127
109
|
mode: params.mode,
|
|
128
|
-
message: `
|
|
110
|
+
message: `Found ${branches.length} branches.`,
|
|
111
|
+
branches,
|
|
112
|
+
currentBranch: branches.find(b => b.isCurrent)?.name || null,
|
|
129
113
|
};
|
|
130
114
|
}
|
|
131
|
-
|
|
132
|
-
const
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
throw new McpError(BaseErrorCode.CONFLICT, `Branch '${params.branchName}' already exists. Use force=true to overwrite.`);
|
|
140
|
-
}
|
|
141
|
-
if (params.mode === "delete" && errorMessage.includes("not found")) {
|
|
142
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Branch '${params.branchName}' not found.`);
|
|
143
|
-
}
|
|
144
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git branch ${params.mode} failed: ${errorMessage}`);
|
|
115
|
+
if (params.mode === "show-current") {
|
|
116
|
+
const currentBranchName = stdout.trim() || null;
|
|
117
|
+
return {
|
|
118
|
+
success: true,
|
|
119
|
+
mode: params.mode,
|
|
120
|
+
message: currentBranchName ? `Current branch is '${currentBranchName}'.` : "Currently in detached HEAD state.",
|
|
121
|
+
currentBranch: currentBranchName,
|
|
122
|
+
};
|
|
145
123
|
}
|
|
124
|
+
return {
|
|
125
|
+
success: true,
|
|
126
|
+
mode: params.mode,
|
|
127
|
+
message: `Operation '${params.mode}' on branch '${params.branchName || params.newBranchName}' completed successfully.`,
|
|
128
|
+
};
|
|
146
129
|
}
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
* @fileoverview Handles registration and error handling for the git_branch tool.
|
|
3
3
|
* @module src/mcp-server/tools/gitBranch/registration
|
|
4
4
|
*/
|
|
5
|
-
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
6
5
|
import { ErrorHandler, logger, requestContextService } from "../../../utils/index.js";
|
|
7
6
|
import { GitBranchBaseSchema, gitBranchLogic, GitBranchOutputSchema } from "./logic.js";
|
|
8
7
|
const TOOL_NAME = "git_branch";
|
|
@@ -45,18 +44,19 @@ export const registerGitBranchTool = async (server, getWorkingDirectory, getSess
|
|
|
45
44
|
}
|
|
46
45
|
catch (error) {
|
|
47
46
|
logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
|
|
48
|
-
const
|
|
47
|
+
const mcpError = ErrorHandler.handleError(error, {
|
|
49
48
|
operation: `tool:${TOOL_NAME}`,
|
|
50
49
|
context: handlerContext,
|
|
51
50
|
input: params,
|
|
52
51
|
});
|
|
53
|
-
const mcpError = handledError instanceof McpError
|
|
54
|
-
? handledError
|
|
55
|
-
: new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred.", { originalError: handledError });
|
|
56
52
|
return {
|
|
57
53
|
isError: true,
|
|
58
|
-
content: [{ type: "text", text: mcpError.message }],
|
|
59
|
-
structuredContent:
|
|
54
|
+
content: [{ type: "text", text: `Error: ${mcpError.message}` }],
|
|
55
|
+
structuredContent: {
|
|
56
|
+
code: mcpError.code,
|
|
57
|
+
message: mcpError.message,
|
|
58
|
+
details: mcpError.details,
|
|
59
|
+
},
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
});
|
|
@@ -40,44 +40,22 @@ export async function checkoutGit(params, context) {
|
|
|
40
40
|
if (params.newBranch)
|
|
41
41
|
args.push("-b", params.newBranch);
|
|
42
42
|
args.push(params.branchOrPath);
|
|
43
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
|
|
44
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
45
|
+
const message = stderr.trim() || stdout.trim();
|
|
46
|
+
logger.info("git checkout executed successfully", { ...context, operation, message });
|
|
47
|
+
let currentBranch;
|
|
43
48
|
try {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
const message = stderr.trim() || stdout.trim();
|
|
47
|
-
logger.info("git checkout executed successfully", { ...context, operation, message });
|
|
48
|
-
let currentBranch;
|
|
49
|
-
try {
|
|
50
|
-
const { stdout: branchStdout } = await execFileAsync("git", ["-C", targetPath, "branch", "--show-current"]);
|
|
51
|
-
currentBranch = branchStdout.trim();
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
currentBranch = "Detached HEAD";
|
|
55
|
-
}
|
|
56
|
-
return {
|
|
57
|
-
success: true,
|
|
58
|
-
message,
|
|
59
|
-
currentBranch,
|
|
60
|
-
newBranchCreated: !!params.newBranch,
|
|
61
|
-
};
|
|
49
|
+
const { stdout: branchStdout } = await execFileAsync("git", ["-C", targetPath, "branch", "--show-current"]);
|
|
50
|
+
currentBranch = branchStdout.trim();
|
|
62
51
|
}
|
|
63
|
-
catch
|
|
64
|
-
|
|
65
|
-
logger.error(`Failed to execute git checkout command`, { ...context, operation, errorMessage });
|
|
66
|
-
if (errorMessage.toLowerCase().includes("not a git repository")) {
|
|
67
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`);
|
|
68
|
-
}
|
|
69
|
-
if (errorMessage.match(/pathspec '.*?' did not match/)) {
|
|
70
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Branch or pathspec not found: ${params.branchOrPath}.`);
|
|
71
|
-
}
|
|
72
|
-
if (errorMessage.includes("already exists")) {
|
|
73
|
-
throw new McpError(BaseErrorCode.CONFLICT, `Cannot create new branch '${params.newBranch}': it already exists.`);
|
|
74
|
-
}
|
|
75
|
-
if (errorMessage.includes("overwritten by checkout")) {
|
|
76
|
-
throw new McpError(BaseErrorCode.CONFLICT, "Checkout failed due to uncommitted local changes. Stash or commit them, or use --force.");
|
|
77
|
-
}
|
|
78
|
-
if (errorMessage.includes("invalid reference")) {
|
|
79
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid branch name or reference: ${params.branchOrPath}.`);
|
|
80
|
-
}
|
|
81
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git checkout failed: ${errorMessage}`);
|
|
52
|
+
catch {
|
|
53
|
+
currentBranch = "Detached HEAD";
|
|
82
54
|
}
|
|
55
|
+
return {
|
|
56
|
+
success: true,
|
|
57
|
+
message,
|
|
58
|
+
currentBranch,
|
|
59
|
+
newBranchCreated: !!params.newBranch,
|
|
60
|
+
};
|
|
83
61
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @module src/mcp-server/tools/gitCheckout/registration
|
|
4
4
|
*/
|
|
5
5
|
import { ErrorHandler, logger, requestContextService } from "../../../utils/index.js";
|
|
6
|
-
import { McpError, BaseErrorCode } from "../../../types-global/errors.js";
|
|
7
6
|
import { checkoutGit, GitCheckoutInputSchema, GitCheckoutOutputSchema, } from "./logic.js";
|
|
8
7
|
const TOOL_NAME = "git_checkout";
|
|
9
8
|
const TOOL_DESCRIPTION = "Switches branches or restores working tree files. Can checkout branches, commits, tags, or specific file paths. Supports creating new branches and forcing checkout.";
|
|
@@ -45,18 +44,19 @@ export const registerGitCheckoutTool = async (server, getWorkingDirectory, getSe
|
|
|
45
44
|
}
|
|
46
45
|
catch (error) {
|
|
47
46
|
logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
|
|
48
|
-
const
|
|
47
|
+
const mcpError = ErrorHandler.handleError(error, {
|
|
49
48
|
operation: `tool:${TOOL_NAME}`,
|
|
50
49
|
context: handlerContext,
|
|
51
50
|
input: params,
|
|
52
51
|
});
|
|
53
|
-
const mcpError = handledError instanceof McpError
|
|
54
|
-
? handledError
|
|
55
|
-
: new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred.", { originalError: handledError });
|
|
56
52
|
return {
|
|
57
53
|
isError: true,
|
|
58
|
-
content: [{ type: "text", text: mcpError.message }],
|
|
59
|
-
structuredContent:
|
|
54
|
+
content: [{ type: "text", text: `Error: ${mcpError.message}` }],
|
|
55
|
+
structuredContent: {
|
|
56
|
+
code: mcpError.code,
|
|
57
|
+
message: mcpError.message,
|
|
58
|
+
details: mcpError.details,
|
|
59
|
+
},
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
});
|
|
@@ -46,32 +46,13 @@ export async function gitCherryPickLogic(params, context) {
|
|
|
46
46
|
if (params.signoff)
|
|
47
47
|
args.push("--signoff");
|
|
48
48
|
args.push(params.commitRef);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
return { success: true, message, commitCreated, conflicts };
|
|
59
|
-
}
|
|
60
|
-
catch (error) {
|
|
61
|
-
const errorMessage = error.stderr || error.stdout || error.message || "";
|
|
62
|
-
logger.error(`Failed to execute git cherry-pick command`, { ...context, operation, errorMessage });
|
|
63
|
-
if (errorMessage.toLowerCase().includes("not a git repository")) {
|
|
64
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`);
|
|
65
|
-
}
|
|
66
|
-
if (/conflict/i.test(errorMessage)) {
|
|
67
|
-
throw new McpError(BaseErrorCode.CONFLICT, `Failed to cherry-pick due to conflicts. Resolve conflicts and use 'git cherry-pick --continue' or '--abort'.`);
|
|
68
|
-
}
|
|
69
|
-
if (/bad revision/i.test(errorMessage)) {
|
|
70
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid commit reference '${params.commitRef}'.`);
|
|
71
|
-
}
|
|
72
|
-
if (/your local changes would be overwritten/i.test(errorMessage)) {
|
|
73
|
-
throw new McpError(BaseErrorCode.CONFLICT, "Your local changes would be overwritten. Please commit or stash them first.");
|
|
74
|
-
}
|
|
75
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git cherry-pick failed: ${errorMessage}`);
|
|
76
|
-
}
|
|
49
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
|
|
50
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
51
|
+
const output = stdout + stderr;
|
|
52
|
+
const conflicts = /conflict/i.test(output);
|
|
53
|
+
const commitCreated = !params.noCommit && !conflicts;
|
|
54
|
+
const message = conflicts
|
|
55
|
+
? `Cherry-pick resulted in conflicts for commit(s) '${params.commitRef}'. Manual resolution required.`
|
|
56
|
+
: `Successfully cherry-picked commit(s) '${params.commitRef}'.`;
|
|
57
|
+
return { success: true, message, commitCreated, conflicts };
|
|
77
58
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @module src/mcp-server/tools/gitCherryPick/registration
|
|
4
4
|
*/
|
|
5
5
|
import { ErrorHandler, logger, requestContextService } from "../../../utils/index.js";
|
|
6
|
-
import { McpError, BaseErrorCode } from "../../../types-global/errors.js";
|
|
7
6
|
import { gitCherryPickLogic, GitCherryPickInputSchema, GitCherryPickOutputSchema, } from "./logic.js";
|
|
8
7
|
const TOOL_NAME = "git_cherry_pick";
|
|
9
8
|
const TOOL_DESCRIPTION = "Applies the changes introduced by existing commits. Supports picking single commits or ranges, handling merge commits, and options like --no-commit and --signoff. Returns results as a JSON object, indicating success, failure, or conflicts.";
|
|
@@ -45,18 +44,19 @@ export const registerGitCherryPickTool = async (server, getWorkingDirectory, get
|
|
|
45
44
|
}
|
|
46
45
|
catch (error) {
|
|
47
46
|
logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
|
|
48
|
-
const
|
|
47
|
+
const mcpError = ErrorHandler.handleError(error, {
|
|
49
48
|
operation: `tool:${TOOL_NAME}`,
|
|
50
49
|
context: handlerContext,
|
|
51
50
|
input: params,
|
|
52
51
|
});
|
|
53
|
-
const mcpError = handledError instanceof McpError
|
|
54
|
-
? handledError
|
|
55
|
-
: new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred.", { originalError: handledError });
|
|
56
52
|
return {
|
|
57
53
|
isError: true,
|
|
58
|
-
content: [{ type: "text", text: mcpError.message }],
|
|
59
|
-
structuredContent:
|
|
54
|
+
content: [{ type: "text", text: `Error: ${mcpError.message}` }],
|
|
55
|
+
structuredContent: {
|
|
56
|
+
code: mcpError.code,
|
|
57
|
+
message: mcpError.message,
|
|
58
|
+
details: mcpError.details,
|
|
59
|
+
},
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
});
|
|
@@ -46,24 +46,14 @@ export async function gitCleanLogic(params, context) {
|
|
|
46
46
|
args.push("-d");
|
|
47
47
|
if (params.ignored)
|
|
48
48
|
args.push("-x");
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
logger.warning(`Git clean command produced stderr`, { ...context, operation, stderr });
|
|
54
|
-
}
|
|
55
|
-
const filesAffected = stdout.trim().split("\n").map(line => line.replace(/^Would remove |^Removing /i, "").trim()).filter(Boolean);
|
|
56
|
-
const message = params.dryRun
|
|
57
|
-
? `Dry run complete. Files that would be removed: ${filesAffected.length}`
|
|
58
|
-
: `Clean operation complete. Files removed: ${filesAffected.length}`;
|
|
59
|
-
return { success: true, message, filesAffected, dryRun: params.dryRun };
|
|
60
|
-
}
|
|
61
|
-
catch (error) {
|
|
62
|
-
const errorMessage = error.stderr || error.message || "";
|
|
63
|
-
logger.error(`Failed to execute git clean command`, { ...context, operation, errorMessage });
|
|
64
|
-
if (errorMessage.toLowerCase().includes("not a git repository")) {
|
|
65
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`);
|
|
66
|
-
}
|
|
67
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git clean failed: ${errorMessage}`);
|
|
49
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
|
|
50
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
51
|
+
if (stderr) {
|
|
52
|
+
logger.warning(`Git clean command produced stderr`, { ...context, operation, stderr });
|
|
68
53
|
}
|
|
54
|
+
const filesAffected = stdout.trim().split("\n").map(line => line.replace(/^Would remove |^Removing /i, "").trim()).filter(Boolean);
|
|
55
|
+
const message = params.dryRun
|
|
56
|
+
? `Dry run complete. Files that would be removed: ${filesAffected.length}`
|
|
57
|
+
: `Clean operation complete. Files removed: ${filesAffected.length}`;
|
|
58
|
+
return { success: true, message, filesAffected, dryRun: params.dryRun };
|
|
69
59
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @module src/mcp-server/tools/gitClean/registration
|
|
4
4
|
*/
|
|
5
5
|
import { ErrorHandler, logger, requestContextService } from "../../../utils/index.js";
|
|
6
|
-
import { McpError, BaseErrorCode } from "../../../types-global/errors.js";
|
|
7
6
|
import { gitCleanLogic, GitCleanInputSchema, GitCleanOutputSchema, } from "./logic.js";
|
|
8
7
|
const TOOL_NAME = "git_clean";
|
|
9
8
|
const TOOL_DESCRIPTION = "Removes untracked files from the working directory. Supports dry runs, removing directories, and removing ignored files. CRITICAL: Requires explicit `force: true` parameter for safety as this is a destructive operation. Returns results as a JSON object.";
|
|
@@ -45,18 +44,19 @@ export const registerGitCleanTool = async (server, getWorkingDirectory, getSessi
|
|
|
45
44
|
}
|
|
46
45
|
catch (error) {
|
|
47
46
|
logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
|
|
48
|
-
const
|
|
47
|
+
const mcpError = ErrorHandler.handleError(error, {
|
|
49
48
|
operation: `tool:${TOOL_NAME}`,
|
|
50
49
|
context: handlerContext,
|
|
51
50
|
input: params,
|
|
52
51
|
});
|
|
53
|
-
const mcpError = handledError instanceof McpError
|
|
54
|
-
? handledError
|
|
55
|
-
: new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred.", { originalError: handledError });
|
|
56
52
|
return {
|
|
57
53
|
isError: true,
|
|
58
|
-
content: [{ type: "text", text: mcpError.message }],
|
|
59
|
-
structuredContent:
|
|
54
|
+
content: [{ type: "text", text: `Error: ${mcpError.message}` }],
|
|
55
|
+
structuredContent: {
|
|
56
|
+
code: mcpError.code,
|
|
57
|
+
message: mcpError.message,
|
|
58
|
+
details: mcpError.details,
|
|
59
|
+
},
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
});
|
|
@@ -4,7 +4,6 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
import { logger } from "../../../utils/index.js";
|
|
7
|
-
import { McpError, BaseErrorCode } from "../../../types-global/errors.js";
|
|
8
7
|
// 1. DEFINE the Zod input schema.
|
|
9
8
|
export const GitClearWorkingDirInputSchema = z.object({});
|
|
10
9
|
// 2. DEFINE the Zod response schema.
|
|
@@ -19,14 +18,8 @@ export const GitClearWorkingDirOutputSchema = z.object({
|
|
|
19
18
|
export async function gitClearWorkingDirLogic(params, context) {
|
|
20
19
|
const operation = "gitClearWorkingDirLogic";
|
|
21
20
|
logger.debug(`Executing ${operation}`, { ...context, params });
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
return { success: true, message };
|
|
27
|
-
}
|
|
28
|
-
catch (error) {
|
|
29
|
-
logger.error("Failed to clear working directory in session state", { ...context, operation, error });
|
|
30
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, "Failed to update session state.");
|
|
31
|
-
}
|
|
21
|
+
context.clearWorkingDirectory();
|
|
22
|
+
const message = "Session working directory cleared successfully.";
|
|
23
|
+
logger.info(message, { ...context, operation });
|
|
24
|
+
return { success: true, message };
|
|
32
25
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @module src/mcp-server/tools/gitClearWorkingDir/registration
|
|
4
4
|
*/
|
|
5
5
|
import { ErrorHandler, logger, requestContextService } from "../../../utils/index.js";
|
|
6
|
-
import { McpError, BaseErrorCode } from "../../../types-global/errors.js";
|
|
7
6
|
import { gitClearWorkingDirLogic, GitClearWorkingDirInputSchema, GitClearWorkingDirOutputSchema, } from "./logic.js";
|
|
8
7
|
const TOOL_NAME = "git_clear_working_dir";
|
|
9
8
|
const TOOL_DESCRIPTION = "Clears the session-specific working directory previously set by `git_set_working_dir`. Subsequent Git tool calls in this session will require an explicit `path` parameter or will default to the server's current working directory. Returns the result as a JSON object.";
|
|
@@ -45,18 +44,19 @@ export const registerGitClearWorkingDirTool = async (server, clearWorkingDirecto
|
|
|
45
44
|
}
|
|
46
45
|
catch (error) {
|
|
47
46
|
logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
|
|
48
|
-
const
|
|
47
|
+
const mcpError = ErrorHandler.handleError(error, {
|
|
49
48
|
operation: `tool:${TOOL_NAME}`,
|
|
50
49
|
context: handlerContext,
|
|
51
50
|
input: params,
|
|
52
51
|
});
|
|
53
|
-
const mcpError = handledError instanceof McpError
|
|
54
|
-
? handledError
|
|
55
|
-
: new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred.", { originalError: handledError });
|
|
56
52
|
return {
|
|
57
53
|
isError: true,
|
|
58
|
-
content: [{ type: "text", text: mcpError.message }],
|
|
59
|
-
structuredContent:
|
|
54
|
+
content: [{ type: "text", text: `Error: ${mcpError.message}` }],
|
|
55
|
+
structuredContent: {
|
|
56
|
+
code: mcpError.code,
|
|
57
|
+
message: mcpError.message,
|
|
58
|
+
details: mcpError.details,
|
|
59
|
+
},
|
|
60
60
|
};
|
|
61
61
|
}
|
|
62
62
|
});
|
|
@@ -31,8 +31,12 @@ export async function gitCloneLogic(params, context) {
|
|
|
31
31
|
const operation = "gitCloneLogic";
|
|
32
32
|
logger.debug(`Executing ${operation}`, { ...context, params });
|
|
33
33
|
const sanitizedTargetPath = sanitization.sanitizePath(params.targetPath, { allowAbsolute: true }).sanitizedPath;
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
const stats = await fs.stat(sanitizedTargetPath).catch(err => {
|
|
35
|
+
if (err.code === 'ENOENT')
|
|
36
|
+
return null;
|
|
37
|
+
throw err;
|
|
38
|
+
});
|
|
39
|
+
if (stats) {
|
|
36
40
|
if (stats.isDirectory()) {
|
|
37
41
|
const files = await fs.readdir(sanitizedTargetPath);
|
|
38
42
|
if (files.length > 0) {
|
|
@@ -43,13 +47,6 @@ export async function gitCloneLogic(params, context) {
|
|
|
43
47
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Target path exists but is not a directory: ${sanitizedTargetPath}`);
|
|
44
48
|
}
|
|
45
49
|
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
if (error instanceof McpError)
|
|
48
|
-
throw error;
|
|
49
|
-
if (error.code !== "ENOENT") {
|
|
50
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to check target directory: ${error.message}`);
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
50
|
const args = ["clone"];
|
|
54
51
|
if (params.quiet)
|
|
55
52
|
args.push("--quiet");
|
|
@@ -58,24 +55,8 @@ export async function gitCloneLogic(params, context) {
|
|
|
58
55
|
if (params.depth)
|
|
59
56
|
args.push("--depth", String(params.depth));
|
|
60
57
|
args.push(params.repositoryUrl, sanitizedTargetPath);
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return { success: true, message: successMessage, path: sanitizedTargetPath };
|
|
66
|
-
}
|
|
67
|
-
catch (error) {
|
|
68
|
-
const errorMessage = error.stderr || error.message || "";
|
|
69
|
-
logger.error(`Failed to execute git clone command`, { ...context, operation, errorMessage });
|
|
70
|
-
if (errorMessage.toLowerCase().includes("repository not found")) {
|
|
71
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Repository not found or access denied: ${params.repositoryUrl}`);
|
|
72
|
-
}
|
|
73
|
-
if (errorMessage.toLowerCase().includes("permission denied")) {
|
|
74
|
-
throw new McpError(BaseErrorCode.FORBIDDEN, `Permission denied for path: ${sanitizedTargetPath}`);
|
|
75
|
-
}
|
|
76
|
-
if (errorMessage.toLowerCase().includes("timeout")) {
|
|
77
|
-
throw new McpError(BaseErrorCode.TIMEOUT, `Git clone operation timed out for repository: ${params.repositoryUrl}`);
|
|
78
|
-
}
|
|
79
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to clone repository: ${errorMessage}`);
|
|
80
|
-
}
|
|
58
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
|
|
59
|
+
await execFileAsync("git", args, { timeout: 300000 }); // 5 minutes timeout
|
|
60
|
+
const successMessage = `Repository cloned successfully into ${sanitizedTargetPath}`;
|
|
61
|
+
return { success: true, message: successMessage, path: sanitizedTargetPath };
|
|
81
62
|
}
|