@cyanheads/git-mcp-server 2.1.0 → 2.1.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/README.md +8 -11
- package/dist/config/index.js +7 -7
- package/dist/index.js +35 -21
- package/dist/mcp-server/server.js +72 -56
- package/dist/mcp-server/tools/gitAdd/index.js +1 -1
- package/dist/mcp-server/tools/gitAdd/logic.js +88 -39
- package/dist/mcp-server/tools/gitAdd/registration.js +17 -14
- package/dist/mcp-server/tools/gitBranch/index.js +1 -1
- package/dist/mcp-server/tools/gitBranch/logic.js +213 -85
- package/dist/mcp-server/tools/gitBranch/registration.js +16 -13
- package/dist/mcp-server/tools/gitCheckout/index.js +1 -1
- package/dist/mcp-server/tools/gitCheckout/logic.js +85 -145
- package/dist/mcp-server/tools/gitCheckout/registration.js +16 -14
- package/dist/mcp-server/tools/gitCherryPick/index.js +1 -1
- package/dist/mcp-server/tools/gitCherryPick/logic.js +100 -41
- package/dist/mcp-server/tools/gitCherryPick/registration.js +21 -14
- package/dist/mcp-server/tools/gitClean/index.js +1 -1
- package/dist/mcp-server/tools/gitClean/logic.js +93 -41
- package/dist/mcp-server/tools/gitClean/registration.js +19 -16
- package/dist/mcp-server/tools/gitClearWorkingDir/index.js +1 -1
- package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +14 -11
- package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +19 -13
- package/dist/mcp-server/tools/gitClone/index.js +1 -1
- package/dist/mcp-server/tools/gitClone/logic.js +89 -30
- package/dist/mcp-server/tools/gitClone/registration.js +15 -12
- package/dist/mcp-server/tools/gitCommit/index.js +1 -1
- package/dist/mcp-server/tools/gitCommit/logic.js +198 -76
- package/dist/mcp-server/tools/gitCommit/registration.js +23 -20
- package/dist/mcp-server/tools/gitDiff/index.js +1 -1
- package/dist/mcp-server/tools/gitDiff/logic.js +124 -44
- package/dist/mcp-server/tools/gitDiff/registration.js +16 -14
- package/dist/mcp-server/tools/gitFetch/index.js +1 -1
- package/dist/mcp-server/tools/gitFetch/logic.js +78 -49
- package/dist/mcp-server/tools/gitFetch/registration.js +16 -14
- package/dist/mcp-server/tools/gitInit/index.js +1 -1
- package/dist/mcp-server/tools/gitInit/logic.js +88 -34
- package/dist/mcp-server/tools/gitInit/registration.js +32 -18
- package/dist/mcp-server/tools/gitLog/index.js +1 -1
- package/dist/mcp-server/tools/gitLog/logic.js +133 -47
- package/dist/mcp-server/tools/gitLog/registration.js +16 -14
- package/dist/mcp-server/tools/gitMerge/index.js +1 -1
- package/dist/mcp-server/tools/gitMerge/logic.js +102 -61
- package/dist/mcp-server/tools/gitMerge/registration.js +17 -14
- package/dist/mcp-server/tools/gitPull/index.js +1 -1
- package/dist/mcp-server/tools/gitPull/logic.js +90 -69
- package/dist/mcp-server/tools/gitPull/registration.js +16 -14
- package/dist/mcp-server/tools/gitPush/index.js +1 -1
- package/dist/mcp-server/tools/gitPush/logic.js +116 -100
- package/dist/mcp-server/tools/gitPush/registration.js +16 -14
- package/dist/mcp-server/tools/gitRebase/index.js +1 -1
- package/dist/mcp-server/tools/gitRebase/logic.js +121 -82
- package/dist/mcp-server/tools/gitRebase/registration.js +21 -14
- package/dist/mcp-server/tools/gitRemote/index.js +1 -1
- package/dist/mcp-server/tools/gitRemote/logic.js +108 -52
- package/dist/mcp-server/tools/gitRemote/registration.js +14 -11
- package/dist/mcp-server/tools/gitReset/index.js +1 -1
- package/dist/mcp-server/tools/gitReset/logic.js +65 -37
- package/dist/mcp-server/tools/gitReset/registration.js +14 -12
- package/dist/mcp-server/tools/gitSetWorkingDir/index.js +1 -1
- package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +74 -34
- package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +18 -11
- package/dist/mcp-server/tools/gitShow/index.js +1 -1
- package/dist/mcp-server/tools/gitShow/logic.js +78 -35
- package/dist/mcp-server/tools/gitShow/registration.js +17 -12
- package/dist/mcp-server/tools/gitStash/index.js +1 -1
- package/dist/mcp-server/tools/gitStash/logic.js +143 -58
- package/dist/mcp-server/tools/gitStash/registration.js +19 -12
- package/dist/mcp-server/tools/gitStatus/index.js +1 -1
- package/dist/mcp-server/tools/gitStatus/logic.js +100 -58
- package/dist/mcp-server/tools/gitStatus/registration.js +15 -12
- package/dist/mcp-server/tools/gitTag/index.js +1 -1
- package/dist/mcp-server/tools/gitTag/logic.js +124 -51
- package/dist/mcp-server/tools/gitTag/registration.js +14 -11
- package/dist/mcp-server/tools/gitWorktree/index.js +1 -1
- package/dist/mcp-server/tools/gitWorktree/logic.js +204 -95
- package/dist/mcp-server/tools/gitWorktree/registration.js +14 -11
- package/dist/mcp-server/tools/gitWrapupInstructions/index.js +1 -1
- package/dist/mcp-server/tools/gitWrapupInstructions/logic.js +23 -11
- package/dist/mcp-server/tools/gitWrapupInstructions/registration.js +14 -12
- package/dist/mcp-server/transports/httpTransport.js +187 -79
- package/dist/mcp-server/transports/stdioTransport.js +14 -8
- package/dist/types-global/errors.js +9 -4
- package/dist/utils/index.js +4 -4
- package/dist/utils/internal/errorHandler.js +62 -40
- package/dist/utils/internal/index.js +3 -3
- package/dist/utils/internal/logger.js +97 -54
- package/dist/utils/internal/requestContext.js +7 -5
- package/dist/utils/metrics/index.js +1 -1
- package/dist/utils/metrics/tokenCounter.js +18 -14
- package/dist/utils/parsing/dateParser.js +5 -5
- package/dist/utils/parsing/index.js +2 -2
- package/dist/utils/parsing/jsonParser.js +20 -11
- package/dist/utils/security/idGenerator.js +8 -10
- package/dist/utils/security/index.js +3 -3
- package/dist/utils/security/rateLimiter.js +16 -14
- package/dist/utils/security/sanitization.js +139 -82
- package/package.json +45 -23
|
@@ -1,31 +1,64 @@
|
|
|
1
|
-
import { exec } from
|
|
2
|
-
import { promisify } from
|
|
3
|
-
import { z } from
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { z } from "zod";
|
|
4
4
|
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
5
|
-
import { logger } from
|
|
5
|
+
import { logger } from "../../../utils/index.js";
|
|
6
6
|
// Import utils from barrel (RequestContext from ../utils/internal/requestContext.js)
|
|
7
|
-
import { BaseErrorCode, McpError } from
|
|
7
|
+
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
8
8
|
// Import utils from barrel (sanitization from ../utils/security/sanitization.js)
|
|
9
|
-
import { sanitization } from
|
|
9
|
+
import { sanitization } from "../../../utils/index.js";
|
|
10
10
|
const execAsync = promisify(exec);
|
|
11
11
|
// Define the BASE input schema for the git_branch tool using Zod
|
|
12
12
|
export const GitBranchBaseSchema = z.object({
|
|
13
|
-
path: z
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
13
|
+
path: z
|
|
14
|
+
.string()
|
|
15
|
+
.min(1)
|
|
16
|
+
.optional()
|
|
17
|
+
.default(".")
|
|
18
|
+
.describe("Path to the local Git repository. Defaults to the directory set via `git_set_working_dir` for the session; set 'git_set_working_dir' if not set."),
|
|
19
|
+
mode: z
|
|
20
|
+
.enum(["list", "create", "delete", "rename", "show-current"])
|
|
21
|
+
.describe("The branch operation to perform: 'list', 'create', 'delete', 'rename', 'show-current'."),
|
|
22
|
+
branchName: z
|
|
23
|
+
.string()
|
|
24
|
+
.min(1)
|
|
25
|
+
.optional()
|
|
26
|
+
.describe("The name of the branch (e.g., 'feat/new-login', 'main'). Required for 'create', 'delete', 'rename' modes."),
|
|
27
|
+
newBranchName: z
|
|
28
|
+
.string()
|
|
29
|
+
.min(1)
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("The new name for the branch (e.g., 'fix/typo-in-readme'). Required for 'rename' mode."),
|
|
32
|
+
startPoint: z
|
|
33
|
+
.string()
|
|
34
|
+
.min(1)
|
|
35
|
+
.optional()
|
|
36
|
+
.describe("Optional commit hash, tag, or existing branch name (e.g., 'main', 'v1.0.0', 'commit-hash') to start the new branch from. Used only in 'create' mode. Defaults to HEAD."),
|
|
37
|
+
force: z
|
|
38
|
+
.boolean()
|
|
39
|
+
.default(false)
|
|
40
|
+
.describe("Force the operation. Use -D for delete, -M for rename, -f for create (if branch exists). Use with caution, as forcing operations can lead to data loss."),
|
|
41
|
+
all: z
|
|
42
|
+
.boolean()
|
|
43
|
+
.default(false)
|
|
44
|
+
.describe("List both local and remote-tracking branches. Used only in 'list' mode."),
|
|
45
|
+
remote: z
|
|
46
|
+
.boolean()
|
|
47
|
+
.default(false)
|
|
48
|
+
.describe("Act on remote-tracking branches. Used with 'list' (-r) or 'delete' (-r)."),
|
|
21
49
|
});
|
|
22
50
|
// Apply refinements and export the FINAL schema for validation within the handler
|
|
23
|
-
export const GitBranchInputSchema = GitBranchBaseSchema.refine(data => !(data.mode ===
|
|
24
|
-
message: "A 'branchName' is required for 'create' mode.",
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
message: "
|
|
51
|
+
export const GitBranchInputSchema = GitBranchBaseSchema.refine((data) => !(data.mode === "create" && !data.branchName), {
|
|
52
|
+
message: "A 'branchName' is required for 'create' mode.",
|
|
53
|
+
path: ["branchName"],
|
|
54
|
+
})
|
|
55
|
+
.refine((data) => !(data.mode === "delete" && !data.branchName), {
|
|
56
|
+
message: "A 'branchName' is required for 'delete' mode.",
|
|
57
|
+
path: ["branchName"],
|
|
58
|
+
})
|
|
59
|
+
.refine((data) => !(data.mode === "rename" && (!data.branchName || !data.newBranchName)), {
|
|
60
|
+
message: "Both 'branchName' (old name) and 'newBranchName' are required for 'rename' mode.",
|
|
61
|
+
path: ["branchName", "newBranchName"],
|
|
29
62
|
});
|
|
30
63
|
/**
|
|
31
64
|
* Executes git branch commands based on the specified mode.
|
|
@@ -42,25 +75,41 @@ export async function gitBranchLogic(input, context) {
|
|
|
42
75
|
try {
|
|
43
76
|
// Resolve and sanitize the target path
|
|
44
77
|
const workingDir = context.getWorkingDirectory();
|
|
45
|
-
targetPath =
|
|
46
|
-
? input.path
|
|
47
|
-
|
|
48
|
-
if (targetPath === '.' && !workingDir) {
|
|
78
|
+
targetPath =
|
|
79
|
+
input.path && input.path !== "." ? input.path : (workingDir ?? ".");
|
|
80
|
+
if (targetPath === "." && !workingDir) {
|
|
49
81
|
logger.warning("Executing git branch in server's CWD as no path provided and no session WD set.", { ...context, operation });
|
|
50
82
|
targetPath = process.cwd();
|
|
51
83
|
}
|
|
52
|
-
else if (targetPath ===
|
|
84
|
+
else if (targetPath === "." && workingDir) {
|
|
53
85
|
targetPath = workingDir;
|
|
54
|
-
logger.debug(`Using session working directory: ${targetPath}`, {
|
|
86
|
+
logger.debug(`Using session working directory: ${targetPath}`, {
|
|
87
|
+
...context,
|
|
88
|
+
operation,
|
|
89
|
+
sessionId: context.sessionId,
|
|
90
|
+
});
|
|
55
91
|
}
|
|
56
92
|
else {
|
|
57
|
-
logger.debug(`Using provided path: ${targetPath}`, {
|
|
93
|
+
logger.debug(`Using provided path: ${targetPath}`, {
|
|
94
|
+
...context,
|
|
95
|
+
operation,
|
|
96
|
+
});
|
|
58
97
|
}
|
|
59
|
-
targetPath = sanitization.sanitizePath(targetPath, {
|
|
60
|
-
|
|
98
|
+
targetPath = sanitization.sanitizePath(targetPath, {
|
|
99
|
+
allowAbsolute: true,
|
|
100
|
+
}).sanitizedPath;
|
|
101
|
+
logger.debug("Sanitized path", {
|
|
102
|
+
...context,
|
|
103
|
+
operation,
|
|
104
|
+
sanitizedPath: targetPath,
|
|
105
|
+
});
|
|
61
106
|
}
|
|
62
107
|
catch (error) {
|
|
63
|
-
logger.error(
|
|
108
|
+
logger.error("Path resolution or sanitization failed", {
|
|
109
|
+
...context,
|
|
110
|
+
operation,
|
|
111
|
+
error,
|
|
112
|
+
});
|
|
64
113
|
if (error instanceof McpError)
|
|
65
114
|
throw error;
|
|
66
115
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
@@ -69,82 +118,129 @@ export async function gitBranchLogic(input, context) {
|
|
|
69
118
|
let command;
|
|
70
119
|
let result;
|
|
71
120
|
switch (input.mode) {
|
|
72
|
-
case
|
|
121
|
+
case "list":
|
|
73
122
|
command = `git -C "${targetPath}" branch --list --no-color`; // Start with basic list
|
|
74
123
|
if (input.all)
|
|
75
|
-
command +=
|
|
124
|
+
command += " -a"; // Add -a if requested
|
|
76
125
|
else if (input.remote)
|
|
77
|
-
command +=
|
|
78
|
-
command +=
|
|
79
|
-
logger.debug(`Executing command: ${command}`, {
|
|
126
|
+
command += " -r"; // Add -r if requested (exclusive with -a)
|
|
127
|
+
command += " --verbose"; // Add verbose for commit info
|
|
128
|
+
logger.debug(`Executing command: ${command}`, {
|
|
129
|
+
...context,
|
|
130
|
+
operation,
|
|
131
|
+
});
|
|
80
132
|
const { stdout: listStdout } = await execAsync(command);
|
|
81
|
-
const branches = listStdout
|
|
82
|
-
.
|
|
83
|
-
.
|
|
84
|
-
|
|
85
|
-
|
|
133
|
+
const branches = listStdout
|
|
134
|
+
.trim()
|
|
135
|
+
.split("\n")
|
|
136
|
+
.filter((line) => line && !line.match(/^\s*->\s*/)) // Filter out HEAD pointer lines if any
|
|
137
|
+
.map((line) => {
|
|
138
|
+
const isCurrent = line.startsWith("* ");
|
|
139
|
+
const trimmedLine = line.replace(/^\*?\s+/, ""); // Remove leading '*' and spaces
|
|
86
140
|
// Determine isRemote based on the raw trimmed line BEFORE splitting
|
|
87
|
-
const isRemote = trimmedLine.startsWith(
|
|
141
|
+
const isRemote = trimmedLine.startsWith("remotes/");
|
|
88
142
|
const parts = trimmedLine.split(/\s+/);
|
|
89
143
|
const name = parts[0]; // This might be 'remotes/origin/main' or just 'main'
|
|
90
144
|
const commitHash = parts[1] || undefined; // Verbose gives hash
|
|
91
|
-
const commitSubject = parts.slice(2).join(
|
|
145
|
+
const commitSubject = parts.slice(2).join(" ") || undefined; // Verbose gives subject
|
|
92
146
|
// Return the correct name (without 'remotes/' prefix if it was remote) and the isRemote flag
|
|
93
147
|
return {
|
|
94
|
-
name: isRemote ? name.split(
|
|
148
|
+
name: isRemote ? name.split("/").slice(2).join("/") : name, // e.g., 'origin/main' or 'main'
|
|
95
149
|
isCurrent,
|
|
96
150
|
isRemote, // Use the flag determined before splitting
|
|
97
151
|
commitHash,
|
|
98
|
-
commitSubject
|
|
152
|
+
commitSubject,
|
|
99
153
|
};
|
|
100
154
|
});
|
|
101
|
-
const currentBranch = branches.find(b => b.isCurrent)?.name;
|
|
102
|
-
result = { success: true, mode:
|
|
155
|
+
const currentBranch = branches.find((b) => b.isCurrent)?.name;
|
|
156
|
+
result = { success: true, mode: "list", branches, currentBranch };
|
|
103
157
|
break;
|
|
104
|
-
case
|
|
158
|
+
case "create":
|
|
105
159
|
// branchName is validated by Zod refine
|
|
106
160
|
command = `git -C "${targetPath}" branch `;
|
|
107
161
|
if (input.force)
|
|
108
|
-
command +=
|
|
162
|
+
command += "-f ";
|
|
109
163
|
command += `"${input.branchName}"`; // branchName is guaranteed by refine
|
|
110
164
|
if (input.startPoint)
|
|
111
165
|
command += ` "${input.startPoint}"`;
|
|
112
|
-
logger.debug(`Executing command: ${command}`, {
|
|
166
|
+
logger.debug(`Executing command: ${command}`, {
|
|
167
|
+
...context,
|
|
168
|
+
operation,
|
|
169
|
+
});
|
|
113
170
|
await execAsync(command);
|
|
114
|
-
result = {
|
|
171
|
+
result = {
|
|
172
|
+
success: true,
|
|
173
|
+
mode: "create",
|
|
174
|
+
branchName: input.branchName,
|
|
175
|
+
message: `Branch '${input.branchName}' created successfully.`,
|
|
176
|
+
};
|
|
115
177
|
break;
|
|
116
|
-
case
|
|
178
|
+
case "delete":
|
|
117
179
|
// branchName is validated by Zod refine
|
|
118
180
|
command = `git -C "${targetPath}" branch `;
|
|
119
181
|
if (input.remote)
|
|
120
|
-
command +=
|
|
121
|
-
command += input.force ?
|
|
182
|
+
command += "-r ";
|
|
183
|
+
command += input.force ? "-D " : "-d ";
|
|
122
184
|
command += `"${input.branchName}"`; // branchName is guaranteed by refine
|
|
123
|
-
logger.debug(`Executing command: ${command}`, {
|
|
185
|
+
logger.debug(`Executing command: ${command}`, {
|
|
186
|
+
...context,
|
|
187
|
+
operation,
|
|
188
|
+
});
|
|
124
189
|
const { stdout: deleteStdout } = await execAsync(command);
|
|
125
|
-
result = {
|
|
190
|
+
result = {
|
|
191
|
+
success: true,
|
|
192
|
+
mode: "delete",
|
|
193
|
+
branchName: input.branchName,
|
|
194
|
+
wasRemote: input.remote,
|
|
195
|
+
message: deleteStdout.trim() ||
|
|
196
|
+
`Branch '${input.branchName}' deleted successfully.`,
|
|
197
|
+
};
|
|
126
198
|
break;
|
|
127
|
-
case
|
|
199
|
+
case "rename":
|
|
128
200
|
// branchName and newBranchName validated by Zod refine
|
|
129
201
|
command = `git -C "${targetPath}" branch `;
|
|
130
|
-
command += input.force ?
|
|
202
|
+
command += input.force ? "-M " : "-m ";
|
|
131
203
|
command += `"${input.branchName}" "${input.newBranchName}"`;
|
|
132
|
-
logger.debug(`Executing command: ${command}`, {
|
|
204
|
+
logger.debug(`Executing command: ${command}`, {
|
|
205
|
+
...context,
|
|
206
|
+
operation,
|
|
207
|
+
});
|
|
133
208
|
await execAsync(command);
|
|
134
|
-
result = {
|
|
209
|
+
result = {
|
|
210
|
+
success: true,
|
|
211
|
+
mode: "rename",
|
|
212
|
+
oldBranchName: input.branchName,
|
|
213
|
+
newBranchName: input.newBranchName,
|
|
214
|
+
message: `Branch '${input.branchName}' renamed to '${input.newBranchName}' successfully.`,
|
|
215
|
+
};
|
|
135
216
|
break;
|
|
136
|
-
case
|
|
217
|
+
case "show-current":
|
|
137
218
|
command = `git -C "${targetPath}" branch --show-current`;
|
|
138
|
-
logger.debug(`Executing command: ${command}`, {
|
|
219
|
+
logger.debug(`Executing command: ${command}`, {
|
|
220
|
+
...context,
|
|
221
|
+
operation,
|
|
222
|
+
});
|
|
139
223
|
try {
|
|
140
224
|
const { stdout: currentStdout } = await execAsync(command);
|
|
141
225
|
const currentBranchName = currentStdout.trim();
|
|
142
|
-
result = {
|
|
226
|
+
result = {
|
|
227
|
+
success: true,
|
|
228
|
+
mode: "show-current",
|
|
229
|
+
currentBranch: currentBranchName || null,
|
|
230
|
+
message: currentBranchName
|
|
231
|
+
? `Current branch is '${currentBranchName}'.`
|
|
232
|
+
: "Currently in detached HEAD state.",
|
|
233
|
+
};
|
|
143
234
|
}
|
|
144
235
|
catch (showError) {
|
|
145
236
|
// Handle detached HEAD state specifically if command fails
|
|
146
|
-
if (showError.stderr?.includes(
|
|
147
|
-
result = {
|
|
237
|
+
if (showError.stderr?.includes("HEAD detached")) {
|
|
238
|
+
result = {
|
|
239
|
+
success: true,
|
|
240
|
+
mode: "show-current",
|
|
241
|
+
currentBranch: null,
|
|
242
|
+
message: "Currently in detached HEAD state.",
|
|
243
|
+
};
|
|
148
244
|
}
|
|
149
245
|
else {
|
|
150
246
|
throw showError; // Re-throw other errors
|
|
@@ -155,37 +251,69 @@ export async function gitBranchLogic(input, context) {
|
|
|
155
251
|
// Should not happen due to Zod validation
|
|
156
252
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid mode: ${input.mode}`, { context, operation });
|
|
157
253
|
}
|
|
158
|
-
logger.info(
|
|
254
|
+
logger.info(`git branch ${input.mode} executed successfully`, {
|
|
255
|
+
...context,
|
|
256
|
+
operation,
|
|
257
|
+
path: targetPath,
|
|
258
|
+
result,
|
|
259
|
+
});
|
|
159
260
|
return result;
|
|
160
261
|
}
|
|
161
262
|
catch (error) {
|
|
162
|
-
const errorMessage = error.stderr || error.stdout || error.message ||
|
|
163
|
-
logger.error(`Failed to execute git branch command`, {
|
|
263
|
+
const errorMessage = error.stderr || error.stdout || error.message || ""; // stdout might contain error messages too
|
|
264
|
+
logger.error(`Failed to execute git branch command`, {
|
|
265
|
+
...context,
|
|
266
|
+
operation,
|
|
267
|
+
path: targetPath,
|
|
268
|
+
error: errorMessage,
|
|
269
|
+
stderr: error.stderr,
|
|
270
|
+
stdout: error.stdout,
|
|
271
|
+
});
|
|
164
272
|
// Specific error handling
|
|
165
|
-
if (errorMessage.toLowerCase().includes(
|
|
273
|
+
if (errorMessage.toLowerCase().includes("not a git repository")) {
|
|
166
274
|
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`, { context, operation, originalError: error });
|
|
167
275
|
}
|
|
168
|
-
if (input.mode ===
|
|
169
|
-
return {
|
|
276
|
+
if (input.mode === "create" && errorMessage.includes("already exists")) {
|
|
277
|
+
return {
|
|
278
|
+
success: false,
|
|
279
|
+
mode: "create",
|
|
280
|
+
message: `Failed to create branch: Branch '${input.branchName}' already exists. Use force=true to overwrite.`,
|
|
281
|
+
error: errorMessage,
|
|
282
|
+
};
|
|
170
283
|
}
|
|
171
|
-
if (input.mode ===
|
|
172
|
-
return {
|
|
284
|
+
if (input.mode === "delete" && errorMessage.includes("not found")) {
|
|
285
|
+
return {
|
|
286
|
+
success: false,
|
|
287
|
+
mode: "delete",
|
|
288
|
+
message: `Failed to delete branch: Branch '${input.branchName}' not found.`,
|
|
289
|
+
error: errorMessage,
|
|
290
|
+
};
|
|
173
291
|
}
|
|
174
|
-
if (input.mode ===
|
|
175
|
-
return {
|
|
292
|
+
if (input.mode === "delete" && errorMessage.includes("not fully merged")) {
|
|
293
|
+
return {
|
|
294
|
+
success: false,
|
|
295
|
+
mode: "delete",
|
|
296
|
+
message: `Failed to delete branch: Branch '${input.branchName}' is not fully merged. Use force=true to delete.`,
|
|
297
|
+
error: errorMessage,
|
|
298
|
+
};
|
|
176
299
|
}
|
|
177
|
-
if (input.mode ===
|
|
178
|
-
return {
|
|
300
|
+
if (input.mode === "rename" && errorMessage.includes("already exists")) {
|
|
301
|
+
return {
|
|
302
|
+
success: false,
|
|
303
|
+
mode: "rename",
|
|
304
|
+
message: `Failed to rename branch: Branch '${input.newBranchName}' already exists. Use force=true to overwrite.`,
|
|
305
|
+
error: errorMessage,
|
|
306
|
+
};
|
|
179
307
|
}
|
|
180
|
-
if (input.mode ===
|
|
181
|
-
return {
|
|
308
|
+
if (input.mode === "rename" && errorMessage.includes("not found")) {
|
|
309
|
+
return {
|
|
310
|
+
success: false,
|
|
311
|
+
mode: "rename",
|
|
312
|
+
message: `Failed to rename branch: Branch '${input.branchName}' not found.`,
|
|
313
|
+
error: errorMessage,
|
|
314
|
+
};
|
|
182
315
|
}
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
success: false,
|
|
186
|
-
mode: input.mode,
|
|
187
|
-
message: `Git branch ${input.mode} failed for path: ${targetPath}.`,
|
|
188
|
-
error: errorMessage
|
|
189
|
-
};
|
|
316
|
+
// Throw a generic McpError for other failures
|
|
317
|
+
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git branch ${input.mode} failed for path: ${targetPath}. Error: ${errorMessage}`, { context, operation, originalError: error });
|
|
190
318
|
}
|
|
191
319
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
2
|
-
import { logger } from
|
|
2
|
+
import { logger } from "../../../utils/index.js";
|
|
3
3
|
// Import utils from barrel (ErrorHandler from ../utils/internal/errorHandler.js)
|
|
4
|
-
import { ErrorHandler } from
|
|
4
|
+
import { ErrorHandler } from "../../../utils/index.js";
|
|
5
5
|
// Import utils from barrel (requestContextService from ../utils/internal/requestContext.js)
|
|
6
|
-
import { BaseErrorCode } from
|
|
7
|
-
import { requestContextService } from
|
|
8
|
-
import { GitBranchBaseSchema, gitBranchLogic } from
|
|
6
|
+
import { BaseErrorCode } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
7
|
+
import { requestContextService } from "../../../utils/index.js";
|
|
8
|
+
import { GitBranchBaseSchema, gitBranchLogic, } from "./logic.js";
|
|
9
9
|
let _getWorkingDirectory;
|
|
10
10
|
let _getSessionId;
|
|
11
11
|
/**
|
|
@@ -16,10 +16,10 @@ let _getSessionId;
|
|
|
16
16
|
export function initializeGitBranchStateAccessors(getWdFn, getSidFn) {
|
|
17
17
|
_getWorkingDirectory = getWdFn;
|
|
18
18
|
_getSessionId = getSidFn;
|
|
19
|
-
logger.info(
|
|
19
|
+
logger.info("State accessors initialized for git_branch tool registration.");
|
|
20
20
|
}
|
|
21
|
-
const TOOL_NAME =
|
|
22
|
-
const TOOL_DESCRIPTION =
|
|
21
|
+
const TOOL_NAME = "git_branch";
|
|
22
|
+
const TOOL_DESCRIPTION = "Manages Git branches. Supports listing (local, remote, all), creating, deleting (with force), renaming (with force), and showing the current branch. Returns results as a JSON object.";
|
|
23
23
|
/**
|
|
24
24
|
* Registers the git_branch tool with the MCP server.
|
|
25
25
|
*
|
|
@@ -29,9 +29,9 @@ const TOOL_DESCRIPTION = 'Manages Git branches. Supports listing (local, remote,
|
|
|
29
29
|
*/
|
|
30
30
|
export const registerGitBranchTool = async (server) => {
|
|
31
31
|
if (!_getWorkingDirectory || !_getSessionId) {
|
|
32
|
-
throw new Error(
|
|
32
|
+
throw new Error("State accessors for git_branch must be initialized before registration.");
|
|
33
33
|
}
|
|
34
|
-
const operation =
|
|
34
|
+
const operation = "registerGitBranchTool";
|
|
35
35
|
const context = requestContextService.createRequestContext({ operation });
|
|
36
36
|
await ErrorHandler.tryCatch(async () => {
|
|
37
37
|
// Register using the BASE schema shape
|
|
@@ -40,7 +40,10 @@ export const registerGitBranchTool = async (server) => {
|
|
|
40
40
|
async (validatedArgs, callContext) => {
|
|
41
41
|
const toolInput = validatedArgs; // Cast for use
|
|
42
42
|
const toolOperation = `tool:${TOOL_NAME}:${toolInput.mode}`;
|
|
43
|
-
const requestContext = requestContextService.createRequestContext({
|
|
43
|
+
const requestContext = requestContextService.createRequestContext({
|
|
44
|
+
operation: toolOperation,
|
|
45
|
+
parentContext: callContext,
|
|
46
|
+
});
|
|
44
47
|
const sessionId = _getSessionId(requestContext);
|
|
45
48
|
const getWorkingDirectoryForSession = () => _getWorkingDirectory(sessionId);
|
|
46
49
|
const logicContext = {
|
|
@@ -52,9 +55,9 @@ export const registerGitBranchTool = async (server) => {
|
|
|
52
55
|
return await ErrorHandler.tryCatch(async () => {
|
|
53
56
|
const branchResult = await gitBranchLogic(toolInput, logicContext);
|
|
54
57
|
const resultContent = {
|
|
55
|
-
type:
|
|
58
|
+
type: "text",
|
|
56
59
|
text: JSON.stringify(branchResult, null, 2), // Pretty-print JSON
|
|
57
|
-
contentType:
|
|
60
|
+
contentType: "application/json",
|
|
58
61
|
};
|
|
59
62
|
if (branchResult.success) {
|
|
60
63
|
logger.info(`Tool ${TOOL_NAME} (mode: ${toolInput.mode}) executed successfully, returning JSON`, logicContext);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Barrel file for the gitCheckout tool.
|
|
3
3
|
*/
|
|
4
|
-
export { registerGitCheckoutTool, initializeGitCheckoutStateAccessors } from
|
|
4
|
+
export { registerGitCheckoutTool, initializeGitCheckoutStateAccessors, } from "./registration.js";
|
|
5
5
|
// Export types if needed elsewhere, e.g.:
|
|
6
6
|
// export type { GitCheckoutInput, GitCheckoutResult } from './logic.js';
|