@cyanheads/git-mcp-server 2.14.0 → 2.14.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 +10 -10
  2. package/dist/index.js +148 -10
  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.14.0-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.14.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.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
 
@@ -17,15 +17,15 @@
17
17
 
18
18
  28 git operations organized into seven categories:
19
19
 
20
- | Category | Tools | Description |
21
- | :------------------------ | :----------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------- |
22
- | **Repository Management** | `git_init`, `git_clone`, `git_status`, `git_clean` | Initialize repos, clone from remotes, check status, clean untracked files |
23
- | **Staging & Commits** | `git_add`, `git_commit`, `git_diff` | Stage changes, create commits, compare changes |
24
- | **History & Inspection** | `git_log`, `git_show`, `git_blame`, `git_reflog` | View commit history, inspect objects, trace authorship, view ref logs |
25
- | **Analysis** | `git_changelog_analyze` | Gather git context and instructions for LLM-driven changelog analysis |
26
- | **Branching & Merging** | `git_branch`, `git_checkout`, `git_merge`, `git_rebase`, `git_cherry_pick` | Manage branches, switch contexts, integrate changes, apply specific commits |
27
- | **Remote Operations** | `git_remote`, `git_fetch`, `git_pull`, `git_push` | Configure remotes, fetch updates, synchronize repositories, publish changes |
28
- | **Advanced Workflows** | `git_tag`, `git_stash`, `git_reset`, `git_worktree`, `git_set_working_dir`, `git_clear_working_dir`, `git_wrapup_instructions` | Tag releases, stash changes, reset state, manage worktrees, set/clear session directory |
20
+ | Category | Tools | Description |
21
+ | :------------------------ | :----------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------ |
22
+ | **Repository Management** | `git_init`, `git_clone`, `git_status`, `git_clean` | Initialize repos, clone from remotes, check status, clean untracked files |
23
+ | **Staging & Commits** | `git_add`, `git_commit`, `git_diff` | Stage changes, create commits, compare changes |
24
+ | **History & Inspection** | `git_log`, `git_show`, `git_blame`, `git_reflog` | View commit history, inspect objects, trace authorship, view ref logs |
25
+ | **Analysis** | `git_changelog_analyze` | Gather git context and instructions for LLM-driven changelog analysis |
26
+ | **Branching & Merging** | `git_branch`, `git_checkout`, `git_merge`, `git_rebase`, `git_cherry_pick` | Manage branches, switch contexts, integrate changes, apply specific commits |
27
+ | **Remote Operations** | `git_remote`, `git_fetch`, `git_pull`, `git_push` | Configure remotes, fetch updates, synchronize repositories, publish changes |
28
+ | **Advanced Workflows** | `git_tag`, `git_stash`, `git_reset`, `git_worktree`, `git_set_working_dir`, `git_clear_working_dir`, `git_wrapup_instructions` | Tag releases (list/create/delete/verify), stash changes, reset state, manage worktrees, set/clear session directory |
29
29
 
30
30
  ## Resources
31
31
 
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.13.0",
15287
+ version: "2.14.2",
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",
@@ -134454,6 +134454,7 @@ function parseFilesChanged(stdout) {
134454
134454
  return files;
134455
134455
  }
134456
134456
  // src/services/git/providers/cli/operations/tags/tag.ts
134457
+ init_errors3();
134457
134458
  async function executeTag(options, context, execGit) {
134458
134459
  try {
134459
134460
  const args = [];
@@ -134572,6 +134573,17 @@ async function executeTag(options, context, execGit) {
134572
134573
  };
134573
134574
  return deleteResult;
134574
134575
  }
134576
+ case "verify": {
134577
+ if (!options.tagName) {
134578
+ throw new Error("Tag name is required for verify operation");
134579
+ }
134580
+ const cmd = buildGitCommand({
134581
+ command: "tag",
134582
+ args: ["-v", options.tagName]
134583
+ });
134584
+ const result = await execGit(cmd, context.workingDirectory, context.requestContext, { allowNonZeroExit: true });
134585
+ return parseVerifyOutput(options.tagName, result.stderr, result.exitCode ?? 0);
134586
+ }
134575
134587
  default:
134576
134588
  throw new Error("Unknown tag operation mode");
134577
134589
  }
@@ -134579,6 +134591,93 @@ async function executeTag(options, context, execGit) {
134579
134591
  throw mapGitError(error48, "tag");
134580
134592
  }
134581
134593
  }
134594
+ function parseVerifyOutput(tagName, stderr, exitCode) {
134595
+ if (/^error: tag '.+' not found/m.test(stderr)) {
134596
+ throw new McpError(-32600 /* InvalidRequest */, `Tag not found: ${tagName}`, { tagName });
134597
+ }
134598
+ const base = {
134599
+ mode: "verify",
134600
+ verifiedTag: tagName,
134601
+ rawOutput: stderr
134602
+ };
134603
+ if (/^error: no signature found$/m.test(stderr)) {
134604
+ return {
134605
+ ...base,
134606
+ verified: false,
134607
+ warning: "Tag has no signature. Create with a signing key and `GIT_SIGN_COMMITS=true` to produce a signed tag."
134608
+ };
134609
+ }
134610
+ if (/gpg\.ssh\.allowedSignersFile needs to be configured/.test(stderr) || /No principal matched/.test(stderr)) {
134611
+ return {
134612
+ ...base,
134613
+ verified: false,
134614
+ signatureType: "ssh",
134615
+ warning: "SSH signature verification requires `gpg.ssh.allowedSignersFile` to be configured. The tag may be validly signed; this environment cannot verify it."
134616
+ };
134617
+ }
134618
+ const gpgBadMatch = /(?:gpg|gpgsm): BAD signature from "([^"]+)"/.exec(stderr);
134619
+ if (gpgBadMatch) {
134620
+ return {
134621
+ ...base,
134622
+ verified: false,
134623
+ signatureType: stderr.includes("gpgsm:") ? "x509" : "gpg",
134624
+ signerIdentity: gpgBadMatch[1],
134625
+ warning: "Signature does not validate (BAD signature)."
134626
+ };
134627
+ }
134628
+ const sshBadMatch = /Signature verification failed.*? for "([^"]+)"|Could not verify signature/i.exec(stderr);
134629
+ if (sshBadMatch && exitCode !== 0) {
134630
+ const result = {
134631
+ ...base,
134632
+ verified: false,
134633
+ signatureType: "ssh",
134634
+ warning: "SSH signature does not validate."
134635
+ };
134636
+ if (sshBadMatch[1])
134637
+ result.signerIdentity = sshBadMatch[1];
134638
+ return result;
134639
+ }
134640
+ const gpgGoodMatch = /gpg: Good signature from "([^"]+)"/.exec(stderr);
134641
+ if (gpgGoodMatch) {
134642
+ const result = {
134643
+ ...base,
134644
+ verified: true,
134645
+ signatureType: "gpg",
134646
+ signerIdentity: gpgGoodMatch[1]
134647
+ };
134648
+ const keyMatch = /using \S+ key ([0-9A-Fa-f]{8,})/.exec(stderr);
134649
+ if (keyMatch)
134650
+ result.signerKey = keyMatch[1];
134651
+ return result;
134652
+ }
134653
+ const x509GoodMatch = /gpgsm: Good signature from "([^"]+)"/.exec(stderr);
134654
+ if (x509GoodMatch) {
134655
+ return {
134656
+ ...base,
134657
+ verified: true,
134658
+ signatureType: "x509",
134659
+ signerIdentity: x509GoodMatch[1]
134660
+ };
134661
+ }
134662
+ const sshGoodMatch = /Good "git" signature for (.+?) with \S+ key (SHA256:\S+)/.exec(stderr);
134663
+ if (sshGoodMatch) {
134664
+ return {
134665
+ ...base,
134666
+ verified: true,
134667
+ signatureType: "ssh",
134668
+ signerIdentity: sshGoodMatch[1].trim(),
134669
+ signerKey: sshGoodMatch[2]
134670
+ };
134671
+ }
134672
+ if (exitCode === 0) {
134673
+ return { ...base, verified: true };
134674
+ }
134675
+ return {
134676
+ ...base,
134677
+ verified: false,
134678
+ warning: "Verification failed but the output format was not recognized. See `rawOutput` for details."
134679
+ };
134680
+ }
134582
134681
  // src/services/git/providers/cli/operations/stash/stash.ts
134583
134682
  async function executeStash(options, context, execGit) {
134584
134683
  try {
@@ -148014,20 +148113,22 @@ init_zod();
148014
148113
  init_errors3();
148015
148114
  var TOOL_NAME27 = "git_tag";
148016
148115
  var TOOL_TITLE27 = "Git Tag";
148017
- var TOOL_DESCRIPTION27 = "Manage tags: list all tags, create a new tag, or delete a tag. Tags are used to mark specific points in history (releases, milestones).";
148116
+ var TOOL_DESCRIPTION27 = "Manage tags: list all tags, create a new tag, delete a tag, or verify a signed tag. Tags are used to mark specific points in history (releases, milestones). Verify runs `git tag -v` and returns a structured result distinguishing unsigned tags, missing trust configuration, bad signatures, and valid signatures.";
148018
148117
  var InputSchema27 = exports_external.object({
148019
148118
  path: PathSchema,
148020
- mode: exports_external.enum(["list", "create", "delete"]).default("list").describe("The tag operation to perform."),
148021
- tagName: TagNameSchema.optional().describe("Tag name for create/delete operations."),
148119
+ mode: exports_external.enum(["list", "create", "delete", "verify"]).default("list").describe("The tag operation to perform."),
148120
+ tagName: TagNameSchema.optional().describe("Tag name for create/delete/verify operations."),
148022
148121
  commit: CommitRefSchema.optional().describe("Commit to tag (default: HEAD for create operation)."),
148023
148122
  message: exports_external.string().optional().describe("Tag message. Providing a message always produces an annotated tag (git does not support messages on lightweight tags). For release tags, summarize notable changes."),
148024
148123
  annotated: exports_external.boolean().default(false).describe('Create an annotated tag with a default "Tag <name>" message. Only effective when no message is provided and signing is disabled — otherwise the tag is always annotated.'),
148025
- force: ForceSchema.describe("Overwrite an existing tag (create mode only; has no effect on list or delete).")
148124
+ force: ForceSchema.describe("Overwrite an existing tag (create mode only; has no effect on list or delete)."),
148125
+ limit: LimitSchema.describe("For list mode: cap the number of tags returned (applied at the git command via `--count=N`). Use on repos with many tags.")
148026
148126
  });
148027
148127
  var TagInfoSchema = exports_external.object({
148028
148128
  name: exports_external.string().describe("Tag name."),
148029
148129
  commit: exports_external.string().describe("Commit hash the tag points to."),
148030
- message: exports_external.string().optional().describe("Tag message (for annotated tags)."),
148130
+ message: exports_external.string().optional().describe("First line of the tag annotation (annotated tags only). See `annotationBody` for the remainder."),
148131
+ annotationBody: exports_external.string().optional().describe("Remaining annotation body after the subject line (annotated tags only)."),
148031
148132
  tagger: exports_external.string().optional().describe("Tagger name and email."),
148032
148133
  timestamp: exports_external.number().int().optional().describe("Tag creation timestamp.")
148033
148134
  });
@@ -148038,10 +148139,17 @@ var OutputSchema28 = exports_external.object({
148038
148139
  created: exports_external.string().optional().describe("Created tag name (for create mode)."),
148039
148140
  deleted: exports_external.string().optional().describe("Deleted tag name (for delete mode)."),
148040
148141
  signed: exports_external.boolean().optional().describe("Whether the created tag was signed. Only populated for create mode. False when GIT_SIGN_COMMITS=false or when signing failed and fell back to unsigned."),
148041
- signingWarning: exports_external.string().optional().describe("Populated only when signing was requested but failed, and the tag was created unsigned as a fallback.")
148142
+ signingWarning: exports_external.string().optional().describe("Populated only when signing was requested but failed, and the tag was created unsigned as a fallback."),
148143
+ verifiedTag: exports_external.string().optional().describe("Verified tag name (for verify mode). Echoes the input so callers can correlate results in batched flows."),
148144
+ verified: exports_external.boolean().optional().describe("Whether the signature validated (for verify mode). `false` for unsigned tags, missing trust config, bad signatures, or unparseable output — inspect `warning` to distinguish."),
148145
+ signatureType: exports_external.enum(["gpg", "ssh", "x509"]).optional().describe("Signature algorithm family when detectable from `git tag -v` output (verify mode). Absent for unsigned tags or unparseable output."),
148146
+ signerIdentity: exports_external.string().optional().describe("Signer identity as emitted by git — e.g., `Name <email>` for GPG or the SSH principal. Verify mode only."),
148147
+ signerKey: exports_external.string().optional().describe("Key material emitted by git — GPG fingerprint/key ID or SSH key fingerprint (`SHA256:…`). Verify mode only; absent when git did not surface it."),
148148
+ warning: exports_external.string().optional().describe("Populated on verify failure with a human-readable reason distinguishing unsigned tags, missing trust configuration, bad signatures, and unparseable output."),
148149
+ rawOutput: exports_external.string().optional().describe("Raw stderr from `git tag -v` for callers that need the full verification output (verify mode only).")
148042
148150
  });
148043
148151
  async function gitTagLogic(input, { provider, targetPath, appContext }) {
148044
- if ((input.mode === "create" || input.mode === "delete") && !input.tagName) {
148152
+ if ((input.mode === "create" || input.mode === "delete" || input.mode === "verify") && !input.tagName) {
148045
148153
  throw new McpError(-32602 /* InvalidParams */, `Tag name is required for ${input.mode} operation.`);
148046
148154
  }
148047
148155
  const tagOptions = {
@@ -148058,6 +148166,9 @@ async function gitTagLogic(input, { provider, targetPath, appContext }) {
148058
148166
  if (input.message !== undefined) {
148059
148167
  tagOptions.message = normalizeMessage(input.message);
148060
148168
  }
148169
+ if (input.limit !== undefined) {
148170
+ tagOptions.limit = input.limit;
148171
+ }
148061
148172
  const result = await provider.tag(tagOptions, {
148062
148173
  workingDirectory: targetPath,
148063
148174
  requestContext: appContext,
@@ -148076,6 +148187,27 @@ async function gitTagLogic(input, { provider, targetPath, appContext }) {
148076
148187
  if (result.signingWarning) {
148077
148188
  output.signingWarning = result.signingWarning;
148078
148189
  }
148190
+ if (result.verifiedTag !== undefined) {
148191
+ output.verifiedTag = result.verifiedTag;
148192
+ }
148193
+ if (result.verified !== undefined) {
148194
+ output.verified = result.verified;
148195
+ }
148196
+ if (result.signatureType !== undefined) {
148197
+ output.signatureType = result.signatureType;
148198
+ }
148199
+ if (result.signerIdentity !== undefined) {
148200
+ output.signerIdentity = result.signerIdentity;
148201
+ }
148202
+ if (result.signerKey !== undefined) {
148203
+ output.signerKey = result.signerKey;
148204
+ }
148205
+ if (result.warning !== undefined) {
148206
+ output.warning = result.warning;
148207
+ }
148208
+ if (result.rawOutput !== undefined) {
148209
+ output.rawOutput = result.rawOutput;
148210
+ }
148079
148211
  return output;
148080
148212
  }
148081
148213
  function filterGitTagOutput(result, level) {
@@ -148084,10 +148216,16 @@ function filterGitTagOutput(result, level) {
148084
148216
  success: result.success,
148085
148217
  mode: result.mode,
148086
148218
  ...result.signed !== undefined && { signed: result.signed },
148087
- ...result.signingWarning && { signingWarning: result.signingWarning }
148219
+ ...result.signingWarning && { signingWarning: result.signingWarning },
148220
+ ...result.verified !== undefined && { verified: result.verified },
148221
+ ...result.warning && { warning: result.warning }
148088
148222
  };
148089
148223
  }
148090
- return result;
148224
+ if (level === "full") {
148225
+ return result;
148226
+ }
148227
+ const { rawOutput: _raw, ...rest } = result;
148228
+ return rest;
148091
148229
  }
148092
148230
  var responseFormatter27 = createJsonFormatter({
148093
148231
  filter: filterGitTagOutput
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/git-mcp-server",
3
- "version": "2.14.0",
3
+ "version": "2.14.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",