@cyanheads/git-mcp-server 2.1.8 → 2.2.1
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 +4 -4
- package/dist/mcp-server/server.js +69 -228
- package/dist/mcp-server/tools/gitAdd/index.js +2 -4
- package/dist/mcp-server/tools/gitAdd/logic.js +40 -116
- package/dist/mcp-server/tools/gitAdd/registration.js +39 -59
- package/dist/mcp-server/tools/gitBranch/index.js +3 -5
- package/dist/mcp-server/tools/gitBranch/logic.js +109 -304
- package/dist/mcp-server/tools/gitBranch/registration.js +52 -66
- package/dist/mcp-server/tools/gitCheckout/index.js +2 -3
- package/dist/mcp-server/tools/gitCheckout/logic.js +47 -144
- package/dist/mcp-server/tools/gitCheckout/registration.js +53 -72
- package/dist/mcp-server/tools/gitCherryPick/index.js +3 -5
- package/dist/mcp-server/tools/gitCherryPick/logic.js +47 -173
- package/dist/mcp-server/tools/gitCherryPick/registration.js +52 -67
- package/dist/mcp-server/tools/gitClean/index.js +3 -5
- package/dist/mcp-server/tools/gitClean/logic.js +45 -154
- package/dist/mcp-server/tools/gitClean/registration.js +52 -92
- package/dist/mcp-server/tools/gitClearWorkingDir/index.js +3 -5
- package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +18 -32
- package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +55 -73
- package/dist/mcp-server/tools/gitClone/index.js +2 -4
- package/dist/mcp-server/tools/gitClone/logic.js +47 -187
- package/dist/mcp-server/tools/gitClone/registration.js +51 -42
- package/dist/mcp-server/tools/gitCommit/index.js +2 -4
- package/dist/mcp-server/tools/gitCommit/logic.js +75 -310
- package/dist/mcp-server/tools/gitCommit/registration.js +52 -73
- package/dist/mcp-server/tools/gitDiff/index.js +2 -3
- package/dist/mcp-server/tools/gitDiff/logic.js +72 -264
- package/dist/mcp-server/tools/gitDiff/registration.js +53 -68
- package/dist/mcp-server/tools/gitFetch/index.js +2 -3
- package/dist/mcp-server/tools/gitFetch/logic.js +38 -136
- package/dist/mcp-server/tools/gitFetch/registration.js +54 -66
- package/dist/mcp-server/tools/gitInit/index.js +3 -5
- package/dist/mcp-server/tools/gitInit/logic.js +40 -162
- package/dist/mcp-server/tools/gitInit/registration.js +52 -104
- package/dist/mcp-server/tools/gitLog/index.js +2 -3
- package/dist/mcp-server/tools/gitLog/logic.js +71 -266
- package/dist/mcp-server/tools/gitLog/registration.js +54 -66
- package/dist/mcp-server/tools/gitMerge/index.js +3 -5
- package/dist/mcp-server/tools/gitMerge/logic.js +45 -191
- package/dist/mcp-server/tools/gitMerge/registration.js +52 -71
- package/dist/mcp-server/tools/gitPull/index.js +2 -3
- package/dist/mcp-server/tools/gitPull/logic.js +39 -156
- package/dist/mcp-server/tools/gitPull/registration.js +53 -75
- package/dist/mcp-server/tools/gitPush/index.js +2 -3
- package/dist/mcp-server/tools/gitPush/logic.js +65 -192
- package/dist/mcp-server/tools/gitPush/registration.js +53 -75
- package/dist/mcp-server/tools/gitRebase/index.js +3 -5
- package/dist/mcp-server/tools/gitRebase/logic.js +59 -207
- package/dist/mcp-server/tools/gitRebase/registration.js +52 -70
- package/dist/mcp-server/tools/gitRemote/index.js +3 -5
- package/dist/mcp-server/tools/gitRemote/logic.js +76 -200
- package/dist/mcp-server/tools/gitRemote/registration.js +52 -65
- package/dist/mcp-server/tools/gitReset/index.js +2 -3
- package/dist/mcp-server/tools/gitReset/logic.js +33 -133
- package/dist/mcp-server/tools/gitReset/registration.js +53 -60
- package/dist/mcp-server/tools/gitSetWorkingDir/index.js +3 -5
- package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +39 -144
- package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +55 -85
- package/dist/mcp-server/tools/gitShow/index.js +3 -5
- package/dist/mcp-server/tools/gitShow/logic.js +28 -133
- package/dist/mcp-server/tools/gitShow/registration.js +52 -74
- package/dist/mcp-server/tools/gitStash/index.js +3 -5
- package/dist/mcp-server/tools/gitStash/logic.js +59 -219
- package/dist/mcp-server/tools/gitStash/registration.js +52 -77
- package/dist/mcp-server/tools/gitStatus/index.js +2 -4
- package/dist/mcp-server/tools/gitStatus/logic.js +79 -236
- package/dist/mcp-server/tools/gitStatus/registration.js +52 -66
- package/dist/mcp-server/tools/gitTag/index.js +3 -5
- package/dist/mcp-server/tools/gitTag/logic.js +57 -198
- package/dist/mcp-server/tools/gitTag/registration.js +54 -73
- package/dist/mcp-server/tools/gitWorktree/index.js +3 -5
- package/dist/mcp-server/tools/gitWorktree/logic.js +102 -328
- package/dist/mcp-server/tools/gitWorktree/registration.js +54 -58
- package/dist/mcp-server/tools/gitWrapupInstructions/index.js +5 -3
- package/dist/mcp-server/tools/gitWrapupInstructions/logic.js +25 -43
- package/dist/mcp-server/tools/gitWrapupInstructions/registration.js +54 -70
- package/dist/mcp-server/transports/httpTransport.js +2 -3
- package/package.json +8 -8
|
@@ -1,86 +1,64 @@
|
|
|
1
|
-
// Import utils from barrel (ErrorHandler from ../utils/internal/errorHandler.js)
|
|
2
|
-
import { ErrorHandler } from "../../../utils/index.js";
|
|
3
|
-
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
4
|
-
import { logger } from "../../../utils/index.js";
|
|
5
|
-
// Import utils from barrel (requestContextService, RequestContext from ../utils/internal/requestContext.js)
|
|
6
|
-
import { BaseErrorCode } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
7
|
-
import { requestContextService } from "../../../utils/index.js";
|
|
8
|
-
import { GitPushInputSchema, pushGitChanges, } from "./logic.js";
|
|
9
|
-
let _getWorkingDirectory;
|
|
10
|
-
let _getSessionId;
|
|
11
1
|
/**
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
* @param getWdFn - Function to get the working directory for a session.
|
|
15
|
-
* @param getSidFn - Function to get the session ID from context.
|
|
2
|
+
* @fileoverview Handles registration and error handling for the git_push tool.
|
|
3
|
+
* @module src/mcp-server/tools/gitPush/registration
|
|
16
4
|
*/
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
_getSessionId = getSidFn;
|
|
20
|
-
logger.info("State accessors initialized for git_push tool registration.");
|
|
21
|
-
}
|
|
5
|
+
import { ErrorHandler, logger, requestContextService } from "../../../utils/index.js";
|
|
6
|
+
import { pushGitChanges, GitPushOutputSchema, GitPushBaseSchema, } from "./logic.js";
|
|
22
7
|
const TOOL_NAME = "git_push";
|
|
23
8
|
const TOOL_DESCRIPTION = "Updates remote refs using local refs, sending objects necessary to complete the given refs. Supports pushing specific branches, tags, forcing, setting upstream, and deleting remote branches. Returns the push result as a JSON object.";
|
|
24
9
|
/**
|
|
25
|
-
* Registers the git_push tool with the MCP server.
|
|
26
|
-
*
|
|
27
|
-
* @param
|
|
28
|
-
* @
|
|
10
|
+
* Registers the git_push tool with the MCP server instance.
|
|
11
|
+
* @param server The MCP server instance.
|
|
12
|
+
* @param getWorkingDirectory Function to get the session's working directory.
|
|
13
|
+
* @param getSessionId Function to get the session ID from context.
|
|
29
14
|
*/
|
|
30
|
-
export async
|
|
31
|
-
if (!_getWorkingDirectory || !_getSessionId) {
|
|
32
|
-
throw new Error("State accessors for git_push must be initialized before registration.");
|
|
33
|
-
}
|
|
15
|
+
export const registerGitPushTool = async (server, getWorkingDirectory, getSessionId) => {
|
|
34
16
|
const operation = "registerGitPushTool";
|
|
35
17
|
const context = requestContextService.createRequestContext({ operation });
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
18
|
+
server.registerTool(TOOL_NAME, {
|
|
19
|
+
title: "Git Push",
|
|
20
|
+
description: TOOL_DESCRIPTION,
|
|
21
|
+
inputSchema: GitPushBaseSchema.shape,
|
|
22
|
+
outputSchema: GitPushOutputSchema.shape,
|
|
23
|
+
annotations: {
|
|
24
|
+
readOnlyHint: false,
|
|
25
|
+
destructiveHint: true, // Can alter remote history
|
|
26
|
+
idempotentHint: false,
|
|
27
|
+
openWorldHint: true, // Interacts with remote repositories
|
|
28
|
+
},
|
|
29
|
+
}, async (params, callContext) => {
|
|
30
|
+
const handlerContext = requestContextService.createRequestContext({
|
|
31
|
+
toolName: TOOL_NAME,
|
|
32
|
+
parentContext: callContext,
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
const sessionId = getSessionId(handlerContext);
|
|
36
|
+
const result = await pushGitChanges(params, {
|
|
37
|
+
...handlerContext,
|
|
38
|
+
getWorkingDirectory: () => getWorkingDirectory(sessionId),
|
|
43
39
|
});
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
};
|
|
48
|
-
const logicContext = {
|
|
49
|
-
...requestContext,
|
|
50
|
-
sessionId: sessionId,
|
|
51
|
-
getWorkingDirectory: getWorkingDirectoryForSession,
|
|
40
|
+
return {
|
|
41
|
+
structuredContent: result,
|
|
42
|
+
content: [{ type: "text", text: `Success: ${JSON.stringify(result, null, 2)}` }],
|
|
52
43
|
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
text: JSON.stringify(pushResult, null, 2), // Pretty-print JSON
|
|
61
|
-
contentType: "application/json",
|
|
62
|
-
};
|
|
63
|
-
// Log based on the success flag in the result
|
|
64
|
-
if (pushResult.success) {
|
|
65
|
-
logger.info(`Tool ${TOOL_NAME} executed successfully, returning JSON`, logicContext);
|
|
66
|
-
}
|
|
67
|
-
else {
|
|
68
|
-
// Log non-fatal conditions like rejections differently
|
|
69
|
-
const logMessage = pushResult.rejected
|
|
70
|
-
? `Tool ${TOOL_NAME} completed but push was rejected, returning JSON`
|
|
71
|
-
: `Tool ${TOOL_NAME} completed with status: ${pushResult.message}, returning JSON`;
|
|
72
|
-
logger.info(logMessage, logicContext);
|
|
73
|
-
}
|
|
74
|
-
// Even if success is false (e.g., rejected), it's not necessarily a tool execution *error*
|
|
75
|
-
// unless the logic threw an McpError. The success flag in the JSON indicates the Git outcome.
|
|
76
|
-
return { content: [resultContent] };
|
|
77
|
-
}, {
|
|
78
|
-
operation: toolOperation,
|
|
79
|
-
context: logicContext,
|
|
80
|
-
input: validatedArgs,
|
|
81
|
-
errorCode: BaseErrorCode.INTERNAL_ERROR, // Default if unexpected error in logic
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
|
|
47
|
+
const mcpError = ErrorHandler.handleError(error, {
|
|
48
|
+
operation: `tool:${TOOL_NAME}`,
|
|
49
|
+
context: handlerContext,
|
|
50
|
+
input: params,
|
|
82
51
|
});
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
52
|
+
return {
|
|
53
|
+
isError: true,
|
|
54
|
+
content: [{ type: "text", text: `Error: ${mcpError.message}` }],
|
|
55
|
+
structuredContent: {
|
|
56
|
+
code: mcpError.code,
|
|
57
|
+
message: mcpError.message,
|
|
58
|
+
details: mcpError.details,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
logger.info(`Tool '${TOOL_NAME}' registered successfully.`, context);
|
|
64
|
+
};
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Barrel file for the
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Barrel file for the gitRebase tool.
|
|
3
|
+
* @module src/mcp-server/tools/gitRebase/index
|
|
4
4
|
*/
|
|
5
|
-
export { registerGitRebaseTool
|
|
6
|
-
// Export types if needed elsewhere, e.g.:
|
|
7
|
-
// export type { GitRebaseInput, GitRebaseResult } from './logic.js';
|
|
5
|
+
export { registerGitRebaseTool } from "./registration.js";
|
|
@@ -1,222 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Defines the core logic, schemas, and types for the git_rebase tool.
|
|
3
|
+
* @module src/mcp-server/tools/gitRebase/logic
|
|
4
|
+
*/
|
|
1
5
|
import { execFile } from "child_process";
|
|
2
6
|
import { promisify } from "util";
|
|
3
7
|
import { z } from "zod";
|
|
4
|
-
|
|
5
|
-
import {
|
|
6
|
-
// Import utils from barrel (RequestContext from ../utils/internal/requestContext.js)
|
|
7
|
-
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
8
|
-
// Import utils from barrel (sanitization from ../utils/security/sanitization.js)
|
|
9
|
-
import { sanitization } from "../../../utils/index.js";
|
|
8
|
+
import { logger, sanitization } from "../../../utils/index.js";
|
|
9
|
+
import { McpError, BaseErrorCode } from "../../../types-global/errors.js";
|
|
10
10
|
const execFileAsync = promisify(execFile);
|
|
11
|
-
//
|
|
11
|
+
// 1. DEFINE the Zod input schema.
|
|
12
12
|
export const GitRebaseBaseSchema = z.object({
|
|
13
|
-
path: z
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
.default("start")
|
|
22
|
-
.describe("Rebase operation mode: 'start' (initiate rebase), 'continue', 'abort', 'skip' (manage ongoing rebase)."),
|
|
23
|
-
upstream: z
|
|
24
|
-
.string()
|
|
25
|
-
.min(1)
|
|
26
|
-
.optional()
|
|
27
|
-
.describe("The upstream branch or commit to rebase onto. Required for 'start' mode unless 'interactive' is true with default base."),
|
|
28
|
-
branch: z
|
|
29
|
-
.string()
|
|
30
|
-
.min(1)
|
|
31
|
-
.optional()
|
|
32
|
-
.describe("The branch to rebase. Defaults to the current branch if omitted."),
|
|
33
|
-
interactive: z
|
|
34
|
-
.boolean()
|
|
35
|
-
.default(false)
|
|
36
|
-
.describe("Perform an interactive rebase (`-i`). 'upstream' can be omitted to rebase current branch's tracked upstream or use fork-point."),
|
|
37
|
-
strategy: z
|
|
38
|
-
.enum(["recursive", "resolve", "ours", "theirs", "octopus", "subtree"])
|
|
39
|
-
.optional()
|
|
40
|
-
.describe("Specifies the merge strategy to use during rebase."),
|
|
41
|
-
strategyOption: z
|
|
42
|
-
.string()
|
|
43
|
-
.optional()
|
|
44
|
-
.describe("Pass a specific option to the merge strategy (e.g., 'ours', 'theirs' for recursive). Use with -X."),
|
|
45
|
-
onto: z
|
|
46
|
-
.string()
|
|
47
|
-
.min(1)
|
|
48
|
-
.optional()
|
|
49
|
-
.describe("Rebase onto a specific commit/branch instead of the upstream's base. Requires 'upstream' to be specified."),
|
|
50
|
-
// TODO: Add options like --preserve-merges, --autosquash, --autostash?
|
|
13
|
+
path: z.string().default(".").describe("Path to the local Git repository."),
|
|
14
|
+
mode: z.enum(["start", "continue", "abort", "skip"]).default("start").describe("Rebase operation mode."),
|
|
15
|
+
upstream: z.string().min(1).optional().describe("The upstream branch or commit to rebase onto."),
|
|
16
|
+
branch: z.string().min(1).optional().describe("The branch to rebase."),
|
|
17
|
+
interactive: z.boolean().default(false).describe("Perform an interactive rebase."),
|
|
18
|
+
strategy: z.enum(["recursive", "resolve", "ours", "theirs", "octopus", "subtree"]).optional().describe("The merge strategy to use."),
|
|
19
|
+
strategyOption: z.string().optional().describe("Pass a specific option to the merge strategy."),
|
|
20
|
+
onto: z.string().min(1).optional().describe("Rebase onto a specific commit/branch instead of the upstream's base."),
|
|
51
21
|
});
|
|
52
|
-
// Apply refinements and export the FINAL schema for validation within the handler
|
|
53
22
|
export const GitRebaseInputSchema = GitRebaseBaseSchema.refine((data) => !(data.mode === "start" && !data.interactive && !data.upstream), {
|
|
54
23
|
message: "An 'upstream' branch/commit is required for 'start' mode unless 'interactive' is true.",
|
|
55
24
|
path: ["upstream"],
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
path: ["mode"],
|
|
25
|
+
});
|
|
26
|
+
// 2. DEFINE the Zod response schema.
|
|
27
|
+
export const GitRebaseOutputSchema = z.object({
|
|
28
|
+
success: z.boolean().describe("Indicates if the command was successful."),
|
|
29
|
+
message: z.string().describe("A summary message of the result."),
|
|
30
|
+
rebaseCompleted: z.boolean().optional().describe("True if the rebase finished successfully."),
|
|
31
|
+
needsManualAction: z.boolean().optional().describe("True if conflicts or interactive steps require user input."),
|
|
64
32
|
});
|
|
65
33
|
/**
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
* @param {GitRebaseInput} input - The validated input object.
|
|
69
|
-
* @param {RequestContext} context - The request context for logging and error handling.
|
|
70
|
-
* @returns {Promise<GitRebaseResult>} A promise that resolves with the structured result.
|
|
71
|
-
* @throws {McpError} Throws an McpError for path resolution/validation failures or unexpected errors.
|
|
34
|
+
* 4. IMPLEMENT the core logic function.
|
|
35
|
+
* @throws {McpError} If the logic encounters an unrecoverable issue.
|
|
72
36
|
*/
|
|
73
|
-
export async function gitRebaseLogic(
|
|
74
|
-
const operation = `gitRebaseLogic:${
|
|
75
|
-
logger.debug(`Executing ${operation}`, { ...context,
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const workingDir = context.getWorkingDirectory();
|
|
80
|
-
targetPath =
|
|
81
|
-
input.path && input.path !== "." ? input.path : (workingDir ?? ".");
|
|
82
|
-
if (targetPath === "." && !workingDir) {
|
|
83
|
-
logger.warning("Executing git rebase in server's CWD as no path provided and no session WD set.", { ...context, operation });
|
|
84
|
-
targetPath = process.cwd();
|
|
85
|
-
}
|
|
86
|
-
else if (targetPath === "." && workingDir) {
|
|
87
|
-
targetPath = workingDir;
|
|
88
|
-
logger.debug(`Using session working directory: ${targetPath}`, {
|
|
89
|
-
...context,
|
|
90
|
-
operation,
|
|
91
|
-
sessionId: context.sessionId,
|
|
92
|
-
});
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
logger.debug(`Using provided path: ${targetPath}`, {
|
|
96
|
-
...context,
|
|
97
|
-
operation,
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
targetPath = sanitization.sanitizePath(targetPath, {
|
|
101
|
-
allowAbsolute: true,
|
|
102
|
-
}).sanitizedPath;
|
|
103
|
-
logger.debug("Sanitized path", {
|
|
104
|
-
...context,
|
|
105
|
-
operation,
|
|
106
|
-
sanitizedPath: targetPath,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
catch (error) {
|
|
110
|
-
logger.error("Path resolution or sanitization failed", {
|
|
111
|
-
...context,
|
|
112
|
-
operation,
|
|
113
|
-
error,
|
|
114
|
-
});
|
|
115
|
-
if (error instanceof McpError)
|
|
116
|
-
throw error;
|
|
117
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
118
|
-
}
|
|
119
|
-
try {
|
|
120
|
-
const args = ["-C", targetPath, "rebase"];
|
|
121
|
-
switch (input.mode) {
|
|
122
|
-
case "start":
|
|
123
|
-
if (input.interactive) {
|
|
124
|
-
args.push("-i");
|
|
125
|
-
}
|
|
126
|
-
if (input.strategy) {
|
|
127
|
-
args.push(`--strategy=${input.strategy}`);
|
|
128
|
-
}
|
|
129
|
-
if (input.strategyOption) {
|
|
130
|
-
args.push(`-X${input.strategyOption}`);
|
|
131
|
-
} // Note: -X for strategy options
|
|
132
|
-
if (input.onto) {
|
|
133
|
-
args.push("--onto", input.onto);
|
|
134
|
-
}
|
|
135
|
-
// Upstream is required by refine unless interactive
|
|
136
|
-
if (input.upstream) {
|
|
137
|
-
args.push(input.upstream);
|
|
138
|
-
}
|
|
139
|
-
if (input.branch) {
|
|
140
|
-
args.push(input.branch);
|
|
141
|
-
}
|
|
142
|
-
break;
|
|
143
|
-
case "continue":
|
|
144
|
-
args.push("--continue");
|
|
145
|
-
break;
|
|
146
|
-
case "abort":
|
|
147
|
-
args.push("--abort");
|
|
148
|
-
break;
|
|
149
|
-
case "skip":
|
|
150
|
-
args.push("--skip");
|
|
151
|
-
break;
|
|
152
|
-
default:
|
|
153
|
-
// Should not happen due to Zod validation
|
|
154
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid mode: ${input.mode}`, { context, operation });
|
|
155
|
-
}
|
|
156
|
-
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
157
|
-
...context,
|
|
158
|
-
operation,
|
|
159
|
-
});
|
|
160
|
-
try {
|
|
161
|
-
const { stdout, stderr } = await execFileAsync("git", args);
|
|
162
|
-
const output = stdout + stderr;
|
|
163
|
-
const message = `Rebase ${input.mode} executed successfully. Output: ${output.trim()}`;
|
|
164
|
-
logger.info(message, { ...context, operation, path: targetPath });
|
|
165
|
-
return {
|
|
166
|
-
success: true,
|
|
167
|
-
mode: input.mode,
|
|
168
|
-
message,
|
|
169
|
-
rebaseCompleted: /successfully rebased/.test(output),
|
|
170
|
-
needsManualAction: /conflict|stopped at|edit/i.test(output),
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
catch (rebaseError) {
|
|
174
|
-
const errorMessage = rebaseError.stderr || rebaseError.stdout || rebaseError.message || "";
|
|
175
|
-
logger.error(`Git rebase ${input.mode} command failed`, {
|
|
176
|
-
...context,
|
|
177
|
-
operation,
|
|
178
|
-
path: targetPath,
|
|
179
|
-
error: errorMessage,
|
|
180
|
-
stderr: rebaseError.stderr,
|
|
181
|
-
stdout: rebaseError.stdout,
|
|
182
|
-
});
|
|
183
|
-
// Handle specific error cases
|
|
184
|
-
if (/conflict/i.test(errorMessage)) {
|
|
185
|
-
throw new McpError(BaseErrorCode.CONFLICT, `Rebase ${input.mode} failed due to conflicts. Resolve conflicts and use 'git rebase --continue'. Error: ${errorMessage}`, { context, operation, originalError: rebaseError });
|
|
186
|
-
}
|
|
187
|
-
if (/no rebase in progress/i.test(errorMessage)) {
|
|
188
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Failed to ${input.mode} rebase: No rebase is currently in progress. Error: ${errorMessage}`, { context, operation, originalError: rebaseError });
|
|
189
|
-
}
|
|
190
|
-
if (/cannot rebase onto multiple branches/i.test(errorMessage)) {
|
|
191
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Failed to start rebase: Cannot rebase onto multiple branches. Check your 'upstream' parameter. Error: ${errorMessage}`, { context, operation, originalError: rebaseError });
|
|
192
|
-
}
|
|
193
|
-
if (/does not point to a valid commit/i.test(errorMessage)) {
|
|
194
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Failed to start rebase: Invalid upstream, branch, or onto reference provided. Error: ${errorMessage}`, { context, operation, originalError: rebaseError });
|
|
195
|
-
}
|
|
196
|
-
if (/your local changes would be overwritten/i.test(errorMessage)) {
|
|
197
|
-
throw new McpError(BaseErrorCode.CONFLICT, `Failed to ${input.mode} rebase: Your local changes to tracked files would be overwritten. Please commit or stash them. Error: ${errorMessage}`, { context, operation, originalError: rebaseError });
|
|
198
|
-
}
|
|
199
|
-
if (/interactive rebase already started/i.test(errorMessage)) {
|
|
200
|
-
throw new McpError(BaseErrorCode.CONFLICT, `Failed to start rebase: An interactive rebase is already in progress. Use 'continue', 'abort', or 'skip'. Error: ${errorMessage}`, { context, operation, originalError: rebaseError });
|
|
201
|
-
}
|
|
202
|
-
// Throw McpError for critical issues like non-existent repo
|
|
203
|
-
if (errorMessage.toLowerCase().includes("not a git repository")) {
|
|
204
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`, { context, operation, originalError: rebaseError });
|
|
205
|
-
}
|
|
206
|
-
// Throw a generic McpError for other failures
|
|
207
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git rebase ${input.mode} failed for path: ${targetPath}. Error: ${errorMessage}`, { context, operation, originalError: rebaseError });
|
|
208
|
-
}
|
|
37
|
+
export async function gitRebaseLogic(params, context) {
|
|
38
|
+
const operation = `gitRebaseLogic:${params.mode}`;
|
|
39
|
+
logger.debug(`Executing ${operation}`, { ...context, params });
|
|
40
|
+
const workingDir = context.getWorkingDirectory();
|
|
41
|
+
if (params.path === "." && !workingDir) {
|
|
42
|
+
throw new McpError(BaseErrorCode.VALIDATION_ERROR, "No session working directory set. Please specify a 'path' or use 'git_set_working_dir' first.");
|
|
209
43
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
44
|
+
const targetPath = sanitization.sanitizePath(params.path === "." ? workingDir : params.path, { allowAbsolute: true }).sanitizedPath;
|
|
45
|
+
const args = ["-C", targetPath, "rebase"];
|
|
46
|
+
switch (params.mode) {
|
|
47
|
+
case "start":
|
|
48
|
+
if (params.interactive)
|
|
49
|
+
args.push("-i");
|
|
50
|
+
if (params.strategy)
|
|
51
|
+
args.push(`--strategy=${params.strategy}`);
|
|
52
|
+
if (params.strategyOption)
|
|
53
|
+
args.push(`-X${params.strategyOption}`);
|
|
54
|
+
if (params.onto)
|
|
55
|
+
args.push("--onto", params.onto);
|
|
56
|
+
if (params.upstream)
|
|
57
|
+
args.push(params.upstream);
|
|
58
|
+
if (params.branch)
|
|
59
|
+
args.push(params.branch);
|
|
60
|
+
break;
|
|
61
|
+
default:
|
|
62
|
+
args.push(`--${params.mode}`);
|
|
63
|
+
break;
|
|
221
64
|
}
|
|
65
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
|
|
66
|
+
const { stdout, stderr } = await execFileAsync("git", args);
|
|
67
|
+
const output = stdout + stderr;
|
|
68
|
+
return {
|
|
69
|
+
success: true,
|
|
70
|
+
message: `Rebase ${params.mode} executed successfully.`,
|
|
71
|
+
rebaseCompleted: /successfully rebased/i.test(output),
|
|
72
|
+
needsManualAction: /conflict|stopped at|edit/i.test(output),
|
|
73
|
+
};
|
|
222
74
|
}
|
|
@@ -1,82 +1,64 @@
|
|
|
1
|
-
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
2
|
-
import { logger } from "../../../utils/index.js";
|
|
3
|
-
// Import utils from barrel (ErrorHandler from ../utils/internal/errorHandler.js)
|
|
4
|
-
import { ErrorHandler } from "../../../utils/index.js";
|
|
5
|
-
// Import utils from barrel (requestContextService from ../utils/internal/requestContext.js)
|
|
6
|
-
import { BaseErrorCode } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
7
|
-
import { requestContextService } from "../../../utils/index.js";
|
|
8
|
-
import { GitRebaseBaseSchema, gitRebaseLogic, } from "./logic.js";
|
|
9
|
-
let _getWorkingDirectory;
|
|
10
|
-
let _getSessionId;
|
|
11
1
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
14
|
-
* @param getSidFn - Function to get the session ID from context.
|
|
2
|
+
* @fileoverview Handles registration and error handling for the git_rebase tool.
|
|
3
|
+
* @module src/mcp-server/tools/gitRebase/registration
|
|
15
4
|
*/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
_getSessionId = getSidFn;
|
|
19
|
-
logger.info("State accessors initialized for git_rebase tool registration.");
|
|
20
|
-
}
|
|
5
|
+
import { ErrorHandler, logger, requestContextService } from "../../../utils/index.js";
|
|
6
|
+
import { gitRebaseLogic, GitRebaseOutputSchema, GitRebaseBaseSchema, } from "./logic.js";
|
|
21
7
|
const TOOL_NAME = "git_rebase";
|
|
22
8
|
const TOOL_DESCRIPTION = "Reapplies commits on top of another base tip. Supports starting a rebase (standard or interactive), continuing, aborting, or skipping steps in an ongoing rebase. Returns results as a JSON object.";
|
|
23
9
|
/**
|
|
24
|
-
* Registers the git_rebase tool with the MCP server.
|
|
25
|
-
*
|
|
26
|
-
* @param
|
|
27
|
-
* @
|
|
28
|
-
* @throws {Error} If registration fails or state accessors are not initialized.
|
|
10
|
+
* Registers the git_rebase tool with the MCP server instance.
|
|
11
|
+
* @param server The MCP server instance.
|
|
12
|
+
* @param getWorkingDirectory Function to get the session's working directory.
|
|
13
|
+
* @param getSessionId Function to get the session ID from context.
|
|
29
14
|
*/
|
|
30
|
-
export const registerGitRebaseTool = async (server) => {
|
|
31
|
-
if (!_getWorkingDirectory || !_getSessionId) {
|
|
32
|
-
throw new Error("State accessors for git_rebase must be initialized before registration.");
|
|
33
|
-
}
|
|
15
|
+
export const registerGitRebaseTool = async (server, getWorkingDirectory, getSessionId) => {
|
|
34
16
|
const operation = "registerGitRebaseTool";
|
|
35
17
|
const context = requestContextService.createRequestContext({ operation });
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
18
|
+
server.registerTool(TOOL_NAME, {
|
|
19
|
+
title: "Git Rebase",
|
|
20
|
+
description: TOOL_DESCRIPTION,
|
|
21
|
+
inputSchema: GitRebaseBaseSchema.shape,
|
|
22
|
+
outputSchema: GitRebaseOutputSchema.shape,
|
|
23
|
+
annotations: {
|
|
24
|
+
readOnlyHint: false,
|
|
25
|
+
destructiveHint: true, // Rebase alters commit history
|
|
26
|
+
idempotentHint: false,
|
|
27
|
+
openWorldHint: false,
|
|
28
|
+
},
|
|
29
|
+
}, async (params, callContext) => {
|
|
30
|
+
const handlerContext = requestContextService.createRequestContext({
|
|
31
|
+
toolName: TOOL_NAME,
|
|
32
|
+
parentContext: callContext,
|
|
33
|
+
});
|
|
34
|
+
try {
|
|
35
|
+
const sessionId = getSessionId(handlerContext);
|
|
36
|
+
const result = await gitRebaseLogic(params, {
|
|
37
|
+
...handlerContext,
|
|
38
|
+
getWorkingDirectory: () => getWorkingDirectory(sessionId),
|
|
46
39
|
});
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
...requestContext,
|
|
51
|
-
sessionId: sessionId,
|
|
52
|
-
getWorkingDirectory: getWorkingDirectoryForSession,
|
|
40
|
+
return {
|
|
41
|
+
structuredContent: result,
|
|
42
|
+
content: [{ type: "text", text: `Success: ${JSON.stringify(result, null, 2)}` }],
|
|
53
43
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
};
|
|
62
|
-
if (rebaseResult.success) {
|
|
63
|
-
logger.info(`Tool ${TOOL_NAME} (mode: ${toolInput.mode}) executed successfully (Needs Manual Action: ${!!rebaseResult.needsManualAction}), returning JSON`, logicContext);
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
logger.warning(`Tool ${TOOL_NAME} (mode: ${toolInput.mode}) failed: ${rebaseResult.message}`, {
|
|
67
|
-
...logicContext,
|
|
68
|
-
errorDetails: rebaseResult.error,
|
|
69
|
-
conflicts: rebaseResult.conflicts,
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
return { content: [resultContent] };
|
|
73
|
-
}, {
|
|
74
|
-
operation: toolOperation,
|
|
75
|
-
context: logicContext,
|
|
76
|
-
input: validatedArgs,
|
|
77
|
-
errorCode: BaseErrorCode.INTERNAL_ERROR,
|
|
44
|
+
}
|
|
45
|
+
catch (error) {
|
|
46
|
+
logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
|
|
47
|
+
const mcpError = ErrorHandler.handleError(error, {
|
|
48
|
+
operation: `tool:${TOOL_NAME}`,
|
|
49
|
+
context: handlerContext,
|
|
50
|
+
input: params,
|
|
78
51
|
});
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
52
|
+
return {
|
|
53
|
+
isError: true,
|
|
54
|
+
content: [{ type: "text", text: `Error: ${mcpError.message}` }],
|
|
55
|
+
structuredContent: {
|
|
56
|
+
code: mcpError.code,
|
|
57
|
+
message: mcpError.message,
|
|
58
|
+
details: mcpError.details,
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
logger.info(`Tool '${TOOL_NAME}' registered successfully.`, context);
|
|
82
64
|
};
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @fileoverview Barrel file for the
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview Barrel file for the gitRemote tool.
|
|
3
|
+
* @module src/mcp-server/tools/gitRemote/index
|
|
4
4
|
*/
|
|
5
|
-
export {
|
|
6
|
-
// Export types if needed elsewhere, e.g.:
|
|
7
|
-
// export type { GitRemoteInput, GitRemoteResult } from './logic.js';
|
|
5
|
+
export { registerGitRemoteTool } from "./registration.js";
|