@cyanheads/git-mcp-server 2.10.0 → 2.10.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 +60 -60
  2. package/dist/index.js +110 -38
  3. package/package.json +3 -3
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.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.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.10.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
 
@@ -17,27 +17,27 @@
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, stash changes, reset state, manage worktrees, set/clear session directory |
29
29
 
30
30
  ## Resources
31
31
 
32
- | Resource | URI | Description |
33
- | :------------------------ | :------------------------ | :--------------------------------------------------------------------------- |
34
- | **Git Working Directory** | `git://working-directory` | The current session working directory, set via `git_set_working_dir`. |
32
+ | Resource | URI | Description |
33
+ | :------------------------ | :------------------------ | :-------------------------------------------------------------------- |
34
+ | **Git Working Directory** | `git://working-directory` | The current session working directory, set via `git_set_working_dir`. |
35
35
 
36
36
  ## Prompts
37
37
 
38
- | Prompt | Description | Parameters |
39
- | :-------------- | :----------------------------------------------------------------------------------------- | :------------------------------------------------------------------------- |
40
- | **Git Wrap-up** | Workflow protocol for completing git sessions: review, document, commit, and tag changes. | `changelogPath`, `skipDocumentation`, `createTag`, `updateAgentFiles`. |
38
+ | Prompt | Description | Parameters |
39
+ | :-------------- | :---------------------------------------------------------------------------------------- | :--------------------------------------------------------------------- |
40
+ | **Git Wrap-up** | Workflow protocol for completing git sessions: review, document, commit, and tag changes. | `changelogPath`, `skipDocumentation`, `createTag`, `updateAgentFiles`. |
41
41
 
42
42
  ## Getting started
43
43
 
@@ -83,20 +83,20 @@ For Streamable HTTP, set `MCP_TRANSPORT_TYPE=http` and `MCP_HTTP_PORT=3015`.
83
83
 
84
84
  Built on [`mcp-ts-template`](https://github.com/cyanheads/mcp-ts-template).
85
85
 
86
- | Feature | Details |
87
- | :-- | :-- |
88
- | Declarative tools | Define capabilities in single, self-contained files. The framework handles registration, validation, and execution. |
89
- | Error handling | Unified `McpError` system for consistent, structured error responses. |
90
- | Authentication | Supports `none`, `jwt`, and `oauth` modes. |
91
- | Pluggable storage | Swap backends (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2`) without changing business logic. |
92
- | Observability | Structured logging (Pino) and optional auto-instrumented OpenTelemetry for traces and metrics. |
93
- | Dependency injection | Built with `tsyringe` for decoupled, testable architecture. |
94
- | Cross-runtime | Auto-detects Bun or Node.js and uses the appropriate process spawning method. |
95
- | Provider architecture | Pluggable git provider system. Current: CLI. Planned: isomorphic-git for edge deployment. |
96
- | Working directory management | Session-specific directory context for multi-repo workflows. |
97
- | Configurable git identity | Override author/committer info via environment variables, with fallback to global git config. |
98
- | Commit signing | Optional GPG/SSH signing for commits, merges, rebases, cherry-picks, and tags. |
99
- | Safety | Destructive operations (`git clean`, `git reset --hard`) require explicit confirmation flags. |
86
+ | Feature | Details |
87
+ | :--------------------------- | :------------------------------------------------------------------------------------------------------------------ |
88
+ | Declarative tools | Define capabilities in single, self-contained files. The framework handles registration, validation, and execution. |
89
+ | Error handling | Unified `McpError` system for consistent, structured error responses. |
90
+ | Authentication | Supports `none`, `jwt`, and `oauth` modes. |
91
+ | Pluggable storage | Swap backends (`in-memory`, `filesystem`, `Supabase`, `Cloudflare KV/R2`) without changing business logic. |
92
+ | Observability | Structured logging (Pino) and optional auto-instrumented OpenTelemetry for traces and metrics. |
93
+ | Dependency injection | Built with `tsyringe` for decoupled, testable architecture. |
94
+ | Cross-runtime | Auto-detects Bun or Node.js and uses the appropriate process spawning method. |
95
+ | Provider architecture | Pluggable git provider system. Current: CLI. Planned: isomorphic-git for edge deployment. |
96
+ | Working directory management | Session-specific directory context for multi-repo workflows. |
97
+ | Configurable git identity | Override author/committer info via environment variables, with fallback to global git config. |
98
+ | Commit signing | Optional GPG/SSH signing for commits, merges, rebases, cherry-picks, and tags. |
99
+ | Safety | Destructive operations (`git clean`, `git reset --hard`) require explicit confirmation flags. |
100
100
 
101
101
  ## Security
102
102
 
@@ -111,26 +111,26 @@ Built on [`mcp-ts-template`](https://github.com/cyanheads/mcp-ts-template).
111
111
 
112
112
  All configuration is validated at startup in `src/config/index.ts`. Key environment variables:
113
113
 
114
- | Variable | Description | Default |
115
- | :----------------------------- | :------------------------------------------------------------------------------------------------------------------- | :---------- |
116
- | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
117
- | `MCP_SESSION_MODE` | HTTP session mode: `stateless`, `stateful`, or `auto`. | `auto` |
118
- | `MCP_RESPONSE_FORMAT` | Response format: `json` (LLM-optimized), `markdown` (human-readable), or `auto`. | `json` |
119
- | `MCP_RESPONSE_VERBOSITY` | Detail level: `minimal`, `standard`, or `full`. | `standard` |
120
- | `MCP_HTTP_PORT` | HTTP server port. | `3015` |
121
- | `MCP_HTTP_HOST` | HTTP server hostname. | `127.0.0.1` |
122
- | `MCP_HTTP_ENDPOINT_PATH` | MCP request endpoint path. | `/mcp` |
123
- | `MCP_AUTH_MODE` | Authentication mode: `none`, `jwt`, or `oauth`. | `none` |
124
- | `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv`, `r2`. | `in-memory` |
125
- | `OTEL_ENABLED` | Enable OpenTelemetry. | `false` |
126
- | `MCP_LOG_LEVEL` | Minimum log level: `debug`, `info`, `warn`, `error`. | `info` |
127
- | `GIT_SIGN_COMMITS` | Enable GPG/SSH signing for commits, merges, rebases, cherry-picks, and tags. | `false` |
128
- | `GIT_AUTHOR_NAME` | Git author name. Aliases: `GIT_USERNAME`, `GIT_USER`. Falls back to global git config. | `(none)` |
129
- | `GIT_AUTHOR_EMAIL` | Git author email. Aliases: `GIT_EMAIL`, `GIT_USER_EMAIL`. Falls back to global git config. | `(none)` |
130
- | `GIT_BASE_DIR` | Absolute path to restrict all git operations to a specific directory tree. | `(none)` |
131
- | `GIT_WRAPUP_INSTRUCTIONS_PATH` | Path to custom markdown file with workflow instructions. | `(none)` |
132
- | `MCP_AUTH_SECRET_KEY` | Required for `jwt` auth. 32+ character secret key. | `(none)` |
133
- | `OAUTH_ISSUER_URL` | Required for `oauth` auth. OIDC provider URL. | `(none)` |
114
+ | Variable | Description | Default |
115
+ | :----------------------------- | :----------------------------------------------------------------------------------------- | :---------- |
116
+ | `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`. | `stdio` |
117
+ | `MCP_SESSION_MODE` | HTTP session mode: `stateless`, `stateful`, or `auto`. | `auto` |
118
+ | `MCP_RESPONSE_FORMAT` | Response format: `json` (LLM-optimized), `markdown` (human-readable), or `auto`. | `json` |
119
+ | `MCP_RESPONSE_VERBOSITY` | Detail level: `minimal`, `standard`, or `full`. | `standard` |
120
+ | `MCP_HTTP_PORT` | HTTP server port. | `3015` |
121
+ | `MCP_HTTP_HOST` | HTTP server hostname. | `127.0.0.1` |
122
+ | `MCP_HTTP_ENDPOINT_PATH` | MCP request endpoint path. | `/mcp` |
123
+ | `MCP_AUTH_MODE` | Authentication mode: `none`, `jwt`, or `oauth`. | `none` |
124
+ | `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv`, `r2`. | `in-memory` |
125
+ | `OTEL_ENABLED` | Enable OpenTelemetry. | `false` |
126
+ | `MCP_LOG_LEVEL` | Minimum log level: `debug`, `info`, `warn`, `error`. | `info` |
127
+ | `GIT_SIGN_COMMITS` | Enable GPG/SSH signing for commits, merges, rebases, cherry-picks, and tags. | `false` |
128
+ | `GIT_AUTHOR_NAME` | Git author name. Aliases: `GIT_USERNAME`, `GIT_USER`. Falls back to global git config. | `(none)` |
129
+ | `GIT_AUTHOR_EMAIL` | Git author email. Aliases: `GIT_EMAIL`, `GIT_USER_EMAIL`. Falls back to global git config. | `(none)` |
130
+ | `GIT_BASE_DIR` | Absolute path to restrict all git operations to a specific directory tree. | `(none)` |
131
+ | `GIT_WRAPUP_INSTRUCTIONS_PATH` | Path to custom markdown file with workflow instructions. | `(none)` |
132
+ | `MCP_AUTH_SECRET_KEY` | Required for `jwt` auth. 32+ character secret key. | `(none)` |
133
+ | `OAUTH_ISSUER_URL` | Required for `oauth` auth. OIDC provider URL. | `(none)` |
134
134
 
135
135
  ## Running the server
136
136
 
@@ -167,17 +167,17 @@ npm run deploy:prod # Deploy to Cloudflare
167
167
 
168
168
  ## Project structure
169
169
 
170
- | Directory | Purpose |
171
- | :-------------------------- | :--------------------------------------------------------------- |
172
- | `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). Git capabilities live here. |
170
+ | Directory | Purpose |
171
+ | :-------------------------- | :---------------------------------------------------------------- |
172
+ | `src/mcp-server/tools` | Tool definitions (`*.tool.ts`). Git capabilities live here. |
173
173
  | `src/mcp-server/resources` | Resource definitions (`*.resource.ts`). Git context data sources. |
174
- | `src/mcp-server/transports` | HTTP and STDIO transport implementations, including auth. |
175
- | `src/storage` | `StorageService` abstraction and provider implementations. |
176
- | `src/services` | Git service provider (CLI-based git operations). |
177
- | `src/container` | DI container registrations and tokens. |
178
- | `src/utils` | Logging, error handling, performance, security utilities. |
179
- | `src/config` | Environment variable parsing and validation (Zod). |
180
- | `tests/` | Unit and integration tests, mirroring `src/` structure. |
174
+ | `src/mcp-server/transports` | HTTP and STDIO transport implementations, including auth. |
175
+ | `src/storage` | `StorageService` abstraction and provider implementations. |
176
+ | `src/services` | Git service provider (CLI-based git operations). |
177
+ | `src/container` | DI container registrations and tokens. |
178
+ | `src/utils` | Logging, error handling, performance, security utilities. |
179
+ | `src/config` | Environment variable parsing and validation (Zod). |
180
+ | `tests/` | Unit and integration tests, mirroring `src/` structure. |
181
181
 
182
182
  ## Response format
183
183
 
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.10.0",
15338
+ version: "2.10.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",
@@ -15407,7 +15407,7 @@ var init_package = __esm(() => {
15407
15407
  rollup: "4.59.0"
15408
15408
  },
15409
15409
  devDependencies: {
15410
- "@cloudflare/workers-types": "^4.20260305.1",
15410
+ "@cloudflare/workers-types": "^4.20260307.1",
15411
15411
  "@eslint/js": "^10.0.1",
15412
15412
  "@hono/mcp": "^0.2.4",
15413
15413
  "@hono/node-server": "1.19.11",
@@ -15431,7 +15431,7 @@ var init_package = __esm(() => {
15431
15431
  "bun-types": "^1.3.10",
15432
15432
  depcheck: "^1.4.7",
15433
15433
  dotenv: "^17.3.1",
15434
- eslint: "^10.0.2",
15434
+ eslint: "^10.0.3",
15435
15435
  execa: "^9.6.1",
15436
15436
  globals: "^17.4.0",
15437
15437
  hono: "4.12.5",
@@ -15576,7 +15576,7 @@ __export(exports_config, {
15576
15576
  });
15577
15577
  import { homedir } from "os";
15578
15578
  import path from "node:path";
15579
- var import_dotenv, packageManifest, hasFileSystemAccess, emptyStringAsUndefined = (val) => {
15579
+ var import_dotenv, packageManifest, emptyStringAsUndefined = (val) => {
15580
15580
  if (typeof val === "string" && val.trim() === "") {
15581
15581
  return;
15582
15582
  }
@@ -15680,7 +15680,7 @@ var import_dotenv, packageManifest, hasFileSystemAccess, emptyStringAsUndefined
15680
15680
  const finalRawConfig = {
15681
15681
  ...rawConfig,
15682
15682
  pkg: parsedPkg,
15683
- logsPath: rawConfig.logsPath ?? (hasFileSystemAccess ? "logs" : undefined),
15683
+ logsPath: rawConfig.logsPath,
15684
15684
  mcpServerName: env.MCP_SERVER_NAME ?? parsedPkg.name,
15685
15685
  mcpServerVersion: env.MCP_SERVER_VERSION ?? parsedPkg.version,
15686
15686
  mcpServerDescription: env.MCP_SERVER_DESCRIPTION ?? parsedPkg.description,
@@ -15707,7 +15707,6 @@ var init_config = __esm(() => {
15707
15707
  init_errors3();
15708
15708
  import_dotenv = __toESM(require_main(), 1);
15709
15709
  packageManifest = package_default;
15710
- hasFileSystemAccess = typeof process !== "undefined" && typeof process.versions === "object" && process.versions !== null && typeof process.versions.node === "string";
15711
15710
  import_dotenv.default.config({ quiet: true });
15712
15711
  ConfigSchema = exports_external.object({
15713
15712
  pkg: exports_external.object({
@@ -136778,53 +136777,76 @@ async function executeShow(options, context, execGit) {
136778
136777
  // src/services/git/providers/cli/operations/commits/diff.ts
136779
136778
  async function executeDiff(options, context, execGit) {
136780
136779
  try {
136781
- const flags = [];
136780
+ const baseFlags = [];
136782
136781
  const pathArgs = [];
136783
136782
  if (options.staged) {
136784
- flags.push("--cached");
136783
+ baseFlags.push("--cached");
136785
136784
  }
136785
+ if (options.commit1) {
136786
+ baseFlags.push(options.commit1);
136787
+ }
136788
+ if (options.commit2) {
136789
+ baseFlags.push(options.commit2);
136790
+ }
136791
+ const flags = [...baseFlags];
136786
136792
  if (options.nameOnly) {
136787
136793
  flags.push("--name-only");
136788
136794
  }
136789
136795
  if (options.unified !== undefined) {
136790
136796
  flags.push(`--unified=${options.unified}`);
136791
136797
  }
136792
- if (options.commit1) {
136793
- flags.push(options.commit1);
136794
- }
136795
- if (options.commit2) {
136796
- flags.push(options.commit2);
136798
+ if (options.paths?.length || options.excludePatterns?.length) {
136799
+ pathArgs.push("--");
136800
+ if (options.paths?.length) {
136801
+ pathArgs.push(...options.paths);
136802
+ }
136803
+ if (options.excludePatterns?.length) {
136804
+ pathArgs.push(...options.excludePatterns.map((p) => `:(exclude)${p}`));
136805
+ }
136797
136806
  }
136798
- if (options.paths?.length) {
136799
- pathArgs.push("--", ...options.paths);
136807
+ let excludedFiles;
136808
+ if (options.excludePatterns?.length) {
136809
+ const checkCmd = buildGitCommand({
136810
+ command: "diff",
136811
+ args: [...baseFlags, "--name-only", "--", ...options.excludePatterns]
136812
+ });
136813
+ const checkResult = await execGit(checkCmd, context.workingDirectory, context.requestContext);
136814
+ const matched = checkResult.stdout.split(`
136815
+ `).filter((f3) => f3.trim());
136816
+ if (matched.length > 0) {
136817
+ excludedFiles = matched;
136818
+ }
136800
136819
  }
136801
136820
  if (options.stat) {
136802
- const statFlags2 = flags.filter((f3) => f3 !== "--name-only" && !f3.startsWith("--unified="));
136803
136821
  const statCmd2 = buildGitCommand({
136804
136822
  command: "diff",
136805
- args: [...statFlags2, "--stat", ...pathArgs]
136823
+ args: [...baseFlags, "--stat", ...pathArgs]
136806
136824
  });
136807
136825
  const statResult2 = await execGit(statCmd2, context.workingDirectory, context.requestContext);
136808
136826
  const stats2 = parseGitDiffStat(statResult2.stdout);
136809
- let untrackedStatOutput = "";
136827
+ let untrackedStatOutput2 = "";
136810
136828
  let untrackedFileCount2 = 0;
136811
136829
  if (options.includeUntracked) {
136812
- const untrackedFiles = await getUntrackedFiles(execGit, context);
136830
+ let untrackedFiles = await getUntrackedFiles(execGit, context);
136831
+ if (options.excludePatterns?.length) {
136832
+ ({ files: untrackedFiles, excludedFiles } = applyUntrackedExclusions(untrackedFiles, options.excludePatterns, excludedFiles));
136833
+ }
136813
136834
  for (const file2 of untrackedFiles) {
136814
136835
  const result = await execUntrackedDiff(execGit, context, file2, true);
136815
136836
  if (result) {
136816
- untrackedStatOutput += result;
136837
+ untrackedStatOutput2 += result;
136817
136838
  untrackedFileCount2++;
136818
136839
  }
136819
136840
  }
136820
136841
  }
136821
- const untrackedStats = untrackedStatOutput ? parseGitDiffStat(untrackedStatOutput) : { totalAdditions: 0, totalDeletions: 0 };
136842
+ const untrackedStats2 = untrackedStatOutput2 ? parseGitDiffStat(untrackedStatOutput2) : { totalAdditions: 0, totalDeletions: 0 };
136822
136843
  return {
136823
- diff: statResult2.stdout + untrackedStatOutput,
136844
+ diff: statResult2.stdout + untrackedStatOutput2,
136824
136845
  filesChanged: stats2.files.length + untrackedFileCount2,
136825
- insertions: stats2.totalAdditions + untrackedStats.totalAdditions,
136826
- deletions: stats2.totalDeletions + untrackedStats.totalDeletions,
136827
- binary: statResult2.stdout.includes("Binary files") || untrackedStatOutput.includes("Binary files")
136846
+ insertions: stats2.totalAdditions + untrackedStats2.totalAdditions,
136847
+ deletions: stats2.totalDeletions + untrackedStats2.totalDeletions,
136848
+ binary: statResult2.stdout.includes("Binary files") || untrackedStatOutput2.includes("Binary files"),
136849
+ ...excludedFiles && { excludedFiles }
136828
136850
  };
136829
136851
  }
136830
136852
  const args = [...flags, ...pathArgs];
@@ -136832,8 +136854,12 @@ async function executeDiff(options, context, execGit) {
136832
136854
  const diffResult = await execGit(diffCmd, context.workingDirectory, context.requestContext);
136833
136855
  let untrackedDiff = "";
136834
136856
  let untrackedFileCount = 0;
136857
+ let untrackedStatOutput = "";
136835
136858
  if (options.includeUntracked) {
136836
- const untrackedFiles = await getUntrackedFiles(execGit, context);
136859
+ let untrackedFiles = await getUntrackedFiles(execGit, context);
136860
+ if (options.excludePatterns?.length) {
136861
+ ({ files: untrackedFiles, excludedFiles } = applyUntrackedExclusions(untrackedFiles, options.excludePatterns, excludedFiles));
136862
+ }
136837
136863
  untrackedFileCount = untrackedFiles.length;
136838
136864
  for (const file2 of untrackedFiles) {
136839
136865
  if (options.nameOnly) {
@@ -136844,6 +136870,10 @@ async function executeDiff(options, context, execGit) {
136844
136870
  if (result) {
136845
136871
  untrackedDiff += result;
136846
136872
  }
136873
+ const statResult2 = await execUntrackedDiff(execGit, context, file2, true);
136874
+ if (statResult2) {
136875
+ untrackedStatOutput += statResult2;
136876
+ }
136847
136877
  }
136848
136878
  }
136849
136879
  }
@@ -136854,23 +136884,25 @@ async function executeDiff(options, context, execGit) {
136854
136884
  return {
136855
136885
  diff: combinedDiff,
136856
136886
  filesChanged: files.length,
136857
- binary: false
136887
+ binary: false,
136888
+ ...excludedFiles && { excludedFiles }
136858
136889
  };
136859
136890
  }
136860
- const statFlags = flags.filter((f3) => f3 !== "--name-only" && !f3.startsWith("--unified="));
136861
136891
  const statCmd = buildGitCommand({
136862
136892
  command: "diff",
136863
- args: [...statFlags, "--stat", ...pathArgs]
136893
+ args: [...baseFlags, "--stat", ...pathArgs]
136864
136894
  });
136865
136895
  const statResult = await execGit(statCmd, context.workingDirectory, context.requestContext);
136866
136896
  const stats = parseGitDiffStat(statResult.stdout);
136897
+ const untrackedStats = untrackedStatOutput ? parseGitDiffStat(untrackedStatOutput) : { totalAdditions: 0, totalDeletions: 0 };
136867
136898
  const hasBinary = combinedDiff.includes("Binary files");
136868
136899
  return {
136869
136900
  diff: combinedDiff,
136870
136901
  filesChanged: stats.files.length + untrackedFileCount,
136871
- insertions: stats.totalAdditions,
136872
- deletions: stats.totalDeletions,
136873
- binary: hasBinary
136902
+ insertions: stats.totalAdditions + untrackedStats.totalAdditions,
136903
+ deletions: stats.totalDeletions + untrackedStats.totalDeletions,
136904
+ binary: hasBinary,
136905
+ ...excludedFiles && { excludedFiles }
136874
136906
  };
136875
136907
  } catch (error48) {
136876
136908
  throw mapGitError(error48, "diff");
@@ -136903,6 +136935,18 @@ async function execUntrackedDiff(execGit, context, file2, stat2) {
136903
136935
  return null;
136904
136936
  }
136905
136937
  }
136938
+ function applyUntrackedExclusions(files, patterns, currentExcluded) {
136939
+ const patternSet = new Set(patterns);
136940
+ const matched = files.filter((f3) => patternSet.has(f3));
136941
+ if (matched.length === 0) {
136942
+ return { files, excludedFiles: currentExcluded };
136943
+ }
136944
+ const matchedSet = new Set(matched);
136945
+ return {
136946
+ files: files.filter((f3) => !matchedSet.has(f3)),
136947
+ excludedFiles: [...currentExcluded || [], ...matched]
136948
+ };
136949
+ }
136906
136950
  // src/services/git/providers/cli/operations/branches/branch.ts
136907
136951
  async function executeBranch(options, context, execGit) {
136908
136952
  try {
@@ -149713,6 +149757,25 @@ init_zod();
149713
149757
  var TOOL_NAME13 = "git_diff";
149714
149758
  var TOOL_TITLE13 = "Git Diff";
149715
149759
  var TOOL_DESCRIPTION13 = "View differences between commits, branches, or working tree. Shows changes in unified diff format.";
149760
+ var AUTO_EXCLUDE_PATTERNS = [
149761
+ "package-lock.json",
149762
+ "yarn.lock",
149763
+ "pnpm-lock.yaml",
149764
+ "bun.lock",
149765
+ "bun.lockb",
149766
+ "poetry.lock",
149767
+ "Pipfile.lock",
149768
+ "uv.lock",
149769
+ "composer.lock",
149770
+ "Gemfile.lock",
149771
+ "go.sum",
149772
+ "Cargo.lock",
149773
+ "flake.lock",
149774
+ "pubspec.lock",
149775
+ "mix.lock",
149776
+ "Podfile.lock",
149777
+ "packages.lock.json"
149778
+ ];
149716
149779
  var InputSchema13 = exports_external.object({
149717
149780
  path: PathSchema,
149718
149781
  target: CommitRefSchema.optional().describe("Target commit/branch to compare against. If not specified, shows unstaged changes in working tree."),
@@ -149722,14 +149785,16 @@ var InputSchema13 = exports_external.object({
149722
149785
  includeUntracked: exports_external.boolean().default(false).describe("Include untracked files in the diff. Useful for reviewing all upcoming changes."),
149723
149786
  nameOnly: exports_external.boolean().default(false).describe("Show only names of changed files, not the diff content."),
149724
149787
  stat: exports_external.boolean().default(false).describe("Show diffstat (summary of changes) instead of full diff content."),
149725
- contextLines: exports_external.number().int().min(0).max(100).default(3).describe("Number of context lines to show around changes.")
149788
+ contextLines: exports_external.number().int().min(0).max(100).default(3).describe("Number of context lines to show around changes."),
149789
+ autoExclude: exports_external.boolean().default(true).describe("Automatically exclude lock files and other generated files (e.g., package-lock.json, yarn.lock, bun.lock, poetry.lock, go.sum) from diff output to reduce context bloat. Set to false if you need to inspect these files.")
149726
149790
  });
149727
149791
  var OutputSchema14 = exports_external.object({
149728
149792
  success: exports_external.boolean().describe("Indicates if the operation was successful."),
149729
149793
  diff: exports_external.string().describe("The diff output in unified diff format."),
149730
149794
  filesChanged: exports_external.number().int().describe("Number of files with differences."),
149731
149795
  insertions: exports_external.number().int().optional().describe("Total number of line insertions."),
149732
- deletions: exports_external.number().int().optional().describe("Total number of line deletions.")
149796
+ deletions: exports_external.number().int().optional().describe("Total number of line deletions."),
149797
+ excludedFiles: exports_external.array(exports_external.string()).optional().describe("Files that were automatically excluded from the diff (e.g., lock files). Call again with autoExclude=false to include them.")
149733
149798
  });
149734
149799
  async function gitDiffLogic(input, { provider, targetPath, appContext }) {
149735
149800
  const result = await provider.diff({
@@ -149740,7 +149805,10 @@ async function gitDiffLogic(input, { provider, targetPath, appContext }) {
149740
149805
  includeUntracked: input.includeUntracked,
149741
149806
  nameOnly: input.nameOnly,
149742
149807
  stat: input.stat,
149743
- unified: input.contextLines
149808
+ unified: input.contextLines,
149809
+ ...input.autoExclude && {
149810
+ excludePatterns: [...AUTO_EXCLUDE_PATTERNS]
149811
+ }
149744
149812
  }, {
149745
149813
  workingDirectory: targetPath,
149746
149814
  requestContext: appContext,
@@ -149751,7 +149819,10 @@ async function gitDiffLogic(input, { provider, targetPath, appContext }) {
149751
149819
  diff: result.diff,
149752
149820
  filesChanged: result.filesChanged || 0,
149753
149821
  insertions: result.insertions,
149754
- deletions: result.deletions
149822
+ deletions: result.deletions,
149823
+ ...result.excludedFiles?.length && {
149824
+ excludedFiles: result.excludedFiles
149825
+ }
149755
149826
  };
149756
149827
  }
149757
149828
  function filterGitDiffOutput(result, level) {
@@ -149760,7 +149831,8 @@ function filterGitDiffOutput(result, level) {
149760
149831
  success: result.success,
149761
149832
  filesChanged: result.filesChanged,
149762
149833
  insertions: result.insertions,
149763
- deletions: result.deletions
149834
+ deletions: result.deletions,
149835
+ excludedFiles: result.excludedFiles
149764
149836
  };
149765
149837
  }
149766
149838
  return result;
@@ -150885,7 +150957,7 @@ var InputSchema27 = exports_external.object({
150885
150957
  mode: exports_external.enum(["list", "create", "delete"]).default("list").describe("The tag operation to perform."),
150886
150958
  tagName: TagNameSchema.optional().describe("Tag name for create/delete operations."),
150887
150959
  commit: CommitRefSchema.optional().describe("Commit to tag (default: HEAD for create operation)."),
150888
- message: exports_external.string().optional().describe("Tag message (creates annotated tag)."),
150960
+ message: exports_external.string().optional().describe("Tag message (creates annotated tag). For release tags, summarize the notable changes."),
150889
150961
  annotated: exports_external.boolean().default(false).describe("Create annotated tag. Automatically set to true when message is provided."),
150890
150962
  sign: SignSchema,
150891
150963
  forceUnsignedOnFailure: exports_external.boolean().default(false).describe("If GPG/SSH signing fails, retry the tag creation without signing instead of failing."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/git-mcp-server",
3
- "version": "2.10.0",
3
+ "version": "2.10.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",
@@ -72,7 +72,7 @@
72
72
  "rollup": "4.59.0"
73
73
  },
74
74
  "devDependencies": {
75
- "@cloudflare/workers-types": "^4.20260305.1",
75
+ "@cloudflare/workers-types": "^4.20260307.1",
76
76
  "@eslint/js": "^10.0.1",
77
77
  "@hono/mcp": "^0.2.4",
78
78
  "@hono/node-server": "1.19.11",
@@ -96,7 +96,7 @@
96
96
  "bun-types": "^1.3.10",
97
97
  "depcheck": "^1.4.7",
98
98
  "dotenv": "^17.3.1",
99
- "eslint": "^10.0.2",
99
+ "eslint": "^10.0.3",
100
100
  "execa": "^9.6.1",
101
101
  "globals": "^17.4.0",
102
102
  "hono": "4.12.5",