@cyanheads/git-mcp-server 2.1.3 → 2.1.5
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 +10 -8
- package/dist/config/index.js +10 -2
- package/dist/mcp-server/server.js +33 -32
- package/dist/mcp-server/tools/gitAdd/logic.js +18 -45
- package/dist/mcp-server/tools/gitBranch/logic.js +39 -34
- package/dist/mcp-server/tools/gitCheckout/logic.js +17 -12
- package/dist/mcp-server/tools/gitCherryPick/logic.js +22 -15
- package/dist/mcp-server/tools/gitClean/logic.js +11 -8
- package/dist/mcp-server/tools/gitClone/logic.js +15 -11
- package/dist/mcp-server/tools/gitCommit/logic.js +29 -65
- package/dist/mcp-server/tools/gitDiff/logic.js +29 -14
- package/dist/mcp-server/tools/gitFetch/logic.js +13 -12
- package/dist/mcp-server/tools/gitInit/logic.js +12 -9
- package/dist/mcp-server/tools/gitLog/logic.js +17 -30
- package/dist/mcp-server/tools/gitMerge/logic.js +17 -12
- package/dist/mcp-server/tools/gitPull/logic.js +13 -14
- package/dist/mcp-server/tools/gitPush/logic.js +19 -21
- package/dist/mcp-server/tools/gitRebase/logic.js +29 -20
- package/dist/mcp-server/tools/gitRemote/logic.js +15 -15
- package/dist/mcp-server/tools/gitReset/logic.js +11 -10
- package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +6 -4
- package/dist/mcp-server/tools/gitShow/logic.js +9 -8
- package/dist/mcp-server/tools/gitStash/logic.js +16 -17
- package/dist/mcp-server/tools/gitStatus/logic.js +10 -8
- package/dist/mcp-server/tools/gitTag/logic.js +15 -15
- package/dist/mcp-server/tools/gitWorktree/logic.js +54 -38
- package/dist/mcp-server/transports/auth/core/authContext.js +24 -0
- package/dist/mcp-server/transports/auth/core/authTypes.js +5 -0
- package/dist/mcp-server/transports/auth/core/authUtils.js +45 -0
- package/dist/mcp-server/transports/auth/index.js +9 -0
- package/dist/mcp-server/transports/auth/strategies/jwt/jwtMiddleware.js +149 -0
- package/dist/mcp-server/transports/auth/strategies/oauth/oauthMiddleware.js +127 -0
- package/dist/mcp-server/transports/httpErrorHandler.js +73 -0
- package/dist/mcp-server/transports/httpTransport.js +149 -495
- package/dist/mcp-server/transports/stdioTransport.js +18 -48
- package/package.json +4 -13
- package/dist/mcp-server/transports/authentication/authMiddleware.js +0 -167
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.typescriptlang.org/)
|
|
4
4
|
[](https://modelcontextprotocol.io/)
|
|
5
|
-
[](./CHANGELOG.md)
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
[](https://github.com/cyanheads/git-mcp-server/issues)
|
|
8
8
|
[](https://github.com/cyanheads/git-mcp-server)
|
|
@@ -29,10 +29,9 @@ This server equips your AI with a comprehensive suite of tools to interact with
|
|
|
29
29
|
|
|
30
30
|
## Table of Contents
|
|
31
31
|
|
|
32
|
-
| [Overview](#overview)
|
|
33
|
-
| :------------------------------ | :-------------------------------------- | :---------------------------- | ------------------- |
|
|
32
|
+
| [Overview](#overview) | [Features](#features) | [Installation](#installation) |
|
|
34
33
|
| [Configuration](#configuration) | [Project Structure](#project-structure) |
|
|
35
|
-
| [Tools](#tools)
|
|
34
|
+
| [Tools](#tools) | [Resources](#resources) | [Development](#development) | [License](#license) |
|
|
36
35
|
|
|
37
36
|
## Overview
|
|
38
37
|
|
|
@@ -61,7 +60,7 @@ Leverages the robust utilities provided by the `mcp-ts-template`:
|
|
|
61
60
|
- **Input Validation/Sanitization**: Uses `zod` for schema validation and custom sanitization logic (crucial for paths).
|
|
62
61
|
- **Request Context**: Tracking and correlation of operations via unique request IDs using `AsyncLocalStorage`.
|
|
63
62
|
- **Type Safety**: Strong typing enforced by TypeScript and Zod schemas.
|
|
64
|
-
- **HTTP Transport**: High-performance HTTP server using **Hono**, featuring session management
|
|
63
|
+
- **HTTP Transport**: High-performance HTTP server using **Hono**, featuring session management, CORS, and authentication support.
|
|
65
64
|
- **Deployment**: Multi-stage `Dockerfile` for creating small, secure production images with native dependency support.
|
|
66
65
|
|
|
67
66
|
### Git Integration
|
|
@@ -100,7 +99,7 @@ Add the following to your MCP client's configuration file (e.g., `cline_mcp_sett
|
|
|
100
99
|
}
|
|
101
100
|
```
|
|
102
101
|
|
|
103
|
-
### If running manually (not via MCP client for development or testing
|
|
102
|
+
### If running manually (not via MCP client) for development or testing
|
|
104
103
|
|
|
105
104
|
#### Install via npm
|
|
106
105
|
|
|
@@ -143,7 +142,10 @@ Configure the server using environment variables. These environmental variables
|
|
|
143
142
|
| `MCP_ALLOWED_ORIGINS` | Comma-separated list of allowed origins for CORS (if `MCP_TRANSPORT_TYPE=http`). | (none) |
|
|
144
143
|
| `MCP_LOG_LEVEL` | Logging level (`debug`, `info`, `notice`, `warning`, `error`, `crit`, `alert`, `emerg`). Inherited from template. | `info` |
|
|
145
144
|
| `GIT_SIGN_COMMITS` | Set to `"true"` to enable signing attempts for commits made by the `git_commit` tool. Requires server-side Git/key setup (see below). | `false` |
|
|
146
|
-
| `
|
|
145
|
+
| `MCP_AUTH_MODE` | Authentication mode: `jwt`, `oauth`, or `none`. | `none` |
|
|
146
|
+
| `MCP_AUTH_SECRET_KEY` | Secret key for JWT validation (if `MCP_AUTH_MODE=jwt`). | `''` |
|
|
147
|
+
| `OAUTH_ISSUER_URL` | OIDC issuer URL for OAuth validation (if `MCP_AUTH_MODE=oauth`). | `''` |
|
|
148
|
+
| `OAUTH_AUDIENCE` | Audience claim for OAuth validation (if `MCP_AUTH_MODE=oauth`). | `''` |
|
|
147
149
|
|
|
148
150
|
## Project Structure
|
|
149
151
|
|
|
@@ -201,7 +203,7 @@ _Note: The `path` parameter for most tools defaults to the session's working dir
|
|
|
201
203
|
|
|
202
204
|
## Resources
|
|
203
205
|
|
|
204
|
-
**MCP Resources are not implemented in this version (v2.1.
|
|
206
|
+
**MCP Resources are not implemented in this version (v2.1.4).**
|
|
205
207
|
|
|
206
208
|
This version focuses on the refactored Git tools implementation based on the latest `mcp-ts-template` and MCP SDK v1.13.0. Resource capabilities, previously available, have been temporarily removed during this major update.
|
|
207
209
|
|
package/dist/config/index.js
CHANGED
|
@@ -40,14 +40,22 @@ export const config = {
|
|
|
40
40
|
mcpAllowedOrigins: process.env.MCP_ALLOWED_ORIGINS?.split(",") || [],
|
|
41
41
|
/** Flag to enable GPG signing for commits made by the git_commit tool. Requires server-side GPG setup. */
|
|
42
42
|
gitSignCommits: process.env.GIT_SIGN_COMMITS === "true",
|
|
43
|
+
/** The authentication mode ('jwt', 'oauth', or 'none'). Defaults to 'none'. */
|
|
44
|
+
mcpAuthMode: process.env.MCP_AUTH_MODE || "none",
|
|
45
|
+
/** Secret key for signing/verifying JWTs. Required if mcpAuthMode is 'jwt'. */
|
|
46
|
+
mcpAuthSecretKey: process.env.MCP_AUTH_SECRET_KEY,
|
|
47
|
+
/** The OIDC issuer URL for OAuth token validation. Required if mcpAuthMode is 'oauth'. */
|
|
48
|
+
oauthIssuerUrl: process.env.OAUTH_ISSUER_URL,
|
|
49
|
+
/** The audience claim for OAuth token validation. Required if mcpAuthMode is 'oauth'. */
|
|
50
|
+
oauthAudience: process.env.OAUTH_AUDIENCE,
|
|
51
|
+
/** The JWKS URI for fetching public keys for OAuth. Optional, can be derived from issuer URL. */
|
|
52
|
+
oauthJwksUri: process.env.OAUTH_JWKS_URI,
|
|
43
53
|
/** Security-related configurations. */
|
|
44
54
|
security: {
|
|
45
55
|
// Placeholder for security settings
|
|
46
56
|
// Example: authRequired: process.env.AUTH_REQUIRED === 'true'
|
|
47
57
|
/** Indicates if authentication is required for server operations. */
|
|
48
58
|
authRequired: false,
|
|
49
|
-
/** Secret key for signing/verifying authentication tokens (required if authRequired is true). */
|
|
50
|
-
mcpAuthSecretKey: process.env.MCP_AUTH_SECRET_KEY || "", // Default to empty string, validation should happen elsewhere
|
|
51
59
|
},
|
|
52
60
|
// Note: mcpClient configuration is now loaded separately from mcp-config.json
|
|
53
61
|
};
|
|
@@ -43,9 +43,9 @@ import { initializeGitStatusStateAccessors, registerGitStatusTool, } from "./too
|
|
|
43
43
|
import { initializeGitTagStateAccessors, registerGitTagTool, } from "./tools/gitTag/index.js";
|
|
44
44
|
import { initializeGitWorktreeStateAccessors, registerGitWorktreeTool, } from "./tools/gitWorktree/index.js";
|
|
45
45
|
import { initializeGitWrapupInstructionsStateAccessors, registerGitWrapupInstructionsTool, } from "./tools/gitWrapupInstructions/index.js";
|
|
46
|
-
// Import transport setup functions
|
|
47
|
-
import {
|
|
48
|
-
import { connectStdioTransport
|
|
46
|
+
// Import transport setup functions
|
|
47
|
+
import { startHttpTransport } from "./transports/httpTransport.js";
|
|
48
|
+
import { connectStdioTransport } from "./transports/stdioTransport.js";
|
|
49
49
|
/**
|
|
50
50
|
* Creates and configures a new instance of the McpServer.
|
|
51
51
|
*
|
|
@@ -79,7 +79,9 @@ import { connectStdioTransport, getStdioWorkingDirectory, setStdioWorkingDirecto
|
|
|
79
79
|
*/
|
|
80
80
|
// Removed sessionId parameter, it will be retrieved from context within tool handlers
|
|
81
81
|
async function createMcpServerInstance() {
|
|
82
|
-
const context = {
|
|
82
|
+
const context = requestContextService.createRequestContext({
|
|
83
|
+
operation: "createMcpServerInstance",
|
|
84
|
+
});
|
|
83
85
|
logger.info("Initializing MCP server instance", context);
|
|
84
86
|
// Configure the request context service (used for correlating logs/errors).
|
|
85
87
|
requestContextService.configure({
|
|
@@ -110,6 +112,9 @@ async function createMcpServerInstance() {
|
|
|
110
112
|
tools: { listChanged: true },
|
|
111
113
|
},
|
|
112
114
|
});
|
|
115
|
+
// Each server instance is isolated per session. This variable will hold the
|
|
116
|
+
// working directory for the duration of this session.
|
|
117
|
+
let sessionWorkingDirectory = undefined;
|
|
113
118
|
// --- Define Unified State Accessor Functions ---
|
|
114
119
|
// These functions abstract away the transport type to get/set session state.
|
|
115
120
|
/** Gets the session ID from the tool's execution context. */
|
|
@@ -117,34 +122,21 @@ async function createMcpServerInstance() {
|
|
|
117
122
|
// The RequestContext created by the tool registration wrapper should contain the sessionId.
|
|
118
123
|
return toolContext?.sessionId;
|
|
119
124
|
};
|
|
120
|
-
/** Gets the working directory
|
|
125
|
+
/** Gets the working directory for the current session. */
|
|
121
126
|
const getWorkingDirectory = (sessionId) => {
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
return undefined;
|
|
126
|
-
}
|
|
127
|
-
return getHttpSessionWorkingDirectory(sessionId);
|
|
128
|
-
}
|
|
129
|
-
else {
|
|
130
|
-
// For stdio, there's only one implicit session, ID is not needed.
|
|
131
|
-
return getStdioWorkingDirectory();
|
|
132
|
-
}
|
|
127
|
+
// The working directory is now stored in a variable scoped to this server instance.
|
|
128
|
+
// The sessionId is kept for potential logging or more complex future state management.
|
|
129
|
+
return sessionWorkingDirectory;
|
|
133
130
|
};
|
|
134
|
-
/** Sets the working directory
|
|
131
|
+
/** Sets the working directory for the current session. */
|
|
135
132
|
const setWorkingDirectory = (sessionId, dir) => {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
}
|
|
144
|
-
else {
|
|
145
|
-
// For stdio, set the single session's directory.
|
|
146
|
-
setStdioWorkingDirectory(dir);
|
|
147
|
-
}
|
|
133
|
+
// The working directory is now stored in a variable scoped to this server instance.
|
|
134
|
+
logger.debug("Setting session working directory", {
|
|
135
|
+
...context,
|
|
136
|
+
sessionId,
|
|
137
|
+
newDirectory: dir,
|
|
138
|
+
});
|
|
139
|
+
sessionWorkingDirectory = dir;
|
|
148
140
|
};
|
|
149
141
|
// --- Initialize Tool State Accessors BEFORE Registration ---
|
|
150
142
|
// Pass the defined unified accessor functions to the initializers.
|
|
@@ -254,7 +246,10 @@ async function createMcpServerInstance() {
|
|
|
254
246
|
async function startTransport() {
|
|
255
247
|
// Determine the transport type from the validated configuration.
|
|
256
248
|
const transportType = config.mcpTransportType;
|
|
257
|
-
const context = {
|
|
249
|
+
const context = requestContextService.createRequestContext({
|
|
250
|
+
operation: "startTransport",
|
|
251
|
+
transport: transportType,
|
|
252
|
+
});
|
|
258
253
|
logger.info(`Starting transport: ${transportType}`, context);
|
|
259
254
|
// --- HTTP Transport Setup ---
|
|
260
255
|
if (transportType === "http") {
|
|
@@ -294,7 +289,9 @@ async function startTransport() {
|
|
|
294
289
|
* @returns {Promise<void | McpServer>} Resolves upon successful startup (void for http, McpServer for stdio). Rejects on critical failure.
|
|
295
290
|
*/
|
|
296
291
|
export async function initializeAndStartServer() {
|
|
297
|
-
const context = {
|
|
292
|
+
const context = requestContextService.createRequestContext({
|
|
293
|
+
operation: "initializeAndStartServer",
|
|
294
|
+
});
|
|
298
295
|
logger.info("MCP Server initialization sequence started.", context);
|
|
299
296
|
try {
|
|
300
297
|
// Initiate the transport setup based on configuration.
|
|
@@ -310,7 +307,11 @@ export async function initializeAndStartServer() {
|
|
|
310
307
|
stack: err instanceof Error ? err.stack : undefined,
|
|
311
308
|
});
|
|
312
309
|
// Use the centralized error handler for consistent critical error reporting.
|
|
313
|
-
ErrorHandler.handleError(err, {
|
|
310
|
+
ErrorHandler.handleError(err, {
|
|
311
|
+
...context,
|
|
312
|
+
operation: "initializeAndStartServer_Catch",
|
|
313
|
+
critical: true,
|
|
314
|
+
});
|
|
314
315
|
// Exit the process with a non-zero code to indicate failure.
|
|
315
316
|
logger.info("Exiting process due to critical initialization error.", context);
|
|
316
317
|
process.exit(1);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
@@ -7,7 +7,7 @@ import { logger } from "../../../utils/index.js";
|
|
|
7
7
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
8
8
|
// Import utils from barrel (sanitization from ../utils/security/sanitization.js)
|
|
9
9
|
import { sanitization } from "../../../utils/index.js";
|
|
10
|
-
const
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
11
|
// Define the input schema for the git_add tool using Zod
|
|
12
12
|
export const GitAddInputSchema = z.object({
|
|
13
13
|
path: z
|
|
@@ -76,52 +76,25 @@ export async function addGitFiles(input, context) {
|
|
|
76
76
|
}
|
|
77
77
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
78
78
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
if (filesToStage.length === 0) {
|
|
85
|
-
logger.warning("Empty array provided for files, defaulting to staging all changes.", { ...context, operation });
|
|
86
|
-
filesArg = "."; // Default to staging all if array is empty
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
// Quote each file path individually
|
|
90
|
-
filesArg = filesToStage
|
|
91
|
-
.map((file) => {
|
|
92
|
-
const sanitizedFile = file.startsWith("-") ? `./${file}` : file; // Prefix with './' if it starts with a dash
|
|
93
|
-
return `"${sanitizedFile.replace(/"/g, '\\"')}"`; // Escape quotes within path
|
|
94
|
-
})
|
|
95
|
-
.join(" ");
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
else {
|
|
99
|
-
// Single string case
|
|
100
|
-
const sanitizedFile = filesToStage.startsWith("-")
|
|
101
|
-
? `./${filesToStage}`
|
|
102
|
-
: filesToStage; // Prefix with './' if it starts with a dash
|
|
103
|
-
filesArg = `"${sanitizedFile.replace(/"/g, '\\"')}"`;
|
|
104
|
-
}
|
|
79
|
+
const filesToStage = Array.isArray(input.files)
|
|
80
|
+
? input.files
|
|
81
|
+
: [input.files];
|
|
82
|
+
if (filesToStage.length === 0) {
|
|
83
|
+
filesToStage.push("."); // Default to staging all if array is empty
|
|
105
84
|
}
|
|
106
|
-
|
|
107
|
-
|
|
85
|
+
try {
|
|
86
|
+
const args = ["-C", targetPath, "add", "--"];
|
|
87
|
+
filesToStage.forEach((file) => {
|
|
88
|
+
// Sanitize each file path. Although execFile is safer,
|
|
89
|
+
// this prevents arguments like "-v" from being treated as flags by git.
|
|
90
|
+
const sanitizedFile = file.startsWith("-") ? `./${file}` : file;
|
|
91
|
+
args.push(sanitizedFile);
|
|
92
|
+
});
|
|
93
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
108
94
|
...context,
|
|
109
95
|
operation,
|
|
110
|
-
files: filesToStage,
|
|
111
|
-
error: err,
|
|
112
96
|
});
|
|
113
|
-
|
|
114
|
-
}
|
|
115
|
-
// This check should ideally not be needed now due to the logic above
|
|
116
|
-
if (!filesArg) {
|
|
117
|
-
logger.error("Internal error: filesArg is unexpectedly empty after processing.", { ...context, operation });
|
|
118
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, "Internal error preparing git add command.", { context, operation });
|
|
119
|
-
}
|
|
120
|
-
try {
|
|
121
|
-
// Use the resolved targetPath
|
|
122
|
-
const command = `git -C "${targetPath}" add -- ${filesArg}`;
|
|
123
|
-
logger.debug(`Executing command: ${command}`, { ...context, operation });
|
|
124
|
-
const { stdout, stderr } = await execAsync(command);
|
|
97
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
125
98
|
if (stderr) {
|
|
126
99
|
// Log stderr as warning, as 'git add' can produce warnings but still succeed.
|
|
127
100
|
logger.warning(`Git add command produced stderr`, {
|
|
@@ -162,7 +135,7 @@ export async function addGitFiles(input, context) {
|
|
|
162
135
|
}
|
|
163
136
|
if (errorMessage.toLowerCase().includes("did not match any files")) {
|
|
164
137
|
// Still throw an error, but return structured info in the catch block of the registration
|
|
165
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Specified files/patterns did not match any files in ${targetPath}: ${
|
|
138
|
+
throw new McpError(BaseErrorCode.NOT_FOUND, `Specified files/patterns did not match any files in ${targetPath}: ${filesToStage.join(", ")}`, { context, operation, originalError: error, filesStaged: filesToStage });
|
|
166
139
|
}
|
|
167
140
|
// Throw generic error for other cases
|
|
168
141
|
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to stage files for path: ${targetPath}. Error: ${errorMessage}`, { context, operation, originalError: error, filesStaged: filesToStage });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
@@ -7,7 +7,7 @@ import { logger } from "../../../utils/index.js";
|
|
|
7
7
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
8
8
|
// Import utils from barrel (sanitization from ../utils/security/sanitization.js)
|
|
9
9
|
import { sanitization } from "../../../utils/index.js";
|
|
10
|
-
const
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
11
|
// Define the BASE input schema for the git_branch tool using Zod
|
|
12
12
|
export const GitBranchBaseSchema = z.object({
|
|
13
13
|
path: z
|
|
@@ -115,21 +115,23 @@ export async function gitBranchLogic(input, context) {
|
|
|
115
115
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
116
116
|
}
|
|
117
117
|
try {
|
|
118
|
-
let
|
|
118
|
+
let args;
|
|
119
119
|
let result;
|
|
120
120
|
switch (input.mode) {
|
|
121
121
|
case "list":
|
|
122
|
-
|
|
123
|
-
if (input.all)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
122
|
+
args = ["-C", targetPath, "branch", "--list", "--no-color"]; // Start with basic list
|
|
123
|
+
if (input.all) {
|
|
124
|
+
args.push("-a"); // Add -a if requested
|
|
125
|
+
}
|
|
126
|
+
else if (input.remote) {
|
|
127
|
+
args.push("-r"); // Add -r if requested (exclusive with -a)
|
|
128
|
+
}
|
|
129
|
+
args.push("--verbose"); // Add verbose for commit info
|
|
130
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
129
131
|
...context,
|
|
130
132
|
operation,
|
|
131
133
|
});
|
|
132
|
-
const { stdout: listStdout } = await
|
|
134
|
+
const { stdout: listStdout } = await execFileAsync("git", args);
|
|
133
135
|
const branches = listStdout
|
|
134
136
|
.trim()
|
|
135
137
|
.split("\n")
|
|
@@ -157,17 +159,19 @@ export async function gitBranchLogic(input, context) {
|
|
|
157
159
|
break;
|
|
158
160
|
case "create":
|
|
159
161
|
// branchName is validated by Zod refine
|
|
160
|
-
|
|
161
|
-
if (input.force)
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
162
|
+
args = ["-C", targetPath, "branch"];
|
|
163
|
+
if (input.force) {
|
|
164
|
+
args.push("-f");
|
|
165
|
+
}
|
|
166
|
+
args.push(input.branchName); // branchName is guaranteed by refine
|
|
167
|
+
if (input.startPoint) {
|
|
168
|
+
args.push(input.startPoint);
|
|
169
|
+
}
|
|
170
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
167
171
|
...context,
|
|
168
172
|
operation,
|
|
169
173
|
});
|
|
170
|
-
await
|
|
174
|
+
await execFileAsync("git", args);
|
|
171
175
|
result = {
|
|
172
176
|
success: true,
|
|
173
177
|
mode: "create",
|
|
@@ -177,16 +181,17 @@ export async function gitBranchLogic(input, context) {
|
|
|
177
181
|
break;
|
|
178
182
|
case "delete":
|
|
179
183
|
// branchName is validated by Zod refine
|
|
180
|
-
|
|
181
|
-
if (input.remote)
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
184
|
+
args = ["-C", targetPath, "branch"];
|
|
185
|
+
if (input.remote) {
|
|
186
|
+
args.push("-r");
|
|
187
|
+
}
|
|
188
|
+
args.push(input.force ? "-D" : "-d");
|
|
189
|
+
args.push(input.branchName); // branchName is guaranteed by refine
|
|
190
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
186
191
|
...context,
|
|
187
192
|
operation,
|
|
188
193
|
});
|
|
189
|
-
const { stdout: deleteStdout } = await
|
|
194
|
+
const { stdout: deleteStdout } = await execFileAsync("git", args);
|
|
190
195
|
result = {
|
|
191
196
|
success: true,
|
|
192
197
|
mode: "delete",
|
|
@@ -198,14 +203,14 @@ export async function gitBranchLogic(input, context) {
|
|
|
198
203
|
break;
|
|
199
204
|
case "rename":
|
|
200
205
|
// branchName and newBranchName validated by Zod refine
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
logger.debug(`Executing command: ${
|
|
206
|
+
args = ["-C", targetPath, "branch"];
|
|
207
|
+
args.push(input.force ? "-M" : "-m");
|
|
208
|
+
args.push(input.branchName, input.newBranchName);
|
|
209
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
205
210
|
...context,
|
|
206
211
|
operation,
|
|
207
212
|
});
|
|
208
|
-
await
|
|
213
|
+
await execFileAsync("git", args);
|
|
209
214
|
result = {
|
|
210
215
|
success: true,
|
|
211
216
|
mode: "rename",
|
|
@@ -215,13 +220,13 @@ export async function gitBranchLogic(input, context) {
|
|
|
215
220
|
};
|
|
216
221
|
break;
|
|
217
222
|
case "show-current":
|
|
218
|
-
|
|
219
|
-
logger.debug(`Executing command: ${
|
|
223
|
+
args = ["-C", targetPath, "branch", "--show-current"];
|
|
224
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
220
225
|
...context,
|
|
221
226
|
operation,
|
|
222
227
|
});
|
|
223
228
|
try {
|
|
224
|
-
const { stdout: currentStdout } = await
|
|
229
|
+
const { stdout: currentStdout } = await execFileAsync("git", args);
|
|
225
230
|
const currentBranchName = currentStdout.trim();
|
|
226
231
|
result = {
|
|
227
232
|
success: true,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
@@ -7,7 +7,7 @@ import { logger } from "../../../utils/index.js";
|
|
|
7
7
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
8
8
|
// Import utils from barrel (sanitization from ../utils/security/sanitization.js)
|
|
9
9
|
import { sanitization } from "../../../utils/index.js";
|
|
10
|
-
const
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
11
|
// Define the input schema for the git_checkout tool using Zod
|
|
12
12
|
export const GitCheckoutInputSchema = z.object({
|
|
13
13
|
path: z
|
|
@@ -75,22 +75,22 @@ export async function checkoutGit(input, context) {
|
|
|
75
75
|
throw error;
|
|
76
76
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
77
77
|
}
|
|
78
|
-
// Basic sanitization for branch/path argument
|
|
79
|
-
const safeBranchOrPath = input.branchOrPath.replace(/[`$&;*()|<>]/g, ""); // Remove potentially dangerous characters
|
|
80
78
|
try {
|
|
81
79
|
// Construct the git checkout command
|
|
82
|
-
|
|
80
|
+
const args = ["-C", targetPath, "checkout"];
|
|
83
81
|
if (input.force) {
|
|
84
|
-
|
|
82
|
+
args.push("--force");
|
|
85
83
|
}
|
|
86
84
|
if (input.newBranch) {
|
|
87
|
-
|
|
88
|
-
command += ` -b ${safeNewBranch}`;
|
|
85
|
+
args.push("-b", input.newBranch);
|
|
89
86
|
}
|
|
90
|
-
|
|
91
|
-
logger.debug(`Executing command: ${
|
|
87
|
+
args.push(input.branchOrPath); // Add the target branch/path
|
|
88
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
89
|
+
...context,
|
|
90
|
+
operation,
|
|
91
|
+
});
|
|
92
92
|
// Execute command. Checkout often uses stderr for status messages.
|
|
93
|
-
const { stdout, stderr } = await
|
|
93
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
94
94
|
const message = stderr.trim() || stdout.trim();
|
|
95
95
|
logger.debug(`Git checkout stdout: ${stdout}`, { ...context, operation });
|
|
96
96
|
if (stderr) {
|
|
@@ -99,7 +99,12 @@ export async function checkoutGit(input, context) {
|
|
|
99
99
|
// Get the current branch name after the checkout operation
|
|
100
100
|
let currentBranch;
|
|
101
101
|
try {
|
|
102
|
-
const { stdout: branchStdout } = await
|
|
102
|
+
const { stdout: branchStdout } = await execFileAsync("git", [
|
|
103
|
+
"-C",
|
|
104
|
+
targetPath,
|
|
105
|
+
"branch",
|
|
106
|
+
"--show-current",
|
|
107
|
+
]);
|
|
103
108
|
currentBranch = branchStdout.trim();
|
|
104
109
|
}
|
|
105
110
|
catch (e) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
@@ -7,7 +7,7 @@ import { logger } from "../../../utils/index.js";
|
|
|
7
7
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
8
8
|
// Import utils from barrel (sanitization from ../utils/security/sanitization.js)
|
|
9
9
|
import { sanitization } from "../../../utils/index.js";
|
|
10
|
-
const
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
11
|
// Define the input schema for the git_cherry-pick tool using Zod
|
|
12
12
|
export const GitCherryPickInputSchema = z.object({
|
|
13
13
|
path: z
|
|
@@ -95,20 +95,27 @@ export async function gitCherryPickLogic(input, context) {
|
|
|
95
95
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
96
96
|
}
|
|
97
97
|
try {
|
|
98
|
-
|
|
99
|
-
if (input.mainline)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (input.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
98
|
+
const args = ["-C", targetPath, "cherry-pick"];
|
|
99
|
+
if (input.mainline) {
|
|
100
|
+
args.push("-m", String(input.mainline));
|
|
101
|
+
}
|
|
102
|
+
if (input.strategy) {
|
|
103
|
+
args.push(`-X${input.strategy}`);
|
|
104
|
+
} // Note: -X for strategy options
|
|
105
|
+
if (input.noCommit) {
|
|
106
|
+
args.push("--no-commit");
|
|
107
|
+
}
|
|
108
|
+
if (input.signoff) {
|
|
109
|
+
args.push("--signoff");
|
|
110
|
+
}
|
|
111
|
+
// Add the commit reference(s)
|
|
112
|
+
args.push(input.commitRef);
|
|
113
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
114
|
+
...context,
|
|
115
|
+
operation,
|
|
116
|
+
});
|
|
110
117
|
try {
|
|
111
|
-
const { stdout, stderr } = await
|
|
118
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
112
119
|
// Check stdout/stderr for conflict messages, although exit code 0 usually means success
|
|
113
120
|
const output = stdout + stderr;
|
|
114
121
|
const conflicts = /conflict/i.test(output);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { execFile } from "child_process";
|
|
2
2
|
import { promisify } from "util";
|
|
3
3
|
import { z } from "zod";
|
|
4
4
|
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
@@ -7,7 +7,7 @@ import { logger } from "../../../utils/index.js";
|
|
|
7
7
|
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
8
8
|
// Import utils from barrel (sanitization from ../utils/security/sanitization.js)
|
|
9
9
|
import { sanitization } from "../../../utils/index.js";
|
|
10
|
-
const
|
|
10
|
+
const execFileAsync = promisify(execFile);
|
|
11
11
|
// Define the input schema for the git_clean tool using Zod
|
|
12
12
|
// No refinements needed here, but the 'force' check is critical in the logic
|
|
13
13
|
export const GitCleanInputSchema = z.object({
|
|
@@ -102,18 +102,21 @@ export async function gitCleanLogic(input, context) {
|
|
|
102
102
|
try {
|
|
103
103
|
// Construct the command
|
|
104
104
|
// Force (-f) is always added because the logic checks input.force
|
|
105
|
-
|
|
105
|
+
const args = ["-C", targetPath, "clean", "-f"];
|
|
106
106
|
if (input.dryRun) {
|
|
107
|
-
|
|
107
|
+
args.push("-n");
|
|
108
108
|
}
|
|
109
109
|
if (input.directories) {
|
|
110
|
-
|
|
110
|
+
args.push("-d");
|
|
111
111
|
}
|
|
112
112
|
if (input.ignored) {
|
|
113
|
-
|
|
113
|
+
args.push("-x");
|
|
114
114
|
}
|
|
115
|
-
logger.debug(`Executing command: ${
|
|
116
|
-
|
|
115
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
116
|
+
...context,
|
|
117
|
+
operation,
|
|
118
|
+
});
|
|
119
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
117
120
|
if (stderr) {
|
|
118
121
|
// Log stderr as warning, as git clean might report non-fatal issues here
|
|
119
122
|
logger.warning(`Git clean command produced stderr`, {
|