@cyanheads/git-mcp-server 2.1.8 → 2.2.0

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 +17 -74
  5. package/dist/mcp-server/tools/gitAdd/registration.js +38 -59
  6. package/dist/mcp-server/tools/gitBranch/index.js +3 -5
  7. package/dist/mcp-server/tools/gitBranch/logic.js +118 -296
  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 -122
  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 +55 -162
  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 +44 -143
  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 +19 -26
  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 +50 -171
  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 +90 -295
  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 +78 -254
  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 +47 -129
  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 +46 -152
  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 +75 -257
  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 +52 -179
  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 +48 -146
  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 +73 -181
  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 +73 -202
  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 +85 -193
  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 +37 -121
  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 +45 -133
  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 +33 -122
  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 +70 -214
  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 +82 -229
  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 +66 -188
  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 +112 -322
  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 +26 -38
  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,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 { GitPullInputSchema, pullGitChanges, } 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_pull tool.
3
+ * @module src/mcp-server/tools/gitPull/registration
16
4
  */
17
- export function initializeGitPullStateAccessors(getWdFn, getSidFn) {
18
- _getWorkingDirectory = getWdFn;
19
- _getSessionId = getSidFn;
20
- logger.info("State accessors initialized for git_pull tool registration.");
21
- }
5
+ import { ErrorHandler, logger, requestContextService } from "../../../utils/index.js";
6
+ import { McpError, BaseErrorCode } from "../../../types-global/errors.js";
7
+ import { pullGitChanges, GitPullInputSchema, GitPullOutputSchema, } from "./logic.js";
22
8
  const TOOL_NAME = "git_pull";
23
9
  const TOOL_DESCRIPTION = "Fetches from and integrates with another repository or a local branch (e.g., 'git pull origin main'). Supports rebase and fast-forward only options. Returns the pull result as a JSON object.";
24
10
  /**
25
- * Registers the git_pull tool with the MCP server.
26
- *
27
- * @param {McpServer} server - The MCP server instance.
28
- * @throws {Error} If state accessors are not initialized.
11
+ * Registers the git_pull tool with the MCP server instance.
12
+ * @param server The MCP server instance.
13
+ * @param getWorkingDirectory Function to get the session's working directory.
14
+ * @param getSessionId Function to get the session ID from context.
29
15
  */
30
- export async function registerGitPullTool(server) {
31
- if (!_getWorkingDirectory || !_getSessionId) {
32
- throw new Error("State accessors for git_pull must be initialized before registration.");
33
- }
16
+ export const registerGitPullTool = async (server, getWorkingDirectory, getSessionId) => {
34
17
  const operation = "registerGitPullTool";
35
18
  const context = requestContextService.createRequestContext({ operation });
36
- await ErrorHandler.tryCatch(async () => {
37
- server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitPullInputSchema.shape, // Provide the Zod schema shape
38
- async (validatedArgs, callContext) => {
39
- const toolOperation = "tool:git_pull";
40
- const requestContext = requestContextService.createRequestContext({
41
- operation: toolOperation,
42
- parentContext: callContext,
19
+ server.registerTool(TOOL_NAME, {
20
+ title: "Git Pull",
21
+ description: TOOL_DESCRIPTION,
22
+ inputSchema: GitPullInputSchema.shape,
23
+ outputSchema: GitPullOutputSchema.shape,
24
+ annotations: {
25
+ readOnlyHint: false,
26
+ destructiveHint: true, // Can change local files and history
27
+ idempotentHint: false,
28
+ openWorldHint: true, // Interacts with remote repositories
29
+ },
30
+ }, async (params, callContext) => {
31
+ const handlerContext = requestContextService.createRequestContext({
32
+ toolName: TOOL_NAME,
33
+ parentContext: callContext,
34
+ });
35
+ try {
36
+ const sessionId = getSessionId(handlerContext);
37
+ const result = await pullGitChanges(params, {
38
+ ...handlerContext,
39
+ getWorkingDirectory: () => getWorkingDirectory(sessionId),
43
40
  });
44
- const sessionId = _getSessionId(requestContext);
45
- const getWorkingDirectoryForSession = () => {
46
- return _getWorkingDirectory(sessionId);
47
- };
48
- const logicContext = {
49
- ...requestContext,
50
- sessionId: sessionId,
51
- getWorkingDirectory: getWorkingDirectoryForSession,
41
+ return {
42
+ structuredContent: result,
43
+ content: [{ type: "text", text: `Success: ${JSON.stringify(result, null, 2)}` }],
52
44
  };
53
- logger.info(`Executing tool: ${TOOL_NAME}`, logicContext);
54
- return await ErrorHandler.tryCatch(async () => {
55
- // Call the core logic function
56
- const pullResult = await pullGitChanges(validatedArgs, logicContext);
57
- // Format the result as a JSON string within TextContent
58
- const resultContent = {
59
- type: "text",
60
- text: JSON.stringify(pullResult, null, 2), // Pretty-print JSON
61
- contentType: "application/json",
62
- };
63
- // Log based on the success flag in the result
64
- if (pullResult.success) {
65
- logger.info(`Tool ${TOOL_NAME} executed successfully, returning JSON`, logicContext);
66
- }
67
- else {
68
- // Log non-fatal conditions like conflicts or already up-to-date differently
69
- const logMessage = pullResult.conflict
70
- ? `Tool ${TOOL_NAME} completed with merge conflicts, returning JSON`
71
- : `Tool ${TOOL_NAME} completed with status: ${pullResult.message}, returning JSON`;
72
- logger.info(logMessage, logicContext);
73
- }
74
- // Even if success is false (e.g., conflicts), 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
45
+ }
46
+ catch (error) {
47
+ logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
48
+ const handledError = ErrorHandler.handleError(error, {
49
+ operation: `tool:${TOOL_NAME}`,
50
+ context: handlerContext,
51
+ input: params,
82
52
  });
83
- });
84
- logger.info(`Tool registered: ${TOOL_NAME}`, context);
85
- }, { operation, context, critical: true });
86
- }
53
+ const mcpError = handledError instanceof McpError
54
+ ? handledError
55
+ : new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred.", { originalError: handledError });
56
+ return {
57
+ isError: true,
58
+ content: [{ type: "text", text: mcpError.message }],
59
+ structuredContent: mcpError.details,
60
+ };
61
+ }
62
+ });
63
+ logger.info(`Tool '${TOOL_NAME}' registered successfully.`, context);
64
+ };
@@ -1,6 +1,5 @@
1
1
  /**
2
2
  * @fileoverview Barrel file for the gitPush tool.
3
+ * @module src/mcp-server/tools/gitPush/index
3
4
  */
4
- export { registerGitPushTool, initializeGitPushStateAccessors, } from "./registration.js";
5
- // Export types if needed elsewhere, e.g.:
6
- // export type { GitPushInput, GitPushResult } from './logic.js';
5
+ export { registerGitPushTool } from "./registration.js";
@@ -1,205 +1,97 @@
1
+ /**
2
+ * @fileoverview Defines the core logic, schemas, and types for the git_push tool.
3
+ * @module src/mcp-server/tools/gitPush/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 { 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
- // Define the input schema for the git_push tool using Zod
12
- export const GitPushInputSchema = z.object({
13
- path: z
14
- .string()
15
- .min(1)
16
- .optional()
17
- .default(".")
18
- .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."),
19
- remote: z
20
- .string()
21
- .optional()
22
- .describe("The remote repository to push to (e.g., 'origin'). Defaults to the tracked upstream or 'origin'."),
23
- branch: z
24
- .string()
25
- .optional()
26
- .describe("The local branch to push (e.g., 'main', 'feat/new-login'). Defaults to the current branch."),
27
- remoteBranch: z
28
- .string()
29
- .optional()
30
- .describe("The remote branch to push to (e.g., 'main', 'develop'). Defaults to the same name as the local branch."),
31
- force: z
32
- .boolean()
33
- .optional()
34
- .default(false)
35
- .describe("Force the push (use with caution: `--force-with-lease` is generally safer)."),
36
- forceWithLease: z
37
- .boolean()
38
- .optional()
39
- .default(false)
40
- .describe("Force the push only if the remote ref is the expected value (`--force-with-lease`). Safer than --force."),
41
- setUpstream: z
42
- .boolean()
43
- .optional()
44
- .default(false)
45
- .describe("Set the upstream tracking configuration (`-u` or `--set-upstream`)."),
46
- tags: z
47
- .boolean()
48
- .optional()
49
- .default(false)
50
- .describe("Push all tags (`--tags`)."),
51
- delete: z
52
- .boolean()
53
- .optional()
54
- .default(false)
55
- .describe("Delete the remote branch (`--delete`). Requires `branch` to be specified. Use with caution, as deleting remote branches can affect collaborators."),
56
- // Add other relevant git push options as needed (e.g., --prune, --all)
11
+ // 1. DEFINE the Zod input schema.
12
+ export const GitPushBaseSchema = z.object({
13
+ path: z.string().default(".").describe("Path to the Git repository."),
14
+ remote: z.string().optional().describe("The remote repository to push to (e.g., 'origin')."),
15
+ branch: z.string().optional().describe("The local branch to push."),
16
+ remoteBranch: z.string().optional().describe("The remote branch to push to."),
17
+ force: z.boolean().default(false).describe("Force the push (use with caution)."),
18
+ forceWithLease: z.boolean().default(false).describe("Force the push only if the remote ref is as expected."),
19
+ setUpstream: z.boolean().default(false).describe("Set the upstream tracking configuration."),
20
+ tags: z.boolean().default(false).describe("Push all tags."),
21
+ delete: z.boolean().default(false).describe("Delete the remote branch."),
22
+ });
23
+ export const GitPushInputSchema = GitPushBaseSchema.refine(data => !(data.delete && !data.branch), {
24
+ message: "Cannot use --delete without specifying a branch to delete.",
25
+ path: ["delete", "branch"],
26
+ }).refine(data => !(data.force && data.forceWithLease), {
27
+ message: "Cannot use --force and --force-with-lease together.",
28
+ path: ["force", "forceWithLease"],
29
+ });
30
+ // 2. DEFINE the Zod response schema.
31
+ export const GitPushOutputSchema = z.object({
32
+ success: z.boolean().describe("Indicates if the command was successful."),
33
+ message: z.string().describe("A summary message of the result."),
34
+ rejected: z.boolean().optional().describe("True if the push was rejected."),
35
+ deleted: z.boolean().optional().describe("True if a remote branch was deleted."),
57
36
  });
58
37
  /**
59
- * Executes the 'git push' command and returns structured JSON output.
60
- *
61
- * @param {GitPushInput} input - The validated input object.
62
- * @param {RequestContext} context - The request context for logging and error handling.
63
- * @returns {Promise<GitPushResult>} A promise that resolves with the structured push result.
64
- * @throws {McpError} Throws an McpError if path resolution, validation, or the git command fails unexpectedly.
38
+ * 4. IMPLEMENT the core logic function.
39
+ * @throws {McpError} If the logic encounters an unrecoverable issue.
65
40
  */
66
- export async function pushGitChanges(input, context) {
41
+ export async function pushGitChanges(params, context) {
67
42
  const operation = "pushGitChanges";
68
- logger.debug(`Executing ${operation}`, { ...context, input });
69
- let targetPath;
70
- try {
71
- // Resolve and sanitize the target path
72
- if (input.path && input.path !== ".") {
73
- targetPath = input.path;
43
+ logger.debug(`Executing ${operation}`, { ...context, params });
44
+ const workingDir = context.getWorkingDirectory();
45
+ if (params.path === "." && !workingDir) {
46
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, "No session working directory set. Please specify a 'path' or use 'git_set_working_dir' first.");
47
+ }
48
+ const targetPath = sanitization.sanitizePath(params.path === "." ? workingDir : params.path, { allowAbsolute: true }).sanitizedPath;
49
+ const args = ["-C", targetPath, "push"];
50
+ if (params.force)
51
+ args.push("--force");
52
+ else if (params.forceWithLease)
53
+ args.push("--force-with-lease");
54
+ if (params.setUpstream)
55
+ args.push("--set-upstream");
56
+ if (params.tags)
57
+ args.push("--tags");
58
+ if (params.delete)
59
+ args.push("--delete");
60
+ args.push(params.remote || "origin");
61
+ if (params.branch) {
62
+ if (params.remoteBranch && !params.delete) {
63
+ args.push(`${params.branch}:${params.remoteBranch}`);
74
64
  }
75
65
  else {
76
- const workingDir = context.getWorkingDirectory();
77
- if (!workingDir) {
78
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, "No path provided and no working directory set for the session.", { context, operation });
79
- }
80
- targetPath = workingDir;
66
+ args.push(params.branch);
81
67
  }
82
- targetPath = sanitization.sanitizePath(targetPath, {
83
- allowAbsolute: true,
84
- }).sanitizedPath;
85
- logger.debug("Sanitized path", {
86
- ...context,
87
- operation,
88
- sanitizedPath: targetPath,
89
- });
90
- }
91
- catch (error) {
92
- logger.error("Path resolution or sanitization failed", {
93
- ...context,
94
- operation,
95
- error,
96
- });
97
- if (error instanceof McpError)
98
- throw error;
99
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
100
- }
101
- // Validate specific input combinations
102
- if (input.delete && !input.branch) {
103
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Cannot use --delete without specifying a branch to delete.", { context, operation });
104
- }
105
- if (input.force && input.forceWithLease) {
106
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Cannot use --force and --force-with-lease together.", { context, operation });
107
- }
108
- if (input.delete &&
109
- (input.force || input.forceWithLease || input.setUpstream || input.tags)) {
110
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Cannot combine --delete with --force, --force-with-lease, --set-upstream, or --tags.", { context, operation });
111
68
  }
112
69
  try {
113
- // Construct the git push command
114
- const args = ["-C", targetPath, "push"];
115
- if (input.force) {
116
- args.push("--force");
117
- }
118
- else if (input.forceWithLease) {
119
- args.push("--force-with-lease");
120
- }
121
- if (input.setUpstream) {
122
- args.push("--set-upstream");
123
- }
124
- if (input.tags) {
125
- args.push("--tags");
126
- }
127
- if (input.delete) {
128
- args.push("--delete");
129
- }
130
- // Add remote and branch specification
131
- const remote = input.remote || "origin"; // Default to origin
132
- args.push(remote);
133
- if (input.branch) {
134
- if (input.remoteBranch && !input.delete) {
135
- args.push(`${input.branch}:${input.remoteBranch}`);
136
- }
137
- else {
138
- args.push(input.branch);
139
- }
140
- }
141
- else if (!input.tags && !input.delete) {
142
- // If no branch, tags, or delete specified, push the current branch by default
143
- logger.debug("No specific branch, tags, or delete specified. Relying on default git push behavior for current branch.", { ...context, operation });
144
- }
145
- logger.debug(`Executing command: git ${args.join(" ")}`, {
146
- ...context,
147
- operation,
148
- });
149
- // Execute command. Note: Git push often uses stderr for progress and success messages.
70
+ logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
150
71
  const { stdout, stderr } = await execFileAsync("git", args);
151
- logger.debug(`Git push stdout: ${stdout}`, { ...context, operation });
152
- if (stderr) {
153
- logger.debug(`Git push stderr: ${stderr}`, { ...context, operation });
154
- }
155
- // Analyze stderr primarily, fallback to stdout
156
- const message = stderr.trim() || stdout.trim() || "Push command executed.";
157
- const summary = message;
158
- const rejected = message.includes("[rejected]");
159
- const deleted = message.includes("[deleted]");
160
- logger.info("git push executed successfully", {
161
- ...context,
162
- operation,
163
- path: targetPath,
164
- summary,
165
- rejected,
166
- deleted,
167
- });
168
- return { success: true, message, summary, rejected, deleted };
72
+ const message = stderr.trim() || stdout.trim() || "Push command executed successfully.";
73
+ return {
74
+ success: true,
75
+ message,
76
+ rejected: message.includes("[rejected]"),
77
+ deleted: message.includes("[deleted]"),
78
+ };
169
79
  }
170
80
  catch (error) {
171
- logger.error(`Failed to execute git push command`, {
172
- ...context,
173
- operation,
174
- path: targetPath,
175
- error: error.message,
176
- stderr: error.stderr,
177
- stdout: error.stdout,
178
- });
179
81
  const errorMessage = error.stderr || error.stdout || error.message || "";
180
- // Handle specific error cases
82
+ logger.error(`Failed to execute git push command`, { ...context, operation, errorMessage });
181
83
  if (errorMessage.toLowerCase().includes("not a git repository")) {
182
- throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`, { context, operation, originalError: error });
183
- }
184
- if (errorMessage.includes("resolve host") ||
185
- errorMessage.includes("Could not read from remote repository") ||
186
- errorMessage.includes("Connection timed out")) {
187
- throw new McpError(BaseErrorCode.SERVICE_UNAVAILABLE, `Failed to connect to remote repository. Error: ${errorMessage}`, { context, operation, originalError: error });
84
+ throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`);
188
85
  }
189
- if (errorMessage.includes("rejected") ||
190
- errorMessage.includes("failed to push some refs")) {
191
- // This might be caught here if execAsync throws due to non-zero exit code on rejection
192
- throw new McpError(BaseErrorCode.CONFLICT, `Push rejected: ${errorMessage}`, { context, operation, originalError: error });
86
+ if (errorMessage.includes("Could not read from remote repository")) {
87
+ throw new McpError(BaseErrorCode.SERVICE_UNAVAILABLE, "Failed to connect to remote repository.");
193
88
  }
194
- if (errorMessage.includes("Authentication failed") ||
195
- errorMessage.includes("Permission denied")) {
196
- throw new McpError(BaseErrorCode.UNAUTHORIZED, `Authentication failed for remote repository. Error: ${errorMessage}`, { context, operation, originalError: error });
89
+ if (errorMessage.includes("rejected")) {
90
+ throw new McpError(BaseErrorCode.CONFLICT, `Push rejected: ${errorMessage}`);
197
91
  }
198
- if (errorMessage.includes("src refspec") &&
199
- errorMessage.includes("does not match any")) {
200
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Push failed: Source branch/refspec does not exist locally. Error: ${errorMessage}`, { context, operation, originalError: error });
92
+ if (errorMessage.includes("Authentication failed")) {
93
+ throw new McpError(BaseErrorCode.UNAUTHORIZED, "Authentication failed for remote repository.");
201
94
  }
202
- // Generic internal error for other failures
203
- throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to push changes for path: ${targetPath}. Error: ${errorMessage}`, { context, operation, originalError: error });
95
+ throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git push failed: ${errorMessage}`);
204
96
  }
205
97
  }
@@ -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
- * 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_push tool.
3
+ * @module src/mcp-server/tools/gitPush/registration
16
4
  */
17
- export function initializeGitPushStateAccessors(getWdFn, getSidFn) {
18
- _getWorkingDirectory = getWdFn;
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 { McpError, BaseErrorCode } from "../../../types-global/errors.js";
7
+ import { pushGitChanges, GitPushOutputSchema, GitPushBaseSchema, } from "./logic.js";
22
8
  const TOOL_NAME = "git_push";
23
9
  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
10
  /**
25
- * Registers the git_push tool with the MCP server.
26
- *
27
- * @param {McpServer} server - The MCP server instance.
28
- * @throws {Error} If state accessors are not initialized.
11
+ * Registers the git_push tool with the MCP server instance.
12
+ * @param server The MCP server instance.
13
+ * @param getWorkingDirectory Function to get the session's working directory.
14
+ * @param getSessionId Function to get the session ID from context.
29
15
  */
30
- export async function registerGitPushTool(server) {
31
- if (!_getWorkingDirectory || !_getSessionId) {
32
- throw new Error("State accessors for git_push must be initialized before registration.");
33
- }
16
+ export const registerGitPushTool = async (server, getWorkingDirectory, getSessionId) => {
34
17
  const operation = "registerGitPushTool";
35
18
  const context = requestContextService.createRequestContext({ operation });
36
- await ErrorHandler.tryCatch(async () => {
37
- server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitPushInputSchema.shape, // Provide the Zod schema shape
38
- async (validatedArgs, callContext) => {
39
- const toolOperation = "tool:git_push";
40
- const requestContext = requestContextService.createRequestContext({
41
- operation: toolOperation,
42
- parentContext: callContext,
19
+ server.registerTool(TOOL_NAME, {
20
+ title: "Git Push",
21
+ description: TOOL_DESCRIPTION,
22
+ inputSchema: GitPushBaseSchema.shape,
23
+ outputSchema: GitPushOutputSchema.shape,
24
+ annotations: {
25
+ readOnlyHint: false,
26
+ destructiveHint: true, // Can alter remote history
27
+ idempotentHint: false,
28
+ openWorldHint: true, // Interacts with remote repositories
29
+ },
30
+ }, async (params, callContext) => {
31
+ const handlerContext = requestContextService.createRequestContext({
32
+ toolName: TOOL_NAME,
33
+ parentContext: callContext,
34
+ });
35
+ try {
36
+ const sessionId = getSessionId(handlerContext);
37
+ const result = await pushGitChanges(params, {
38
+ ...handlerContext,
39
+ getWorkingDirectory: () => getWorkingDirectory(sessionId),
43
40
  });
44
- const sessionId = _getSessionId(requestContext);
45
- const getWorkingDirectoryForSession = () => {
46
- return _getWorkingDirectory(sessionId);
47
- };
48
- const logicContext = {
49
- ...requestContext,
50
- sessionId: sessionId,
51
- getWorkingDirectory: getWorkingDirectoryForSession,
41
+ return {
42
+ structuredContent: result,
43
+ content: [{ type: "text", text: `Success: ${JSON.stringify(result, null, 2)}` }],
52
44
  };
53
- logger.info(`Executing tool: ${TOOL_NAME}`, logicContext);
54
- return await ErrorHandler.tryCatch(async () => {
55
- // Call the core logic function
56
- const pushResult = await pushGitChanges(validatedArgs, logicContext);
57
- // Format the result as a JSON string within TextContent
58
- const resultContent = {
59
- type: "text",
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
45
+ }
46
+ catch (error) {
47
+ logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
48
+ const handledError = ErrorHandler.handleError(error, {
49
+ operation: `tool:${TOOL_NAME}`,
50
+ context: handlerContext,
51
+ input: params,
82
52
  });
83
- });
84
- logger.info(`Tool registered: ${TOOL_NAME}`, context);
85
- }, { operation, context, critical: true });
86
- }
53
+ const mcpError = handledError instanceof McpError
54
+ ? handledError
55
+ : new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred.", { originalError: handledError });
56
+ return {
57
+ isError: true,
58
+ content: [{ type: "text", text: mcpError.message }],
59
+ structuredContent: mcpError.details,
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_rebase tool.
3
- * Exports the registration function and state accessor initialization function.
2
+ * @fileoverview Barrel file for the gitRebase tool.
3
+ * @module src/mcp-server/tools/gitRebase/index
4
4
  */
5
- export { registerGitRebaseTool, initializeGitRebaseStateAccessors, } from "./registration.js";
6
- // Export types if needed elsewhere, e.g.:
7
- // export type { GitRebaseInput, GitRebaseResult } from './logic.js';
5
+ export { registerGitRebaseTool } from "./registration.js";