@cyanheads/git-mcp-server 2.1.0 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -11
- package/dist/config/index.js +7 -7
- package/dist/index.js +35 -21
- package/dist/mcp-server/server.js +72 -56
- package/dist/mcp-server/tools/gitAdd/index.js +1 -1
- package/dist/mcp-server/tools/gitAdd/logic.js +88 -39
- package/dist/mcp-server/tools/gitAdd/registration.js +17 -14
- package/dist/mcp-server/tools/gitBranch/index.js +1 -1
- package/dist/mcp-server/tools/gitBranch/logic.js +213 -85
- package/dist/mcp-server/tools/gitBranch/registration.js +16 -13
- package/dist/mcp-server/tools/gitCheckout/index.js +1 -1
- package/dist/mcp-server/tools/gitCheckout/logic.js +85 -145
- package/dist/mcp-server/tools/gitCheckout/registration.js +16 -14
- package/dist/mcp-server/tools/gitCherryPick/index.js +1 -1
- package/dist/mcp-server/tools/gitCherryPick/logic.js +100 -41
- package/dist/mcp-server/tools/gitCherryPick/registration.js +21 -14
- package/dist/mcp-server/tools/gitClean/index.js +1 -1
- package/dist/mcp-server/tools/gitClean/logic.js +93 -41
- package/dist/mcp-server/tools/gitClean/registration.js +19 -16
- package/dist/mcp-server/tools/gitClearWorkingDir/index.js +1 -1
- package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +14 -11
- package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +19 -13
- package/dist/mcp-server/tools/gitClone/index.js +1 -1
- package/dist/mcp-server/tools/gitClone/logic.js +89 -30
- package/dist/mcp-server/tools/gitClone/registration.js +15 -12
- package/dist/mcp-server/tools/gitCommit/index.js +1 -1
- package/dist/mcp-server/tools/gitCommit/logic.js +198 -76
- package/dist/mcp-server/tools/gitCommit/registration.js +23 -20
- package/dist/mcp-server/tools/gitDiff/index.js +1 -1
- package/dist/mcp-server/tools/gitDiff/logic.js +124 -44
- package/dist/mcp-server/tools/gitDiff/registration.js +16 -14
- package/dist/mcp-server/tools/gitFetch/index.js +1 -1
- package/dist/mcp-server/tools/gitFetch/logic.js +78 -49
- package/dist/mcp-server/tools/gitFetch/registration.js +16 -14
- package/dist/mcp-server/tools/gitInit/index.js +1 -1
- package/dist/mcp-server/tools/gitInit/logic.js +88 -34
- package/dist/mcp-server/tools/gitInit/registration.js +32 -18
- package/dist/mcp-server/tools/gitLog/index.js +1 -1
- package/dist/mcp-server/tools/gitLog/logic.js +133 -47
- package/dist/mcp-server/tools/gitLog/registration.js +16 -14
- package/dist/mcp-server/tools/gitMerge/index.js +1 -1
- package/dist/mcp-server/tools/gitMerge/logic.js +102 -61
- package/dist/mcp-server/tools/gitMerge/registration.js +17 -14
- package/dist/mcp-server/tools/gitPull/index.js +1 -1
- package/dist/mcp-server/tools/gitPull/logic.js +90 -69
- package/dist/mcp-server/tools/gitPull/registration.js +16 -14
- package/dist/mcp-server/tools/gitPush/index.js +1 -1
- package/dist/mcp-server/tools/gitPush/logic.js +116 -100
- package/dist/mcp-server/tools/gitPush/registration.js +16 -14
- package/dist/mcp-server/tools/gitRebase/index.js +1 -1
- package/dist/mcp-server/tools/gitRebase/logic.js +121 -82
- package/dist/mcp-server/tools/gitRebase/registration.js +21 -14
- package/dist/mcp-server/tools/gitRemote/index.js +1 -1
- package/dist/mcp-server/tools/gitRemote/logic.js +108 -52
- package/dist/mcp-server/tools/gitRemote/registration.js +14 -11
- package/dist/mcp-server/tools/gitReset/index.js +1 -1
- package/dist/mcp-server/tools/gitReset/logic.js +65 -37
- package/dist/mcp-server/tools/gitReset/registration.js +14 -12
- package/dist/mcp-server/tools/gitSetWorkingDir/index.js +1 -1
- package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +74 -34
- package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +18 -11
- package/dist/mcp-server/tools/gitShow/index.js +1 -1
- package/dist/mcp-server/tools/gitShow/logic.js +78 -35
- package/dist/mcp-server/tools/gitShow/registration.js +17 -12
- package/dist/mcp-server/tools/gitStash/index.js +1 -1
- package/dist/mcp-server/tools/gitStash/logic.js +143 -58
- package/dist/mcp-server/tools/gitStash/registration.js +19 -12
- package/dist/mcp-server/tools/gitStatus/index.js +1 -1
- package/dist/mcp-server/tools/gitStatus/logic.js +100 -58
- package/dist/mcp-server/tools/gitStatus/registration.js +15 -12
- package/dist/mcp-server/tools/gitTag/index.js +1 -1
- package/dist/mcp-server/tools/gitTag/logic.js +124 -51
- package/dist/mcp-server/tools/gitTag/registration.js +14 -11
- package/dist/mcp-server/tools/gitWorktree/index.js +1 -1
- package/dist/mcp-server/tools/gitWorktree/logic.js +204 -95
- package/dist/mcp-server/tools/gitWorktree/registration.js +14 -11
- package/dist/mcp-server/tools/gitWrapupInstructions/index.js +1 -1
- package/dist/mcp-server/tools/gitWrapupInstructions/logic.js +23 -11
- package/dist/mcp-server/tools/gitWrapupInstructions/registration.js +14 -12
- package/dist/mcp-server/transports/httpTransport.js +187 -79
- package/dist/mcp-server/transports/stdioTransport.js +14 -8
- package/dist/types-global/errors.js +9 -4
- package/dist/utils/index.js +4 -4
- package/dist/utils/internal/errorHandler.js +62 -40
- package/dist/utils/internal/index.js +3 -3
- package/dist/utils/internal/logger.js +97 -54
- package/dist/utils/internal/requestContext.js +7 -5
- package/dist/utils/metrics/index.js +1 -1
- package/dist/utils/metrics/tokenCounter.js +18 -14
- package/dist/utils/parsing/dateParser.js +5 -5
- package/dist/utils/parsing/index.js +2 -2
- package/dist/utils/parsing/jsonParser.js +20 -11
- package/dist/utils/security/idGenerator.js +8 -10
- package/dist/utils/security/index.js +3 -3
- package/dist/utils/security/rateLimiter.js +16 -14
- package/dist/utils/security/sanitization.js +139 -82
- package/package.json +45 -23
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Import utils from barrel (ErrorHandler from ../utils/internal/errorHandler.js)
|
|
2
|
-
import { ErrorHandler } from
|
|
2
|
+
import { ErrorHandler } from "../../../utils/index.js";
|
|
3
3
|
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
4
|
-
import { logger } from
|
|
4
|
+
import { logger } from "../../../utils/index.js";
|
|
5
5
|
// Import utils from barrel (requestContextService, RequestContext from ../utils/internal/requestContext.js)
|
|
6
|
-
import { BaseErrorCode } from
|
|
7
|
-
import { requestContextService } from
|
|
8
|
-
import { GitPullInputSchema, pullGitChanges } from
|
|
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
9
|
let _getWorkingDirectory;
|
|
10
10
|
let _getSessionId;
|
|
11
11
|
/**
|
|
@@ -17,9 +17,9 @@ let _getSessionId;
|
|
|
17
17
|
export function initializeGitPullStateAccessors(getWdFn, getSidFn) {
|
|
18
18
|
_getWorkingDirectory = getWdFn;
|
|
19
19
|
_getSessionId = getSidFn;
|
|
20
|
-
logger.info(
|
|
20
|
+
logger.info("State accessors initialized for git_pull tool registration.");
|
|
21
21
|
}
|
|
22
|
-
const TOOL_NAME =
|
|
22
|
+
const TOOL_NAME = "git_pull";
|
|
23
23
|
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
24
|
/**
|
|
25
25
|
* Registers the git_pull tool with the MCP server.
|
|
@@ -29,15 +29,18 @@ const TOOL_DESCRIPTION = "Fetches from and integrates with another repository or
|
|
|
29
29
|
*/
|
|
30
30
|
export async function registerGitPullTool(server) {
|
|
31
31
|
if (!_getWorkingDirectory || !_getSessionId) {
|
|
32
|
-
throw new Error(
|
|
32
|
+
throw new Error("State accessors for git_pull must be initialized before registration.");
|
|
33
33
|
}
|
|
34
|
-
const operation =
|
|
34
|
+
const operation = "registerGitPullTool";
|
|
35
35
|
const context = requestContextService.createRequestContext({ operation });
|
|
36
36
|
await ErrorHandler.tryCatch(async () => {
|
|
37
37
|
server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitPullInputSchema.shape, // Provide the Zod schema shape
|
|
38
38
|
async (validatedArgs, callContext) => {
|
|
39
|
-
const toolOperation =
|
|
40
|
-
const requestContext = requestContextService.createRequestContext({
|
|
39
|
+
const toolOperation = "tool:git_pull";
|
|
40
|
+
const requestContext = requestContextService.createRequestContext({
|
|
41
|
+
operation: toolOperation,
|
|
42
|
+
parentContext: callContext,
|
|
43
|
+
});
|
|
41
44
|
const sessionId = _getSessionId(requestContext);
|
|
42
45
|
const getWorkingDirectoryForSession = () => {
|
|
43
46
|
return _getWorkingDirectory(sessionId);
|
|
@@ -53,9 +56,9 @@ export async function registerGitPullTool(server) {
|
|
|
53
56
|
const pullResult = await pullGitChanges(validatedArgs, logicContext);
|
|
54
57
|
// Format the result as a JSON string within TextContent
|
|
55
58
|
const resultContent = {
|
|
56
|
-
type:
|
|
59
|
+
type: "text",
|
|
57
60
|
text: JSON.stringify(pullResult, null, 2), // Pretty-print JSON
|
|
58
|
-
contentType:
|
|
61
|
+
contentType: "application/json",
|
|
59
62
|
};
|
|
60
63
|
// Log based on the success flag in the result
|
|
61
64
|
if (pullResult.success) {
|
|
@@ -81,4 +84,3 @@ export async function registerGitPullTool(server) {
|
|
|
81
84
|
logger.info(`Tool registered: ${TOOL_NAME}`, context);
|
|
82
85
|
}, { operation, context, critical: true });
|
|
83
86
|
}
|
|
84
|
-
;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Barrel file for the gitPush tool.
|
|
3
3
|
*/
|
|
4
|
-
export { registerGitPushTool, initializeGitPushStateAccessors } from
|
|
4
|
+
export { registerGitPushTool, initializeGitPushStateAccessors, } from "./registration.js";
|
|
5
5
|
// Export types if needed elsewhere, e.g.:
|
|
6
6
|
// export type { GitPushInput, GitPushResult } from './logic.js';
|
|
@@ -1,24 +1,58 @@
|
|
|
1
|
-
import { exec } from
|
|
2
|
-
import { promisify } from
|
|
3
|
-
import { z } from
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { z } from "zod";
|
|
4
4
|
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
5
|
-
import { logger } from
|
|
5
|
+
import { logger } from "../../../utils/index.js";
|
|
6
6
|
// Import utils from barrel (RequestContext from ../utils/internal/requestContext.js)
|
|
7
|
-
import { BaseErrorCode, McpError } from
|
|
7
|
+
import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Keep direct import for types-global
|
|
8
8
|
// Import utils from barrel (sanitization from ../utils/security/sanitization.js)
|
|
9
|
-
import { sanitization } from
|
|
9
|
+
import { sanitization } from "../../../utils/index.js";
|
|
10
10
|
const execAsync = promisify(exec);
|
|
11
11
|
// Define the input schema for the git_push tool using Zod
|
|
12
12
|
export const GitPushInputSchema = z.object({
|
|
13
|
-
path: z
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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."),
|
|
22
56
|
// Add other relevant git push options as needed (e.g., --prune, --all)
|
|
23
57
|
});
|
|
24
58
|
/**
|
|
@@ -30,12 +64,12 @@ export const GitPushInputSchema = z.object({
|
|
|
30
64
|
* @throws {McpError} Throws an McpError if path resolution, validation, or the git command fails unexpectedly.
|
|
31
65
|
*/
|
|
32
66
|
export async function pushGitChanges(input, context) {
|
|
33
|
-
const operation =
|
|
67
|
+
const operation = "pushGitChanges";
|
|
34
68
|
logger.debug(`Executing ${operation}`, { ...context, input });
|
|
35
69
|
let targetPath;
|
|
36
70
|
try {
|
|
37
71
|
// Resolve and sanitize the target path
|
|
38
|
-
if (input.path && input.path !==
|
|
72
|
+
if (input.path && input.path !== ".") {
|
|
39
73
|
targetPath = input.path;
|
|
40
74
|
}
|
|
41
75
|
else {
|
|
@@ -45,11 +79,21 @@ export async function pushGitChanges(input, context) {
|
|
|
45
79
|
}
|
|
46
80
|
targetPath = workingDir;
|
|
47
81
|
}
|
|
48
|
-
targetPath = sanitization.sanitizePath(targetPath, {
|
|
49
|
-
|
|
82
|
+
targetPath = sanitization.sanitizePath(targetPath, {
|
|
83
|
+
allowAbsolute: true,
|
|
84
|
+
}).sanitizedPath;
|
|
85
|
+
logger.debug("Sanitized path", {
|
|
86
|
+
...context,
|
|
87
|
+
operation,
|
|
88
|
+
sanitizedPath: targetPath,
|
|
89
|
+
});
|
|
50
90
|
}
|
|
51
91
|
catch (error) {
|
|
52
|
-
logger.error(
|
|
92
|
+
logger.error("Path resolution or sanitization failed", {
|
|
93
|
+
...context,
|
|
94
|
+
operation,
|
|
95
|
+
error,
|
|
96
|
+
});
|
|
53
97
|
if (error instanceof McpError)
|
|
54
98
|
throw error;
|
|
55
99
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
@@ -61,35 +105,39 @@ export async function pushGitChanges(input, context) {
|
|
|
61
105
|
if (input.force && input.forceWithLease) {
|
|
62
106
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Cannot use --force and --force-with-lease together.", { context, operation });
|
|
63
107
|
}
|
|
64
|
-
if (input.delete &&
|
|
108
|
+
if (input.delete &&
|
|
109
|
+
(input.force || input.forceWithLease || input.setUpstream || input.tags)) {
|
|
65
110
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, "Cannot combine --delete with --force, --force-with-lease, --set-upstream, or --tags.", { context, operation });
|
|
66
111
|
}
|
|
67
112
|
try {
|
|
68
113
|
// Construct the git push command
|
|
69
114
|
let command = `git -C "${targetPath}" push`;
|
|
70
115
|
if (input.force) {
|
|
71
|
-
command +=
|
|
116
|
+
command += " --force";
|
|
72
117
|
}
|
|
73
118
|
else if (input.forceWithLease) {
|
|
74
|
-
command +=
|
|
119
|
+
command += " --force-with-lease";
|
|
75
120
|
}
|
|
76
121
|
if (input.setUpstream) {
|
|
77
|
-
command +=
|
|
122
|
+
command += " --set-upstream";
|
|
78
123
|
}
|
|
79
124
|
if (input.tags) {
|
|
80
|
-
command +=
|
|
125
|
+
command += " --tags";
|
|
81
126
|
}
|
|
82
127
|
if (input.delete) {
|
|
83
|
-
command +=
|
|
128
|
+
command += " --delete";
|
|
84
129
|
}
|
|
85
130
|
// Add remote and branch specification
|
|
86
|
-
const remote = input.remote
|
|
131
|
+
const remote = input.remote
|
|
132
|
+
? input.remote.replace(/[^a-zA-Z0-9_.\-/]/g, "")
|
|
133
|
+
: "origin"; // Default to origin
|
|
87
134
|
command += ` ${remote}`;
|
|
88
135
|
if (input.branch) {
|
|
89
|
-
const localBranch = input.branch.replace(/[^a-zA-Z0-9_.\-/]/g,
|
|
136
|
+
const localBranch = input.branch.replace(/[^a-zA-Z0-9_.\-/]/g, "");
|
|
90
137
|
command += ` ${localBranch}`;
|
|
91
|
-
if (input.remoteBranch && !input.delete) {
|
|
92
|
-
|
|
138
|
+
if (input.remoteBranch && !input.delete) {
|
|
139
|
+
// remoteBranch only makes sense if not deleting
|
|
140
|
+
const remoteBranch = input.remoteBranch.replace(/[^a-zA-Z0-9_.\-/]/g, "");
|
|
93
141
|
command += `:${remoteBranch}`;
|
|
94
142
|
}
|
|
95
143
|
}
|
|
@@ -97,92 +145,60 @@ export async function pushGitChanges(input, context) {
|
|
|
97
145
|
// If no branch, tags, or delete specified, push the current branch by default
|
|
98
146
|
// Git might handle this automatically, but being explicit can be clearer
|
|
99
147
|
// command += ' HEAD'; // Or let git figure out the default push behavior
|
|
100
|
-
logger.debug(
|
|
148
|
+
logger.debug("No specific branch, tags, or delete specified. Relying on default git push behavior for current branch.", { ...context, operation });
|
|
101
149
|
}
|
|
102
150
|
logger.debug(`Executing command: ${command}`, { ...context, operation });
|
|
103
151
|
// Execute command. Note: Git push often uses stderr for progress and success messages.
|
|
104
152
|
const { stdout, stderr } = await execAsync(command);
|
|
105
|
-
logger.
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
let message = stderr.trim() || stdout.trim();
|
|
109
|
-
let success = true;
|
|
110
|
-
let rejected = false;
|
|
111
|
-
let deleted = false;
|
|
112
|
-
let summary = undefined;
|
|
113
|
-
// Check for common success/status messages in stderr
|
|
114
|
-
if (message.includes('Everything up-to-date')) {
|
|
115
|
-
message = 'Everything up-to-date.';
|
|
116
|
-
}
|
|
117
|
-
else if (message.match(/->\s+\[new branch\]/) || message.match(/->\s+\[new tag\]/)) {
|
|
118
|
-
message = 'Push successful (new branch/tag created).';
|
|
119
|
-
// Extract summary if possible (e.g., commit range)
|
|
120
|
-
const summaryMatch = message.match(/([a-f0-9]+\.\.[a-f0-9]+)\s+\S+\s+->\s+\S+/);
|
|
121
|
-
if (summaryMatch)
|
|
122
|
-
summary = summaryMatch[1];
|
|
123
|
-
}
|
|
124
|
-
else if (message.includes('Done.')) { // Common part of successful push output
|
|
125
|
-
// Try to find a more specific message
|
|
126
|
-
if (stderr.includes('updating') || stdout.includes('updating')) {
|
|
127
|
-
message = 'Push successful.';
|
|
128
|
-
const summaryMatch = message.match(/([a-f0-9]+\.\.[a-f0-9]+)\s+\S+\s+->\s+\S+/);
|
|
129
|
-
if (summaryMatch)
|
|
130
|
-
summary = summaryMatch[1];
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
133
|
-
message = 'Push completed (check logs for details).'; // Generic success if specific pattern not found
|
|
134
|
-
}
|
|
153
|
+
logger.debug(`Git push stdout: ${stdout}`, { ...context, operation });
|
|
154
|
+
if (stderr) {
|
|
155
|
+
logger.debug(`Git push stderr: ${stderr}`, { ...context, operation });
|
|
135
156
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
message = 'Remote branch deleted successfully.';
|
|
151
|
-
deleted = true;
|
|
152
|
-
}
|
|
153
|
-
else if (message.includes('fatal:')) {
|
|
154
|
-
// If a fatal error wasn't caught by execAsync but is in stderr/stdout
|
|
155
|
-
success = false;
|
|
156
|
-
message = `Push failed: ${message}`;
|
|
157
|
-
logger.error(`Git push command indicated failure: ${message}`, { ...context, operation, stdout, stderr });
|
|
158
|
-
}
|
|
159
|
-
else if (!message && !stdout && !stderr) {
|
|
160
|
-
// If command succeeds with no output (can happen in some cases)
|
|
161
|
-
message = 'Push command executed, but no output received.';
|
|
162
|
-
logger.warning(message, { ...context, operation });
|
|
163
|
-
}
|
|
164
|
-
logger.info(`${operation} completed`, { ...context, operation, path: targetPath, success, message, rejected, deleted });
|
|
165
|
-
return { success, message, summary, rejected, deleted };
|
|
157
|
+
// Analyze stderr primarily, fallback to stdout
|
|
158
|
+
const message = stderr.trim() || stdout.trim() || "Push command executed.";
|
|
159
|
+
const summary = message;
|
|
160
|
+
const rejected = message.includes("[rejected]");
|
|
161
|
+
const deleted = message.includes("[deleted]");
|
|
162
|
+
logger.info("git push executed successfully", {
|
|
163
|
+
...context,
|
|
164
|
+
operation,
|
|
165
|
+
path: targetPath,
|
|
166
|
+
summary,
|
|
167
|
+
rejected,
|
|
168
|
+
deleted,
|
|
169
|
+
});
|
|
170
|
+
return { success: true, message, summary, rejected, deleted };
|
|
166
171
|
}
|
|
167
172
|
catch (error) {
|
|
168
|
-
logger.error(`Failed to execute git push command`, {
|
|
169
|
-
|
|
173
|
+
logger.error(`Failed to execute git push command`, {
|
|
174
|
+
...context,
|
|
175
|
+
operation,
|
|
176
|
+
path: targetPath,
|
|
177
|
+
error: error.message,
|
|
178
|
+
stderr: error.stderr,
|
|
179
|
+
stdout: error.stdout,
|
|
180
|
+
});
|
|
181
|
+
const errorMessage = error.stderr || error.stdout || error.message || "";
|
|
170
182
|
// Handle specific error cases
|
|
171
|
-
if (errorMessage.toLowerCase().includes(
|
|
183
|
+
if (errorMessage.toLowerCase().includes("not a git repository")) {
|
|
172
184
|
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`, { context, operation, originalError: error });
|
|
173
185
|
}
|
|
174
|
-
if (errorMessage.includes(
|
|
186
|
+
if (errorMessage.includes("resolve host") ||
|
|
187
|
+
errorMessage.includes("Could not read from remote repository") ||
|
|
188
|
+
errorMessage.includes("Connection timed out")) {
|
|
175
189
|
throw new McpError(BaseErrorCode.SERVICE_UNAVAILABLE, `Failed to connect to remote repository. Error: ${errorMessage}`, { context, operation, originalError: error });
|
|
176
190
|
}
|
|
177
|
-
if (errorMessage.includes(
|
|
191
|
+
if (errorMessage.includes("rejected") ||
|
|
192
|
+
errorMessage.includes("failed to push some refs")) {
|
|
178
193
|
// This might be caught here if execAsync throws due to non-zero exit code on rejection
|
|
179
|
-
|
|
180
|
-
return { success: false, message: `Push rejected: ${errorMessage}`, rejected: true };
|
|
194
|
+
throw new McpError(BaseErrorCode.CONFLICT, `Push rejected: ${errorMessage}`, { context, operation, originalError: error });
|
|
181
195
|
}
|
|
182
|
-
if (errorMessage.includes(
|
|
196
|
+
if (errorMessage.includes("Authentication failed") ||
|
|
197
|
+
errorMessage.includes("Permission denied")) {
|
|
183
198
|
throw new McpError(BaseErrorCode.UNAUTHORIZED, `Authentication failed for remote repository. Error: ${errorMessage}`, { context, operation, originalError: error });
|
|
184
199
|
}
|
|
185
|
-
if (errorMessage.includes(
|
|
200
|
+
if (errorMessage.includes("src refspec") &&
|
|
201
|
+
errorMessage.includes("does not match any")) {
|
|
186
202
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Push failed: Source branch/refspec does not exist locally. Error: ${errorMessage}`, { context, operation, originalError: error });
|
|
187
203
|
}
|
|
188
204
|
// Generic internal error for other failures
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// Import utils from barrel (ErrorHandler from ../utils/internal/errorHandler.js)
|
|
2
|
-
import { ErrorHandler } from
|
|
2
|
+
import { ErrorHandler } from "../../../utils/index.js";
|
|
3
3
|
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
4
|
-
import { logger } from
|
|
4
|
+
import { logger } from "../../../utils/index.js";
|
|
5
5
|
// Import utils from barrel (requestContextService, RequestContext from ../utils/internal/requestContext.js)
|
|
6
|
-
import { BaseErrorCode } from
|
|
7
|
-
import { requestContextService } from
|
|
8
|
-
import { GitPushInputSchema, pushGitChanges } from
|
|
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
9
|
let _getWorkingDirectory;
|
|
10
10
|
let _getSessionId;
|
|
11
11
|
/**
|
|
@@ -17,9 +17,9 @@ let _getSessionId;
|
|
|
17
17
|
export function initializeGitPushStateAccessors(getWdFn, getSidFn) {
|
|
18
18
|
_getWorkingDirectory = getWdFn;
|
|
19
19
|
_getSessionId = getSidFn;
|
|
20
|
-
logger.info(
|
|
20
|
+
logger.info("State accessors initialized for git_push tool registration.");
|
|
21
21
|
}
|
|
22
|
-
const TOOL_NAME =
|
|
22
|
+
const TOOL_NAME = "git_push";
|
|
23
23
|
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
24
|
/**
|
|
25
25
|
* Registers the git_push tool with the MCP server.
|
|
@@ -29,15 +29,18 @@ const TOOL_DESCRIPTION = "Updates remote refs using local refs, sending objects
|
|
|
29
29
|
*/
|
|
30
30
|
export async function registerGitPushTool(server) {
|
|
31
31
|
if (!_getWorkingDirectory || !_getSessionId) {
|
|
32
|
-
throw new Error(
|
|
32
|
+
throw new Error("State accessors for git_push must be initialized before registration.");
|
|
33
33
|
}
|
|
34
|
-
const operation =
|
|
34
|
+
const operation = "registerGitPushTool";
|
|
35
35
|
const context = requestContextService.createRequestContext({ operation });
|
|
36
36
|
await ErrorHandler.tryCatch(async () => {
|
|
37
37
|
server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitPushInputSchema.shape, // Provide the Zod schema shape
|
|
38
38
|
async (validatedArgs, callContext) => {
|
|
39
|
-
const toolOperation =
|
|
40
|
-
const requestContext = requestContextService.createRequestContext({
|
|
39
|
+
const toolOperation = "tool:git_push";
|
|
40
|
+
const requestContext = requestContextService.createRequestContext({
|
|
41
|
+
operation: toolOperation,
|
|
42
|
+
parentContext: callContext,
|
|
43
|
+
});
|
|
41
44
|
const sessionId = _getSessionId(requestContext);
|
|
42
45
|
const getWorkingDirectoryForSession = () => {
|
|
43
46
|
return _getWorkingDirectory(sessionId);
|
|
@@ -53,9 +56,9 @@ export async function registerGitPushTool(server) {
|
|
|
53
56
|
const pushResult = await pushGitChanges(validatedArgs, logicContext);
|
|
54
57
|
// Format the result as a JSON string within TextContent
|
|
55
58
|
const resultContent = {
|
|
56
|
-
type:
|
|
59
|
+
type: "text",
|
|
57
60
|
text: JSON.stringify(pushResult, null, 2), // Pretty-print JSON
|
|
58
|
-
contentType:
|
|
61
|
+
contentType: "application/json",
|
|
59
62
|
};
|
|
60
63
|
// Log based on the success flag in the result
|
|
61
64
|
if (pushResult.success) {
|
|
@@ -81,4 +84,3 @@ export async function registerGitPushTool(server) {
|
|
|
81
84
|
logger.info(`Tool registered: ${TOOL_NAME}`, context);
|
|
82
85
|
}, { operation, context, critical: true });
|
|
83
86
|
}
|
|
84
|
-
;
|
|
@@ -2,6 +2,6 @@
|
|
|
2
2
|
* @fileoverview Barrel file for the git_rebase tool.
|
|
3
3
|
* Exports the registration function and state accessor initialization function.
|
|
4
4
|
*/
|
|
5
|
-
export { registerGitRebaseTool, initializeGitRebaseStateAccessors } from
|
|
5
|
+
export { registerGitRebaseTool, initializeGitRebaseStateAccessors, } from "./registration.js";
|
|
6
6
|
// Export types if needed elsewhere, e.g.:
|
|
7
7
|
// export type { GitRebaseInput, GitRebaseResult } from './logic.js';
|