@cyanheads/git-mcp-server 2.9.1 → 2.9.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.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +52 -33
  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.9.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.27.1-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-^5.9.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.2.21-blueviolet.svg?style=flat-square)](https://bun.sh/)
10
+ [![Version](https://img.shields.io/badge/Version-2.9.2-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.27.1-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-^5.9.3-3178C6.svg?style=flat-square)](https://www.typescriptlang.org/) [![Bun](https://img.shields.io/badge/Bun-v1.2.21-blueviolet.svg?style=flat-square)](https://bun.sh/)
11
11
 
12
12
  </div>
13
13
 
package/dist/index.js CHANGED
@@ -15335,7 +15335,7 @@ var package_default;
15335
15335
  var init_package = __esm(() => {
15336
15336
  package_default = {
15337
15337
  name: "@cyanheads/git-mcp-server",
15338
- version: "2.9.0",
15338
+ version: "2.9.2",
15339
15339
  mcpName: "io.github.cyanheads/git-mcp-server",
15340
15340
  description: "A secure and scalable Git MCP server enabling AI agents to perform comprehensive Git version control operations via STDIO and Streamable HTTP.",
15341
15341
  main: "dist/index.js",
@@ -179321,20 +179321,24 @@ async function executeClone(options, context, execGit) {
179321
179321
  const cmd = buildGitCommand({ command: "clone", args });
179322
179322
  await execGit(cmd, cloneCwd, context.requestContext);
179323
179323
  let commitHash;
179324
+ let actualBranch = options.branch || "main";
179324
179325
  try {
179325
- const revParseCmd = buildGitCommand({
179326
- command: "rev-parse",
179327
- args: ["HEAD"]
179328
- });
179329
- const revParseResult = await execGit(revParseCmd, resolvedLocalPath, context.requestContext);
179326
+ const [revParseResult, branchResult] = await Promise.all([
179327
+ execGit(buildGitCommand({ command: "rev-parse", args: ["HEAD"] }), resolvedLocalPath, context.requestContext),
179328
+ execGit(buildGitCommand({
179329
+ command: "rev-parse",
179330
+ args: ["--abbrev-ref", "HEAD"]
179331
+ }), resolvedLocalPath, context.requestContext)
179332
+ ]);
179330
179333
  commitHash = revParseResult.stdout.trim() || undefined;
179334
+ actualBranch = branchResult.stdout.trim() || actualBranch;
179331
179335
  } catch {}
179332
179336
  const result = {
179333
179337
  success: true,
179334
- localPath: options.localPath,
179338
+ localPath: resolvedLocalPath,
179335
179339
  remoteUrl: options.remoteUrl,
179336
- branch: options.branch || "main",
179337
- commitHash
179340
+ branch: actualBranch,
179341
+ ...commitHash && { commitHash }
179338
179342
  };
179339
179343
  return result;
179340
179344
  } catch (error48) {
@@ -179645,7 +179649,11 @@ async function executeShow(options, context, execGit) {
179645
179649
  if (options.format === "raw") {
179646
179650
  args.push("--format=raw");
179647
179651
  }
179648
- args.push(options.object);
179652
+ if (options.filePath) {
179653
+ args.push(`${options.object}:${options.filePath}`);
179654
+ } else {
179655
+ args.push(options.object);
179656
+ }
179649
179657
  const typeCmd = buildGitCommand({
179650
179658
  command: "cat-file",
179651
179659
  args: ["-t", options.object]
@@ -179960,7 +179968,7 @@ async function executeMerge(options, context, execGit) {
179960
179968
  }).filter((f3) => f3);
179961
179969
  const mergedFiles = result.stdout.split(`
179962
179970
  `).map((line) => {
179963
- const statMatch = line.match(/^\s*(.+?)\s*\|\s*\d+/);
179971
+ const statMatch = line.match(/^\s(.+?)\s*\|\s*(?:\d+\s*[+-]*|Bin\s)/);
179964
179972
  return statMatch?.[1]?.trim() || "";
179965
179973
  }).filter((f3) => f3);
179966
179974
  const mergeResult = {
@@ -180002,6 +180010,16 @@ async function executeRebase(options, context, execGit) {
180002
180010
  if (!options.upstream) {
180003
180011
  throw new Error("upstream is required for start mode");
180004
180012
  }
180013
+ if (options.interactive) {
180014
+ args.push("--interactive");
180015
+ }
180016
+ if (options.preserve) {
180017
+ args.push("--preserve-merges");
180018
+ }
180019
+ const shouldSign = options.sign ?? shouldSignCommits();
180020
+ if (shouldSign) {
180021
+ args.push("--gpg-sign");
180022
+ }
180005
180023
  if (options.onto) {
180006
180024
  args.push("--onto", options.onto, options.upstream);
180007
180025
  if (options.branch) {
@@ -180013,16 +180031,6 @@ async function executeRebase(options, context, execGit) {
180013
180031
  args.push(options.branch);
180014
180032
  }
180015
180033
  }
180016
- if (options.interactive) {
180017
- args.push("--interactive");
180018
- }
180019
- if (options.preserve) {
180020
- args.push("--preserve-merges");
180021
- }
180022
- const shouldSign = options.sign ?? shouldSignCommits();
180023
- if (shouldSign) {
180024
- args.push("--gpg-sign");
180025
- }
180026
180034
  }
180027
180035
  const cmd = buildGitCommand({ command: "rebase", args });
180028
180036
  const result = await execGit(cmd, context.workingDirectory, context.requestContext);
@@ -180226,7 +180234,7 @@ async function executeFetch(options, context, execGit) {
180226
180234
  if (newRefMatch?.[1]) {
180227
180235
  fetchedRefs.push(newRefMatch[1]);
180228
180236
  } else {
180229
- const updatedRefMatch = line.match(/[a-f0-9]+\.\.[a-f0-9]+\s+\S+\s+->\s+(\S+)/);
180237
+ const updatedRefMatch = line.match(/\+?\s*[a-f0-9]+\.{2,3}[a-f0-9]+\s+\S+\s+->\s+(\S+)/);
180230
180238
  if (updatedRefMatch?.[1]) {
180231
180239
  fetchedRefs.push(updatedRefMatch[1]);
180232
180240
  }
@@ -180253,6 +180261,9 @@ async function executePush(options, context, execGit) {
180253
180261
  const args = [];
180254
180262
  const remote = options.remote || "origin";
180255
180263
  if (options.delete) {
180264
+ if (!options.branch && !options.remoteBranch) {
180265
+ throw new Error("A branch or remote branch name is required for delete operations.");
180266
+ }
180256
180267
  args.push("--delete");
180257
180268
  }
180258
180269
  args.push(remote);
@@ -180461,21 +180472,24 @@ async function executeStash(options, context, execGit) {
180461
180472
  const args = [options.mode];
180462
180473
  switch (options.mode) {
180463
180474
  case "list": {
180475
+ args.push("--format=%gd\t%ct\t%gs");
180464
180476
  const cmd = buildGitCommand({ command: "stash", args });
180465
180477
  const result = await execGit(cmd, context.workingDirectory, context.requestContext);
180466
180478
  const stashes = result.stdout.split(`
180467
180479
  `).filter((line) => line.trim()).map((line, index) => {
180468
- const match = line.match(/^(stash@\{(\d+)\}):\s+(.+)$/);
180469
- if (match) {
180470
- const stashIndex = parseInt(match[2], 10);
180471
- const rest = match[3];
180472
- const branchMatch = rest.match(/^(?:WIP on|On)\s+([^:]+):\s+(.*)$/);
180480
+ const parts = line.split("\t");
180481
+ const [refPart, tsPart, ...subjectParts] = parts;
180482
+ if (refPart && tsPart && subjectParts.length > 0) {
180483
+ const stashIndex = parseInt(refPart, 10);
180484
+ const timestamp2 = parseInt(tsPart, 10);
180485
+ const subject = subjectParts.join("\t");
180486
+ const branchMatch = subject.match(/^(?:WIP on|On)\s+([^:]+):\s+(.*)$/);
180473
180487
  return {
180474
- ref: match[1],
180488
+ ref: `stash@{${stashIndex}}`,
180475
180489
  index: stashIndex,
180476
180490
  branch: branchMatch?.[1] ?? "",
180477
- description: branchMatch?.[2] ?? rest,
180478
- timestamp: 0
180491
+ description: branchMatch?.[2] ?? subject,
180492
+ timestamp: timestamp2
180479
180493
  };
180480
180494
  }
180481
180495
  return {
@@ -180703,6 +180717,8 @@ async function executeBlame(options, context, execGit) {
180703
180717
  }
180704
180718
  if (options.startLine && options.endLine) {
180705
180719
  args.push(`-L${options.startLine},${options.endLine}`);
180720
+ } else if (options.startLine) {
180721
+ args.push(`-L${options.startLine},`);
180706
180722
  }
180707
180723
  args.push("--", options.file);
180708
180724
  const cmd = buildGitCommand({ command: "blame", args });
@@ -198650,10 +198666,13 @@ var TOOL_TITLE11 = "Git Add";
198650
198666
  var TOOL_DESCRIPTION11 = "Stage files for commit. Add file contents to the staging area (index) to prepare for the next commit.";
198651
198667
  var InputSchema11 = exports_external.object({
198652
198668
  path: PathSchema,
198653
- files: exports_external.array(exports_external.string()).min(1).describe('Array of file paths to stage (relative to repository root). Use ["."] to stage all changes.'),
198669
+ files: exports_external.array(exports_external.string()).default([]).describe('Array of file paths to stage (relative to repository root). Use ["."] to stage all changes. Can be omitted when all or update is true.'),
198654
198670
  update: exports_external.boolean().default(false).describe("Stage only modified and deleted files (skip untracked files)."),
198655
198671
  all: AllSchema,
198656
198672
  force: exports_external.boolean().default(false).describe("Allow adding otherwise ignored files.")
198673
+ }).refine((data) => data.all || data.update || data.files.length > 0, {
198674
+ message: "Either files must be provided, or all/update must be true.",
198675
+ path: ["files"]
198657
198676
  });
198658
198677
  var OutputSchema12 = exports_external.object({
198659
198678
  success: exports_external.boolean().describe("Indicates if the operation was successful."),
@@ -199891,7 +199910,7 @@ var gitRemoteTool = {
199891
199910
  inputSchema: InputSchema24,
199892
199911
  outputSchema: OutputSchema25,
199893
199912
  annotations: { readOnlyHint: false },
199894
- logic: withToolAuth(["tool:git:write"], createToolHandler(gitRemoteLogic)),
199913
+ logic: withToolAuth(["tool:git:read"], createToolHandler(gitRemoteLogic)),
199895
199914
  responseFormatter: responseFormatter24
199896
199915
  };
199897
199916
 
@@ -199905,7 +199924,7 @@ var InputSchema25 = exports_external.object({
199905
199924
  mode: exports_external.enum(["soft", "mixed", "hard", "merge", "keep"]).default("mixed").describe("Reset mode: soft (keep changes staged), mixed (unstage changes), hard (discard all changes), merge (reset and merge), keep (reset but keep local changes)."),
199906
199925
  target: CommitRefSchema.optional().describe("Target commit to reset to (default: HEAD)."),
199907
199926
  paths: exports_external.array(exports_external.string()).optional().describe("Specific file paths to reset (leaves HEAD unchanged)."),
199908
- confirmed: exports_external.boolean().default(false).describe("Explicit confirmation required for hard reset on protected branches (main, master, production, etc.).")
199927
+ confirmed: exports_external.boolean().default(false).describe("Explicit confirmation required for hard, merge, and keep reset modes on protected branches (main, master, production, etc.).")
199909
199928
  });
199910
199929
  var OutputSchema26 = exports_external.object({
199911
199930
  success: exports_external.boolean().describe("Indicates if the operation was successful."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/git-mcp-server",
3
- "version": "2.9.1",
3
+ "version": "2.9.2",
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",