@cyanheads/git-mcp-server 2.12.0 → 2.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -35
- package/dist/index.js +114 -101
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
<div align="center">
|
|
9
9
|
|
|
10
|
-
[](./CHANGELOG.md) [](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-11-25/changelog.mdx) [](https://modelcontextprotocol.io/) [](./LICENSE) [](https://github.com/cyanheads/git-mcp-server/issues) [](https://www.typescriptlang.org/) [](https://bun.sh/)
|
|
11
11
|
|
|
12
12
|
</div>
|
|
13
13
|
|
|
@@ -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 |
|
|
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 | GPG/SSH signing (enabled by default) for commits, merges, rebases, cherry-picks, and tags. Silent fallback to unsigned on failure with `signed`/`signingWarning` fields in responses. |
|
|
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
|
|
115
|
-
| :----------------------------- |
|
|
116
|
-
| `MCP_TRANSPORT_TYPE` | Transport: `stdio` or `http`.
|
|
117
|
-
| `MCP_SESSION_MODE` | HTTP session mode: `stateless`, `stateful`, or `auto`.
|
|
118
|
-
| `MCP_RESPONSE_FORMAT` | Response format: `json` (LLM-optimized), `markdown` (human-readable), or `auto`.
|
|
119
|
-
| `MCP_RESPONSE_VERBOSITY` | Detail level: `minimal`, `standard`, or `full`.
|
|
120
|
-
| `MCP_HTTP_PORT` | HTTP server port.
|
|
121
|
-
| `MCP_HTTP_HOST` | HTTP server hostname.
|
|
122
|
-
| `MCP_HTTP_ENDPOINT_PATH` | MCP request endpoint path.
|
|
123
|
-
| `MCP_AUTH_MODE` | Authentication mode: `none`, `jwt`, or `oauth`.
|
|
124
|
-
| `STORAGE_PROVIDER_TYPE` | Storage backend: `in-memory`, `filesystem`, `supabase`, `cloudflare-kv`, `r2`.
|
|
125
|
-
| `OTEL_ENABLED` | Enable OpenTelemetry.
|
|
126
|
-
| `MCP_LOG_LEVEL` | Minimum log level: `debug`, `info`, `warn`, `error`.
|
|
127
|
-
| `GIT_SIGN_COMMITS` |
|
|
128
|
-
| `GIT_AUTHOR_NAME` | Git author name. Aliases: `GIT_USERNAME`, `GIT_USER`. Falls back to global git config.
|
|
129
|
-
| `GIT_AUTHOR_EMAIL` | Git author email. Aliases: `GIT_EMAIL`, `GIT_USER_EMAIL`. Falls back to global git config.
|
|
130
|
-
| `GIT_BASE_DIR` | Absolute path to restrict all git operations to a specific directory tree.
|
|
131
|
-
| `GIT_WRAPUP_INSTRUCTIONS_PATH` | Path to custom markdown file with workflow instructions.
|
|
132
|
-
| `MCP_AUTH_SECRET_KEY` | Required for `jwt` auth. 32+ character secret key.
|
|
133
|
-
| `OAUTH_ISSUER_URL` | Required for `oauth` auth. OIDC provider URL.
|
|
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` | GPG/SSH signing for commits, merges, rebases, cherry-picks, and tags. Falls back to unsigned on failure (see response `signed`/`signingWarning`). | `true` |
|
|
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
|
|
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.
|
|
15287
|
+
version: "2.13.0",
|
|
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",
|
|
@@ -15517,6 +15517,17 @@ var import_dotenv, packageManifest, emptyStringAsUndefined = (val) => {
|
|
|
15517
15517
|
return;
|
|
15518
15518
|
}
|
|
15519
15519
|
return val;
|
|
15520
|
+
}, parseBoolEnv = (defaultValue) => (val) => {
|
|
15521
|
+
if (typeof val === "boolean")
|
|
15522
|
+
return val;
|
|
15523
|
+
if (typeof val === "string") {
|
|
15524
|
+
const lower = val.trim().toLowerCase();
|
|
15525
|
+
if (["true", "1", "yes", "on"].includes(lower))
|
|
15526
|
+
return true;
|
|
15527
|
+
if (["false", "0", "no", "off", ""].includes(lower))
|
|
15528
|
+
return false;
|
|
15529
|
+
}
|
|
15530
|
+
return defaultValue;
|
|
15520
15531
|
}, expandTildePath = (path2) => {
|
|
15521
15532
|
if (typeof path2 !== "string" || path2.trim() === "") {
|
|
15522
15533
|
return;
|
|
@@ -15737,7 +15748,7 @@ var init_config = __esm(() => {
|
|
|
15737
15748
|
}),
|
|
15738
15749
|
git: exports_external.object({
|
|
15739
15750
|
provider: exports_external.preprocess(emptyStringAsUndefined, exports_external.enum(["auto", "cli", "isomorphic"]).default("auto")),
|
|
15740
|
-
signCommits: exports_external.
|
|
15751
|
+
signCommits: exports_external.preprocess(parseBoolEnv(true), exports_external.boolean()),
|
|
15741
15752
|
authorName: exports_external.string().regex(/^[^\n\r\0]*$/, "Git author name must not contain newlines or null bytes").optional(),
|
|
15742
15753
|
authorEmail: exports_external.string().email().optional(),
|
|
15743
15754
|
committerName: exports_external.string().regex(/^[^\n\r\0]*$/, "Git committer name must not contain newlines or null bytes").optional(),
|
|
@@ -133658,6 +133669,7 @@ async function listDirtyFiles(execGit, cwd, ctx) {
|
|
|
133658
133669
|
return files;
|
|
133659
133670
|
}
|
|
133660
133671
|
// src/services/git/providers/cli/operations/commits/commit.ts
|
|
133672
|
+
init_utils();
|
|
133661
133673
|
async function executeCommit(options, context, execGit) {
|
|
133662
133674
|
try {
|
|
133663
133675
|
if (options.filesToStage?.length) {
|
|
@@ -133677,8 +133689,10 @@ async function executeCommit(options, context, execGit) {
|
|
|
133677
133689
|
if (options.noVerify) {
|
|
133678
133690
|
args.push("--no-verify");
|
|
133679
133691
|
}
|
|
133680
|
-
const
|
|
133681
|
-
|
|
133692
|
+
const signRequested = shouldSignCommits();
|
|
133693
|
+
let signed = false;
|
|
133694
|
+
let signingWarning;
|
|
133695
|
+
if (signRequested) {
|
|
133682
133696
|
args.push("--gpg-sign");
|
|
133683
133697
|
}
|
|
133684
133698
|
if (options.author) {
|
|
@@ -133688,17 +133702,20 @@ async function executeCommit(options, context, execGit) {
|
|
|
133688
133702
|
const cmd = buildGitCommand({ command: "commit", args });
|
|
133689
133703
|
try {
|
|
133690
133704
|
await execGit(cmd, context.workingDirectory, context.requestContext);
|
|
133705
|
+
signed = signRequested;
|
|
133691
133706
|
} catch (error48) {
|
|
133692
|
-
if (
|
|
133693
|
-
const unsignedArgs = args.filter((a) => a !== "--gpg-sign");
|
|
133694
|
-
const unsignedCmd = buildGitCommand({
|
|
133695
|
-
command: "commit",
|
|
133696
|
-
args: unsignedArgs
|
|
133697
|
-
});
|
|
133698
|
-
await execGit(unsignedCmd, context.workingDirectory, context.requestContext);
|
|
133699
|
-
} else {
|
|
133707
|
+
if (!signRequested) {
|
|
133700
133708
|
throw error48;
|
|
133701
133709
|
}
|
|
133710
|
+
const errorMessage = error48 instanceof Error ? error48.message : String(error48);
|
|
133711
|
+
logger.warning("Commit signing failed; retrying unsigned. Set GIT_SIGN_COMMITS=false to suppress this attempt.", { ...context.requestContext, error: error48 });
|
|
133712
|
+
signingWarning = `GIT_SIGN_COMMITS is enabled but signing failed; commit created unsigned. Check signing key availability (gpg-agent running, SSH key accessible). Underlying error: ${errorMessage}`;
|
|
133713
|
+
const unsignedArgs = args.filter((a) => a !== "--gpg-sign");
|
|
133714
|
+
const unsignedCmd = buildGitCommand({
|
|
133715
|
+
command: "commit",
|
|
133716
|
+
args: unsignedArgs
|
|
133717
|
+
});
|
|
133718
|
+
await execGit(unsignedCmd, context.workingDirectory, context.requestContext);
|
|
133702
133719
|
}
|
|
133703
133720
|
const hashCmd = buildGitCommand({
|
|
133704
133721
|
command: "rev-parse",
|
|
@@ -133727,8 +133744,12 @@ async function executeCommit(options, context, execGit) {
|
|
|
133727
133744
|
message: options.message,
|
|
133728
133745
|
author: authorName,
|
|
133729
133746
|
timestamp,
|
|
133730
|
-
filesChanged
|
|
133747
|
+
filesChanged,
|
|
133748
|
+
signed
|
|
133731
133749
|
};
|
|
133750
|
+
if (signingWarning) {
|
|
133751
|
+
result.signingWarning = signingWarning;
|
|
133752
|
+
}
|
|
133732
133753
|
return result;
|
|
133733
133754
|
} catch (error48) {
|
|
133734
133755
|
throw mapGitError(error48, "commit");
|
|
@@ -134185,8 +134206,7 @@ async function executeMerge(options, context, execGit) {
|
|
|
134185
134206
|
if (options.message) {
|
|
134186
134207
|
args.push("-m", options.message);
|
|
134187
134208
|
}
|
|
134188
|
-
|
|
134189
|
-
if (shouldSign) {
|
|
134209
|
+
if (shouldSignCommits()) {
|
|
134190
134210
|
args.push("-S");
|
|
134191
134211
|
}
|
|
134192
134212
|
args.push(options.branch);
|
|
@@ -134283,8 +134303,7 @@ ${continueResult.stderr}`),
|
|
|
134283
134303
|
if (options.preserve) {
|
|
134284
134304
|
args.push("--preserve-merges");
|
|
134285
134305
|
}
|
|
134286
|
-
|
|
134287
|
-
if (shouldSign) {
|
|
134306
|
+
if (shouldSignCommits()) {
|
|
134288
134307
|
args.push("--gpg-sign");
|
|
134289
134308
|
}
|
|
134290
134309
|
if (options.onto) {
|
|
@@ -134366,8 +134385,7 @@ async function executeCherryPick(options, context, execGit) {
|
|
|
134366
134385
|
if (options.signoff) {
|
|
134367
134386
|
args.push("--signoff");
|
|
134368
134387
|
}
|
|
134369
|
-
|
|
134370
|
-
if (shouldSign) {
|
|
134388
|
+
if (shouldSignCommits()) {
|
|
134371
134389
|
args.push("--gpg-sign");
|
|
134372
134390
|
}
|
|
134373
134391
|
args.push(...options.commits);
|
|
@@ -134643,6 +134661,9 @@ async function executePull(options, context, execGit) {
|
|
|
134643
134661
|
if (options.fastForwardOnly) {
|
|
134644
134662
|
args.push("--ff-only");
|
|
134645
134663
|
}
|
|
134664
|
+
if (shouldSignCommits()) {
|
|
134665
|
+
args.push("-S");
|
|
134666
|
+
}
|
|
134646
134667
|
const cmd = buildGitCommand({ command: "pull", args });
|
|
134647
134668
|
const result = await execGit(cmd, context.workingDirectory, context.requestContext, { allowNonZeroExit: true });
|
|
134648
134669
|
const hasConflicts = result.stdout.includes("CONFLICT") || result.stderr.includes("CONFLICT");
|
|
@@ -134697,6 +134718,7 @@ function parseFilesChanged(stdout) {
|
|
|
134697
134718
|
return files;
|
|
134698
134719
|
}
|
|
134699
134720
|
// src/services/git/providers/cli/operations/tags/tag.ts
|
|
134721
|
+
init_utils();
|
|
134700
134722
|
async function executeTag(options, context, execGit) {
|
|
134701
134723
|
try {
|
|
134702
134724
|
const args = [];
|
|
@@ -134738,7 +134760,9 @@ async function executeTag(options, context, execGit) {
|
|
|
134738
134760
|
if (!options.tagName) {
|
|
134739
134761
|
throw new Error("Tag name is required for create operation");
|
|
134740
134762
|
}
|
|
134741
|
-
const
|
|
134763
|
+
const signRequested = shouldSignCommits();
|
|
134764
|
+
let signed = false;
|
|
134765
|
+
let signingWarning;
|
|
134742
134766
|
const buildCreateArgs = (sign) => {
|
|
134743
134767
|
const createArgs = [options.tagName];
|
|
134744
134768
|
if (sign) {
|
|
@@ -134757,31 +134781,36 @@ async function executeTag(options, context, execGit) {
|
|
|
134757
134781
|
}
|
|
134758
134782
|
return createArgs;
|
|
134759
134783
|
};
|
|
134760
|
-
const
|
|
134761
|
-
|
|
134762
|
-
|
|
134763
|
-
|
|
134764
|
-
|
|
134765
|
-
|
|
134766
|
-
|
|
134767
|
-
|
|
134784
|
+
const buildCmd = (sign) => {
|
|
134785
|
+
const configOverride = sign ? [] : ["-c", "tag.gpgSign=false"];
|
|
134786
|
+
return [
|
|
134787
|
+
...configOverride,
|
|
134788
|
+
...buildGitCommand({
|
|
134789
|
+
command: "tag",
|
|
134790
|
+
args: buildCreateArgs(sign)
|
|
134791
|
+
})
|
|
134792
|
+
];
|
|
134793
|
+
};
|
|
134768
134794
|
try {
|
|
134769
|
-
await execGit(
|
|
134795
|
+
await execGit(buildCmd(signRequested), context.workingDirectory, context.requestContext);
|
|
134796
|
+
signed = signRequested;
|
|
134770
134797
|
} catch (error48) {
|
|
134771
|
-
if (
|
|
134772
|
-
const unsignedCmd = buildGitCommand({
|
|
134773
|
-
command: "tag",
|
|
134774
|
-
args: buildCreateArgs(false)
|
|
134775
|
-
});
|
|
134776
|
-
await execGit(unsignedCmd, context.workingDirectory, context.requestContext);
|
|
134777
|
-
} else {
|
|
134798
|
+
if (!signRequested) {
|
|
134778
134799
|
throw error48;
|
|
134779
134800
|
}
|
|
134801
|
+
const errorMessage = error48 instanceof Error ? error48.message : String(error48);
|
|
134802
|
+
logger.warning("Tag signing failed; retrying unsigned. Set GIT_SIGN_COMMITS=false to suppress this attempt.", { ...context.requestContext, error: error48 });
|
|
134803
|
+
signingWarning = `GIT_SIGN_COMMITS is enabled but signing failed; tag created unsigned. Check signing key availability (gpg-agent running, SSH key accessible). Underlying error: ${errorMessage}`;
|
|
134804
|
+
await execGit(buildCmd(false), context.workingDirectory, context.requestContext);
|
|
134780
134805
|
}
|
|
134781
134806
|
const createResult = {
|
|
134782
134807
|
mode: "create",
|
|
134783
|
-
created: options.tagName
|
|
134808
|
+
created: options.tagName,
|
|
134809
|
+
signed
|
|
134784
134810
|
};
|
|
134811
|
+
if (signingWarning) {
|
|
134812
|
+
createResult.signingWarning = signingWarning;
|
|
134813
|
+
}
|
|
134785
134814
|
return createResult;
|
|
134786
134815
|
}
|
|
134787
134816
|
case "delete": {
|
|
@@ -145251,12 +145280,10 @@ var import_tsyringe7 = __toESM(require_cjs2(), 1);
|
|
|
145251
145280
|
// src/mcp-server/prompts/definitions/git-wrapup.prompt.ts
|
|
145252
145281
|
init_zod();
|
|
145253
145282
|
var PROMPT_NAME = "git_wrapup";
|
|
145254
|
-
var PROMPT_DESCRIPTION = "
|
|
145283
|
+
var PROMPT_DESCRIPTION = "Orchestrates a full git wrap-up: loads the project-aware acceptance-criteria protocol from git_wrapup_instructions, analyzes changes, satisfies each criterion per project convention, commits atomically, and (optionally) tags the release.";
|
|
145255
145284
|
var ArgumentsSchema = exports_external.object({
|
|
145256
|
-
changelogPath: exports_external.string().optional().describe("Path to the changelog file
|
|
145257
|
-
|
|
145258
|
-
createTag: exports_external.string().optional().describe("Whether to create a git tag after committing ('true' | 'false'). Defaults to 'false'."),
|
|
145259
|
-
updateAgentFiles: exports_external.string().optional().describe("Whether to update agent meta files like CLAUDE.md, AGENTS.md ('true' | 'false'). Defaults to 'false'.")
|
|
145285
|
+
changelogPath: exports_external.string().optional().describe("Path to the changelog file when the project uses a flat one (defaults to CHANGELOG.md). The protocol itself defers to project convention."),
|
|
145286
|
+
createTag: exports_external.string().optional().describe("Whether to include the tag criterion in the protocol ('true' | 'false'). Defaults to 'true' — set to 'false' when tagging is deferred to a separate release step.")
|
|
145260
145287
|
});
|
|
145261
145288
|
var gitWrapupPrompt = {
|
|
145262
145289
|
name: PROMPT_NAME,
|
|
@@ -145264,57 +145291,34 @@ var gitWrapupPrompt = {
|
|
|
145264
145291
|
argumentsSchema: ArgumentsSchema,
|
|
145265
145292
|
generate: (args) => {
|
|
145266
145293
|
const changelogPath = args.changelogPath || "CHANGELOG.md";
|
|
145267
|
-
const
|
|
145268
|
-
const createTag = args.createTag === "true";
|
|
145269
|
-
const updateAgentFiles = args.updateAgentFiles === "true";
|
|
145270
|
-
const documentationSection = skipDocumentation ? "" : `
|
|
145271
|
-
4. **Review Documentation**: Read the README.md file and verify it accurately reflects the current codebase state. Update as necessary to maintain currency and accuracy.
|
|
145272
|
-
`;
|
|
145273
|
-
const agentFilesSection = updateAgentFiles ? `
|
|
145274
|
-
5. **Update Agent Files**: If present, review and update agent-specific meta files (CLAUDE.md, AGENTS.md, .clinerules/) to reflect any architectural or protocol changes.
|
|
145275
|
-
` : "";
|
|
145276
|
-
const tagSection = createTag ? `
|
|
145277
|
-
After all commits are complete and verified via git_status, create an annotated git tag using the git_tag tool. Use semantic versioning (e.g., v1.2.3) and include a summary of key changes in the annotation message.
|
|
145278
|
-
` : "";
|
|
145294
|
+
const includeTag = args.createTag !== "false";
|
|
145279
145295
|
return [
|
|
145280
145296
|
{
|
|
145281
145297
|
role: "user",
|
|
145282
145298
|
content: {
|
|
145283
145299
|
type: "text",
|
|
145284
|
-
text: `You are an expert git workflow manager.
|
|
145300
|
+
text: `You are an expert git workflow manager. Run a complete wrap-up for the current git session.
|
|
145285
145301
|
|
|
145286
|
-
##
|
|
145302
|
+
## Session Flow
|
|
145287
145303
|
|
|
145288
|
-
|
|
145304
|
+
1. **Load Protocol**: Call \`git_wrapup_instructions\` with \`acknowledgement: "yes"\`${includeTag ? "" : " and `createTag: false`"}. It returns an acceptance-criteria checklist — every box must be satisfied before the wrap-up is complete — plus the current repository status.
|
|
145289
145305
|
|
|
145290
|
-
|
|
145306
|
+
2. **Set Working Directory**: If not already set, call \`git_set_working_dir\` to establish the session context. Required before any git operations.
|
|
145291
145307
|
|
|
145292
|
-
|
|
145308
|
+
3. **Analyze Changes**: Run \`git_diff\` with \`includeUntracked: true\`. Understand the "why" behind every modification end-to-end before grouping anything — the diff drives your commit plan and messages.
|
|
145293
145309
|
|
|
145294
|
-
|
|
145310
|
+
4. **Satisfy the Acceptance Criteria**: Work each checkbox from step 1. The protocol is strict on outcomes, generic on mechanism — follow this project's own conventions for where versions live, how the changelog is formatted (default path when flat: \`${changelogPath}\`), and what the verification suite looks like. If the root agent-instruction file (\`AGENTS.md\`, \`CLAUDE.md\`, or equivalent) documents a project-specific wrap-up procedure, that takes precedence over the generic checklist.
|
|
145295
145311
|
|
|
145296
|
-
|
|
145297
|
-
- Uses past tense and concise language
|
|
145298
|
-
- Categorizes changes (Added, Changed, Fixed, Deprecated, Removed, Security)
|
|
145299
|
-
- Follows the existing changelog format
|
|
145300
|
-
- Provides enough detail for users to understand the impact
|
|
145301
|
-
${documentationSection}${agentFilesSection}
|
|
145302
|
-
5. **Commit Changes**: Use \`git_commit\` to create atomic, logical commits. For each commit:
|
|
145303
|
-
- Group related changes together using the \`filesToStage\` parameter
|
|
145304
|
-
- Write commit messages following Conventional Commits format (e.g., \`feat(auth): add password reset\`, \`fix(parser): handle edge case\`)
|
|
145305
|
-
- Ensure commits are self-contained and buildable
|
|
145306
|
-
- Do not mix unrelated changes in a single commit
|
|
145312
|
+
5. **Commit Atomically**: Use \`git_commit\` to create logical, self-contained commits in Conventional Commits form. Group related changes with the \`filesToStage\` parameter. No mixing unrelated changes in one commit.
|
|
145307
145313
|
|
|
145308
|
-
6. **Verify
|
|
145309
|
-
${tagSection}
|
|
145310
|
-
## Important Guidelines
|
|
145314
|
+
6. **Verify Clean**: Run \`git_status\` to confirm the working tree is clean and every change is committed.${includeTag ? "\n\n7. **Tag the Release**: Create an annotated git tag with `git_tag` using semantic versioning (e.g., `v1.2.3`). The annotation message should summarize the real changes — no filler." : ""}
|
|
145311
145315
|
|
|
145312
|
-
|
|
145313
|
-
|
|
145314
|
-
-
|
|
145315
|
-
-
|
|
145316
|
-
-
|
|
145317
|
-
-
|
|
145316
|
+
## Constraints
|
|
145317
|
+
|
|
145318
|
+
- **Do not push** to the remote unless explicitly instructed.
|
|
145319
|
+
- Create a task list before starting so progress is trackable.
|
|
145320
|
+
- Do not bypass verification failures to land a green commit.
|
|
145321
|
+
- On merge conflicts or unexpected errors: stop and surface the blocker.
|
|
145318
145322
|
|
|
145319
145323
|
Begin by calling \`git_wrapup_instructions\` and creating your task list.`
|
|
145320
145324
|
}
|
|
@@ -145403,7 +145407,6 @@ var AllSchema = exports_external.boolean().default(false).describe("Include all
|
|
|
145403
145407
|
var MergeStrategySchema = exports_external.enum(["ort", "recursive", "octopus", "ours", "subtree"]).optional().describe("Merge strategy to use (ort, recursive, octopus, ours, subtree).");
|
|
145404
145408
|
var PruneSchema = exports_external.boolean().default(false).describe("Prune remote-tracking references that no longer exist on remote.");
|
|
145405
145409
|
var DepthSchema = exports_external.number().int().min(1).optional().describe("Create a shallow clone with history truncated to N commits.");
|
|
145406
|
-
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.");
|
|
145407
145410
|
var NoVerifySchema = exports_external.boolean().default(false).describe("Bypass pre-commit and commit-msg hooks.");
|
|
145408
145411
|
|
|
145409
145412
|
// src/mcp-server/tools/utils/json-response-formatter.ts
|
|
@@ -146841,10 +146844,8 @@ var InputSchema12 = exports_external.object({
|
|
|
146841
146844
|
}).optional().describe("Override commit author (defaults to git config)."),
|
|
146842
146845
|
amend: exports_external.boolean().default(false).describe("Amend the previous commit instead of creating a new one. Use with caution."),
|
|
146843
146846
|
allowEmpty: exports_external.boolean().default(false).describe("Allow creating a commit with no changes."),
|
|
146844
|
-
sign: SignSchema,
|
|
146845
146847
|
noVerify: NoVerifySchema,
|
|
146846
|
-
filesToStage: exports_external.array(exports_external.string()).optional().describe("File paths to stage before committing (atomic stage+commit operation).")
|
|
146847
|
-
forceUnsignedOnFailure: exports_external.boolean().default(false).describe("If GPG/SSH signing fails, retry the commit without signing instead of failing.")
|
|
146848
|
+
filesToStage: exports_external.array(exports_external.string()).optional().describe("File paths to stage before committing (atomic stage+commit operation).")
|
|
146848
146849
|
});
|
|
146849
146850
|
var OutputSchema13 = exports_external.object({
|
|
146850
146851
|
success: exports_external.boolean().describe("Indicates if the operation was successful."),
|
|
@@ -146856,6 +146857,8 @@ var OutputSchema13 = exports_external.object({
|
|
|
146856
146857
|
committedFiles: exports_external.array(exports_external.string()).describe("List of files that were committed."),
|
|
146857
146858
|
insertions: exports_external.number().int().optional().describe("Number of line insertions."),
|
|
146858
146859
|
deletions: exports_external.number().int().optional().describe("Number of line deletions."),
|
|
146860
|
+
signed: exports_external.boolean().describe("Whether the commit was signed. False when GIT_SIGN_COMMITS=false or when signing was attempted and fell back to unsigned on failure."),
|
|
146861
|
+
signingWarning: exports_external.string().optional().describe("Populated only when signing was requested but failed, and the commit was created unsigned as a fallback."),
|
|
146859
146862
|
status: exports_external.object({
|
|
146860
146863
|
current_branch: exports_external.string().nullable().describe("Current branch name after commit."),
|
|
146861
146864
|
staged_changes: exports_external.record(exports_external.string(), exports_external.any()).describe("Remaining staged changes after commit."),
|
|
@@ -146877,15 +146880,11 @@ async function gitCommitLogic(input, { provider, targetPath, appContext }) {
|
|
|
146877
146880
|
message: input.message,
|
|
146878
146881
|
amend: input.amend,
|
|
146879
146882
|
allowEmpty: input.allowEmpty,
|
|
146880
|
-
noVerify: input.noVerify
|
|
146881
|
-
forceUnsignedOnFailure: input.forceUnsignedOnFailure
|
|
146883
|
+
noVerify: input.noVerify
|
|
146882
146884
|
};
|
|
146883
146885
|
if (input.author !== undefined) {
|
|
146884
146886
|
commitOptions.author = input.author;
|
|
146885
146887
|
}
|
|
146886
|
-
if (input.sign !== undefined) {
|
|
146887
|
-
commitOptions.sign = input.sign;
|
|
146888
|
-
}
|
|
146889
146888
|
const result = await provider.commit(commitOptions, {
|
|
146890
146889
|
workingDirectory: targetPath,
|
|
146891
146890
|
requestContext: appContext,
|
|
@@ -146896,7 +146895,7 @@ async function gitCommitLogic(input, { provider, targetPath, appContext }) {
|
|
|
146896
146895
|
requestContext: appContext,
|
|
146897
146896
|
tenantId: appContext.tenantId || "default-tenant"
|
|
146898
146897
|
});
|
|
146899
|
-
|
|
146898
|
+
const output = {
|
|
146900
146899
|
success: result.success,
|
|
146901
146900
|
commitHash: result.commitHash,
|
|
146902
146901
|
message: result.message,
|
|
@@ -146904,6 +146903,7 @@ async function gitCommitLogic(input, { provider, targetPath, appContext }) {
|
|
|
146904
146903
|
timestamp: result.timestamp,
|
|
146905
146904
|
filesChanged: result.filesChanged.length,
|
|
146906
146905
|
committedFiles: result.filesChanged,
|
|
146906
|
+
signed: result.signed,
|
|
146907
146907
|
status: {
|
|
146908
146908
|
current_branch: statusResult.currentBranch,
|
|
146909
146909
|
staged_changes: flattenChanges(statusResult.stagedChanges),
|
|
@@ -146913,6 +146913,10 @@ async function gitCommitLogic(input, { provider, targetPath, appContext }) {
|
|
|
146913
146913
|
is_clean: statusResult.isClean
|
|
146914
146914
|
}
|
|
146915
146915
|
};
|
|
146916
|
+
if (result.signingWarning) {
|
|
146917
|
+
output.signingWarning = result.signingWarning;
|
|
146918
|
+
}
|
|
146919
|
+
return output;
|
|
146916
146920
|
}
|
|
146917
146921
|
function filterGitCommitOutput(result, level) {
|
|
146918
146922
|
if (level === "minimal") {
|
|
@@ -146920,6 +146924,8 @@ function filterGitCommitOutput(result, level) {
|
|
|
146920
146924
|
success: result.success,
|
|
146921
146925
|
commitHash: result.commitHash,
|
|
146922
146926
|
message: result.message,
|
|
146927
|
+
signed: result.signed,
|
|
146928
|
+
...result.signingWarning && { signingWarning: result.signingWarning },
|
|
146923
146929
|
status: {
|
|
146924
146930
|
current_branch: result.status.current_branch,
|
|
146925
146931
|
is_clean: result.status.is_clean,
|
|
@@ -146941,6 +146947,8 @@ function filterGitCommitOutput(result, level) {
|
|
|
146941
146947
|
insertions: result.insertions,
|
|
146942
146948
|
deletions: result.deletions,
|
|
146943
146949
|
committedFiles: result.committedFiles,
|
|
146950
|
+
signed: result.signed,
|
|
146951
|
+
...result.signingWarning && { signingWarning: result.signingWarning },
|
|
146944
146952
|
status: {
|
|
146945
146953
|
current_branch: result.status.current_branch,
|
|
146946
146954
|
is_clean: result.status.is_clean,
|
|
@@ -148189,9 +148197,7 @@ var InputSchema27 = exports_external.object({
|
|
|
148189
148197
|
tagName: TagNameSchema.optional().describe("Tag name for create/delete operations."),
|
|
148190
148198
|
commit: CommitRefSchema.optional().describe("Commit to tag (default: HEAD for create operation)."),
|
|
148191
148199
|
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."),
|
|
148192
|
-
annotated: exports_external.boolean().default(false).describe('Create an annotated tag with a default "Tag <name>" message. Only effective when
|
|
148193
|
-
sign: SignSchema,
|
|
148194
|
-
forceUnsignedOnFailure: exports_external.boolean().default(false).describe("If GPG/SSH signing fails, retry the tag creation without signing instead of failing."),
|
|
148200
|
+
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.'),
|
|
148195
148201
|
force: ForceSchema.describe("Overwrite an existing tag (create mode only; has no effect on list or delete).")
|
|
148196
148202
|
});
|
|
148197
148203
|
var TagInfoSchema = exports_external.object({
|
|
@@ -148206,7 +148212,9 @@ var OutputSchema28 = exports_external.object({
|
|
|
148206
148212
|
mode: exports_external.string().describe("Operation mode that was performed."),
|
|
148207
148213
|
tags: exports_external.array(TagInfoSchema).optional().describe("List of tags (for list mode)."),
|
|
148208
148214
|
created: exports_external.string().optional().describe("Created tag name (for create mode)."),
|
|
148209
|
-
deleted: exports_external.string().optional().describe("Deleted tag name (for delete mode).")
|
|
148215
|
+
deleted: exports_external.string().optional().describe("Deleted tag name (for delete mode)."),
|
|
148216
|
+
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."),
|
|
148217
|
+
signingWarning: exports_external.string().optional().describe("Populated only when signing was requested but failed, and the tag was created unsigned as a fallback.")
|
|
148210
148218
|
});
|
|
148211
148219
|
async function gitTagLogic(input, { provider, targetPath, appContext }) {
|
|
148212
148220
|
if ((input.mode === "create" || input.mode === "delete") && !input.tagName) {
|
|
@@ -148215,8 +148223,7 @@ async function gitTagLogic(input, { provider, targetPath, appContext }) {
|
|
|
148215
148223
|
const tagOptions = {
|
|
148216
148224
|
mode: input.mode,
|
|
148217
148225
|
annotated: input.annotated,
|
|
148218
|
-
force: input.force
|
|
148219
|
-
forceUnsignedOnFailure: input.forceUnsignedOnFailure
|
|
148226
|
+
force: input.force
|
|
148220
148227
|
};
|
|
148221
148228
|
if (input.tagName !== undefined) {
|
|
148222
148229
|
tagOptions.tagName = input.tagName;
|
|
@@ -148227,27 +148234,33 @@ async function gitTagLogic(input, { provider, targetPath, appContext }) {
|
|
|
148227
148234
|
if (input.message !== undefined) {
|
|
148228
148235
|
tagOptions.message = normalizeMessage(input.message);
|
|
148229
148236
|
}
|
|
148230
|
-
if (input.sign !== undefined) {
|
|
148231
|
-
tagOptions.sign = input.sign;
|
|
148232
|
-
}
|
|
148233
148237
|
const result = await provider.tag(tagOptions, {
|
|
148234
148238
|
workingDirectory: targetPath,
|
|
148235
148239
|
requestContext: appContext,
|
|
148236
148240
|
tenantId: appContext.tenantId || "default-tenant"
|
|
148237
148241
|
});
|
|
148238
|
-
|
|
148242
|
+
const output = {
|
|
148239
148243
|
success: true,
|
|
148240
148244
|
mode: result.mode,
|
|
148241
148245
|
tags: result.tags,
|
|
148242
148246
|
created: result.created,
|
|
148243
148247
|
deleted: result.deleted
|
|
148244
148248
|
};
|
|
148249
|
+
if (result.signed !== undefined) {
|
|
148250
|
+
output.signed = result.signed;
|
|
148251
|
+
}
|
|
148252
|
+
if (result.signingWarning) {
|
|
148253
|
+
output.signingWarning = result.signingWarning;
|
|
148254
|
+
}
|
|
148255
|
+
return output;
|
|
148245
148256
|
}
|
|
148246
148257
|
function filterGitTagOutput(result, level) {
|
|
148247
148258
|
if (level === "minimal") {
|
|
148248
148259
|
return {
|
|
148249
148260
|
success: result.success,
|
|
148250
|
-
mode: result.mode
|
|
148261
|
+
mode: result.mode,
|
|
148262
|
+
...result.signed !== undefined && { signed: result.signed },
|
|
148263
|
+
...result.signingWarning && { signingWarning: result.signingWarning }
|
|
148251
148264
|
};
|
|
148252
148265
|
}
|
|
148253
148266
|
return result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/git-mcp-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.13.0",
|
|
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",
|