@cyanheads/git-mcp-server 2.1.4 → 2.1.6
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 +2 -2
- package/dist/mcp-server/tools/gitAdd/logic.js +16 -45
- package/dist/mcp-server/tools/gitBranch/logic.js +39 -34
- package/dist/mcp-server/tools/gitCheckout/logic.js +17 -12
- package/dist/mcp-server/tools/gitCherryPick/logic.js +22 -15
- package/dist/mcp-server/tools/gitClean/logic.js +11 -8
- package/dist/mcp-server/tools/gitClone/logic.js +15 -11
- package/dist/mcp-server/tools/gitCommit/logic.js +29 -65
- package/dist/mcp-server/tools/gitDiff/logic.js +29 -14
- package/dist/mcp-server/tools/gitFetch/logic.js +13 -12
- package/dist/mcp-server/tools/gitInit/logic.js +12 -9
- package/dist/mcp-server/tools/gitLog/logic.js +17 -30
- package/dist/mcp-server/tools/gitMerge/logic.js +17 -12
- package/dist/mcp-server/tools/gitPull/logic.js +13 -14
- package/dist/mcp-server/tools/gitPush/logic.js +19 -21
- package/dist/mcp-server/tools/gitRebase/logic.js +29 -20
- package/dist/mcp-server/tools/gitRemote/logic.js +15 -15
- package/dist/mcp-server/tools/gitReset/logic.js +11 -10
- package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +6 -4
- package/dist/mcp-server/tools/gitShow/logic.js +10 -7
- package/dist/mcp-server/tools/gitStash/logic.js +16 -17
- package/dist/mcp-server/tools/gitStatus/logic.js +16 -8
- package/dist/mcp-server/tools/gitTag/logic.js +15 -15
- package/dist/mcp-server/tools/gitWorktree/logic.js +54 -38
- package/package.json +9 -20
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Direct import for types-global
|
|
5
5
|
import { logger, sanitization } from "../../../utils/index.js"; // logger (./utils/internal/logger.js), RequestContext (./utils/internal/requestContext.js), sanitization (./utils/security/sanitization.js)
|
|
6
|
-
const
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
7
|
// Define the reset modes
|
|
8
8
|
const ResetModeEnum = z.enum(["soft", "mixed", "hard", "merge", "keep"]);
|
|
9
9
|
// Define the input schema for the git_reset tool using Zod
|
|
@@ -73,24 +73,25 @@ export async function resetGitState(input, context) {
|
|
|
73
73
|
throw error;
|
|
74
74
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
75
75
|
}
|
|
76
|
-
// Basic sanitization for commit ref
|
|
77
|
-
const safeCommit = input.commit?.replace(/[`$&;*()|<>]/g, "");
|
|
78
76
|
try {
|
|
79
77
|
// Construct the git reset command
|
|
80
|
-
|
|
78
|
+
const args = ["-C", targetPath, "reset"];
|
|
81
79
|
if (input.mode) {
|
|
82
|
-
|
|
80
|
+
args.push(`--${input.mode}`);
|
|
83
81
|
}
|
|
84
|
-
if (
|
|
85
|
-
|
|
82
|
+
if (input.commit) {
|
|
83
|
+
args.push(input.commit);
|
|
86
84
|
}
|
|
87
85
|
// Handling file paths requires careful command construction, often without a commit ref.
|
|
88
86
|
// Example: `git reset HEAD -- path/to/file` or `git reset -- path/to/file` (unstages)
|
|
89
87
|
// For simplicity, this initial version focuses on resetting the whole HEAD/index/tree.
|
|
90
88
|
// Add file path logic here if needed, adjusting command structure.
|
|
91
|
-
logger.debug(`Executing command: ${
|
|
89
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
90
|
+
...context,
|
|
91
|
+
operation,
|
|
92
|
+
});
|
|
92
93
|
// Execute command. Reset output is often minimal on success, but stderr might indicate issues.
|
|
93
|
-
const { stdout, stderr } = await
|
|
94
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
94
95
|
logger.debug(`Git reset stdout: ${stdout}`, { ...context, operation });
|
|
95
96
|
if (stderr) {
|
|
96
97
|
// Log stderr as info, as it often contains the primary status message
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import fs from "fs/promises";
|
|
3
3
|
import { promisify } from "util";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Direct import for types-global
|
|
6
6
|
import { logger, sanitization } from "../../../utils/index.js"; // RequestContext (./utils/internal/requestContext.js), logger (./utils/internal/logger.js), sanitization (./utils/security/sanitization.js)
|
|
7
|
-
const
|
|
7
|
+
const execFileAsync = promisify(execFile);
|
|
8
8
|
// Define the Zod schema for input validation
|
|
9
9
|
export const GitSetWorkingDirInputSchema = z.object({
|
|
10
10
|
path: z
|
|
@@ -70,7 +70,7 @@ export async function gitSetWorkingDirLogic(input, context) {
|
|
|
70
70
|
let isGitRepo = false;
|
|
71
71
|
let initializedRepo = false;
|
|
72
72
|
try {
|
|
73
|
-
const { stdout } = await
|
|
73
|
+
const { stdout } = await execFileAsync("git", ["rev-parse", "--is-inside-work-tree"], {
|
|
74
74
|
cwd: sanitizedPath,
|
|
75
75
|
});
|
|
76
76
|
if (stdout.trim() === "true") {
|
|
@@ -94,7 +94,9 @@ export async function gitSetWorkingDirLogic(input, context) {
|
|
|
94
94
|
if (!isGitRepo && input.initializeIfNotPresent) {
|
|
95
95
|
logger.info(`Path is not a Git repository. Attempting to initialize (initializeIfNotPresent=true) with initial branch 'main'.`, { ...context, operation, path: sanitizedPath });
|
|
96
96
|
try {
|
|
97
|
-
await
|
|
97
|
+
await execFileAsync("git", ["init", "--initial-branch=main"], {
|
|
98
|
+
cwd: sanitizedPath,
|
|
99
|
+
});
|
|
98
100
|
initializedRepo = true;
|
|
99
101
|
isGitRepo = true; // Now it is a git repo
|
|
100
102
|
logger.info('Successfully initialized Git repository with initial branch "main".', { ...context, operation, path: sanitizedPath });
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Direct import for types-global
|
|
5
5
|
import { logger, sanitization } from "../../../utils/index.js"; // logger (./utils/internal/logger.js), RequestContext (./utils/internal/requestContext.js), sanitization (./utils/security/sanitization.js)
|
|
6
|
-
const
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
7
|
// Define the input schema for the git_show tool using Zod
|
|
8
8
|
// No refinements needed here, so we don't need a separate BaseSchema
|
|
9
9
|
export const GitShowInputSchema = z.object({
|
|
@@ -89,14 +89,17 @@ export async function gitShowLogic(input, context) {
|
|
|
89
89
|
try {
|
|
90
90
|
// Construct the refspec, combining ref and filePath if needed
|
|
91
91
|
const refSpec = input.filePath
|
|
92
|
-
? `${input.ref}
|
|
93
|
-
:
|
|
92
|
+
? `${input.ref}:${input.filePath}`
|
|
93
|
+
: input.ref;
|
|
94
94
|
// Construct the command
|
|
95
|
-
const
|
|
96
|
-
logger.debug(`Executing command: ${
|
|
95
|
+
const args = ["-C", targetPath, "show", refSpec];
|
|
96
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
97
|
+
...context,
|
|
98
|
+
operation,
|
|
99
|
+
});
|
|
97
100
|
// Execute command. Note: git show might write to stderr for non-error info (like commit details before diff)
|
|
98
101
|
// We primarily care about stdout for the content. Errors usually have non-zero exit code.
|
|
99
|
-
const { stdout, stderr } = await
|
|
102
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
100
103
|
if (stderr) {
|
|
101
104
|
// Log stderr as debug info, as it might contain commit details etc.
|
|
102
105
|
logger.debug(`Git show command produced stderr (may be informational)`, {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Direct import for types-global
|
|
5
5
|
import { logger, sanitization } from "../../../utils/index.js"; // logger (./utils/internal/logger.js), RequestContext (./utils/internal/requestContext.js), sanitization (./utils/security/sanitization.js)
|
|
6
|
-
const
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
7
|
// Define the BASE input schema for the git_stash tool using Zod
|
|
8
8
|
export const GitStashBaseSchema = z.object({
|
|
9
9
|
path: z
|
|
@@ -90,16 +90,16 @@ export async function gitStashLogic(input, context) {
|
|
|
90
90
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid stash reference format: ${input.stashRef}. Expected format: stash@{n}`, { context, operation });
|
|
91
91
|
}
|
|
92
92
|
try {
|
|
93
|
-
let
|
|
93
|
+
let args;
|
|
94
94
|
let result;
|
|
95
95
|
switch (input.mode) {
|
|
96
96
|
case "list":
|
|
97
|
-
|
|
98
|
-
logger.debug(`Executing command: ${
|
|
97
|
+
args = ["-C", targetPath, "stash", "list"];
|
|
98
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
99
99
|
...context,
|
|
100
100
|
operation,
|
|
101
101
|
});
|
|
102
|
-
const { stdout: listStdout } = await
|
|
102
|
+
const { stdout: listStdout } = await execFileAsync("git", args);
|
|
103
103
|
const stashes = listStdout
|
|
104
104
|
.trim()
|
|
105
105
|
.split("\n")
|
|
@@ -121,13 +121,13 @@ export async function gitStashLogic(input, context) {
|
|
|
121
121
|
case "pop":
|
|
122
122
|
// stashRef is validated by Zod refine
|
|
123
123
|
const stashRefApplyPop = input.stashRef;
|
|
124
|
-
|
|
125
|
-
logger.debug(`Executing command: ${
|
|
124
|
+
args = ["-C", targetPath, "stash", input.mode, stashRefApplyPop];
|
|
125
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
126
126
|
...context,
|
|
127
127
|
operation,
|
|
128
128
|
});
|
|
129
129
|
try {
|
|
130
|
-
const { stdout, stderr } = await
|
|
130
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
131
131
|
// Check stdout/stderr for conflict messages, although exit code 0 usually means success
|
|
132
132
|
const conflicts = /conflict/i.test(stdout) || /conflict/i.test(stderr);
|
|
133
133
|
const message = conflicts
|
|
@@ -166,12 +166,12 @@ export async function gitStashLogic(input, context) {
|
|
|
166
166
|
case "drop":
|
|
167
167
|
// stashRef is validated by Zod refine
|
|
168
168
|
const stashRefDrop = input.stashRef;
|
|
169
|
-
|
|
170
|
-
logger.debug(`Executing command: ${
|
|
169
|
+
args = ["-C", targetPath, "stash", "drop", stashRefDrop];
|
|
170
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
171
171
|
...context,
|
|
172
172
|
operation,
|
|
173
173
|
});
|
|
174
|
-
await
|
|
174
|
+
await execFileAsync("git", args);
|
|
175
175
|
result = {
|
|
176
176
|
success: true,
|
|
177
177
|
mode: "drop",
|
|
@@ -180,16 +180,15 @@ export async function gitStashLogic(input, context) {
|
|
|
180
180
|
};
|
|
181
181
|
break;
|
|
182
182
|
case "save":
|
|
183
|
-
|
|
183
|
+
args = ["-C", targetPath, "stash", "save"];
|
|
184
184
|
if (input.message) {
|
|
185
|
-
|
|
186
|
-
command += ` "${input.message.replace(/"/g, '\\"')}"`;
|
|
185
|
+
args.push(input.message);
|
|
187
186
|
}
|
|
188
|
-
logger.debug(`Executing command: ${
|
|
187
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
189
188
|
...context,
|
|
190
189
|
operation,
|
|
191
190
|
});
|
|
192
|
-
const { stdout: saveStdout } = await
|
|
191
|
+
const { stdout: saveStdout } = await execFileAsync("git", args);
|
|
193
192
|
const stashCreated = !/no local changes to save/i.test(saveStdout);
|
|
194
193
|
const saveMessage = stashCreated
|
|
195
194
|
? `Changes stashed successfully.` +
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Direct import for types-global
|
|
5
5
|
import { logger, sanitization } from "../../../utils/index.js"; // logger (./utils/internal/logger.js), RequestContext (./utils/internal/requestContext.js), sanitization (./utils/security/sanitization.js)
|
|
6
|
-
const
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
7
|
// Define the input schema for the git_status tool using Zod
|
|
8
8
|
export const GitStatusInputSchema = z.object({
|
|
9
9
|
path: z
|
|
@@ -197,10 +197,12 @@ export async function getGitStatus(input, context) {
|
|
|
197
197
|
}
|
|
198
198
|
try {
|
|
199
199
|
// Using --porcelain=v1 for stable, scriptable output and -b for branch info
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
200
|
+
const args = ["-C", targetPath, "status", "--porcelain=v1", "-b"];
|
|
201
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
202
|
+
...context,
|
|
203
|
+
operation,
|
|
204
|
+
});
|
|
205
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
204
206
|
if (stderr) {
|
|
205
207
|
// Log stderr as warning but proceed to parse stdout
|
|
206
208
|
logger.warning(`Git status command produced stderr (may be informational)`, { ...context, operation, stderr });
|
|
@@ -216,8 +218,14 @@ export async function getGitStatus(input, context) {
|
|
|
216
218
|
// This handles the case of an empty repo after init but before first commit
|
|
217
219
|
if (structuredResult.is_clean && !structuredResult.current_branch) {
|
|
218
220
|
try {
|
|
219
|
-
const
|
|
220
|
-
|
|
221
|
+
const branchArgs = [
|
|
222
|
+
"-C",
|
|
223
|
+
targetPath,
|
|
224
|
+
"rev-parse",
|
|
225
|
+
"--abbrev-ref",
|
|
226
|
+
"HEAD",
|
|
227
|
+
];
|
|
228
|
+
const { stdout: branchStdout } = await execFileAsync("git", branchArgs);
|
|
221
229
|
const currentBranchName = branchStdout.trim(); // Renamed variable for clarity
|
|
222
230
|
if (currentBranchName && currentBranchName !== "HEAD") {
|
|
223
231
|
structuredResult.current_branch = currentBranchName;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Direct import for types-global
|
|
5
5
|
import { logger, sanitization } from "../../../utils/index.js"; // logger (./utils/internal/logger.js), RequestContext (./utils/internal/requestContext.js), sanitization (./utils/security/sanitization.js)
|
|
6
|
-
const
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
7
|
// Define the base input schema for the git_tag tool using Zod
|
|
8
8
|
// We export this separately to access its .shape for registration
|
|
9
9
|
export const GitTagBaseSchema = z.object({
|
|
@@ -111,16 +111,16 @@ export async function gitTagLogic(input, context) {
|
|
|
111
111
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid commit reference format: ${input.commitRef}`, { context, operation });
|
|
112
112
|
}
|
|
113
113
|
try {
|
|
114
|
-
let
|
|
114
|
+
let args;
|
|
115
115
|
let result;
|
|
116
116
|
switch (input.mode) {
|
|
117
117
|
case "list":
|
|
118
|
-
|
|
119
|
-
logger.debug(`Executing command: ${
|
|
118
|
+
args = ["-C", targetPath, "tag", "--list"];
|
|
119
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
120
120
|
...context,
|
|
121
121
|
operation,
|
|
122
122
|
});
|
|
123
|
-
const { stdout: listStdout } = await
|
|
123
|
+
const { stdout: listStdout } = await execFileAsync("git", args);
|
|
124
124
|
const tags = listStdout
|
|
125
125
|
.trim()
|
|
126
126
|
.split("\n")
|
|
@@ -130,20 +130,20 @@ export async function gitTagLogic(input, context) {
|
|
|
130
130
|
case "create":
|
|
131
131
|
// TagName is validated by Zod refine
|
|
132
132
|
const tagNameCreate = input.tagName;
|
|
133
|
-
|
|
133
|
+
args = ["-C", targetPath, "tag"];
|
|
134
134
|
if (input.annotate) {
|
|
135
135
|
// Message is validated by Zod refine
|
|
136
|
-
|
|
136
|
+
args.push("-a", "-m", input.message);
|
|
137
137
|
}
|
|
138
|
-
|
|
138
|
+
args.push(tagNameCreate);
|
|
139
139
|
if (input.commitRef) {
|
|
140
|
-
|
|
140
|
+
args.push(input.commitRef);
|
|
141
141
|
}
|
|
142
|
-
logger.debug(`Executing command: ${
|
|
142
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
143
143
|
...context,
|
|
144
144
|
operation,
|
|
145
145
|
});
|
|
146
|
-
await
|
|
146
|
+
await execFileAsync("git", args);
|
|
147
147
|
result = {
|
|
148
148
|
success: true,
|
|
149
149
|
mode: "create",
|
|
@@ -154,12 +154,12 @@ export async function gitTagLogic(input, context) {
|
|
|
154
154
|
case "delete":
|
|
155
155
|
// TagName is validated by Zod refine
|
|
156
156
|
const tagNameDelete = input.tagName;
|
|
157
|
-
|
|
158
|
-
logger.debug(`Executing command: ${
|
|
157
|
+
args = ["-C", targetPath, "tag", "-d", tagNameDelete];
|
|
158
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
159
159
|
...context,
|
|
160
160
|
operation,
|
|
161
161
|
});
|
|
162
|
-
await
|
|
162
|
+
await execFileAsync("git", args);
|
|
163
163
|
result = {
|
|
164
164
|
success: true,
|
|
165
165
|
mode: "delete",
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
import { logger, sanitization } from "../../../utils/index.js";
|
|
5
5
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
6
|
-
const
|
|
6
|
+
const execFileAsync = promisify(execFile);
|
|
7
7
|
// Define the BASE input schema for the git_worktree tool using Zod
|
|
8
8
|
export const GitWorktreeBaseSchema = z.object({
|
|
9
9
|
path: z
|
|
@@ -176,18 +176,19 @@ export async function gitWorktreeLogic(input, context) {
|
|
|
176
176
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
177
177
|
}
|
|
178
178
|
try {
|
|
179
|
-
let
|
|
179
|
+
let args;
|
|
180
180
|
let result;
|
|
181
181
|
switch (input.mode) {
|
|
182
182
|
case "list":
|
|
183
|
-
|
|
184
|
-
if (input.verbose)
|
|
185
|
-
|
|
186
|
-
|
|
183
|
+
args = ["-C", targetPath, "worktree", "list"];
|
|
184
|
+
if (input.verbose) {
|
|
185
|
+
args.push("--porcelain");
|
|
186
|
+
} // Use porcelain for structured output
|
|
187
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
187
188
|
...context,
|
|
188
189
|
operation,
|
|
189
190
|
});
|
|
190
|
-
const { stdout: listStdout } = await
|
|
191
|
+
const { stdout: listStdout } = await execFileAsync("git", args);
|
|
191
192
|
if (input.verbose) {
|
|
192
193
|
const worktrees = parsePorcelainWorktreeList(listStdout);
|
|
193
194
|
result = { success: true, mode: "list", worktrees };
|
|
@@ -214,21 +215,25 @@ export async function gitWorktreeLogic(input, context) {
|
|
|
214
215
|
case "add":
|
|
215
216
|
// worktreePath is guaranteed by refine
|
|
216
217
|
const sanitizedWorktreePathAdd = sanitization.sanitizePath(input.worktreePath, { allowAbsolute: true, rootDir: targetPath }).sanitizedPath;
|
|
217
|
-
|
|
218
|
-
if (input.force)
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
218
|
+
args = ["-C", targetPath, "worktree", "add"];
|
|
219
|
+
if (input.force) {
|
|
220
|
+
args.push("--force");
|
|
221
|
+
}
|
|
222
|
+
if (input.detach) {
|
|
223
|
+
args.push("--detach");
|
|
224
|
+
}
|
|
225
|
+
if (input.newBranch) {
|
|
226
|
+
args.push("-b", input.newBranch);
|
|
227
|
+
}
|
|
228
|
+
args.push(sanitizedWorktreePathAdd);
|
|
229
|
+
if (input.commitish) {
|
|
230
|
+
args.push(input.commitish);
|
|
231
|
+
}
|
|
232
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
228
233
|
...context,
|
|
229
234
|
operation,
|
|
230
235
|
});
|
|
231
|
-
await
|
|
236
|
+
await execFileAsync("git", args);
|
|
232
237
|
// To get the HEAD of the new worktree, we might need another command or parse output if available
|
|
233
238
|
// For simplicity, we'll report success. A more robust solution might `git -C new_worktree_path rev-parse HEAD`
|
|
234
239
|
result = {
|
|
@@ -243,15 +248,16 @@ export async function gitWorktreeLogic(input, context) {
|
|
|
243
248
|
case "remove":
|
|
244
249
|
// worktreePath is guaranteed by refine
|
|
245
250
|
const sanitizedWorktreePathRemove = sanitization.sanitizePath(input.worktreePath, { allowAbsolute: true, rootDir: targetPath }).sanitizedPath;
|
|
246
|
-
|
|
247
|
-
if (input.force)
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
+
args = ["-C", targetPath, "worktree", "remove"];
|
|
252
|
+
if (input.force) {
|
|
253
|
+
args.push("--force");
|
|
254
|
+
}
|
|
255
|
+
args.push(sanitizedWorktreePathRemove);
|
|
256
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
251
257
|
...context,
|
|
252
258
|
operation,
|
|
253
259
|
});
|
|
254
|
-
const { stdout: removeStdout } = await
|
|
260
|
+
const { stdout: removeStdout } = await execFileAsync("git", args);
|
|
255
261
|
result = {
|
|
256
262
|
success: true,
|
|
257
263
|
mode: "remove",
|
|
@@ -267,12 +273,19 @@ export async function gitWorktreeLogic(input, context) {
|
|
|
267
273
|
allowAbsolute: true,
|
|
268
274
|
rootDir: targetPath,
|
|
269
275
|
}).sanitizedPath;
|
|
270
|
-
|
|
271
|
-
|
|
276
|
+
args = [
|
|
277
|
+
"-C",
|
|
278
|
+
targetPath,
|
|
279
|
+
"worktree",
|
|
280
|
+
"move",
|
|
281
|
+
sanitizedOldPathMove,
|
|
282
|
+
sanitizedNewPathMove,
|
|
283
|
+
];
|
|
284
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
272
285
|
...context,
|
|
273
286
|
operation,
|
|
274
287
|
});
|
|
275
|
-
await
|
|
288
|
+
await execFileAsync("git", args);
|
|
276
289
|
result = {
|
|
277
290
|
success: true,
|
|
278
291
|
mode: "move",
|
|
@@ -282,18 +295,21 @@ export async function gitWorktreeLogic(input, context) {
|
|
|
282
295
|
};
|
|
283
296
|
break;
|
|
284
297
|
case "prune":
|
|
285
|
-
|
|
286
|
-
if (input.dryRun)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
298
|
+
args = ["-C", targetPath, "worktree", "prune"];
|
|
299
|
+
if (input.dryRun) {
|
|
300
|
+
args.push("--dry-run");
|
|
301
|
+
}
|
|
302
|
+
if (input.verbose) {
|
|
303
|
+
args.push("--verbose");
|
|
304
|
+
}
|
|
305
|
+
if (input.expire) {
|
|
306
|
+
args.push(`--expire=${input.expire}`);
|
|
307
|
+
}
|
|
308
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
293
309
|
...context,
|
|
294
310
|
operation,
|
|
295
311
|
});
|
|
296
|
-
const { stdout: pruneStdout, stderr: pruneStderr } = await
|
|
312
|
+
const { stdout: pruneStdout, stderr: pruneStderr } = await execFileAsync("git", args);
|
|
297
313
|
// Prune often outputs to stderr even on success for verbose/dry-run
|
|
298
314
|
const pruneMessage = pruneStdout.trim() ||
|
|
299
315
|
pruneStderr.trim() ||
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/git-mcp-server",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.6",
|
|
4
4
|
"description": "An MCP (Model Context Protocol) server enabling LLMs and AI agents to interact with Git repositories. Provides tools for comprehensive Git operations including clone, commit, branch, diff, log, status, push, pull, merge, rebase, worktree, tag management, and more, via the MCP standard. STDIO & HTTP.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"files": [
|
|
@@ -36,22 +36,15 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@hono/node-server": "^1.14.4",
|
|
39
|
-
"@modelcontextprotocol/
|
|
40
|
-
"@
|
|
41
|
-
"@types/jsonwebtoken": "^9.0.10",
|
|
42
|
-
"@types/node": "^24.0.3",
|
|
39
|
+
"@modelcontextprotocol/sdk": "^1.13.2",
|
|
40
|
+
"@types/node": "^24.0.7",
|
|
43
41
|
"@types/sanitize-html": "^2.16.0",
|
|
44
42
|
"@types/validator": "^13.15.2",
|
|
45
|
-
"chalk": "^5.4.1",
|
|
46
43
|
"chrono-node": "2.8.0",
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"express": "^5.1.0",
|
|
50
|
-
"hono": "^4.8.2",
|
|
51
|
-
"ignore": "^7.0.5",
|
|
44
|
+
"dotenv": "^17.0.0",
|
|
45
|
+
"hono": "^4.8.3",
|
|
52
46
|
"jose": "^6.0.11",
|
|
53
|
-
"
|
|
54
|
-
"openai": "^5.6.0",
|
|
47
|
+
"openai": "^5.8.2",
|
|
55
48
|
"partial-json": "^0.1.7",
|
|
56
49
|
"sanitize-html": "^2.17.0",
|
|
57
50
|
"tiktoken": "^1.0.21",
|
|
@@ -59,8 +52,7 @@
|
|
|
59
52
|
"typescript": "^5.8.3",
|
|
60
53
|
"validator": "^13.15.15",
|
|
61
54
|
"winston": "^3.17.0",
|
|
62
|
-
"winston-
|
|
63
|
-
"yargs": "^18.0.0",
|
|
55
|
+
"winston-transport": "^4.9.0",
|
|
64
56
|
"zod": "^3.25.67"
|
|
65
57
|
},
|
|
66
58
|
"keywords": [
|
|
@@ -113,11 +105,8 @@
|
|
|
113
105
|
"node": ">=20.0.0"
|
|
114
106
|
},
|
|
115
107
|
"devDependencies": {
|
|
116
|
-
"
|
|
117
|
-
"
|
|
118
|
-
"js-yaml": "^4.1.0",
|
|
119
|
-
"prettier": "^3.5.3",
|
|
120
|
-
"typedoc": "^0.28.5"
|
|
108
|
+
"prettier": "^3.6.2",
|
|
109
|
+
"typedoc": "^0.28.6"
|
|
121
110
|
},
|
|
122
111
|
"publishConfig": {
|
|
123
112
|
"access": "public"
|