@cyanheads/git-mcp-server 2.8.0 → 2.8.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.
- package/README.md +1 -1
- package/dist/index.js +109 -23
- 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
|
|
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.
|
|
15338
|
+
version: "2.8.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",
|
|
@@ -15855,9 +15855,9 @@ var init_config = __esm(() => {
|
|
|
15855
15855
|
git: exports_external.object({
|
|
15856
15856
|
provider: exports_external.preprocess(emptyStringAsUndefined, exports_external.enum(["auto", "cli", "isomorphic"]).default("auto")),
|
|
15857
15857
|
signCommits: exports_external.coerce.boolean().default(false),
|
|
15858
|
-
authorName: exports_external.string().optional(),
|
|
15858
|
+
authorName: exports_external.string().regex(/^[^\n\r\0]*$/, "Git author name must not contain newlines or null bytes").optional(),
|
|
15859
15859
|
authorEmail: exports_external.string().email().optional(),
|
|
15860
|
-
committerName: exports_external.string().optional(),
|
|
15860
|
+
committerName: exports_external.string().regex(/^[^\n\r\0]*$/, "Git committer name must not contain newlines or null bytes").optional(),
|
|
15861
15861
|
committerEmail: exports_external.string().email().optional(),
|
|
15862
15862
|
wrapupInstructionsPath: exports_external.preprocess(expandTildePath, exports_external.string().optional()),
|
|
15863
15863
|
baseDir: exports_external.preprocess((val) => expandTildePath(emptyStringAsUndefined(val)), exports_external.string().refine((p) => !p || isAbsolutePath(p), {
|
|
@@ -176201,7 +176201,20 @@ async function executeCommit(options, context, execGit) {
|
|
|
176201
176201
|
args.push(`--author=${authorStr}`);
|
|
176202
176202
|
}
|
|
176203
176203
|
const cmd = buildGitCommand({ command: "commit", args });
|
|
176204
|
-
|
|
176204
|
+
try {
|
|
176205
|
+
await execGit(cmd, context.workingDirectory, context.requestContext);
|
|
176206
|
+
} catch (error48) {
|
|
176207
|
+
if (shouldSign && options.forceUnsignedOnFailure) {
|
|
176208
|
+
const unsignedArgs = args.filter((a) => a !== "--gpg-sign");
|
|
176209
|
+
const unsignedCmd = buildGitCommand({
|
|
176210
|
+
command: "commit",
|
|
176211
|
+
args: unsignedArgs
|
|
176212
|
+
});
|
|
176213
|
+
await execGit(unsignedCmd, context.workingDirectory, context.requestContext);
|
|
176214
|
+
} else {
|
|
176215
|
+
throw error48;
|
|
176216
|
+
}
|
|
176217
|
+
}
|
|
176205
176218
|
const hashCmd = buildGitCommand({
|
|
176206
176219
|
command: "rev-parse",
|
|
176207
176220
|
args: ["HEAD"]
|
|
@@ -177015,22 +177028,40 @@ async function executeTag(options, context, execGit) {
|
|
|
177015
177028
|
if (!options.tagName) {
|
|
177016
177029
|
throw new Error("Tag name is required for create operation");
|
|
177017
177030
|
}
|
|
177018
|
-
args.push(options.tagName);
|
|
177019
177031
|
const shouldSign = options.sign ?? shouldSignCommits();
|
|
177020
|
-
|
|
177021
|
-
const
|
|
177022
|
-
|
|
177023
|
-
|
|
177024
|
-
|
|
177025
|
-
|
|
177026
|
-
|
|
177027
|
-
|
|
177028
|
-
|
|
177029
|
-
|
|
177030
|
-
|
|
177032
|
+
const buildCreateArgs = (sign) => {
|
|
177033
|
+
const createArgs = [options.tagName];
|
|
177034
|
+
if (sign) {
|
|
177035
|
+
const message = options.message || `Tag ${options.tagName}`;
|
|
177036
|
+
createArgs.push("-s", "-m", message);
|
|
177037
|
+
} else if (options.message && options.annotated) {
|
|
177038
|
+
createArgs.push("-a", "-m", options.message);
|
|
177039
|
+
}
|
|
177040
|
+
if (options.commit) {
|
|
177041
|
+
createArgs.push(options.commit);
|
|
177042
|
+
}
|
|
177043
|
+
if (options.force) {
|
|
177044
|
+
createArgs.push("--force");
|
|
177045
|
+
}
|
|
177046
|
+
return createArgs;
|
|
177047
|
+
};
|
|
177048
|
+
const createCmd = buildGitCommand({
|
|
177049
|
+
command: "tag",
|
|
177050
|
+
args: buildCreateArgs(shouldSign)
|
|
177051
|
+
});
|
|
177052
|
+
try {
|
|
177053
|
+
await execGit(createCmd, context.workingDirectory, context.requestContext);
|
|
177054
|
+
} catch (error48) {
|
|
177055
|
+
if (shouldSign && options.forceUnsignedOnFailure) {
|
|
177056
|
+
const unsignedCmd = buildGitCommand({
|
|
177057
|
+
command: "tag",
|
|
177058
|
+
args: buildCreateArgs(false)
|
|
177059
|
+
});
|
|
177060
|
+
await execGit(unsignedCmd, context.workingDirectory, context.requestContext);
|
|
177061
|
+
} else {
|
|
177062
|
+
throw error48;
|
|
177063
|
+
}
|
|
177031
177064
|
}
|
|
177032
|
-
const cmd = buildGitCommand({ command: "tag", args });
|
|
177033
|
-
await execGit(cmd, context.workingDirectory, context.requestContext);
|
|
177034
177065
|
const createResult = {
|
|
177035
177066
|
mode: "create",
|
|
177036
177067
|
created: options.tagName
|
|
@@ -194026,6 +194057,25 @@ async function resolveWorkingDirectory(pathInput, appContext, storage) {
|
|
|
194026
194057
|
});
|
|
194027
194058
|
return sanitizedPath;
|
|
194028
194059
|
}
|
|
194060
|
+
var DEFAULT_PROTECTION = {
|
|
194061
|
+
protectedBranches: ["main", "master", "production", "prod", "develop", "dev"],
|
|
194062
|
+
enforce: true
|
|
194063
|
+
};
|
|
194064
|
+
function isProtectedBranch(branchName, config3 = DEFAULT_PROTECTION) {
|
|
194065
|
+
return config3.protectedBranches.includes(branchName.toLowerCase());
|
|
194066
|
+
}
|
|
194067
|
+
function validateProtectedBranchOperation(branchName, operation, confirmed, config3 = DEFAULT_PROTECTION) {
|
|
194068
|
+
if (!config3.enforce) {
|
|
194069
|
+
return;
|
|
194070
|
+
}
|
|
194071
|
+
if (isProtectedBranch(branchName, config3) && !confirmed) {
|
|
194072
|
+
throw new McpError(-32007 /* ValidationError */, `Cannot perform '${operation}' on protected branch '${branchName}' without explicit confirmation.`, {
|
|
194073
|
+
branch: branchName,
|
|
194074
|
+
operation,
|
|
194075
|
+
hint: "Set the confirmation parameter to true to proceed."
|
|
194076
|
+
});
|
|
194077
|
+
}
|
|
194078
|
+
}
|
|
194029
194079
|
|
|
194030
194080
|
// src/mcp-server/tools/utils/toolHandlerFactory.ts
|
|
194031
194081
|
function validateSdkContext(ctx) {
|
|
@@ -196212,7 +196262,8 @@ var InputSchema23 = exports_external.object({
|
|
|
196212
196262
|
tags: exports_external.boolean().default(false).describe("Push all tags to the remote."),
|
|
196213
196263
|
dryRun: DryRunSchema,
|
|
196214
196264
|
delete: exports_external.boolean().default(false).describe("Delete the specified remote branch."),
|
|
196215
|
-
remoteBranch: BranchNameSchema.optional().describe("Remote branch name to push to (if different from local branch name).")
|
|
196265
|
+
remoteBranch: BranchNameSchema.optional().describe("Remote branch name to push to (if different from local branch name)."),
|
|
196266
|
+
confirmed: exports_external.boolean().default(false).describe("Explicit confirmation required for force push or branch deletion on protected branches (main, master, production, etc.).")
|
|
196216
196267
|
});
|
|
196217
196268
|
var OutputSchema24 = exports_external.object({
|
|
196218
196269
|
success: exports_external.boolean().describe("Indicates if the operation was successful."),
|
|
@@ -196223,6 +196274,21 @@ var OutputSchema24 = exports_external.object({
|
|
|
196223
196274
|
rejectedRefs: exports_external.array(exports_external.string()).describe("References that were rejected by the remote.")
|
|
196224
196275
|
});
|
|
196225
196276
|
async function gitPushLogic(input, { provider, targetPath, appContext }) {
|
|
196277
|
+
if (input.force || input.delete) {
|
|
196278
|
+
let branchToCheck = input.branch;
|
|
196279
|
+
if (!branchToCheck) {
|
|
196280
|
+
const status = await provider.status({ includeUntracked: false }, {
|
|
196281
|
+
workingDirectory: targetPath,
|
|
196282
|
+
requestContext: appContext,
|
|
196283
|
+
tenantId: appContext.tenantId || "default-tenant"
|
|
196284
|
+
});
|
|
196285
|
+
branchToCheck = status?.currentBranch ?? undefined;
|
|
196286
|
+
}
|
|
196287
|
+
if (branchToCheck) {
|
|
196288
|
+
const operation = input.force ? "force push" : "branch deletion";
|
|
196289
|
+
validateProtectedBranchOperation(branchToCheck, operation, input.confirmed);
|
|
196290
|
+
}
|
|
196291
|
+
}
|
|
196226
196292
|
const pushOptions = {};
|
|
196227
196293
|
if (input.remote !== undefined) {
|
|
196228
196294
|
pushOptions.remote = input.remote;
|
|
@@ -196383,7 +196449,8 @@ var InputSchema25 = exports_external.object({
|
|
|
196383
196449
|
path: PathSchema,
|
|
196384
196450
|
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)."),
|
|
196385
196451
|
target: CommitRefSchema.optional().describe("Target commit to reset to (default: HEAD)."),
|
|
196386
|
-
paths: exports_external.array(exports_external.string()).optional().describe("Specific file paths to reset (leaves HEAD unchanged).")
|
|
196452
|
+
paths: exports_external.array(exports_external.string()).optional().describe("Specific file paths to reset (leaves HEAD unchanged)."),
|
|
196453
|
+
confirmed: exports_external.boolean().default(false).describe("Explicit confirmation required for hard reset on protected branches (main, master, production, etc.).")
|
|
196387
196454
|
});
|
|
196388
196455
|
var OutputSchema26 = exports_external.object({
|
|
196389
196456
|
success: exports_external.boolean().describe("Indicates if the operation was successful."),
|
|
@@ -196392,6 +196459,16 @@ var OutputSchema26 = exports_external.object({
|
|
|
196392
196459
|
filesReset: exports_external.array(exports_external.string()).describe("Files that were affected by the reset.")
|
|
196393
196460
|
});
|
|
196394
196461
|
async function gitResetLogic(input, { provider, targetPath, appContext }) {
|
|
196462
|
+
if (input.mode === "hard") {
|
|
196463
|
+
const status = await provider.status({ includeUntracked: false }, {
|
|
196464
|
+
workingDirectory: targetPath,
|
|
196465
|
+
requestContext: appContext,
|
|
196466
|
+
tenantId: appContext.tenantId || "default-tenant"
|
|
196467
|
+
});
|
|
196468
|
+
if (status?.currentBranch) {
|
|
196469
|
+
validateProtectedBranchOperation(status.currentBranch, "reset --hard", input.confirmed);
|
|
196470
|
+
}
|
|
196471
|
+
}
|
|
196395
196472
|
const resetOptions = {
|
|
196396
196473
|
mode: input.mode
|
|
196397
196474
|
};
|
|
@@ -196532,6 +196609,8 @@ var InputSchema27 = exports_external.object({
|
|
|
196532
196609
|
commit: CommitRefSchema.optional().describe("Commit to tag (default: HEAD for create operation)."),
|
|
196533
196610
|
message: exports_external.string().optional().describe("Tag message (creates annotated tag)."),
|
|
196534
196611
|
annotated: exports_external.boolean().default(false).describe("Create annotated tag with message."),
|
|
196612
|
+
sign: SignSchema,
|
|
196613
|
+
forceUnsignedOnFailure: exports_external.boolean().default(false).describe("If GPG/SSH signing fails, retry the tag creation without signing instead of failing."),
|
|
196535
196614
|
force: ForceSchema.describe("Force tag creation/deletion (overwrite existing).")
|
|
196536
196615
|
});
|
|
196537
196616
|
var TagInfoSchema = exports_external.object({
|
|
@@ -196550,7 +196629,8 @@ var OutputSchema28 = exports_external.object({
|
|
|
196550
196629
|
});
|
|
196551
196630
|
async function gitTagLogic(input, { provider, targetPath, appContext }) {
|
|
196552
196631
|
const tagOptions = {
|
|
196553
|
-
mode: input.mode
|
|
196632
|
+
mode: input.mode,
|
|
196633
|
+
forceUnsignedOnFailure: input.forceUnsignedOnFailure
|
|
196554
196634
|
};
|
|
196555
196635
|
if (input.tagName !== undefined) {
|
|
196556
196636
|
tagOptions.tagName = input.tagName;
|
|
@@ -196564,6 +196644,9 @@ async function gitTagLogic(input, { provider, targetPath, appContext }) {
|
|
|
196564
196644
|
if (input.annotated !== undefined) {
|
|
196565
196645
|
tagOptions.annotated = input.annotated;
|
|
196566
196646
|
}
|
|
196647
|
+
if (input.sign !== undefined) {
|
|
196648
|
+
tagOptions.sign = input.sign;
|
|
196649
|
+
}
|
|
196567
196650
|
if (input.force !== undefined) {
|
|
196568
196651
|
tagOptions.force = input.force;
|
|
196569
196652
|
}
|
|
@@ -202061,7 +202144,11 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
202061
202144
|
...transportContext,
|
|
202062
202145
|
staleTimeoutMs: config2.mcpStatefulSessionStaleTimeoutMs
|
|
202063
202146
|
});
|
|
202064
|
-
const
|
|
202147
|
+
const explicitOrigins = config2.mcpAllowedOrigins;
|
|
202148
|
+
const allowedOrigin = explicitOrigins && explicitOrigins.length > 0 ? explicitOrigins : "*";
|
|
202149
|
+
if (allowedOrigin === "*" && config2.environment === "production") {
|
|
202150
|
+
logger.warning("MCP_ALLOWED_ORIGINS is not configured. CORS will allow all origins. " + "Set MCP_ALLOWED_ORIGINS to restrict cross-origin access in production.", transportContext);
|
|
202151
|
+
}
|
|
202065
202152
|
app.use("*", cors({
|
|
202066
202153
|
origin: allowedOrigin,
|
|
202067
202154
|
allowMethods: ["GET", "POST", "DELETE", "OPTIONS"],
|
|
@@ -202095,7 +202182,6 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
202095
202182
|
name: config2.mcpServerName,
|
|
202096
202183
|
version: config2.mcpServerVersion,
|
|
202097
202184
|
description: config2.mcpServerDescription,
|
|
202098
|
-
environment: config2.environment,
|
|
202099
202185
|
transport: config2.mcpTransportType,
|
|
202100
202186
|
sessionMode: config2.mcpSessionMode
|
|
202101
202187
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cyanheads/git-mcp-server",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.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",
|