@cyanheads/git-mcp-server 2.8.1 → 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 +56 -7
- 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.8.
|
|
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), {
|
|
@@ -194057,6 +194057,25 @@ async function resolveWorkingDirectory(pathInput, appContext, storage) {
|
|
|
194057
194057
|
});
|
|
194058
194058
|
return sanitizedPath;
|
|
194059
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
|
+
}
|
|
194060
194079
|
|
|
194061
194080
|
// src/mcp-server/tools/utils/toolHandlerFactory.ts
|
|
194062
194081
|
function validateSdkContext(ctx) {
|
|
@@ -196243,7 +196262,8 @@ var InputSchema23 = exports_external.object({
|
|
|
196243
196262
|
tags: exports_external.boolean().default(false).describe("Push all tags to the remote."),
|
|
196244
196263
|
dryRun: DryRunSchema,
|
|
196245
196264
|
delete: exports_external.boolean().default(false).describe("Delete the specified remote branch."),
|
|
196246
|
-
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.).")
|
|
196247
196267
|
});
|
|
196248
196268
|
var OutputSchema24 = exports_external.object({
|
|
196249
196269
|
success: exports_external.boolean().describe("Indicates if the operation was successful."),
|
|
@@ -196254,6 +196274,21 @@ var OutputSchema24 = exports_external.object({
|
|
|
196254
196274
|
rejectedRefs: exports_external.array(exports_external.string()).describe("References that were rejected by the remote.")
|
|
196255
196275
|
});
|
|
196256
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
|
+
}
|
|
196257
196292
|
const pushOptions = {};
|
|
196258
196293
|
if (input.remote !== undefined) {
|
|
196259
196294
|
pushOptions.remote = input.remote;
|
|
@@ -196414,7 +196449,8 @@ var InputSchema25 = exports_external.object({
|
|
|
196414
196449
|
path: PathSchema,
|
|
196415
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)."),
|
|
196416
196451
|
target: CommitRefSchema.optional().describe("Target commit to reset to (default: HEAD)."),
|
|
196417
|
-
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.).")
|
|
196418
196454
|
});
|
|
196419
196455
|
var OutputSchema26 = exports_external.object({
|
|
196420
196456
|
success: exports_external.boolean().describe("Indicates if the operation was successful."),
|
|
@@ -196423,6 +196459,16 @@ var OutputSchema26 = exports_external.object({
|
|
|
196423
196459
|
filesReset: exports_external.array(exports_external.string()).describe("Files that were affected by the reset.")
|
|
196424
196460
|
});
|
|
196425
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
|
+
}
|
|
196426
196472
|
const resetOptions = {
|
|
196427
196473
|
mode: input.mode
|
|
196428
196474
|
};
|
|
@@ -202098,7 +202144,11 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
202098
202144
|
...transportContext,
|
|
202099
202145
|
staleTimeoutMs: config2.mcpStatefulSessionStaleTimeoutMs
|
|
202100
202146
|
});
|
|
202101
|
-
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
|
+
}
|
|
202102
202152
|
app.use("*", cors({
|
|
202103
202153
|
origin: allowedOrigin,
|
|
202104
202154
|
allowMethods: ["GET", "POST", "DELETE", "OPTIONS"],
|
|
@@ -202132,7 +202182,6 @@ function createHttpApp(mcpServer, parentContext) {
|
|
|
202132
202182
|
name: config2.mcpServerName,
|
|
202133
202183
|
version: config2.mcpServerVersion,
|
|
202134
202184
|
description: config2.mcpServerDescription,
|
|
202135
|
-
environment: config2.environment,
|
|
202136
202185
|
transport: config2.mcpTransportType,
|
|
202137
202186
|
sessionMode: config2.mcpSessionMode
|
|
202138
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",
|