@cyanheads/git-mcp-server 2.10.6 → 2.11.1

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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +242 -90
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  <div align="center">
9
9
 
10
- [![Version](https://img.shields.io/badge/Version-2.10.6-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Status](https://img.shields.io/badge/Status-Stable-brightgreen.svg?style=flat-square)](https://github.com/cyanheads/git-mcp-server/issues) [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.11-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-2.11.1-blue.svg?style=flat-square)](./CHANGELOG.md) [![MCP Spec](https://img.shields.io/badge/MCP%20Spec-2025--11--25-8A2BE2.svg?style=flat-square)](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [![MCP SDK](https://img.shields.io/badge/MCP%20SDK-^1.29.0-green.svg?style=flat-square)](https://modelcontextprotocol.io/) [![License](https://img.shields.io/badge/License-Apache%202.0-orange.svg?style=flat-square)](./LICENSE) [![Status](https://img.shields.io/badge/Status-Stable-brightgreen.svg?style=flat-square)](https://github.com/cyanheads/git-mcp-server/issues) [![TypeScript](https://img.shields.io/badge/TypeScript-^6.0.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.3.11-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
package/dist/index.js CHANGED
@@ -15284,7 +15284,7 @@ var package_default;
15284
15284
  var init_package = __esm(() => {
15285
15285
  package_default = {
15286
15286
  name: "@cyanheads/git-mcp-server",
15287
- version: "2.10.6",
15287
+ version: "2.11.1",
15288
15288
  mcpName: "io.github.cyanheads/git-mcp-server",
15289
15289
  description: "A secure and scalable Git MCP server enabling AI agents to perform comprehensive Git version control operations via STDIO and Streamable HTTP.",
15290
15290
  main: "dist/index.js",
@@ -133068,7 +133068,7 @@ function detectRuntime2() {
133068
133068
  }
133069
133069
  return "node";
133070
133070
  }
133071
- async function spawnWithBun(args, cwd, env, timeout, signal) {
133071
+ async function spawnWithBun(args, cwd, env, timeout, signal, allowNonZeroExit = false) {
133072
133072
  const bunApi = globalThis.Bun;
133073
133073
  if (signal?.aborted) {
133074
133074
  throw new Error(`Git command cancelled before execution: git ${args.join(" ")}`);
@@ -133102,15 +133102,15 @@ async function spawnWithBun(args, cwd, env, timeout, signal) {
133102
133102
  proc.stdout.text(),
133103
133103
  proc.stderr.text()
133104
133104
  ]);
133105
- if (exitCode !== 0) {
133105
+ if (exitCode !== 0 && !allowNonZeroExit) {
133106
133106
  const combinedOutput = `Exit Code: ${exitCode}
133107
133107
  Stderr: ${stderr}
133108
133108
  Stdout: ${stdout}`;
133109
133109
  throw new Error(combinedOutput);
133110
133110
  }
133111
- return { stdout, stderr };
133111
+ return { stdout, stderr, exitCode };
133112
133112
  }
133113
- async function spawnWithNode(args, cwd, env, timeout, signal) {
133113
+ async function spawnWithNode(args, cwd, env, timeout, signal, allowNonZeroExit = false) {
133114
133114
  return new Promise((resolve, reject) => {
133115
133115
  if (signal?.aborted) {
133116
133116
  reject(new Error(`Git command cancelled before execution: ${args.join(" ")}`));
@@ -133158,29 +133158,30 @@ async function spawnWithNode(args, cwd, env, timeout, signal) {
133158
133158
  }
133159
133159
  const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
133160
133160
  const stderr = Buffer.concat(stderrChunks).toString("utf-8");
133161
- if (exitCode !== 0) {
133162
- const combinedOutput = `Exit Code: ${exitCode}
133161
+ const code = exitCode ?? 0;
133162
+ if (code !== 0 && !allowNonZeroExit) {
133163
+ const combinedOutput = `Exit Code: ${code}
133163
133164
  Stderr: ${stderr}
133164
133165
  Stdout: ${stdout}`;
133165
133166
  reject(new Error(combinedOutput));
133166
133167
  } else {
133167
- resolve({ stdout, stderr });
133168
+ resolve({ stdout, stderr, exitCode: code });
133168
133169
  }
133169
133170
  });
133170
133171
  });
133171
133172
  }
133172
- async function spawnGitCommand(args, cwd, env, timeout = 60000, signal) {
133173
+ async function spawnGitCommand(args, cwd, env, timeout = 60000, signal, allowNonZeroExit = false) {
133173
133174
  const runtime2 = detectRuntime2();
133174
133175
  if (runtime2 === "bun") {
133175
- return spawnWithBun(args, cwd, env, timeout, signal);
133176
+ return spawnWithBun(args, cwd, env, timeout, signal, allowNonZeroExit);
133176
133177
  } else {
133177
- return spawnWithNode(args, cwd, env, timeout, signal);
133178
+ return spawnWithNode(args, cwd, env, timeout, signal, allowNonZeroExit);
133178
133179
  }
133179
133180
  }
133180
133181
 
133181
133182
  // src/services/git/providers/cli/utils/git-executor.ts
133182
133183
  var GIT_COMMAND_TIMEOUT_MS = 60000;
133183
- async function executeGitCommand(args, cwd) {
133184
+ async function executeGitCommand(args, cwd, options = {}) {
133184
133185
  try {
133185
133186
  validateGitArgs(args);
133186
133187
  if (!existsSync(cwd)) {
@@ -133189,7 +133190,7 @@ async function executeGitCommand(args, cwd) {
133189
133190
  if (!statSync2(cwd).isDirectory()) {
133190
133191
  throw new McpError(-32600 /* InvalidRequest */, `Working directory is not a directory: ${cwd}`, { cwd });
133191
133192
  }
133192
- const result = await spawnGitCommand(args, cwd, buildGitEnv(process.env), GIT_COMMAND_TIMEOUT_MS);
133193
+ const result = await spawnGitCommand(args, cwd, buildGitEnv(process.env), GIT_COMMAND_TIMEOUT_MS, undefined, options.allowNonZeroExit);
133193
133194
  return result;
133194
133195
  } catch (error48) {
133195
133196
  throw mapGitError(error48, args[0] || "unknown");
@@ -133307,7 +133308,7 @@ function parseGitStatus(output) {
133307
133308
  }
133308
133309
  }
133309
133310
  } else if (statusType === "u") {
133310
- const path2 = parts.slice(8).join(" ").trim();
133311
+ const path2 = parts.slice(10).join(" ").trim();
133311
133312
  result.conflictedFiles.push(path2);
133312
133313
  } else if (statusType === "?") {
133313
133314
  const path2 = line.substring(2);
@@ -133558,6 +133559,10 @@ async function executeAdd(options, context, execGit) {
133558
133559
  // src/services/git/providers/cli/operations/staging/reset.ts
133559
133560
  async function executeReset(options, context, execGit) {
133560
133561
  try {
133562
+ const cwd = context.workingDirectory;
133563
+ const ctx = context.requestContext;
133564
+ const previousCommit = await safeRevParse(execGit, cwd, ctx);
133565
+ const dirtyBefore = options.mode === "hard" && !options.paths ? await listDirtyFiles(execGit, cwd, ctx) : [];
133561
133566
  const args = [];
133562
133567
  switch (options.mode) {
133563
133568
  case "soft":
@@ -133576,30 +133581,68 @@ async function executeReset(options, context, execGit) {
133576
133581
  args.push("--keep");
133577
133582
  break;
133578
133583
  }
133579
- if (options.commit) {
133584
+ if (options.commit)
133580
133585
  args.push(options.commit);
133581
- }
133582
133586
  if (options.paths && options.paths.length > 0) {
133583
133587
  args.push("--", ...options.paths);
133584
133588
  }
133585
- const cmd = buildGitCommand({ command: "reset", args });
133586
- await execGit(cmd, context.workingDirectory, context.requestContext);
133587
- const hashCmd = buildGitCommand({
133588
- command: "rev-parse",
133589
- args: ["HEAD"]
133590
- });
133591
- const hashResult = await execGit(hashCmd, context.workingDirectory, context.requestContext);
133589
+ await execGit(buildGitCommand({ command: "reset", args }), cwd, ctx);
133590
+ const currentCommit = await safeRevParse(execGit, cwd, ctx);
133591
+ const affected = new Set;
133592
+ if (options.paths && options.paths.length > 0) {
133593
+ for (const p of options.paths)
133594
+ affected.add(p);
133595
+ } else if (previousCommit && currentCommit && previousCommit !== currentCommit) {
133596
+ const diffResult = await execGit(buildGitCommand({
133597
+ command: "diff",
133598
+ args: ["--name-only", `${previousCommit}..${currentCommit}`]
133599
+ }), cwd, ctx, { allowNonZeroExit: true });
133600
+ for (const line of diffResult.stdout.split(`
133601
+ `)) {
133602
+ const trimmed = line.trim();
133603
+ if (trimmed)
133604
+ affected.add(trimmed);
133605
+ }
133606
+ }
133607
+ if (options.mode === "hard") {
133608
+ for (const p of dirtyBefore)
133609
+ affected.add(p);
133610
+ }
133592
133611
  const result = {
133593
133612
  success: true,
133594
133613
  mode: options.mode,
133595
- commit: hashResult.stdout.trim(),
133596
- filesReset: options.paths || []
133614
+ commit: currentCommit ?? "",
133615
+ filesReset: [...affected]
133597
133616
  };
133617
+ if (previousCommit && previousCommit !== currentCommit) {
133618
+ result.previousCommit = previousCommit;
133619
+ }
133598
133620
  return result;
133599
133621
  } catch (error48) {
133600
133622
  throw mapGitError(error48, "reset");
133601
133623
  }
133602
133624
  }
133625
+ async function safeRevParse(execGit, cwd, ctx) {
133626
+ const result = await execGit(buildGitCommand({ command: "rev-parse", args: ["HEAD"] }), cwd, ctx, { allowNonZeroExit: true });
133627
+ if ((result.exitCode ?? 0) !== 0)
133628
+ return;
133629
+ const hash2 = result.stdout.trim();
133630
+ return hash2 || undefined;
133631
+ }
133632
+ async function listDirtyFiles(execGit, cwd, ctx) {
133633
+ const result = await execGit(buildGitCommand({
133634
+ command: "status",
133635
+ args: ["--porcelain=v1", "--untracked-files=no"]
133636
+ }), cwd, ctx, { allowNonZeroExit: true });
133637
+ const files = [];
133638
+ for (const line of result.stdout.split(`
133639
+ `)) {
133640
+ const path2 = line.slice(3).trim();
133641
+ if (path2)
133642
+ files.push(path2);
133643
+ }
133644
+ return files;
133645
+ }
133603
133646
  // src/services/git/providers/cli/operations/commits/commit.ts
133604
133647
  async function executeCommit(options, context, execGit) {
133605
133648
  try {
@@ -134083,8 +134126,21 @@ async function executeCheckout(options, context, execGit) {
134083
134126
  }
134084
134127
  const cmd = buildGitCommand({ command: "checkout", args });
134085
134128
  const result = await execGit(cmd, context.workingDirectory, context.requestContext);
134129
+ const PORCELAIN_LINE = /^([A-Z])\t(.+)$/;
134130
+ const INFO_PREFIXES = [
134131
+ "Switched",
134132
+ "Already",
134133
+ "Your branch",
134134
+ "(use ",
134135
+ "HEAD is now",
134136
+ "Note: ",
134137
+ "Updated "
134138
+ ];
134086
134139
  const filesModified = result.stdout.split(`
134087
- `).map((line) => line.trim()).filter((line) => line && !line.startsWith("Switched") && !line.startsWith("Already") && !line.startsWith("Your branch") && !line.startsWith("(use ") && !line.startsWith("HEAD is now"));
134140
+ `).map((line) => line.trim()).filter(Boolean).filter((line) => !INFO_PREFIXES.some((p) => line.startsWith(p))).map((line) => {
134141
+ const match = line.match(PORCELAIN_LINE);
134142
+ return match ? match[2] : line;
134143
+ });
134088
134144
  const checkoutResult = {
134089
134145
  success: true,
134090
134146
  target: options.target,
@@ -134122,7 +134178,7 @@ async function executeMerge(options, context, execGit) {
134122
134178
  args.push(options.branch);
134123
134179
  }
134124
134180
  const cmd = buildGitCommand({ command: "merge", args });
134125
- const result = await execGit(cmd, context.workingDirectory, context.requestContext);
134181
+ const result = await execGit(cmd, context.workingDirectory, context.requestContext, { allowNonZeroExit: true });
134126
134182
  if (options.abort) {
134127
134183
  return {
134128
134184
  success: true,
@@ -134135,31 +134191,45 @@ async function executeMerge(options, context, execGit) {
134135
134191
  };
134136
134192
  }
134137
134193
  const hasConflicts = result.stdout.includes("CONFLICT") || result.stderr.includes("CONFLICT");
134194
+ const exitCode = result.exitCode ?? 0;
134195
+ if (exitCode !== 0 && !hasConflicts) {
134196
+ throw new Error(`Exit Code: ${exitCode}
134197
+ Stderr: ${result.stderr}
134198
+ Stdout: ${result.stdout}`);
134199
+ }
134138
134200
  const isFastForward = result.stdout.includes("Fast-forward");
134139
- const conflictedFiles = result.stdout.split(`
134140
- `).filter((line) => line.includes("CONFLICT")).map((line) => {
134141
- const match = line.match(/CONFLICT.*?in (.+)$/);
134142
- return match?.[1] || "";
134143
- }).filter((f3) => f3);
134201
+ const conflictedFiles = parseConflictedFiles(`${result.stdout}
134202
+ ${result.stderr}`);
134144
134203
  const mergedFiles = result.stdout.split(`
134145
134204
  `).map((line) => {
134146
134205
  const statMatch = line.match(/^\s(.+?)\s*\|\s*(?:\d+\s*[+-]*|Bin\s)/);
134147
134206
  return statMatch?.[1]?.trim() || "";
134148
134207
  }).filter((f3) => f3);
134149
134208
  const mergeResult = {
134150
- success: !hasConflicts,
134209
+ success: true,
134151
134210
  strategy: options.strategy || "ort",
134152
134211
  fastForward: isFastForward,
134153
134212
  conflicts: hasConflicts,
134154
134213
  conflictedFiles,
134155
134214
  mergedFiles,
134156
- message: options.message || result.stdout.trim()
134215
+ message: hasConflicts ? `Merge of '${options.branch}' produced conflicts in ${conflictedFiles.length} file(s). Resolve with git_add + git_commit, or pass abort=true to cancel.` : options.message || result.stdout.trim()
134157
134216
  };
134158
134217
  return mergeResult;
134159
134218
  } catch (error48) {
134160
134219
  throw mapGitError(error48, "merge");
134161
134220
  }
134162
134221
  }
134222
+ function parseConflictedFiles(combined) {
134223
+ const seen = new Set;
134224
+ for (const line of combined.split(`
134225
+ `)) {
134226
+ const match = line.match(/CONFLICT.*?\sin\s(.+?)\s*$/);
134227
+ if (match?.[1]) {
134228
+ seen.add(match[1].trim());
134229
+ }
134230
+ }
134231
+ return [...seen];
134232
+ }
134163
134233
  // src/services/git/providers/cli/operations/branches/rebase.ts
134164
134234
  async function executeRebase(options, context, execGit) {
134165
134235
  try {
@@ -134170,11 +134240,19 @@ async function executeRebase(options, context, execGit) {
134170
134240
  command: "rebase",
134171
134241
  args: ["--continue"]
134172
134242
  });
134173
- await execGit(continueCmd, context.workingDirectory, context.requestContext);
134243
+ const continueResult = await execGit(continueCmd, context.workingDirectory, context.requestContext, { allowNonZeroExit: true });
134244
+ const continueHasConflicts = continueResult.stdout.includes("CONFLICT") || continueResult.stderr.includes("CONFLICT");
134245
+ const continueExit = continueResult.exitCode ?? 0;
134246
+ if (continueExit !== 0 && !continueHasConflicts) {
134247
+ throw new Error(`Exit Code: ${continueExit}
134248
+ Stderr: ${continueResult.stderr}
134249
+ Stdout: ${continueResult.stdout}`);
134250
+ }
134174
134251
  return {
134175
134252
  success: true,
134176
- conflicts: false,
134177
- conflictedFiles: [],
134253
+ conflicts: continueHasConflicts,
134254
+ conflictedFiles: parseConflictedFiles2(`${continueResult.stdout}
134255
+ ${continueResult.stderr}`),
134178
134256
  rebasedCommits: 0
134179
134257
  };
134180
134258
  } else if (mode === "abort") {
@@ -134208,13 +134286,16 @@ async function executeRebase(options, context, execGit) {
134208
134286
  }
134209
134287
  }
134210
134288
  const cmd = buildGitCommand({ command: "rebase", args });
134211
- const result = await execGit(cmd, context.workingDirectory, context.requestContext);
134289
+ const result = await execGit(cmd, context.workingDirectory, context.requestContext, { allowNonZeroExit: true });
134212
134290
  const hasConflicts = result.stdout.includes("CONFLICT") || result.stderr.includes("CONFLICT");
134213
- const conflictedFiles = result.stdout.split(`
134214
- `).filter((line) => line.includes("CONFLICT")).map((line) => {
134215
- const match = line.match(/CONFLICT.*?in (.+)$/);
134216
- return match?.[1] || "";
134217
- }).filter((f3) => f3);
134291
+ const exitCode = result.exitCode ?? 0;
134292
+ if (exitCode !== 0 && !hasConflicts) {
134293
+ throw new Error(`Exit Code: ${exitCode}
134294
+ Stderr: ${result.stderr}
134295
+ Stdout: ${result.stdout}`);
134296
+ }
134297
+ const conflictedFiles = parseConflictedFiles2(`${result.stdout}
134298
+ ${result.stderr}`);
134218
134299
  let rebasedCommits = 0;
134219
134300
  const progressLines = result.stderr.split(`
134220
134301
  `).filter((line) => /Rebasing \(\d+\/\d+\)/.test(line));
@@ -134229,7 +134310,7 @@ async function executeRebase(options, context, execGit) {
134229
134310
  `).filter((line) => line.startsWith("Applying:")).length;
134230
134311
  }
134231
134312
  const rebaseResult = {
134232
- success: !hasConflicts,
134313
+ success: true,
134233
134314
  conflicts: hasConflicts,
134234
134315
  conflictedFiles,
134235
134316
  rebasedCommits
@@ -134239,6 +134320,17 @@ async function executeRebase(options, context, execGit) {
134239
134320
  throw mapGitError(error48, "rebase");
134240
134321
  }
134241
134322
  }
134323
+ function parseConflictedFiles2(combined) {
134324
+ const seen = new Set;
134325
+ for (const line of combined.split(`
134326
+ `)) {
134327
+ const match = line.match(/CONFLICT.*?\sin\s(.+?)\s*$/);
134328
+ if (match?.[1]) {
134329
+ seen.add(match[1].trim());
134330
+ }
134331
+ }
134332
+ return [...seen];
134333
+ }
134242
134334
  // src/services/git/providers/cli/operations/branches/cherry-pick.ts
134243
134335
  async function executeCherryPick(options, context, execGit) {
134244
134336
  try {
@@ -134267,15 +134359,18 @@ async function executeCherryPick(options, context, execGit) {
134267
134359
  args.push(...options.commits);
134268
134360
  }
134269
134361
  const cmd = buildGitCommand({ command: "cherry-pick", args });
134270
- const result = await execGit(cmd, context.workingDirectory, context.requestContext);
134362
+ const result = await execGit(cmd, context.workingDirectory, context.requestContext, { allowNonZeroExit: true });
134271
134363
  const hasConflicts = result.stdout.includes("CONFLICT") || result.stderr.includes("CONFLICT");
134272
- const conflictedFiles = result.stdout.split(`
134273
- `).filter((line) => line.includes("CONFLICT")).map((line) => {
134274
- const match = line.match(/CONFLICT.*?in (.+)$/);
134275
- return match?.[1] || "";
134276
- }).filter((f3) => f3);
134364
+ const exitCode = result.exitCode ?? 0;
134365
+ if (exitCode !== 0 && !hasConflicts) {
134366
+ throw new Error(`Exit Code: ${exitCode}
134367
+ Stderr: ${result.stderr}
134368
+ Stdout: ${result.stdout}`);
134369
+ }
134370
+ const conflictedFiles = parseConflictedFiles3(`${result.stdout}
134371
+ ${result.stderr}`);
134277
134372
  const cherryPickResult = {
134278
- success: !hasConflicts,
134373
+ success: true,
134279
134374
  pickedCommits: options.abort || options.continueOperation ? [] : options.commits,
134280
134375
  conflicts: hasConflicts,
134281
134376
  conflictedFiles
@@ -134285,6 +134380,17 @@ async function executeCherryPick(options, context, execGit) {
134285
134380
  throw mapGitError(error48, "cherry-pick");
134286
134381
  }
134287
134382
  }
134383
+ function parseConflictedFiles3(combined) {
134384
+ const seen = new Set;
134385
+ for (const line of combined.split(`
134386
+ `)) {
134387
+ const match = line.match(/CONFLICT.*?\sin\s(.+?)\s*$/);
134388
+ if (match?.[1]) {
134389
+ seen.add(match[1].trim());
134390
+ }
134391
+ }
134392
+ return [...seen];
134393
+ }
134288
134394
  // src/services/git/providers/cli/operations/remotes/remote.ts
134289
134395
  async function executeRemote(options, context, execGit) {
134290
134396
  try {
@@ -134524,29 +134630,58 @@ async function executePull(options, context, execGit) {
134524
134630
  args.push("--ff-only");
134525
134631
  }
134526
134632
  const cmd = buildGitCommand({ command: "pull", args });
134527
- const result = await execGit(cmd, context.workingDirectory, context.requestContext);
134633
+ const result = await execGit(cmd, context.workingDirectory, context.requestContext, { allowNonZeroExit: true });
134634
+ const hasConflicts = result.stdout.includes("CONFLICT") || result.stderr.includes("CONFLICT");
134635
+ const exitCode = result.exitCode ?? 0;
134636
+ if (exitCode !== 0 && !hasConflicts) {
134637
+ throw new Error(`Exit Code: ${exitCode}
134638
+ Stderr: ${result.stderr}
134639
+ Stdout: ${result.stdout}`);
134640
+ }
134528
134641
  let strategy = "merge";
134529
134642
  if (options.rebase) {
134530
134643
  strategy = "rebase";
134531
134644
  } else if (result.stdout.includes("Fast-forward")) {
134532
134645
  strategy = "fast-forward";
134533
134646
  }
134534
- const hasConflicts = result.stdout.includes("CONFLICT") || result.stderr.includes("CONFLICT");
134535
- const filesChanged = result.stdout.split(`
134536
- `).map((line) => line.trim()).filter((line) => line && !line.includes("CONFLICT") && !line.startsWith("Already up to date") && !line.startsWith("From ") && !line.startsWith("Your branch") && !line.startsWith("(use ") && !line.startsWith("Updating ") && !line.startsWith("Fast-forward") && !line.match(/^\d+ files? changed/));
134537
- const pullResult = {
134538
- success: !hasConflicts,
134647
+ const conflictedFiles = parseConflictedFiles4(`${result.stdout}
134648
+ ${result.stderr}`);
134649
+ const filesChanged = parseFilesChanged(result.stdout);
134650
+ return {
134651
+ success: true,
134539
134652
  remote,
134540
134653
  branch: options.branch || "HEAD",
134541
134654
  strategy,
134542
134655
  conflicts: hasConflicts,
134656
+ conflictedFiles,
134543
134657
  filesChanged
134544
134658
  };
134545
- return pullResult;
134546
134659
  } catch (error48) {
134547
134660
  throw mapGitError(error48, "pull");
134548
134661
  }
134549
134662
  }
134663
+ function parseConflictedFiles4(combined) {
134664
+ const seen = new Set;
134665
+ for (const line of combined.split(`
134666
+ `)) {
134667
+ const match = line.match(/CONFLICT.*?\sin\s(.+?)\s*$/);
134668
+ if (match?.[1]) {
134669
+ seen.add(match[1].trim());
134670
+ }
134671
+ }
134672
+ return [...seen];
134673
+ }
134674
+ function parseFilesChanged(stdout) {
134675
+ const files = [];
134676
+ for (const line of stdout.split(`
134677
+ `)) {
134678
+ const statMatch = line.match(/^\s(.+?)\s*\|\s*(?:\d+\s*[+-]*|Bin\s)/);
134679
+ if (statMatch?.[1]) {
134680
+ files.push(statMatch[1].trim());
134681
+ }
134682
+ }
134683
+ return files;
134684
+ }
134550
134685
  // src/services/git/providers/cli/operations/tags/tag.ts
134551
134686
  async function executeTag(options, context, execGit) {
134552
134687
  try {
@@ -135036,28 +135171,28 @@ class CliGitProvider extends BaseGitProvider {
135036
135171
  }
135037
135172
  async init(options, context) {
135038
135173
  this.logOperationStart("init", context, options);
135039
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135174
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135040
135175
  const result = await executeInit(options, context, executor);
135041
135176
  this.logOperationSuccess("init", context, { path: result.path });
135042
135177
  return result;
135043
135178
  }
135044
135179
  async clone(options, context) {
135045
135180
  this.logOperationStart("clone", context, options);
135046
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135181
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135047
135182
  const result = await executeClone(options, context, executor);
135048
135183
  this.logOperationSuccess("clone", context, { path: result.localPath });
135049
135184
  return result;
135050
135185
  }
135051
135186
  async status(options, context) {
135052
135187
  this.logOperationStart("status", context, options);
135053
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135188
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135054
135189
  const result = await executeStatus(options, context, executor);
135055
135190
  this.logOperationSuccess("status", context, { isClean: result.isClean });
135056
135191
  return result;
135057
135192
  }
135058
135193
  async clean(options, context) {
135059
135194
  this.logOperationStart("clean", context, options);
135060
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135195
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135061
135196
  const result = await executeClean(options, context, executor);
135062
135197
  this.logOperationSuccess("clean", context, {
135063
135198
  filesRemoved: result.filesRemoved.length
@@ -135066,7 +135201,7 @@ class CliGitProvider extends BaseGitProvider {
135066
135201
  }
135067
135202
  async add(options, context) {
135068
135203
  this.logOperationStart("add", context, options);
135069
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135204
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135070
135205
  const result = await executeAdd(options, context, executor);
135071
135206
  this.logOperationSuccess("add", context, {
135072
135207
  filesStaged: result.stagedFiles.length
@@ -135075,14 +135210,14 @@ class CliGitProvider extends BaseGitProvider {
135075
135210
  }
135076
135211
  async commit(options, context) {
135077
135212
  this.logOperationStart("commit", context, { message: options.message });
135078
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135213
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135079
135214
  const result = await executeCommit(options, context, executor);
135080
135215
  this.logOperationSuccess("commit", context, { hash: result.commitHash });
135081
135216
  return result;
135082
135217
  }
135083
135218
  async log(options, context) {
135084
135219
  this.logOperationStart("log", context, options);
135085
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135220
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135086
135221
  const result = await executeLog(options, context, executor);
135087
135222
  this.logOperationSuccess("log", context, {
135088
135223
  commitCount: result.totalCount
@@ -135091,14 +135226,14 @@ class CliGitProvider extends BaseGitProvider {
135091
135226
  }
135092
135227
  async show(options, context) {
135093
135228
  this.logOperationStart("show", context, options);
135094
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135229
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135095
135230
  const result = await executeShow(options, context, executor);
135096
135231
  this.logOperationSuccess("show", context, { object: result.object });
135097
135232
  return result;
135098
135233
  }
135099
135234
  async diff(options, context) {
135100
135235
  this.logOperationStart("diff", context, options);
135101
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135236
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135102
135237
  const result = await executeDiff(options, context, executor);
135103
135238
  this.logOperationSuccess("diff", context, {
135104
135239
  filesChanged: result.filesChanged
@@ -135107,21 +135242,21 @@ class CliGitProvider extends BaseGitProvider {
135107
135242
  }
135108
135243
  async branch(options, context) {
135109
135244
  this.logOperationStart("branch", context, options);
135110
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135245
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135111
135246
  const result = await executeBranch(options, context, executor);
135112
135247
  this.logOperationSuccess("branch", context, { mode: result.mode });
135113
135248
  return result;
135114
135249
  }
135115
135250
  async checkout(options, context) {
135116
135251
  this.logOperationStart("checkout", context, options);
135117
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135252
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135118
135253
  const result = await executeCheckout(options, context, executor);
135119
135254
  this.logOperationSuccess("checkout", context, { target: result.target });
135120
135255
  return result;
135121
135256
  }
135122
135257
  async merge(options, context) {
135123
135258
  this.logOperationStart("merge", context, options);
135124
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135259
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135125
135260
  const result = await executeMerge(options, context, executor);
135126
135261
  this.logOperationSuccess("merge", context, {
135127
135262
  conflicts: result.conflicts
@@ -135130,7 +135265,7 @@ class CliGitProvider extends BaseGitProvider {
135130
135265
  }
135131
135266
  async rebase(options, context) {
135132
135267
  this.logOperationStart("rebase", context, options);
135133
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135268
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135134
135269
  const result = await executeRebase(options, context, executor);
135135
135270
  this.logOperationSuccess("rebase", context, {
135136
135271
  conflicts: result.conflicts
@@ -135139,7 +135274,7 @@ class CliGitProvider extends BaseGitProvider {
135139
135274
  }
135140
135275
  async cherryPick(options, context) {
135141
135276
  this.logOperationStart("cherryPick", context, options);
135142
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135277
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135143
135278
  const result = await executeCherryPick(options, context, executor);
135144
135279
  this.logOperationSuccess("cherryPick", context, {
135145
135280
  conflicts: result.conflicts
@@ -135148,21 +135283,21 @@ class CliGitProvider extends BaseGitProvider {
135148
135283
  }
135149
135284
  async remote(options, context) {
135150
135285
  this.logOperationStart("remote", context, options);
135151
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135286
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135152
135287
  const result = await executeRemote(options, context, executor);
135153
135288
  this.logOperationSuccess("remote", context, { mode: result.mode });
135154
135289
  return result;
135155
135290
  }
135156
135291
  async fetch(options, context) {
135157
135292
  this.logOperationStart("fetch", context, options);
135158
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135293
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135159
135294
  const result = await executeFetch(options, context, executor);
135160
135295
  this.logOperationSuccess("fetch", context, { remote: result.remote });
135161
135296
  return result;
135162
135297
  }
135163
135298
  async push(options, context) {
135164
135299
  this.logOperationStart("push", context, options);
135165
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135300
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135166
135301
  const result = await executePush(options, context, executor);
135167
135302
  this.logOperationSuccess("push", context, {
135168
135303
  success: result.success
@@ -135171,7 +135306,7 @@ class CliGitProvider extends BaseGitProvider {
135171
135306
  }
135172
135307
  async pull(options, context) {
135173
135308
  this.logOperationStart("pull", context, options);
135174
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135309
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135175
135310
  const result = await executePull(options, context, executor);
135176
135311
  this.logOperationSuccess("pull", context, {
135177
135312
  strategy: result.strategy
@@ -135180,21 +135315,21 @@ class CliGitProvider extends BaseGitProvider {
135180
135315
  }
135181
135316
  async tag(options, context) {
135182
135317
  this.logOperationStart("tag", context, options);
135183
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135318
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135184
135319
  const result = await executeTag(options, context, executor);
135185
135320
  this.logOperationSuccess("tag", context, { mode: result.mode });
135186
135321
  return result;
135187
135322
  }
135188
135323
  async stash(options, context) {
135189
135324
  this.logOperationStart("stash", context, options);
135190
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135325
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135191
135326
  const result = await executeStash(options, context, executor);
135192
135327
  this.logOperationSuccess("stash", context, { mode: result.mode });
135193
135328
  return result;
135194
135329
  }
135195
135330
  async worktree(options, context) {
135196
135331
  this.logOperationStart("worktree", context, options);
135197
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135332
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135198
135333
  const result = await executeWorktree(options, context, executor);
135199
135334
  this.logOperationSuccess("worktree", context, { mode: result.mode });
135200
135335
  return result;
@@ -135206,7 +135341,7 @@ class CliGitProvider extends BaseGitProvider {
135206
135341
  }
135207
135342
  async reset(options, context) {
135208
135343
  this.logOperationStart("reset", context, options);
135209
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135344
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135210
135345
  const result = await executeReset(options, context, executor);
135211
135346
  this.logOperationSuccess("reset", context, { mode: result.mode });
135212
135347
  return result;
@@ -135214,7 +135349,7 @@ class CliGitProvider extends BaseGitProvider {
135214
135349
  async blame(options, context) {
135215
135350
  this.checkCapability("blame");
135216
135351
  this.logOperationStart("blame", context, options);
135217
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135352
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135218
135353
  const result = await executeBlame(options, context, executor);
135219
135354
  this.logOperationSuccess("blame", context, {
135220
135355
  file: result.file,
@@ -135225,7 +135360,7 @@ class CliGitProvider extends BaseGitProvider {
135225
135360
  async reflog(options, context) {
135226
135361
  this.checkCapability("reflog");
135227
135362
  this.logOperationStart("reflog", context, options);
135228
- const executor = (args, cwd) => executeGitCommand(args, cwd);
135363
+ const executor = (args, cwd, _ctx, options2) => executeGitCommand(args, cwd, options2);
135229
135364
  const result = await executeReflog(options, context, executor);
135230
135365
  this.logOperationSuccess("reflog", context, {
135231
135366
  entries: result.totalEntries
@@ -145254,7 +145389,7 @@ var AllSchema = exports_external.boolean().default(false).describe("Include all
145254
145389
  var MergeStrategySchema = exports_external.enum(["ort", "recursive", "octopus", "ours", "subtree"]).optional().describe("Merge strategy to use (ort, recursive, octopus, ours, subtree).");
145255
145390
  var PruneSchema = exports_external.boolean().default(false).describe("Prune remote-tracking references that no longer exist on remote.");
145256
145391
  var DepthSchema = exports_external.number().int().min(1).optional().describe("Create a shallow clone with history truncated to N commits.");
145257
- var SignSchema = exports_external.boolean().optional().describe("Sign the commit/tag with GPG.");
145392
+ var SignSchema = exports_external.boolean().optional().describe("Sign the commit/tag with GPG/SSH. Omit to use the server's GIT_SIGN_COMMITS default (recommended). Set true to force signing, or false to skip signing even when the default enables it.");
145258
145393
  var NoVerifySchema = exports_external.boolean().default(false).describe("Bypass pre-commit and commit-msg hooks.");
145259
145394
 
145260
145395
  // src/mcp-server/tools/utils/json-response-formatter.ts
@@ -146062,7 +146197,8 @@ function filterGitInitOutput(result, level) {
146062
146197
  return {
146063
146198
  success: result.success,
146064
146199
  path: result.path,
146065
- initialBranch: result.initialBranch
146200
+ initialBranch: result.initialBranch,
146201
+ isBare: result.isBare
146066
146202
  };
146067
146203
  }
146068
146204
  return result;
@@ -147064,7 +147200,8 @@ function filterGitShowOutput(result, level) {
147064
147200
  success: result.success,
147065
147201
  object: result.object,
147066
147202
  type: result.type,
147067
- content: result.content
147203
+ content: result.content,
147204
+ metadata: result.metadata
147068
147205
  };
147069
147206
  }
147070
147207
  return result;
@@ -147602,6 +147739,7 @@ var OutputSchema23 = exports_external.object({
147602
147739
  branch: exports_external.string().describe("Branch that was pulled."),
147603
147740
  strategy: exports_external.enum(["merge", "rebase", "fast-forward"]).describe("Integration strategy used."),
147604
147741
  conflicts: exports_external.boolean().describe("Whether pull had conflicts."),
147742
+ conflictedFiles: exports_external.array(exports_external.string()).describe("Files with conflicts that need resolution (empty if conflicts is false)."),
147605
147743
  filesChanged: exports_external.array(exports_external.string()).describe("Files that were changed.")
147606
147744
  });
147607
147745
  async function gitPullLogic(input, { provider, targetPath, appContext }) {
@@ -147629,18 +147767,23 @@ async function gitPullLogic(input, { provider, targetPath, appContext }) {
147629
147767
  branch: result.branch,
147630
147768
  strategy: result.strategy,
147631
147769
  conflicts: result.conflicts,
147770
+ conflictedFiles: result.conflictedFiles,
147632
147771
  filesChanged: result.filesChanged
147633
147772
  };
147634
147773
  }
147635
147774
  function filterGitPullOutput(result, level) {
147636
147775
  if (level === "minimal") {
147637
- return {
147776
+ const minimal = {
147638
147777
  success: result.success,
147639
147778
  conflicts: result.conflicts,
147640
147779
  remote: result.remote,
147641
147780
  branch: result.branch,
147642
147781
  strategy: result.strategy
147643
147782
  };
147783
+ if (result.conflicts) {
147784
+ minimal.conflictedFiles = result.conflictedFiles;
147785
+ }
147786
+ return minimal;
147644
147787
  }
147645
147788
  return result;
147646
147789
  }
@@ -147868,8 +148011,9 @@ var InputSchema25 = exports_external.object({
147868
148011
  var OutputSchema26 = exports_external.object({
147869
148012
  success: exports_external.boolean().describe("Indicates if the operation was successful."),
147870
148013
  mode: exports_external.string().describe("Reset mode that was used."),
147871
- target: exports_external.string().describe("Target commit that was reset to."),
147872
- filesReset: exports_external.array(exports_external.string()).describe("Files that were affected by the reset.")
148014
+ target: exports_external.string().describe("Commit hash HEAD points to after the reset."),
148015
+ previousCommit: exports_external.string().optional().describe("Commit hash HEAD pointed to before the reset (omitted if HEAD did not move)."),
148016
+ filesReset: exports_external.array(exports_external.string()).describe("Files affected by the reset. For path-only resets, the listed paths. For commit-move resets, files that differ between the old and new HEAD. For --hard with no HEAD move, files whose pending working-tree changes were discarded.")
147873
148017
  });
147874
148018
  async function gitResetLogic(input, { provider, targetPath, appContext }) {
147875
148019
  if (input.mode === "hard" || input.mode === "merge" || input.mode === "keep") {
@@ -147896,20 +148040,28 @@ async function gitResetLogic(input, { provider, targetPath, appContext }) {
147896
148040
  requestContext: appContext,
147897
148041
  tenantId: appContext.tenantId || "default-tenant"
147898
148042
  });
147899
- return {
148043
+ const output = {
147900
148044
  success: result.success,
147901
148045
  mode: result.mode,
147902
148046
  target: result.commit,
147903
148047
  filesReset: result.filesReset
147904
148048
  };
148049
+ if (result.previousCommit !== undefined) {
148050
+ output.previousCommit = result.previousCommit;
148051
+ }
148052
+ return output;
147905
148053
  }
147906
148054
  function filterGitResetOutput(result, level) {
147907
148055
  if (level === "minimal") {
147908
- return {
148056
+ const minimal = {
147909
148057
  success: result.success,
147910
148058
  mode: result.mode,
147911
148059
  target: result.target
147912
148060
  };
148061
+ if (result.previousCommit !== undefined) {
148062
+ minimal.previousCommit = result.previousCommit;
148063
+ }
148064
+ return minimal;
147913
148065
  }
147914
148066
  return result;
147915
148067
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/git-mcp-server",
3
- "version": "2.10.6",
3
+ "version": "2.11.1",
4
4
  "mcpName": "io.github.cyanheads/git-mcp-server",
5
5
  "description": "A secure and scalable Git MCP server enabling AI agents to perform comprehensive Git version control operations via STDIO and Streamable HTTP.",
6
6
  "main": "dist/index.js",