@cyanheads/git-mcp-server 2.1.0 → 2.1.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 +8 -11
- package/dist/config/index.js +7 -7
- package/dist/index.js +35 -21
- package/dist/mcp-server/server.js +72 -56
- package/dist/mcp-server/tools/gitAdd/index.js +1 -1
- package/dist/mcp-server/tools/gitAdd/logic.js +88 -39
- package/dist/mcp-server/tools/gitAdd/registration.js +17 -14
- package/dist/mcp-server/tools/gitBranch/index.js +1 -1
- package/dist/mcp-server/tools/gitBranch/logic.js +213 -85
- package/dist/mcp-server/tools/gitBranch/registration.js +16 -13
- package/dist/mcp-server/tools/gitCheckout/index.js +1 -1
- package/dist/mcp-server/tools/gitCheckout/logic.js +85 -145
- package/dist/mcp-server/tools/gitCheckout/registration.js +16 -14
- package/dist/mcp-server/tools/gitCherryPick/index.js +1 -1
- package/dist/mcp-server/tools/gitCherryPick/logic.js +100 -41
- package/dist/mcp-server/tools/gitCherryPick/registration.js +21 -14
- package/dist/mcp-server/tools/gitClean/index.js +1 -1
- package/dist/mcp-server/tools/gitClean/logic.js +93 -41
- package/dist/mcp-server/tools/gitClean/registration.js +19 -16
- package/dist/mcp-server/tools/gitClearWorkingDir/index.js +1 -1
- package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +14 -11
- package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +19 -13
- package/dist/mcp-server/tools/gitClone/index.js +1 -1
- package/dist/mcp-server/tools/gitClone/logic.js +89 -30
- package/dist/mcp-server/tools/gitClone/registration.js +15 -12
- package/dist/mcp-server/tools/gitCommit/index.js +1 -1
- package/dist/mcp-server/tools/gitCommit/logic.js +198 -76
- package/dist/mcp-server/tools/gitCommit/registration.js +23 -20
- package/dist/mcp-server/tools/gitDiff/index.js +1 -1
- package/dist/mcp-server/tools/gitDiff/logic.js +124 -44
- package/dist/mcp-server/tools/gitDiff/registration.js +16 -14
- package/dist/mcp-server/tools/gitFetch/index.js +1 -1
- package/dist/mcp-server/tools/gitFetch/logic.js +78 -49
- package/dist/mcp-server/tools/gitFetch/registration.js +16 -14
- package/dist/mcp-server/tools/gitInit/index.js +1 -1
- package/dist/mcp-server/tools/gitInit/logic.js +88 -34
- package/dist/mcp-server/tools/gitInit/registration.js +32 -18
- package/dist/mcp-server/tools/gitLog/index.js +1 -1
- package/dist/mcp-server/tools/gitLog/logic.js +133 -47
- package/dist/mcp-server/tools/gitLog/registration.js +16 -14
- package/dist/mcp-server/tools/gitMerge/index.js +1 -1
- package/dist/mcp-server/tools/gitMerge/logic.js +102 -61
- package/dist/mcp-server/tools/gitMerge/registration.js +17 -14
- package/dist/mcp-server/tools/gitPull/index.js +1 -1
- package/dist/mcp-server/tools/gitPull/logic.js +90 -69
- package/dist/mcp-server/tools/gitPull/registration.js +16 -14
- package/dist/mcp-server/tools/gitPush/index.js +1 -1
- package/dist/mcp-server/tools/gitPush/logic.js +116 -100
- package/dist/mcp-server/tools/gitPush/registration.js +16 -14
- package/dist/mcp-server/tools/gitRebase/index.js +1 -1
- package/dist/mcp-server/tools/gitRebase/logic.js +121 -82
- package/dist/mcp-server/tools/gitRebase/registration.js +21 -14
- package/dist/mcp-server/tools/gitRemote/index.js +1 -1
- package/dist/mcp-server/tools/gitRemote/logic.js +108 -52
- package/dist/mcp-server/tools/gitRemote/registration.js +14 -11
- package/dist/mcp-server/tools/gitReset/index.js +1 -1
- package/dist/mcp-server/tools/gitReset/logic.js +65 -37
- package/dist/mcp-server/tools/gitReset/registration.js +14 -12
- package/dist/mcp-server/tools/gitSetWorkingDir/index.js +1 -1
- package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +74 -34
- package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +18 -11
- package/dist/mcp-server/tools/gitShow/index.js +1 -1
- package/dist/mcp-server/tools/gitShow/logic.js +78 -35
- package/dist/mcp-server/tools/gitShow/registration.js +17 -12
- package/dist/mcp-server/tools/gitStash/index.js +1 -1
- package/dist/mcp-server/tools/gitStash/logic.js +143 -58
- package/dist/mcp-server/tools/gitStash/registration.js +19 -12
- package/dist/mcp-server/tools/gitStatus/index.js +1 -1
- package/dist/mcp-server/tools/gitStatus/logic.js +100 -58
- package/dist/mcp-server/tools/gitStatus/registration.js +15 -12
- package/dist/mcp-server/tools/gitTag/index.js +1 -1
- package/dist/mcp-server/tools/gitTag/logic.js +124 -51
- package/dist/mcp-server/tools/gitTag/registration.js +14 -11
- package/dist/mcp-server/tools/gitWorktree/index.js +1 -1
- package/dist/mcp-server/tools/gitWorktree/logic.js +204 -95
- package/dist/mcp-server/tools/gitWorktree/registration.js +14 -11
- package/dist/mcp-server/tools/gitWrapupInstructions/index.js +1 -1
- package/dist/mcp-server/tools/gitWrapupInstructions/logic.js +23 -11
- package/dist/mcp-server/tools/gitWrapupInstructions/registration.js +14 -12
- package/dist/mcp-server/transports/httpTransport.js +187 -79
- package/dist/mcp-server/transports/stdioTransport.js +14 -8
- package/dist/types-global/errors.js +9 -4
- package/dist/utils/index.js +4 -4
- package/dist/utils/internal/errorHandler.js +62 -40
- package/dist/utils/internal/index.js +3 -3
- package/dist/utils/internal/logger.js +97 -54
- package/dist/utils/internal/requestContext.js +7 -5
- package/dist/utils/metrics/index.js +1 -1
- package/dist/utils/metrics/tokenCounter.js +18 -14
- package/dist/utils/parsing/dateParser.js +5 -5
- package/dist/utils/parsing/index.js +2 -2
- package/dist/utils/parsing/jsonParser.js +20 -11
- package/dist/utils/security/idGenerator.js +8 -10
- package/dist/utils/security/index.js +3 -3
- package/dist/utils/security/rateLimiter.js +16 -14
- package/dist/utils/security/sanitization.js +139 -82
- package/package.json +45 -23
|
@@ -1,22 +1,39 @@
|
|
|
1
|
-
import { exec } from
|
|
2
|
-
import fs from
|
|
3
|
-
import { promisify } from
|
|
4
|
-
import { z } from
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import { promisify } from "util";
|
|
4
|
+
import { z } from "zod";
|
|
5
5
|
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
6
|
-
import { logger } from
|
|
6
|
+
import { logger } from "../../../utils/index.js";
|
|
7
7
|
// Import utils from barrel (RequestContext from ../utils/internal/requestContext.js)
|
|
8
|
-
import { BaseErrorCode, McpError } from
|
|
8
|
+
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
9
9
|
// Import utils from barrel (sanitization from ../utils/security/sanitization.js)
|
|
10
|
-
import { sanitization } from
|
|
10
|
+
import { sanitization } from "../../../utils/index.js";
|
|
11
11
|
const execAsync = promisify(exec);
|
|
12
12
|
// Define the input schema for the git_clone tool using Zod
|
|
13
13
|
export const GitCloneInputSchema = z.object({
|
|
14
|
-
repositoryUrl: z
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
repositoryUrl: z
|
|
15
|
+
.string()
|
|
16
|
+
.url("Invalid repository URL format.")
|
|
17
|
+
.describe("The URL of the repository to clone (e.g., https://github.com/cyanheads/git-mcp-server, git@github.com:cyanheads/git-mcp-server.git)."),
|
|
18
|
+
targetPath: z
|
|
19
|
+
.string()
|
|
20
|
+
.min(1)
|
|
21
|
+
.describe("The absolute path to the directory where the repository should be cloned."),
|
|
22
|
+
branch: z
|
|
23
|
+
.string()
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Specify a specific branch to checkout after cloning."),
|
|
26
|
+
depth: z
|
|
27
|
+
.number()
|
|
28
|
+
.int()
|
|
29
|
+
.positive()
|
|
30
|
+
.optional()
|
|
31
|
+
.describe("Create a shallow clone with a history truncated to the specified number of commits."),
|
|
18
32
|
// recursive: z.boolean().default(false).describe("After the clone is created, initialize all submodules within, using their default settings."), // Consider adding later
|
|
19
|
-
quiet: z
|
|
33
|
+
quiet: z
|
|
34
|
+
.boolean()
|
|
35
|
+
.default(false)
|
|
36
|
+
.describe("Operate quietly. Progress is not reported to the standard error stream."),
|
|
20
37
|
});
|
|
21
38
|
/**
|
|
22
39
|
* Executes the 'git clone' command to clone a repository.
|
|
@@ -27,19 +44,29 @@ export const GitCloneInputSchema = z.object({
|
|
|
27
44
|
* @throws {McpError} Throws an McpError if path/URL validation fails or the git command fails unexpectedly.
|
|
28
45
|
*/
|
|
29
46
|
export async function gitCloneLogic(input, context) {
|
|
30
|
-
const operation =
|
|
47
|
+
const operation = "gitCloneLogic";
|
|
31
48
|
logger.debug(`Executing ${operation}`, { ...context, input });
|
|
32
49
|
let sanitizedTargetPath;
|
|
33
50
|
let sanitizedRepoUrl;
|
|
34
51
|
try {
|
|
35
52
|
// Sanitize the target path (must be absolute)
|
|
36
|
-
sanitizedTargetPath = sanitization.sanitizePath(input.targetPath, {
|
|
37
|
-
|
|
53
|
+
sanitizedTargetPath = sanitization.sanitizePath(input.targetPath, {
|
|
54
|
+
allowAbsolute: true,
|
|
55
|
+
}).sanitizedPath;
|
|
56
|
+
logger.debug("Sanitized target path", {
|
|
57
|
+
...context,
|
|
58
|
+
operation,
|
|
59
|
+
sanitizedTargetPath,
|
|
60
|
+
});
|
|
38
61
|
// Basic sanitization/validation for URL (Zod already checks format)
|
|
39
62
|
// Further sanitization might be needed depending on how it's used in the shell command
|
|
40
63
|
// For now, rely on Zod's URL validation and careful command construction.
|
|
41
64
|
sanitizedRepoUrl = input.repositoryUrl; // Assume Zod validation is sufficient for now
|
|
42
|
-
logger.debug(
|
|
65
|
+
logger.debug("Validated repository URL", {
|
|
66
|
+
...context,
|
|
67
|
+
operation,
|
|
68
|
+
sanitizedRepoUrl,
|
|
69
|
+
});
|
|
43
70
|
// Check if target directory already exists and is not empty
|
|
44
71
|
try {
|
|
45
72
|
const stats = await fs.stat(sanitizedTargetPath);
|
|
@@ -56,9 +83,13 @@ export async function gitCloneLogic(input, context) {
|
|
|
56
83
|
catch (error) {
|
|
57
84
|
if (error instanceof McpError)
|
|
58
85
|
throw error; // Re-throw our specific validation errors
|
|
59
|
-
if (error.code !==
|
|
86
|
+
if (error.code !== "ENOENT") {
|
|
60
87
|
// If error is not "does not exist", it's unexpected
|
|
61
|
-
logger.error(`Error checking target directory ${sanitizedTargetPath}`, {
|
|
88
|
+
logger.error(`Error checking target directory ${sanitizedTargetPath}`, {
|
|
89
|
+
...context,
|
|
90
|
+
operation,
|
|
91
|
+
error: error.message,
|
|
92
|
+
});
|
|
62
93
|
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to check target directory: ${error.message}`, { context, operation });
|
|
63
94
|
}
|
|
64
95
|
// ENOENT is expected - directory doesn't exist, which is fine for clone
|
|
@@ -66,7 +97,11 @@ export async function gitCloneLogic(input, context) {
|
|
|
66
97
|
}
|
|
67
98
|
}
|
|
68
99
|
catch (error) {
|
|
69
|
-
logger.error(
|
|
100
|
+
logger.error("Path/URL validation or sanitization failed", {
|
|
101
|
+
...context,
|
|
102
|
+
operation,
|
|
103
|
+
error,
|
|
104
|
+
});
|
|
70
105
|
if (error instanceof McpError)
|
|
71
106
|
throw error;
|
|
72
107
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid input: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
@@ -76,7 +111,7 @@ export async function gitCloneLogic(input, context) {
|
|
|
76
111
|
// Use placeholders and pass args safely if possible, but exec requires string command. Be careful with quoting.
|
|
77
112
|
let command = `git clone`;
|
|
78
113
|
if (input.quiet) {
|
|
79
|
-
command +=
|
|
114
|
+
command += " --quiet";
|
|
80
115
|
}
|
|
81
116
|
if (input.branch) {
|
|
82
117
|
command += ` --branch "${input.branch.replace(/"/g, '\\"')}"`;
|
|
@@ -91,10 +126,18 @@ export async function gitCloneLogic(input, context) {
|
|
|
91
126
|
const { stdout, stderr } = await execAsync(command, { timeout: 300000 }); // 5 minutes timeout
|
|
92
127
|
if (stderr && !input.quiet) {
|
|
93
128
|
// Stderr often contains progress info, log as info if quiet is false
|
|
94
|
-
logger.info(`Git clone command produced stderr (progress/info)`, {
|
|
129
|
+
logger.info(`Git clone command produced stderr (progress/info)`, {
|
|
130
|
+
...context,
|
|
131
|
+
operation,
|
|
132
|
+
stderr,
|
|
133
|
+
});
|
|
95
134
|
}
|
|
96
135
|
if (stdout && !input.quiet) {
|
|
97
|
-
logger.info(`Git clone command produced stdout`, {
|
|
136
|
+
logger.info(`Git clone command produced stdout`, {
|
|
137
|
+
...context,
|
|
138
|
+
operation,
|
|
139
|
+
stdout,
|
|
140
|
+
});
|
|
98
141
|
}
|
|
99
142
|
// Verify the target directory exists after clone
|
|
100
143
|
let repoDirExists = false;
|
|
@@ -108,29 +151,45 @@ export async function gitCloneLogic(input, context) {
|
|
|
108
151
|
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Clone command finished but target directory ${sanitizedTargetPath} not found.`, { context, operation });
|
|
109
152
|
}
|
|
110
153
|
const successMessage = `Repository cloned successfully into ${sanitizedTargetPath}`;
|
|
111
|
-
logger.info(
|
|
154
|
+
logger.info(successMessage, {
|
|
155
|
+
...context,
|
|
156
|
+
operation,
|
|
157
|
+
path: sanitizedTargetPath,
|
|
158
|
+
});
|
|
112
159
|
return {
|
|
113
160
|
success: true,
|
|
114
161
|
message: successMessage,
|
|
115
162
|
path: sanitizedTargetPath,
|
|
116
|
-
repoDirExists: repoDirExists
|
|
163
|
+
repoDirExists: repoDirExists,
|
|
117
164
|
};
|
|
118
165
|
}
|
|
119
166
|
catch (error) {
|
|
120
|
-
const errorMessage = error.stderr || error.message ||
|
|
121
|
-
logger.error(`Failed to execute git clone command`, {
|
|
167
|
+
const errorMessage = error.stderr || error.message || "";
|
|
168
|
+
logger.error(`Failed to execute git clone command`, {
|
|
169
|
+
...context,
|
|
170
|
+
operation,
|
|
171
|
+
path: sanitizedTargetPath,
|
|
172
|
+
error: errorMessage,
|
|
173
|
+
stderr: error.stderr,
|
|
174
|
+
stdout: error.stdout,
|
|
175
|
+
});
|
|
122
176
|
// Handle specific error cases
|
|
123
|
-
if (errorMessage.toLowerCase().includes(
|
|
177
|
+
if (errorMessage.toLowerCase().includes("repository not found") ||
|
|
178
|
+
errorMessage
|
|
179
|
+
.toLowerCase()
|
|
180
|
+
.includes("could not read from remote repository")) {
|
|
124
181
|
throw new McpError(BaseErrorCode.NOT_FOUND, `Repository not found or access denied: ${sanitizedRepoUrl}. Error: ${errorMessage}`, { context, operation, originalError: error });
|
|
125
182
|
}
|
|
126
|
-
if (errorMessage
|
|
183
|
+
if (errorMessage
|
|
184
|
+
.toLowerCase()
|
|
185
|
+
.includes("already exists and is not an empty directory")) {
|
|
127
186
|
// This should have been caught by our pre-check, but handle defensively
|
|
128
187
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Target directory already exists and is not empty: ${sanitizedTargetPath}. Error: ${errorMessage}`, { context, operation, originalError: error });
|
|
129
188
|
}
|
|
130
|
-
if (errorMessage.toLowerCase().includes(
|
|
189
|
+
if (errorMessage.toLowerCase().includes("permission denied")) {
|
|
131
190
|
throw new McpError(BaseErrorCode.FORBIDDEN, `Permission denied during clone operation for path: ${sanitizedTargetPath}. Error: ${errorMessage}`, { context, operation, originalError: error });
|
|
132
191
|
}
|
|
133
|
-
if (errorMessage.toLowerCase().includes(
|
|
192
|
+
if (errorMessage.toLowerCase().includes("timeout")) {
|
|
134
193
|
throw new McpError(BaseErrorCode.TIMEOUT, `Git clone operation timed out for repository: ${sanitizedRepoUrl}. Error: ${errorMessage}`, { context, operation, originalError: error });
|
|
135
194
|
}
|
|
136
195
|
// Generic internal error for other failures
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
// Import utils from barrel (ErrorHandler from ../utils/internal/errorHandler.js)
|
|
2
|
-
import { ErrorHandler } from
|
|
2
|
+
import { ErrorHandler } from "../../../utils/index.js";
|
|
3
3
|
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
4
|
-
import { logger } from
|
|
4
|
+
import { logger } from "../../../utils/index.js";
|
|
5
5
|
// Import utils from barrel (requestContextService from ../utils/internal/requestContext.js)
|
|
6
|
-
import { requestContextService } from
|
|
7
|
-
import { GitCloneInputSchema, gitCloneLogic } from
|
|
8
|
-
import { BaseErrorCode } from
|
|
9
|
-
const TOOL_NAME =
|
|
10
|
-
const TOOL_DESCRIPTION =
|
|
6
|
+
import { requestContextService } from "../../../utils/index.js";
|
|
7
|
+
import { GitCloneInputSchema, gitCloneLogic, } from "./logic.js";
|
|
8
|
+
import { BaseErrorCode } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
9
|
+
const TOOL_NAME = "git_clone";
|
|
10
|
+
const TOOL_DESCRIPTION = "Clones a Git repository from a given URL into a specified absolute directory path. Supports cloning specific branches and setting clone depth.";
|
|
11
11
|
/**
|
|
12
12
|
* Registers the git_clone tool with the MCP server.
|
|
13
13
|
*
|
|
@@ -16,22 +16,25 @@ const TOOL_DESCRIPTION = 'Clones a Git repository from a given URL into a specif
|
|
|
16
16
|
* @throws {Error} If registration fails.
|
|
17
17
|
*/
|
|
18
18
|
export const registerGitCloneTool = async (server) => {
|
|
19
|
-
const operation =
|
|
19
|
+
const operation = "registerGitCloneTool";
|
|
20
20
|
const context = requestContextService.createRequestContext({ operation });
|
|
21
21
|
await ErrorHandler.tryCatch(async () => {
|
|
22
22
|
server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitCloneInputSchema.shape, // Provide the Zod schema shape
|
|
23
23
|
async (validatedArgs, callContext) => {
|
|
24
|
-
const toolOperation =
|
|
25
|
-
const requestContext = requestContextService.createRequestContext({
|
|
24
|
+
const toolOperation = "tool:git_clone";
|
|
25
|
+
const requestContext = requestContextService.createRequestContext({
|
|
26
|
+
operation: toolOperation,
|
|
27
|
+
parentContext: callContext,
|
|
28
|
+
});
|
|
26
29
|
logger.info(`Executing tool: ${TOOL_NAME}`, requestContext);
|
|
27
30
|
return await ErrorHandler.tryCatch(async () => {
|
|
28
31
|
// Call the core logic function
|
|
29
32
|
const cloneResult = await gitCloneLogic(validatedArgs, requestContext);
|
|
30
33
|
// Format the result as a JSON string within TextContent
|
|
31
34
|
const resultContent = {
|
|
32
|
-
type:
|
|
35
|
+
type: "text",
|
|
33
36
|
text: JSON.stringify(cloneResult, null, 2), // Pretty-print JSON
|
|
34
|
-
contentType:
|
|
37
|
+
contentType: "application/json",
|
|
35
38
|
};
|
|
36
39
|
logger.info(`Tool ${TOOL_NAME} executed successfully, returning JSON`, requestContext);
|
|
37
40
|
return { content: [resultContent] };
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* @fileoverview Barrel file for the gitCommit tool.
|
|
3
3
|
* Exports the registration function and state accessor initialization function.
|
|
4
4
|
*/
|
|
5
|
-
export { initializeGitCommitStateAccessors, registerGitCommitTool } from
|
|
5
|
+
export { initializeGitCommitStateAccessors, registerGitCommitTool, } from "./registration.js";
|
|
6
6
|
// Export types if needed elsewhere, e.g.:
|
|
7
7
|
// export type { GitCommitInput, GitCommitResult } from './logic.js';
|