@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
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @module src/mcp-server/tools/gitClone/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 { gitCloneLogic, GitCloneInputSchema, GitCloneOutputSchema, } from "./logic.js";
|
|
8
7
|
const TOOL_NAME = "git_clone";
|
|
9
8
|
const TOOL_DESCRIPTION = "Clones a Git repository from a given URL into a specified absolute directory path. Supports cloning specific branches and setting clone depth.";
|
|
@@ -40,18 +39,19 @@ export const registerGitCloneTool = async (server, getSessionId) => {
|
|
|
40
39
|
}
|
|
41
40
|
catch (error) {
|
|
42
41
|
logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
|
|
43
|
-
const
|
|
42
|
+
const mcpError = ErrorHandler.handleError(error, {
|
|
44
43
|
operation: `tool:${TOOL_NAME}`,
|
|
45
44
|
context: handlerContext,
|
|
46
45
|
input: params,
|
|
47
46
|
});
|
|
48
|
-
const mcpError = handledError instanceof McpError
|
|
49
|
-
? handledError
|
|
50
|
-
: new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred.", { originalError: handledError });
|
|
51
47
|
return {
|
|
52
48
|
isError: true,
|
|
53
|
-
content: [{ type: "text", text: mcpError.message }],
|
|
54
|
-
structuredContent:
|
|
49
|
+
content: [{ type: "text", text: `Error: ${mcpError.message}` }],
|
|
50
|
+
structuredContent: {
|
|
51
|
+
code: mcpError.code,
|
|
52
|
+
message: mcpError.message,
|
|
53
|
+
details: mcpError.details,
|
|
54
|
+
},
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
});
|
|
@@ -30,23 +30,12 @@ export const GitCommitOutputSchema = z.object({
|
|
|
30
30
|
async function stageFiles(targetPath, files, context) {
|
|
31
31
|
const operation = "stageFilesForCommit";
|
|
32
32
|
logger.debug(`Staging files: ${files.join(", ")}`, { ...context, operation });
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
await execFileAsync("git", ["-C", targetPath, "add", "--", ...sanitizedFiles]);
|
|
36
|
-
}
|
|
37
|
-
catch (error) {
|
|
38
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to stage files: ${error.stderr || error.message}`);
|
|
39
|
-
}
|
|
33
|
+
const sanitizedFiles = files.map(file => sanitization.sanitizePath(file, { rootDir: targetPath }).sanitizedPath);
|
|
34
|
+
await execFileAsync("git", ["-C", targetPath, "add", "--", ...sanitizedFiles]);
|
|
40
35
|
}
|
|
41
36
|
async function getCommittedFiles(targetPath, commitHash, context) {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return stdout.trim().split("\n").filter(Boolean);
|
|
45
|
-
}
|
|
46
|
-
catch (error) {
|
|
47
|
-
logger.warning("Failed to retrieve committed files list", { ...context, commitHash, error: error.message });
|
|
48
|
-
return [];
|
|
49
|
-
}
|
|
37
|
+
const { stdout } = await execFileAsync("git", ["-C", targetPath, "show", "--pretty=", "--name-only", commitHash]);
|
|
38
|
+
return stdout.trim().split("\n").filter(Boolean);
|
|
50
39
|
}
|
|
51
40
|
/**
|
|
52
41
|
* 4. IMPLEMENT the core logic function.
|
|
@@ -78,47 +67,28 @@ export async function commitGitChanges(params, context) {
|
|
|
78
67
|
logger.debug(`Executing command: git ${finalArgs.join(" ")}`, { ...context, operation });
|
|
79
68
|
return await execFileAsync("git", finalArgs);
|
|
80
69
|
};
|
|
70
|
+
let result;
|
|
71
|
+
const shouldSign = config.gitSignCommits;
|
|
81
72
|
try {
|
|
82
|
-
|
|
83
|
-
const shouldSign = config.gitSignCommits;
|
|
84
|
-
try {
|
|
85
|
-
result = await attemptCommit(shouldSign);
|
|
86
|
-
}
|
|
87
|
-
catch (error) {
|
|
88
|
-
const isSigningError = (error.stderr || "").includes("gpg failed to sign");
|
|
89
|
-
if (shouldSign && isSigningError && params.forceUnsignedOnFailure) {
|
|
90
|
-
logger.warning("Commit with signing failed. Retrying without signature.", { ...context, operation });
|
|
91
|
-
result = await attemptCommit(false);
|
|
92
|
-
}
|
|
93
|
-
else {
|
|
94
|
-
throw error;
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
const commitHashMatch = result.stdout.match(/([a-f0-9]{7,40})/);
|
|
98
|
-
const commitHash = commitHashMatch ? commitHashMatch[1] : undefined;
|
|
99
|
-
const committedFiles = commitHash ? await getCommittedFiles(targetPath, commitHash, context) : [];
|
|
100
|
-
return {
|
|
101
|
-
success: true,
|
|
102
|
-
message: `Commit successful: ${commitHash}`,
|
|
103
|
-
commitHash,
|
|
104
|
-
committedFiles,
|
|
105
|
-
};
|
|
73
|
+
result = await attemptCommit(shouldSign);
|
|
106
74
|
}
|
|
107
75
|
catch (error) {
|
|
108
|
-
const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
76
|
+
const isSigningError = (error.stderr || "").includes("gpg failed to sign");
|
|
77
|
+
if (shouldSign && isSigningError && params.forceUnsignedOnFailure) {
|
|
78
|
+
logger.warning("Commit with signing failed. Retrying without signature.", { ...context, operation });
|
|
79
|
+
result = await attemptCommit(false);
|
|
112
80
|
}
|
|
113
|
-
|
|
114
|
-
|
|
81
|
+
else {
|
|
82
|
+
throw error;
|
|
115
83
|
}
|
|
116
|
-
if (errorMessage.includes("conflicts")) {
|
|
117
|
-
throw new McpError(BaseErrorCode.CONFLICT, `Commit failed due to unresolved conflicts.`);
|
|
118
|
-
}
|
|
119
|
-
if (errorMessage.includes("pre-commit hook")) {
|
|
120
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Commit failed due to pre-commit hook failure.`);
|
|
121
|
-
}
|
|
122
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git commit failed: ${errorMessage}`);
|
|
123
84
|
}
|
|
85
|
+
const commitHashMatch = result.stdout.match(/([a-f0-9]{7,40})/);
|
|
86
|
+
const commitHash = commitHashMatch ? commitHashMatch[1] : undefined;
|
|
87
|
+
const committedFiles = commitHash ? await getCommittedFiles(targetPath, commitHash, context) : [];
|
|
88
|
+
return {
|
|
89
|
+
success: true,
|
|
90
|
+
message: `Commit successful: ${commitHash}`,
|
|
91
|
+
commitHash,
|
|
92
|
+
committedFiles,
|
|
93
|
+
};
|
|
124
94
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @module src/mcp-server/tools/gitCommit/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 { commitGitChanges, GitCommitInputSchema, GitCommitOutputSchema, } from "./logic.js";
|
|
8
7
|
const TOOL_NAME = "git_commit";
|
|
9
8
|
const TOOL_DESCRIPTION = `Commits staged changes to the Git repository index with a descriptive message. Supports author override, amending, and empty commits. Returns a JSON result.
|
|
@@ -67,18 +66,19 @@ export const registerGitCommitTool = async (server, getWorkingDirectory, getSess
|
|
|
67
66
|
}
|
|
68
67
|
catch (error) {
|
|
69
68
|
logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
|
|
70
|
-
const
|
|
69
|
+
const mcpError = ErrorHandler.handleError(error, {
|
|
71
70
|
operation: `tool:${TOOL_NAME}`,
|
|
72
71
|
context: handlerContext,
|
|
73
72
|
input: params,
|
|
74
73
|
});
|
|
75
|
-
const mcpError = handledError instanceof McpError
|
|
76
|
-
? handledError
|
|
77
|
-
: new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred.", { originalError: handledError });
|
|
78
74
|
return {
|
|
79
75
|
isError: true,
|
|
80
|
-
content: [{ type: "text", text: mcpError.message }],
|
|
81
|
-
structuredContent:
|
|
76
|
+
content: [{ type: "text", text: `Error: ${mcpError.message}` }],
|
|
77
|
+
structuredContent: {
|
|
78
|
+
code: mcpError.code,
|
|
79
|
+
message: mcpError.message,
|
|
80
|
+
details: mcpError.details,
|
|
81
|
+
},
|
|
82
82
|
};
|
|
83
83
|
}
|
|
84
84
|
});
|
|
@@ -34,16 +34,13 @@ async function getUntrackedFilesDiff(targetPath, context) {
|
|
|
34
34
|
return "";
|
|
35
35
|
let diffs = "";
|
|
36
36
|
for (const file of untrackedFiles) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
else
|
|
45
|
-
logger.warning(`Failed to diff untracked file: ${file}`, { ...context, error: error.message });
|
|
46
|
-
}
|
|
37
|
+
const { stdout: diffOut } = await execFileAsync("git", ["-C", targetPath, "diff", "--no-index", "/dev/null", file]).catch(err => {
|
|
38
|
+
if (err.stdout)
|
|
39
|
+
return { stdout: err.stdout };
|
|
40
|
+
logger.warning(`Failed to diff untracked file: ${file}`, { ...context, error: err.message });
|
|
41
|
+
return { stdout: "" };
|
|
42
|
+
});
|
|
43
|
+
diffs += diffOut;
|
|
47
44
|
}
|
|
48
45
|
return diffs;
|
|
49
46
|
}
|
|
@@ -71,33 +68,20 @@ export async function diffGitChanges(params, context) {
|
|
|
71
68
|
}
|
|
72
69
|
if (params.file)
|
|
73
70
|
args.push("--", params.file);
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
combinedDiff += (combinedDiff ? "\n" : "") + untrackedDiff;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
const noChanges = combinedDiff.trim() === "";
|
|
85
|
-
const message = noChanges ? "No changes found." : `Diff generated successfully.${params.includeUntracked ? " Untracked files included." : ""}`;
|
|
86
|
-
return {
|
|
87
|
-
success: true,
|
|
88
|
-
diff: noChanges ? "No changes found." : combinedDiff,
|
|
89
|
-
message,
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
catch (error) {
|
|
93
|
-
const errorMessage = error.stderr || error.message || "";
|
|
94
|
-
logger.error(`Failed to execute git diff command`, { ...context, operation, errorMessage });
|
|
95
|
-
if (errorMessage.toLowerCase().includes("not a git repository")) {
|
|
96
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`);
|
|
97
|
-
}
|
|
98
|
-
if (errorMessage.includes("bad object") || errorMessage.includes("unknown revision")) {
|
|
99
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Invalid commit reference or file path specified.`);
|
|
71
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
|
|
72
|
+
const { stdout } = await execFileAsync("git", args, { maxBuffer: 1024 * 1024 * 20 });
|
|
73
|
+
let combinedDiff = stdout;
|
|
74
|
+
if (params.includeUntracked) {
|
|
75
|
+
const untrackedDiff = await getUntrackedFilesDiff(targetPath, context);
|
|
76
|
+
if (untrackedDiff) {
|
|
77
|
+
combinedDiff += (combinedDiff ? "\n" : "") + untrackedDiff;
|
|
100
78
|
}
|
|
101
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git diff failed: ${errorMessage}`);
|
|
102
79
|
}
|
|
80
|
+
const noChanges = combinedDiff.trim() === "";
|
|
81
|
+
const message = noChanges ? "No changes found." : `Diff generated successfully.${params.includeUntracked ? " Untracked files included." : ""}`;
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
diff: noChanges ? "No changes found." : combinedDiff,
|
|
85
|
+
message,
|
|
86
|
+
};
|
|
103
87
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @module src/mcp-server/tools/gitDiff/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 { diffGitChanges, GitDiffOutputSchema, GitDiffBaseSchema, } from "./logic.js";
|
|
8
7
|
const TOOL_NAME = "git_diff";
|
|
9
8
|
const TOOL_DESCRIPTION = "Shows changes between commits, commit and working tree, etc. Can show staged changes or diff specific files. An optional 'includeUntracked' parameter (boolean) can be used to also show the content of untracked files. Returns the diff output as plain text.";
|
|
@@ -45,18 +44,19 @@ export const registerGitDiffTool = async (server, getWorkingDirectory, getSessio
|
|
|
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
|
});
|
|
@@ -44,24 +44,8 @@ export async function fetchGitRemote(params, context) {
|
|
|
44
44
|
else if (params.remote) {
|
|
45
45
|
args.push(params.remote);
|
|
46
46
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
return { success: true, message };
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
const errorMessage = error.stderr || error.message || "";
|
|
55
|
-
logger.error(`Failed to execute git fetch command`, { ...context, operation, errorMessage });
|
|
56
|
-
if (errorMessage.toLowerCase().includes("not a git repository")) {
|
|
57
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`);
|
|
58
|
-
}
|
|
59
|
-
if (errorMessage.includes("Could not read from remote repository")) {
|
|
60
|
-
throw new McpError(BaseErrorCode.SERVICE_UNAVAILABLE, `Failed to connect to remote repository '${params.remote || "default"}'.`);
|
|
61
|
-
}
|
|
62
|
-
if (errorMessage.includes("Authentication failed")) {
|
|
63
|
-
throw new McpError(BaseErrorCode.UNAUTHORIZED, `Authentication failed for remote repository '${params.remote || "default"}'.`);
|
|
64
|
-
}
|
|
65
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git fetch failed: ${errorMessage}`);
|
|
66
|
-
}
|
|
47
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
|
|
48
|
+
const { stderr } = await execFileAsync("git", args);
|
|
49
|
+
const message = stderr.trim() || "Fetch successful.";
|
|
50
|
+
return { success: true, message };
|
|
67
51
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @module src/mcp-server/tools/gitFetch/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 { fetchGitRemote, GitFetchInputSchema, GitFetchOutputSchema, } from "./logic.js";
|
|
8
7
|
const TOOL_NAME = "git_fetch";
|
|
9
8
|
const TOOL_DESCRIPTION = "Downloads objects and refs from one or more repositories. Can fetch specific remotes or all, prune stale branches, and fetch tags.";
|
|
@@ -45,18 +44,19 @@ export const registerGitFetchTool = 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
|
});
|
|
@@ -8,7 +8,6 @@ import path from "path";
|
|
|
8
8
|
import { promisify } from "util";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { logger, sanitization } from "../../../utils/index.js";
|
|
11
|
-
import { McpError, BaseErrorCode } from "../../../types-global/errors.js";
|
|
12
11
|
const execFileAsync = promisify(execFile);
|
|
13
12
|
// 1. DEFINE the Zod input schema.
|
|
14
13
|
export const GitInitInputSchema = z.object({
|
|
@@ -33,12 +32,7 @@ export async function gitInitLogic(params, context) {
|
|
|
33
32
|
logger.debug(`Executing ${operation}`, { ...context, params });
|
|
34
33
|
const targetPath = sanitization.sanitizePath(params.path, { allowAbsolute: true }).sanitizedPath;
|
|
35
34
|
const parentDir = path.dirname(targetPath);
|
|
36
|
-
|
|
37
|
-
await fs.access(parentDir, fs.constants.W_OK);
|
|
38
|
-
}
|
|
39
|
-
catch (error) {
|
|
40
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Cannot access parent directory: ${parentDir}`);
|
|
41
|
-
}
|
|
35
|
+
await fs.access(parentDir, fs.constants.W_OK);
|
|
42
36
|
const args = ["init"];
|
|
43
37
|
if (params.quiet)
|
|
44
38
|
args.push("--quiet");
|
|
@@ -46,24 +40,14 @@ export async function gitInitLogic(params, context) {
|
|
|
46
40
|
args.push("--bare");
|
|
47
41
|
args.push(`--initial-branch=${params.initialBranch || 'main'}`);
|
|
48
42
|
args.push(targetPath);
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const successMessage = stdout.trim() || `Successfully initialized Git repository in ${targetPath}`;
|
|
55
|
-
return { success: true, message: successMessage, path: targetPath, gitDirExists };
|
|
56
|
-
}
|
|
57
|
-
catch (error) {
|
|
58
|
-
const errorMessage = error.stderr || error.message || "";
|
|
59
|
-
logger.error(`Failed to execute git init command`, { ...context, operation, errorMessage });
|
|
60
|
-
if (errorMessage.toLowerCase().includes("permission denied")) {
|
|
61
|
-
throw new McpError(BaseErrorCode.FORBIDDEN, `Permission denied to initialize repository at: ${targetPath}`);
|
|
62
|
-
}
|
|
63
|
-
// Re-initializing is not an error, so we check for it and return a success response.
|
|
64
|
-
if (errorMessage.toLowerCase().includes("reinitialized existing git repository")) {
|
|
65
|
-
return { success: true, message: `Reinitialized existing Git repository in ${targetPath}`, path: targetPath, gitDirExists: true };
|
|
66
|
-
}
|
|
67
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to initialize repository: ${errorMessage}`);
|
|
43
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
|
|
44
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
45
|
+
// Re-initializing is not an error, so we check for it and return a success response.
|
|
46
|
+
if ((stderr || stdout).toLowerCase().includes("reinitialized existing git repository")) {
|
|
47
|
+
return { success: true, message: `Reinitialized existing Git repository in ${targetPath}`, path: targetPath, gitDirExists: true };
|
|
68
48
|
}
|
|
49
|
+
const gitDirPath = params.bare ? targetPath : path.join(targetPath, ".git");
|
|
50
|
+
const gitDirExists = await fs.access(gitDirPath).then(() => true).catch(() => false);
|
|
51
|
+
const successMessage = stdout.trim() || `Successfully initialized Git repository in ${targetPath}`;
|
|
52
|
+
return { success: true, message: successMessage, path: targetPath, gitDirExists };
|
|
69
53
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @module src/mcp-server/tools/gitInit/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 { gitInitLogic, GitInitInputSchema, GitInitOutputSchema, } from "./logic.js";
|
|
8
7
|
const TOOL_NAME = "git_init";
|
|
9
8
|
const TOOL_DESCRIPTION = "Initializes a new Git repository at the specified path. If path is relative or omitted, it resolves against the session working directory (if you have set the git_working_dir). Can optionally set the initial branch name and create a bare repository.";
|
|
@@ -41,18 +40,19 @@ export const registerGitInitTool = async (server, getSessionId) => {
|
|
|
41
40
|
}
|
|
42
41
|
catch (error) {
|
|
43
42
|
logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
|
|
44
|
-
const
|
|
43
|
+
const mcpError = ErrorHandler.handleError(error, {
|
|
45
44
|
operation: `tool:${TOOL_NAME}`,
|
|
46
45
|
context: handlerContext,
|
|
47
46
|
input: params,
|
|
48
47
|
});
|
|
49
|
-
const mcpError = handledError instanceof McpError
|
|
50
|
-
? handledError
|
|
51
|
-
: new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred.", { originalError: handledError });
|
|
52
48
|
return {
|
|
53
49
|
isError: true,
|
|
54
|
-
content: [{ type: "text", text: mcpError.message }],
|
|
55
|
-
structuredContent:
|
|
50
|
+
content: [{ type: "text", text: `Error: ${mcpError.message}` }],
|
|
51
|
+
structuredContent: {
|
|
52
|
+
code: mcpError.code,
|
|
53
|
+
message: mcpError.message,
|
|
54
|
+
details: mcpError.details,
|
|
55
|
+
},
|
|
56
56
|
};
|
|
57
57
|
}
|
|
58
58
|
});
|
|
@@ -65,38 +65,25 @@ export async function logGitHistory(params, context) {
|
|
|
65
65
|
args.push(`--until=${params.until}`);
|
|
66
66
|
if (params.branchOrFile)
|
|
67
67
|
args.push(params.branchOrFile);
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
return { success: true, message: "Raw log output with signature status.", rawOutput: stdout };
|
|
73
|
-
}
|
|
74
|
-
const commitRecords = stdout.split(RECORD_SEP).filter(r => r.trim());
|
|
75
|
-
const commits = commitRecords.map(record => {
|
|
76
|
-
const fields = record.trim().split(FIELD_SEP);
|
|
77
|
-
return {
|
|
78
|
-
hash: fields[0],
|
|
79
|
-
authorName: fields[1],
|
|
80
|
-
authorEmail: fields[2],
|
|
81
|
-
timestamp: parseInt(fields[3], 10),
|
|
82
|
-
subject: fields[4],
|
|
83
|
-
body: fields[5] || undefined,
|
|
84
|
-
};
|
|
85
|
-
});
|
|
86
|
-
return { success: true, message: `Found ${commits.length} commit(s).`, commits };
|
|
68
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
|
|
69
|
+
const { stdout, stderr } = await execFileAsync("git", args, { maxBuffer: 1024 * 1024 * 10 });
|
|
70
|
+
if (stderr && stderr.toLowerCase().includes("does not have any commits yet")) {
|
|
71
|
+
return { success: true, message: "Repository has no commits yet.", commits: [] };
|
|
87
72
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
logger.error(`Failed to execute git log command`, { ...context, operation, errorMessage });
|
|
91
|
-
if (errorMessage.toLowerCase().includes("not a git repository")) {
|
|
92
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`);
|
|
93
|
-
}
|
|
94
|
-
if (errorMessage.includes("bad revision")) {
|
|
95
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Invalid branch, tag, or revision specified: '${params.branchOrFile}'.`);
|
|
96
|
-
}
|
|
97
|
-
if (errorMessage.includes("does not have any commits yet")) {
|
|
98
|
-
return { success: true, message: "Repository has no commits yet.", commits: [] };
|
|
99
|
-
}
|
|
100
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git log failed: ${errorMessage}`);
|
|
73
|
+
if (params.showSignature) {
|
|
74
|
+
return { success: true, message: "Raw log output with signature status.", rawOutput: stdout };
|
|
101
75
|
}
|
|
76
|
+
const commitRecords = stdout.split(RECORD_SEP).filter(r => r.trim());
|
|
77
|
+
const commits = commitRecords.map(record => {
|
|
78
|
+
const fields = record.trim().split(FIELD_SEP);
|
|
79
|
+
return {
|
|
80
|
+
hash: fields[0],
|
|
81
|
+
authorName: fields[1],
|
|
82
|
+
authorEmail: fields[2],
|
|
83
|
+
timestamp: parseInt(fields[3], 10),
|
|
84
|
+
subject: fields[4],
|
|
85
|
+
body: fields[5] || undefined,
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
return { success: true, message: `Found ${commits.length} commit(s).`, commits };
|
|
102
89
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @module src/mcp-server/tools/gitLog/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 { logGitHistory, GitLogInputSchema, GitLogOutputSchema, } from "./logic.js";
|
|
8
7
|
const TOOL_NAME = "git_log";
|
|
9
8
|
const TOOL_DESCRIPTION = "Shows commit logs for the repository. Supports limiting count, filtering by author, date range, and specific branch/file. Returns a JSON object containing a list of commit objects (`commits` array) by default. If `showSignature: true` is used, it returns a JSON object where the `commits` array is empty and the raw signature verification output is included in the `rawOutput` field.";
|
|
@@ -45,18 +44,19 @@ export const registerGitLogTool = async (server, getWorkingDirectory, getSession
|
|
|
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
|
});
|
|
@@ -51,32 +51,13 @@ export async function gitMergeLogic(params, context) {
|
|
|
51
51
|
args.push("-m", params.commitMessage);
|
|
52
52
|
args.push(params.branch);
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
catch (error) {
|
|
66
|
-
const errorMessage = error.stderr || error.stdout || error.message || "";
|
|
67
|
-
logger.error(`Git merge command failed`, { ...context, operation, errorMessage });
|
|
68
|
-
if (errorMessage.includes("CONFLICT")) {
|
|
69
|
-
throw new McpError(BaseErrorCode.CONFLICT, "Merge failed due to conflicts. Please resolve them and commit.");
|
|
70
|
-
}
|
|
71
|
-
if (errorMessage.includes("unrelated histories")) {
|
|
72
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Merge failed: Refusing to merge unrelated histories.");
|
|
73
|
-
}
|
|
74
|
-
if (errorMessage.includes("not a git repository")) {
|
|
75
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`);
|
|
76
|
-
}
|
|
77
|
-
if (errorMessage.match(/fatal: '.*?' does not point to a commit/)) {
|
|
78
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Merge failed: Branch '${params.branch}' not found.`);
|
|
79
|
-
}
|
|
80
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git merge failed: ${errorMessage}`);
|
|
81
|
-
}
|
|
54
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
|
|
55
|
+
const { stdout } = await execFileAsync("git", args);
|
|
56
|
+
return {
|
|
57
|
+
success: true,
|
|
58
|
+
message: stdout.trim() || "Merge command executed successfully.",
|
|
59
|
+
fastForward: stdout.includes("Fast-forward"),
|
|
60
|
+
needsManualCommit: params.squash,
|
|
61
|
+
aborted: params.abort,
|
|
62
|
+
};
|
|
82
63
|
}
|
|
@@ -3,7 +3,6 @@
|
|
|
3
3
|
* @module src/mcp-server/tools/gitMerge/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 { gitMergeLogic, GitMergeInputSchema, GitMergeOutputSchema, } from "./logic.js";
|
|
8
7
|
const TOOL_NAME = "git_merge";
|
|
9
8
|
const TOOL_DESCRIPTION = "Merges the specified branch into the current branch. Supports options like --no-ff, --squash, and --abort. Returns the merge result as a JSON object.";
|
|
@@ -45,18 +44,19 @@ export const registerGitMergeTool = 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
|
});
|
|
@@ -43,27 +43,8 @@ export async function pullGitChanges(params, context) {
|
|
|
43
43
|
args.push(params.remote);
|
|
44
44
|
if (params.branch)
|
|
45
45
|
args.push(params.branch);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return { success: true, message, conflict: message.includes("CONFLICT") };
|
|
51
|
-
}
|
|
52
|
-
catch (error) {
|
|
53
|
-
const errorMessage = error.stderr || error.stdout || error.message || "";
|
|
54
|
-
logger.error(`Failed to execute git pull command`, { ...context, operation, errorMessage });
|
|
55
|
-
if (errorMessage.toLowerCase().includes("not a git repository")) {
|
|
56
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`);
|
|
57
|
-
}
|
|
58
|
-
if (errorMessage.includes("Could not read from remote repository")) {
|
|
59
|
-
throw new McpError(BaseErrorCode.SERVICE_UNAVAILABLE, "Failed to connect to remote repository.");
|
|
60
|
-
}
|
|
61
|
-
if (errorMessage.includes("merge conflict")) {
|
|
62
|
-
throw new McpError(BaseErrorCode.CONFLICT, "Pull resulted in merge conflicts.");
|
|
63
|
-
}
|
|
64
|
-
if (errorMessage.includes("unrelated histories")) {
|
|
65
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Pull failed: Refusing to merge unrelated histories.");
|
|
66
|
-
}
|
|
67
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git pull failed: ${errorMessage}`);
|
|
68
|
-
}
|
|
46
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
|
|
47
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
48
|
+
const message = stdout.trim() || stderr.trim() || "Pull command executed successfully.";
|
|
49
|
+
return { success: true, message, conflict: message.includes("CONFLICT") };
|
|
69
50
|
}
|