@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.
Files changed (79) hide show
  1. package/README.md +4 -4
  2. package/dist/mcp-server/server.js +69 -228
  3. package/dist/mcp-server/tools/gitAdd/index.js +2 -4
  4. package/dist/mcp-server/tools/gitAdd/logic.js +40 -116
  5. package/dist/mcp-server/tools/gitAdd/registration.js +39 -59
  6. package/dist/mcp-server/tools/gitBranch/index.js +3 -5
  7. package/dist/mcp-server/tools/gitBranch/logic.js +109 -304
  8. package/dist/mcp-server/tools/gitBranch/registration.js +52 -66
  9. package/dist/mcp-server/tools/gitCheckout/index.js +2 -3
  10. package/dist/mcp-server/tools/gitCheckout/logic.js +47 -144
  11. package/dist/mcp-server/tools/gitCheckout/registration.js +53 -72
  12. package/dist/mcp-server/tools/gitCherryPick/index.js +3 -5
  13. package/dist/mcp-server/tools/gitCherryPick/logic.js +47 -173
  14. package/dist/mcp-server/tools/gitCherryPick/registration.js +52 -67
  15. package/dist/mcp-server/tools/gitClean/index.js +3 -5
  16. package/dist/mcp-server/tools/gitClean/logic.js +45 -154
  17. package/dist/mcp-server/tools/gitClean/registration.js +52 -92
  18. package/dist/mcp-server/tools/gitClearWorkingDir/index.js +3 -5
  19. package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +18 -32
  20. package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +55 -73
  21. package/dist/mcp-server/tools/gitClone/index.js +2 -4
  22. package/dist/mcp-server/tools/gitClone/logic.js +47 -187
  23. package/dist/mcp-server/tools/gitClone/registration.js +51 -42
  24. package/dist/mcp-server/tools/gitCommit/index.js +2 -4
  25. package/dist/mcp-server/tools/gitCommit/logic.js +75 -310
  26. package/dist/mcp-server/tools/gitCommit/registration.js +52 -73
  27. package/dist/mcp-server/tools/gitDiff/index.js +2 -3
  28. package/dist/mcp-server/tools/gitDiff/logic.js +72 -264
  29. package/dist/mcp-server/tools/gitDiff/registration.js +53 -68
  30. package/dist/mcp-server/tools/gitFetch/index.js +2 -3
  31. package/dist/mcp-server/tools/gitFetch/logic.js +38 -136
  32. package/dist/mcp-server/tools/gitFetch/registration.js +54 -66
  33. package/dist/mcp-server/tools/gitInit/index.js +3 -5
  34. package/dist/mcp-server/tools/gitInit/logic.js +40 -162
  35. package/dist/mcp-server/tools/gitInit/registration.js +52 -104
  36. package/dist/mcp-server/tools/gitLog/index.js +2 -3
  37. package/dist/mcp-server/tools/gitLog/logic.js +71 -266
  38. package/dist/mcp-server/tools/gitLog/registration.js +54 -66
  39. package/dist/mcp-server/tools/gitMerge/index.js +3 -5
  40. package/dist/mcp-server/tools/gitMerge/logic.js +45 -191
  41. package/dist/mcp-server/tools/gitMerge/registration.js +52 -71
  42. package/dist/mcp-server/tools/gitPull/index.js +2 -3
  43. package/dist/mcp-server/tools/gitPull/logic.js +39 -156
  44. package/dist/mcp-server/tools/gitPull/registration.js +53 -75
  45. package/dist/mcp-server/tools/gitPush/index.js +2 -3
  46. package/dist/mcp-server/tools/gitPush/logic.js +65 -192
  47. package/dist/mcp-server/tools/gitPush/registration.js +53 -75
  48. package/dist/mcp-server/tools/gitRebase/index.js +3 -5
  49. package/dist/mcp-server/tools/gitRebase/logic.js +59 -207
  50. package/dist/mcp-server/tools/gitRebase/registration.js +52 -70
  51. package/dist/mcp-server/tools/gitRemote/index.js +3 -5
  52. package/dist/mcp-server/tools/gitRemote/logic.js +76 -200
  53. package/dist/mcp-server/tools/gitRemote/registration.js +52 -65
  54. package/dist/mcp-server/tools/gitReset/index.js +2 -3
  55. package/dist/mcp-server/tools/gitReset/logic.js +33 -133
  56. package/dist/mcp-server/tools/gitReset/registration.js +53 -60
  57. package/dist/mcp-server/tools/gitSetWorkingDir/index.js +3 -5
  58. package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +39 -144
  59. package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +55 -85
  60. package/dist/mcp-server/tools/gitShow/index.js +3 -5
  61. package/dist/mcp-server/tools/gitShow/logic.js +28 -133
  62. package/dist/mcp-server/tools/gitShow/registration.js +52 -74
  63. package/dist/mcp-server/tools/gitStash/index.js +3 -5
  64. package/dist/mcp-server/tools/gitStash/logic.js +59 -219
  65. package/dist/mcp-server/tools/gitStash/registration.js +52 -77
  66. package/dist/mcp-server/tools/gitStatus/index.js +2 -4
  67. package/dist/mcp-server/tools/gitStatus/logic.js +79 -236
  68. package/dist/mcp-server/tools/gitStatus/registration.js +52 -66
  69. package/dist/mcp-server/tools/gitTag/index.js +3 -5
  70. package/dist/mcp-server/tools/gitTag/logic.js +57 -198
  71. package/dist/mcp-server/tools/gitTag/registration.js +54 -73
  72. package/dist/mcp-server/tools/gitWorktree/index.js +3 -5
  73. package/dist/mcp-server/tools/gitWorktree/logic.js +102 -328
  74. package/dist/mcp-server/tools/gitWorktree/registration.js +54 -58
  75. package/dist/mcp-server/tools/gitWrapupInstructions/index.js +5 -3
  76. package/dist/mcp-server/tools/gitWrapupInstructions/logic.js +25 -43
  77. package/dist/mcp-server/tools/gitWrapupInstructions/registration.js +54 -70
  78. package/dist/mcp-server/transports/httpTransport.js +2 -3
  79. package/package.json +8 -8
@@ -1,76 +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 { GitLogInputSchema, logGitHistory, } from "./logic.js";
9
- let _getWorkingDirectory;
10
- let _getSessionId;
11
1
  /**
12
- * Initializes the state accessors needed by the tool registration.
13
- * This should be called once during server setup.
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_log tool.
3
+ * @module src/mcp-server/tools/gitLog/registration
16
4
  */
17
- export function initializeGitLogStateAccessors(getWdFn, getSidFn) {
18
- _getWorkingDirectory = getWdFn;
19
- _getSessionId = getSidFn;
20
- logger.info("State accessors initialized for git_log tool registration.");
21
- }
5
+ import { ErrorHandler, logger, requestContextService } from "../../../utils/index.js";
6
+ import { logGitHistory, GitLogInputSchema, GitLogOutputSchema, } from "./logic.js";
22
7
  const TOOL_NAME = "git_log";
23
- const TOOL_DESCRIPTION = "Shows commit logs for the repository. Supports limiting count, filtering by author, date range, and specific branch/file. Returns a JSON object containing a list of commit objects (`commits` array) by default. If `showSignature: true` is used, it returns a JSON object where the `commits` array is empty and the raw signature verification output is included in the `message` field.";
8
+ const TOOL_DESCRIPTION = "Shows commit logs for the repository. Supports limiting count, filtering by author, date range, and specific branch/file. Returns a JSON object containing a list of commit objects (`commits` array) by default. If `showSignature: true` is used, it returns a JSON object where the `commits` array is empty and the raw signature verification output is included in the `rawOutput` field.";
24
9
  /**
25
- * Registers the git_log tool with the MCP server.
26
- *
27
- * @param {McpServer} server - The MCP server instance.
28
- * @throws {Error} If state accessors are not initialized.
10
+ * Registers the git_log 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 function registerGitLogTool(server) {
31
- if (!_getWorkingDirectory || !_getSessionId) {
32
- throw new Error("State accessors for git_log must be initialized before registration.");
33
- }
15
+ export const registerGitLogTool = async (server, getWorkingDirectory, getSessionId) => {
34
16
  const operation = "registerGitLogTool";
35
17
  const context = requestContextService.createRequestContext({ operation });
36
- await ErrorHandler.tryCatch(async () => {
37
- server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitLogInputSchema.shape, // Provide the Zod schema shape
38
- async (validatedArgs, callContext) => {
39
- const toolOperation = "tool:git_log";
40
- const requestContext = requestContextService.createRequestContext({
41
- operation: toolOperation,
42
- parentContext: callContext,
18
+ server.registerTool(TOOL_NAME, {
19
+ title: "Git Log",
20
+ description: TOOL_DESCRIPTION,
21
+ inputSchema: GitLogInputSchema.shape,
22
+ outputSchema: GitLogOutputSchema.shape,
23
+ annotations: {
24
+ readOnlyHint: true,
25
+ destructiveHint: false,
26
+ idempotentHint: true,
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 logGitHistory(params, {
37
+ ...handlerContext,
38
+ getWorkingDirectory: () => getWorkingDirectory(sessionId),
43
39
  });
44
- const sessionId = _getSessionId(requestContext);
45
- const getWorkingDirectoryForSession = () => {
46
- return _getWorkingDirectory(sessionId);
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
- logger.info(`Executing tool: ${TOOL_NAME}`, logicContext);
54
- return await ErrorHandler.tryCatch(async () => {
55
- // Call the core logic function
56
- const logResult = await logGitHistory(validatedArgs, logicContext);
57
- // Format the result (array of commits) as a JSON string within TextContent
58
- const resultContent = {
59
- type: "text",
60
- // Stringify the entire GitLogResult object which includes the commits array and success flag
61
- text: JSON.stringify(logResult, null, 2), // Pretty-print JSON
62
- contentType: "application/json",
63
- };
64
- logger.info(`Tool ${TOOL_NAME} executed successfully, returning JSON`, logicContext);
65
- // Success is determined by the logic function and included in the result object
66
- return { content: [resultContent] };
67
- }, {
68
- operation: toolOperation,
69
- context: logicContext,
70
- input: validatedArgs,
71
- 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,
72
51
  });
73
- });
74
- logger.info(`Tool registered: ${TOOL_NAME}`, context);
75
- }, { operation, context, critical: true });
76
- }
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 git_merge tool.
3
- * Exports the registration function and state accessor initialization function.
2
+ * @fileoverview Barrel file for the gitMerge tool.
3
+ * @module src/mcp-server/tools/gitMerge/index
4
4
  */
5
- export { registerGitMergeTool, initializeGitMergeStateAccessors, } from "./registration.js";
6
- // Export types if needed elsewhere, e.g.:
7
- // export type { GitMergeInput, GitMergeResult } from './logic.js';
5
+ export { registerGitMergeTool } from "./registration.js";
@@ -1,209 +1,63 @@
1
+ /**
2
+ * @fileoverview Defines the core logic, schemas, and types for the git_merge tool.
3
+ * @module src/mcp-server/tools/gitMerge/logic
4
+ */
1
5
  import { execFile } from "child_process";
2
6
  import { promisify } from "util";
3
7
  import { z } from "zod";
4
- // Import utils from barrel (logger from ../utils/internal/logger.js)
5
- import { logger } from "../../../utils/index.js";
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 path from "path"; // Import path module
10
- import { sanitization } from "../../../utils/index.js";
8
+ import { logger, sanitization } from "../../../utils/index.js";
9
+ import { McpError, BaseErrorCode } from "../../../types-global/errors.js";
11
10
  const execFileAsync = promisify(execFile);
12
- // Define the input schema for the git_merge tool
11
+ // 1. DEFINE the Zod input schema.
13
12
  export const GitMergeInputSchema = z.object({
14
- path: z
15
- .string()
16
- .min(1)
17
- .optional()
18
- .default(".")
19
- .describe("Path to the Git repository. Defaults to the directory set via `git_set_working_dir` for the session; set 'git_set_working_dir' if not set."),
20
- branch: z
21
- .string()
22
- .min(1)
23
- .describe("The name of the branch to merge into the current branch."),
24
- commitMessage: z
25
- .string()
26
- .optional()
27
- .describe("Commit message to use for the merge commit (if required, e.g., not fast-forward)."),
28
- noFf: z
29
- .boolean()
30
- .default(false)
31
- .describe("Create a merge commit even when the merge resolves as a fast-forward (`--no-ff`)."),
32
- squash: z
33
- .boolean()
34
- .default(false)
35
- .describe("Combine merged changes into a single commit (`--squash`). Requires manual commit afterwards."),
36
- abort: z
37
- .boolean()
38
- .default(false)
39
- .describe("Abort the current merge process (resolves conflicts)."),
40
- // 'continue' might be too complex for initial implementation due to requiring index manipulation
13
+ path: z.string().default(".").describe("Path to the Git repository."),
14
+ branch: z.string().min(1).describe("The name of the branch to merge into the current branch."),
15
+ commitMessage: z.string().optional().describe("Commit message for the merge commit."),
16
+ noFf: z.boolean().default(false).describe("Create a merge commit even if a fast-forward is possible."),
17
+ squash: z.boolean().default(false).describe("Combine merged changes into a single commit (requires manual commit)."),
18
+ abort: z.boolean().default(false).describe("Abort the current merge process."),
19
+ });
20
+ // 2. DEFINE the Zod response schema.
21
+ export const GitMergeOutputSchema = z.object({
22
+ success: z.boolean().describe("Indicates if the command was successful."),
23
+ message: z.string().describe("A summary message of the result."),
24
+ conflict: z.boolean().optional().describe("True if the merge resulted in conflicts."),
25
+ fastForward: z.boolean().optional().describe("True if the merge was a fast-forward."),
26
+ aborted: z.boolean().optional().describe("True if the merge was aborted."),
27
+ needsManualCommit: z.boolean().optional().describe("True if --squash was used."),
41
28
  });
42
29
  /**
43
- * Executes the 'git merge' command.
44
- *
45
- * @param {GitMergeInput} input - The validated input object.
46
- * @param {RequestContext} context - The request context.
47
- * @returns {Promise<GitMergeResult>} A promise that resolves with the structured merge result.
48
- * @throws {McpError} Throws an McpError for path issues, command failures, or unexpected errors.
30
+ * 4. IMPLEMENT the core logic function.
31
+ * @throws {McpError} If the logic encounters an unrecoverable issue.
49
32
  */
50
- export async function gitMergeLogic(input, context) {
33
+ export async function gitMergeLogic(params, context) {
51
34
  const operation = "gitMergeLogic";
52
- logger.debug(`Executing ${operation}`, { ...context, input });
53
- let targetPath;
54
- try {
55
- // Resolve the target path
56
- let resolvedPath;
57
- if (input.path && input.path !== ".") {
58
- // If a specific path is given, resolve it absolutely first
59
- // Assuming input.path could be relative *to the server's CWD* if no session WD is set,
60
- // but it's safer to require absolute paths or rely on session WD.
61
- // For simplicity, let's assume input.path is intended relative to session WD if set, or absolute otherwise.
62
- const workingDir = context.getWorkingDirectory();
63
- if (workingDir) {
64
- resolvedPath = path.resolve(workingDir, input.path); // Resolve relative to session WD
65
- }
66
- else if (path.isAbsolute(input.path)) {
67
- resolvedPath = input.path; // Use absolute path directly
68
- }
69
- else {
70
- // If relative path given without session WD, it's ambiguous. Error out.
71
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Relative path provided but no working directory set for the session.", { context, operation });
72
- }
73
- logger.debug(`Resolved provided path: ${resolvedPath}`, {
74
- ...context,
75
- operation,
76
- });
77
- }
78
- else {
79
- const workingDir = context.getWorkingDirectory();
80
- if (!workingDir) {
81
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, "No path provided and no working directory set for the session.", { context, operation });
82
- }
83
- resolvedPath = workingDir; // Use session working directory
84
- logger.debug(`Using session working directory: ${resolvedPath}`, {
85
- ...context,
86
- operation,
87
- sessionId: context.sessionId,
88
- });
89
- }
90
- // Sanitize the resolved path
91
- // We assume the resolved path should be absolute for git commands.
92
- // sanitizePath checks for traversal and normalizes.
93
- targetPath = sanitization.sanitizePath(resolvedPath, {
94
- allowAbsolute: true,
95
- }).sanitizedPath;
96
- logger.debug(`Sanitized path: ${targetPath}`, { ...context, operation });
35
+ logger.debug(`Executing ${operation}`, { ...context, params });
36
+ const workingDir = context.getWorkingDirectory();
37
+ if (params.path === "." && !workingDir) {
38
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, "No session working directory set. Please specify a 'path' or use 'git_set_working_dir' first.");
97
39
  }
98
- catch (error) {
99
- logger.error("Path resolution or sanitization failed", {
100
- ...context,
101
- operation,
102
- error,
103
- });
104
- if (error instanceof McpError) {
105
- throw error;
106
- }
107
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
108
- }
109
- // --- Construct the git merge command ---
40
+ const targetPath = sanitization.sanitizePath(params.path === "." ? workingDir : params.path, { allowAbsolute: true }).sanitizedPath;
110
41
  const args = ["-C", targetPath, "merge"];
111
- if (input.abort) {
42
+ if (params.abort) {
112
43
  args.push("--abort");
113
44
  }
114
45
  else {
115
- // Standard merge options
116
- if (input.noFf) {
46
+ if (params.noFf)
117
47
  args.push("--no-ff");
118
- }
119
- if (input.squash) {
48
+ if (params.squash)
120
49
  args.push("--squash");
121
- }
122
- if (input.commitMessage && !input.squash) {
123
- // Commit message only relevant if not squashing (squash requires separate commit)
124
- args.push("-m", input.commitMessage);
125
- }
126
- else if (input.squash && input.commitMessage) {
127
- logger.warning("Commit message provided with --squash, but it will be ignored. Squash requires a separate commit.", { ...context, operation });
128
- }
129
- args.push(input.branch); // Add branch to merge
130
- }
131
- logger.debug(`Executing command: git ${args.join(" ")}`, {
132
- ...context,
133
- operation,
134
- });
135
- // --- Execute and Parse ---
136
- try {
137
- const { stdout, stderr } = await execFileAsync("git", args);
138
- logger.debug(`Command stdout: ${stdout}`, { ...context, operation });
139
- if (stderr)
140
- logger.debug(`Command stderr: ${stderr}`, { ...context, operation }); // Log stderr even on success
141
- const result = {
142
- success: true,
143
- message: stdout.trim() || stderr.trim() || "Merge command executed.",
144
- };
145
- if (input.abort) {
146
- result.aborted = true;
147
- result.message = "Merge aborted successfully.";
148
- }
149
- else if (stdout.includes("Fast-forward")) {
150
- result.fastForward = true;
151
- }
152
- else if (stdout.includes("Merge made by") || stdout.includes("merging")) {
153
- const match = stdout.match(/Merge commit '([a-f0-9]+)'/);
154
- result.mergedCommitHash = match ? match[1] : undefined;
155
- result.fastForward = false;
156
- }
157
- else if (stdout.includes("Squash commit -- not updating HEAD")) {
158
- result.needsManualCommit = true;
159
- }
160
- else if (stdout.includes("Already up to date")) {
161
- result.fastForward = true;
162
- }
163
- logger.info("git merge executed successfully", {
164
- ...context,
165
- operation,
166
- path: targetPath,
167
- result,
168
- });
169
- return result;
170
- }
171
- catch (error) {
172
- const errorMessage = error.stderr || error.stdout || error.message || ""; // Git often puts errors in stdout/stderr
173
- logger.error(`Git merge command failed`, {
174
- ...context,
175
- operation,
176
- path: targetPath,
177
- error: error.message,
178
- output: errorMessage,
179
- });
180
- if (input.abort) {
181
- // If abort failed, it's likely there was no merge in progress
182
- if (errorMessage.includes("fatal: There is no merge to abort")) {
183
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, `No merge in progress to abort.`, { context, operation, originalError: error });
184
- }
185
- throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to abort merge: ${errorMessage}`, { context, operation, originalError: error });
186
- }
187
- // Check for specific failure scenarios
188
- if (errorMessage.includes("CONFLICT")) {
189
- throw new McpError(BaseErrorCode.CONFLICT, `Merge failed due to conflicts. Please resolve conflicts and commit. Output: ${errorMessage}`, { context, operation, originalError: error });
190
- }
191
- if (errorMessage.includes("refusing to merge unrelated histories")) {
192
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Merge failed: Refusing to merge unrelated histories. Consider using '--allow-unrelated-histories'.`, { context, operation, originalError: error });
193
- }
194
- if (errorMessage.includes("fatal: Not possible to fast-forward, aborting.")) {
195
- throw new McpError(BaseErrorCode.CONFLICT, `Merge failed: Not possible to fast-forward. Merge required.`, { context, operation, originalError: error });
196
- }
197
- if (errorMessage.match(/fatal: '.*?' does not point to a commit/)) {
198
- throw new McpError(BaseErrorCode.NOT_FOUND, `Merge failed: Branch '${input.branch}' not found or does not point to a commit.`, { context, operation, originalError: error });
199
- }
200
- if (errorMessage.includes("fatal: You have not concluded your merge")) {
201
- throw new McpError(BaseErrorCode.CONFLICT, `Merge failed: Conflicts still exist from a previous merge. Resolve conflicts or abort. Output: ${errorMessage}`, { context, operation, originalError: error });
202
- }
203
- if (errorMessage.includes("error: Your local changes to the following files would be overwritten by merge")) {
204
- throw new McpError(BaseErrorCode.CONFLICT, `Merge failed: Local changes would be overwritten. Please commit or stash them.`, { context, operation, originalError: error });
205
- }
206
- // Generic error
207
- throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git merge command failed for path ${targetPath}: ${errorMessage}`, { context, operation, originalError: error });
50
+ if (params.commitMessage && !params.squash)
51
+ args.push("-m", params.commitMessage);
52
+ args.push(params.branch);
208
53
  }
54
+ logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
55
+ const { stdout } = await execFileAsync("git", args);
56
+ return {
57
+ success: true,
58
+ message: stdout.trim() || "Merge command executed successfully.",
59
+ fastForward: stdout.includes("Fast-forward"),
60
+ needsManualCommit: params.squash,
61
+ aborted: params.abort,
62
+ };
209
63
  }
@@ -1,83 +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 { GitMergeInputSchema, gitMergeLogic, } from "./logic.js";
9
- let _getWorkingDirectory;
10
- let _getSessionId;
11
1
  /**
12
- * Initializes the state accessors needed by the gitMerge tool registration.
13
- * @param getWdFn - Function to get the working directory for a session.
14
- * @param getSidFn - Function to get the session ID from context.
2
+ * @fileoverview Handles registration and error handling for the git_merge tool.
3
+ * @module src/mcp-server/tools/gitMerge/registration
15
4
  */
16
- export function initializeGitMergeStateAccessors(getWdFn, getSidFn) {
17
- _getWorkingDirectory = getWdFn;
18
- _getSessionId = getSidFn;
19
- logger.info("State accessors initialized for git_merge tool registration.");
20
- }
5
+ import { ErrorHandler, logger, requestContextService } from "../../../utils/index.js";
6
+ import { gitMergeLogic, GitMergeInputSchema, GitMergeOutputSchema, } from "./logic.js";
21
7
  const TOOL_NAME = "git_merge";
22
8
  const TOOL_DESCRIPTION = "Merges the specified branch into the current branch. Supports options like --no-ff, --squash, and --abort. Returns the merge result as a JSON object.";
23
9
  /**
24
- * Registers the git_merge tool with the MCP server.
25
- *
26
- * @param {McpServer} server - The McpServer instance.
27
- * @returns {Promise<void>}
28
- * @throws {Error} If registration fails or state accessors are not initialized.
10
+ * Registers the git_merge 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 registerGitMergeTool = async (server) => {
31
- if (!_getWorkingDirectory || !_getSessionId) {
32
- throw new Error("State accessors for git_merge must be initialized before registration.");
33
- }
15
+ export const registerGitMergeTool = async (server, getWorkingDirectory, getSessionId) => {
34
16
  const operation = "registerGitMergeTool";
35
17
  const context = requestContextService.createRequestContext({ operation });
36
- await ErrorHandler.tryCatch(async () => {
37
- server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitMergeInputSchema.shape, // Provide the Zod schema shape
38
- async (validatedArgs, callContext) => {
39
- const toolOperation = "tool:git_merge";
40
- const requestContext = requestContextService.createRequestContext({
41
- operation: toolOperation,
42
- parentContext: callContext,
18
+ server.registerTool(TOOL_NAME, {
19
+ title: "Git Merge",
20
+ description: TOOL_DESCRIPTION,
21
+ inputSchema: GitMergeInputSchema.shape,
22
+ outputSchema: GitMergeOutputSchema.shape,
23
+ annotations: {
24
+ readOnlyHint: false,
25
+ destructiveHint: true, // Can create merge commits and change 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 gitMergeLogic(params, {
37
+ ...handlerContext,
38
+ getWorkingDirectory: () => getWorkingDirectory(sessionId),
43
39
  });
44
- const sessionId = _getSessionId(requestContext);
45
- const getWorkingDirectoryForSession = () => {
46
- return _getWorkingDirectory(sessionId);
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
- logger.info(`Executing tool: ${TOOL_NAME}`, logicContext);
54
- return await ErrorHandler.tryCatch(async () => {
55
- // Call the core logic function
56
- const mergeResult = await gitMergeLogic(validatedArgs, logicContext);
57
- // Format the result as a JSON string within TextContent
58
- const resultContent = {
59
- type: "text",
60
- text: JSON.stringify(mergeResult, null, 2), // Pretty-print JSON
61
- contentType: "application/json",
62
- };
63
- // Log based on the success flag in the result
64
- if (mergeResult.success) {
65
- logger.info(`Tool ${TOOL_NAME} executed successfully, returning JSON`, logicContext);
66
- }
67
- else {
68
- // Log non-fatal conditions (like conflicts) differently from execution errors
69
- logger.info(`Tool ${TOOL_NAME} completed with specific condition (e.g., conflict, no merge to abort), returning JSON`, logicContext);
70
- }
71
- // Even if success is false (e.g., conflicts), it's not a tool execution *error* in the MCP sense,
72
- // the tool ran, but the git operation failed predictably. Return the structured result.
73
- return { content: [resultContent] };
74
- }, {
75
- operation: toolOperation,
76
- context: logicContext,
77
- input: validatedArgs,
78
- errorCode: BaseErrorCode.INTERNAL_ERROR, // Default for unexpected logic errors
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,
79
51
  });
80
- });
81
- logger.info(`Tool registered: ${TOOL_NAME}`, context);
82
- }, { operation, context, critical: true });
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);
83
64
  };
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * @fileoverview Barrel file for the gitPull tool.
3
+ * @module src/mcp-server/tools/gitPull/index
3
4
  */
4
- export { registerGitPullTool, initializeGitPullStateAccessors, } from "./registration.js";
5
- // Export types if needed elsewhere, e.g.:
6
- // export type { GitPullInput, GitPullResult } from './logic.js';
5
+ export { registerGitPullTool } from "./registration.js";