@cyanheads/git-mcp-server 2.1.8 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/mcp-server/server.js +69 -228
- package/dist/mcp-server/tools/gitAdd/index.js +2 -4
- package/dist/mcp-server/tools/gitAdd/logic.js +40 -116
- package/dist/mcp-server/tools/gitAdd/registration.js +39 -59
- package/dist/mcp-server/tools/gitBranch/index.js +3 -5
- package/dist/mcp-server/tools/gitBranch/logic.js +109 -304
- package/dist/mcp-server/tools/gitBranch/registration.js +52 -66
- package/dist/mcp-server/tools/gitCheckout/index.js +2 -3
- package/dist/mcp-server/tools/gitCheckout/logic.js +47 -144
- package/dist/mcp-server/tools/gitCheckout/registration.js +53 -72
- package/dist/mcp-server/tools/gitCherryPick/index.js +3 -5
- package/dist/mcp-server/tools/gitCherryPick/logic.js +47 -173
- package/dist/mcp-server/tools/gitCherryPick/registration.js +52 -67
- package/dist/mcp-server/tools/gitClean/index.js +3 -5
- package/dist/mcp-server/tools/gitClean/logic.js +45 -154
- package/dist/mcp-server/tools/gitClean/registration.js +52 -92
- package/dist/mcp-server/tools/gitClearWorkingDir/index.js +3 -5
- package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +18 -32
- package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +55 -73
- package/dist/mcp-server/tools/gitClone/index.js +2 -4
- package/dist/mcp-server/tools/gitClone/logic.js +47 -187
- package/dist/mcp-server/tools/gitClone/registration.js +51 -42
- package/dist/mcp-server/tools/gitCommit/index.js +2 -4
- package/dist/mcp-server/tools/gitCommit/logic.js +75 -310
- package/dist/mcp-server/tools/gitCommit/registration.js +52 -73
- package/dist/mcp-server/tools/gitDiff/index.js +2 -3
- package/dist/mcp-server/tools/gitDiff/logic.js +72 -264
- package/dist/mcp-server/tools/gitDiff/registration.js +53 -68
- package/dist/mcp-server/tools/gitFetch/index.js +2 -3
- package/dist/mcp-server/tools/gitFetch/logic.js +38 -136
- package/dist/mcp-server/tools/gitFetch/registration.js +54 -66
- package/dist/mcp-server/tools/gitInit/index.js +3 -5
- package/dist/mcp-server/tools/gitInit/logic.js +40 -162
- package/dist/mcp-server/tools/gitInit/registration.js +52 -104
- package/dist/mcp-server/tools/gitLog/index.js +2 -3
- package/dist/mcp-server/tools/gitLog/logic.js +71 -266
- package/dist/mcp-server/tools/gitLog/registration.js +54 -66
- package/dist/mcp-server/tools/gitMerge/index.js +3 -5
- package/dist/mcp-server/tools/gitMerge/logic.js +45 -191
- package/dist/mcp-server/tools/gitMerge/registration.js +52 -71
- package/dist/mcp-server/tools/gitPull/index.js +2 -3
- package/dist/mcp-server/tools/gitPull/logic.js +39 -156
- package/dist/mcp-server/tools/gitPull/registration.js +53 -75
- package/dist/mcp-server/tools/gitPush/index.js +2 -3
- package/dist/mcp-server/tools/gitPush/logic.js +65 -192
- package/dist/mcp-server/tools/gitPush/registration.js +53 -75
- package/dist/mcp-server/tools/gitRebase/index.js +3 -5
- package/dist/mcp-server/tools/gitRebase/logic.js +59 -207
- package/dist/mcp-server/tools/gitRebase/registration.js +52 -70
- package/dist/mcp-server/tools/gitRemote/index.js +3 -5
- package/dist/mcp-server/tools/gitRemote/logic.js +76 -200
- package/dist/mcp-server/tools/gitRemote/registration.js +52 -65
- package/dist/mcp-server/tools/gitReset/index.js +2 -3
- package/dist/mcp-server/tools/gitReset/logic.js +33 -133
- package/dist/mcp-server/tools/gitReset/registration.js +53 -60
- package/dist/mcp-server/tools/gitSetWorkingDir/index.js +3 -5
- package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +39 -144
- package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +55 -85
- package/dist/mcp-server/tools/gitShow/index.js +3 -5
- package/dist/mcp-server/tools/gitShow/logic.js +28 -133
- package/dist/mcp-server/tools/gitShow/registration.js +52 -74
- package/dist/mcp-server/tools/gitStash/index.js +3 -5
- package/dist/mcp-server/tools/gitStash/logic.js +59 -219
- package/dist/mcp-server/tools/gitStash/registration.js +52 -77
- package/dist/mcp-server/tools/gitStatus/index.js +2 -4
- package/dist/mcp-server/tools/gitStatus/logic.js +79 -236
- package/dist/mcp-server/tools/gitStatus/registration.js +52 -66
- package/dist/mcp-server/tools/gitTag/index.js +3 -5
- package/dist/mcp-server/tools/gitTag/logic.js +57 -198
- package/dist/mcp-server/tools/gitTag/registration.js +54 -73
- package/dist/mcp-server/tools/gitWorktree/index.js +3 -5
- package/dist/mcp-server/tools/gitWorktree/logic.js +102 -328
- package/dist/mcp-server/tools/gitWorktree/registration.js +54 -58
- package/dist/mcp-server/tools/gitWrapupInstructions/index.js +5 -3
- package/dist/mcp-server/tools/gitWrapupInstructions/logic.js +25 -43
- package/dist/mcp-server/tools/gitWrapupInstructions/registration.js +54 -70
- package/dist/mcp-server/transports/httpTransport.js +2 -3
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# Git MCP Server
|
|
2
2
|
|
|
3
3
|
[](https://www.typescriptlang.org/)
|
|
4
|
-
[](https://modelcontextprotocol.io/)
|
|
5
|
+
[](./CHANGELOG.md)
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
[](https://github.com/cyanheads/git-mcp-server/issues)
|
|
8
8
|
[](https://github.com/cyanheads/git-mcp-server)
|
|
@@ -203,9 +203,9 @@ _Note: The `path` parameter for most tools defaults to the session's working dir
|
|
|
203
203
|
|
|
204
204
|
## Resources
|
|
205
205
|
|
|
206
|
-
**MCP Resources are not implemented in this version (v2.
|
|
206
|
+
**MCP Resources are not implemented in this version (v2.2.0).**
|
|
207
207
|
|
|
208
|
-
This version focuses on the refactored Git tools implementation based on the latest `mcp-ts-template` and MCP SDK v1.
|
|
208
|
+
This version focuses on the refactored Git tools implementation based on the latest `mcp-ts-template` and MCP SDK v1.15.1. Resource capabilities, previously available, have been temporarily removed during this major update.
|
|
209
209
|
|
|
210
210
|
If you require MCP Resource access (e.g., for reading file content directly via the server), please use the stable **[v1.2.4 release](https://github.com/cyanheads/git-mcp-server/releases/tag/v1.2.4)**.
|
|
211
211
|
|
|
@@ -6,313 +6,154 @@
|
|
|
6
6
|
* 3. Selects and starts the appropriate communication transport (stdio or Streamable HTTP)
|
|
7
7
|
* based on configuration.
|
|
8
8
|
* 4. Handles top-level error management during startup.
|
|
9
|
-
*
|
|
10
|
-
* MCP Specification References:
|
|
11
|
-
* - Lifecycle: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/lifecycle.mdx
|
|
12
|
-
* - Overview (Capabilities): https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/index.mdx
|
|
13
|
-
* - Transports: https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/docs/specification/2025-03-26/basic/transports.mdx
|
|
14
9
|
*/
|
|
15
10
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
16
|
-
// Import validated configuration and environment details.
|
|
17
11
|
import { config, environment } from "../config/index.js";
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import {
|
|
33
|
-
import {
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
import {
|
|
37
|
-
import {
|
|
38
|
-
import {
|
|
39
|
-
import {
|
|
40
|
-
import {
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
45
|
-
import { initializeGitWrapupInstructionsStateAccessors, registerGitWrapupInstructionsTool, } from "./tools/gitWrapupInstructions/index.js";
|
|
12
|
+
import { ErrorHandler, logger, requestContextService } from "../utils/index.js";
|
|
13
|
+
// Import registration functions for ALL Git tools (alphabetized)
|
|
14
|
+
import { registerGitAddTool } from "./tools/gitAdd/index.js";
|
|
15
|
+
import { registerGitBranchTool } from "./tools/gitBranch/index.js";
|
|
16
|
+
import { registerGitCheckoutTool } from "./tools/gitCheckout/index.js";
|
|
17
|
+
import { registerGitCherryPickTool } from "./tools/gitCherryPick/index.js";
|
|
18
|
+
import { registerGitCleanTool } from "./tools/gitClean/index.js";
|
|
19
|
+
import { registerGitClearWorkingDirTool } from "./tools/gitClearWorkingDir/index.js";
|
|
20
|
+
import { registerGitCloneTool } from "./tools/gitClone/index.js";
|
|
21
|
+
import { registerGitCommitTool } from "./tools/gitCommit/index.js";
|
|
22
|
+
import { registerGitDiffTool } from "./tools/gitDiff/index.js";
|
|
23
|
+
import { registerGitFetchTool } from "./tools/gitFetch/index.js";
|
|
24
|
+
import { registerGitInitTool } from "./tools/gitInit/index.js";
|
|
25
|
+
import { registerGitLogTool } from "./tools/gitLog/index.js";
|
|
26
|
+
import { registerGitMergeTool } from "./tools/gitMerge/index.js";
|
|
27
|
+
import { registerGitPullTool } from "./tools/gitPull/index.js";
|
|
28
|
+
import { registerGitPushTool } from "./tools/gitPush/index.js";
|
|
29
|
+
import { registerGitRebaseTool } from "./tools/gitRebase/index.js";
|
|
30
|
+
import { registerGitRemoteTool } from "./tools/gitRemote/index.js";
|
|
31
|
+
import { registerGitResetTool } from "./tools/gitReset/index.js";
|
|
32
|
+
import { registerGitSetWorkingDirTool } from "./tools/gitSetWorkingDir/index.js";
|
|
33
|
+
import { registerGitShowTool } from "./tools/gitShow/index.js";
|
|
34
|
+
import { registerGitStashTool } from "./tools/gitStash/index.js";
|
|
35
|
+
import { registerGitStatusTool } from "./tools/gitStatus/index.js";
|
|
36
|
+
import { registerGitTagTool } from "./tools/gitTag/index.js";
|
|
37
|
+
import { registerGitWorktreeTool } from "./tools/gitWorktree/index.js";
|
|
38
|
+
import { registerGitWrapupInstructionsTool } from "./tools/gitWrapupInstructions/index.js";
|
|
46
39
|
// Import transport setup functions
|
|
47
40
|
import { startHttpTransport } from "./transports/httpTransport.js";
|
|
48
41
|
import { connectStdioTransport } from "./transports/stdioTransport.js";
|
|
49
|
-
/**
|
|
50
|
-
* Creates and configures a new instance of the McpServer.
|
|
51
|
-
*
|
|
52
|
-
* This function is central to defining the server's identity and functionality
|
|
53
|
-
* as presented to connecting clients during the MCP initialization phase.
|
|
54
|
-
*
|
|
55
|
-
* MCP Spec Relevance:
|
|
56
|
-
* - Server Identity (`serverInfo`): The `name` and `version` provided here are part
|
|
57
|
-
* of the `ServerInformation` object returned in the `InitializeResult` message,
|
|
58
|
-
* allowing clients to identify the server they are connected to.
|
|
59
|
-
* - Capabilities Declaration: The `capabilities` object declares the features this
|
|
60
|
-
* server supports, enabling clients to tailor their interactions.
|
|
61
|
-
* - `logging: {}`: Indicates the server can receive `logging/setLevel` requests
|
|
62
|
-
* and may send `notifications/message` log messages (handled by the logger utility).
|
|
63
|
-
* - `resources: { listChanged: true }`: Signals that the server supports dynamic
|
|
64
|
-
* resource lists and will send `notifications/resources/list_changed` if the
|
|
65
|
-
* available resources change after initialization. (Currently no resources registered)
|
|
66
|
-
* - `tools: { listChanged: true }`: Signals support for dynamic tool lists and
|
|
67
|
-
* `notifications/tools/list_changed`.
|
|
68
|
-
* - Resource/Tool Registration: This function calls specific registration functions
|
|
69
|
-
* (e.g., `registerGitAdd`) which use SDK methods (`server.resource`, `server.tool`)
|
|
70
|
-
* to make capabilities available for discovery (`resources/list`, `tools/list`) and
|
|
71
|
-
* invocation (`resources/read`, `tools/call`).
|
|
72
|
-
*
|
|
73
|
-
* Design Note: This factory function is used to create server instances. For the 'stdio'
|
|
74
|
-
* transport, it's called once. For the 'http' transport, it's passed to `startHttpTransport`
|
|
75
|
-
* and called *per session* to ensure session isolation.
|
|
76
|
-
*
|
|
77
|
-
* @returns {Promise<McpServer>} A promise resolving with the configured McpServer instance.
|
|
78
|
-
* @throws {Error} If any resource or tool registration fails.
|
|
79
|
-
*/
|
|
80
|
-
// Removed sessionId parameter, it will be retrieved from context within tool handlers
|
|
81
42
|
async function createMcpServerInstance() {
|
|
82
43
|
const context = requestContextService.createRequestContext({
|
|
83
44
|
operation: "createMcpServerInstance",
|
|
84
45
|
});
|
|
85
46
|
logger.info("Initializing MCP server instance", context);
|
|
86
|
-
// Configure the request context service (used for correlating logs/errors).
|
|
87
47
|
requestContextService.configure({
|
|
88
48
|
appName: config.mcpServerName,
|
|
89
49
|
appVersion: config.mcpServerVersion,
|
|
90
50
|
environment,
|
|
91
51
|
});
|
|
92
|
-
|
|
93
|
-
// Provide server identity (name, version) and declare supported capabilities.
|
|
94
|
-
// Note: Resources capability declared, but none are registered currently.
|
|
95
|
-
logger.debug("Instantiating McpServer with capabilities", {
|
|
96
|
-
...context,
|
|
97
|
-
serverInfo: {
|
|
98
|
-
name: config.mcpServerName,
|
|
99
|
-
version: config.mcpServerVersion,
|
|
100
|
-
},
|
|
52
|
+
const server = new McpServer({ name: config.mcpServerName, version: config.mcpServerVersion }, {
|
|
101
53
|
capabilities: {
|
|
102
54
|
logging: {},
|
|
103
55
|
resources: { listChanged: true },
|
|
104
56
|
tools: { listChanged: true },
|
|
105
57
|
},
|
|
106
58
|
});
|
|
107
|
-
const
|
|
108
|
-
|
|
109
|
-
capabilities: {
|
|
110
|
-
logging: {},
|
|
111
|
-
resources: { listChanged: true },
|
|
112
|
-
tools: { listChanged: true },
|
|
113
|
-
},
|
|
114
|
-
});
|
|
115
|
-
// Each server instance is isolated per session. This variable will hold the
|
|
116
|
-
// working directory for the duration of this session.
|
|
117
|
-
let sessionWorkingDirectory = undefined;
|
|
118
|
-
// --- Define Unified State Accessor Functions ---
|
|
119
|
-
// These functions abstract away the transport type to get/set session state.
|
|
120
|
-
/** Gets the session ID from the tool's execution context. */
|
|
59
|
+
const sessionWorkingDirectories = new Map();
|
|
60
|
+
const STDIO_SESSION_ID = "stdio_session"; // Constant for single-session transports
|
|
121
61
|
const getSessionIdFromContext = (toolContext) => {
|
|
122
|
-
// The RequestContext created by the tool registration wrapper should contain the sessionId.
|
|
123
62
|
return toolContext?.sessionId;
|
|
124
63
|
};
|
|
125
|
-
/** Gets the working directory for the current session. */
|
|
126
64
|
const getWorkingDirectory = (sessionId) => {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return sessionWorkingDirectory;
|
|
65
|
+
const id = sessionId ?? STDIO_SESSION_ID;
|
|
66
|
+
return sessionWorkingDirectories.get(id);
|
|
130
67
|
};
|
|
131
|
-
/** Sets the working directory for the current session. */
|
|
132
68
|
const setWorkingDirectory = (sessionId, dir) => {
|
|
133
|
-
|
|
134
|
-
logger.debug("Setting session working directory", {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
69
|
+
const id = sessionId ?? STDIO_SESSION_ID;
|
|
70
|
+
logger.debug("Setting session working directory", { ...context, sessionId: id, newDirectory: dir });
|
|
71
|
+
sessionWorkingDirectories.set(id, dir);
|
|
72
|
+
};
|
|
73
|
+
const clearWorkingDirectory = (sessionId) => {
|
|
74
|
+
const id = sessionId ?? STDIO_SESSION_ID;
|
|
75
|
+
logger.debug("Clearing session working directory", { ...context, sessionId: id });
|
|
76
|
+
sessionWorkingDirectories.delete(id);
|
|
140
77
|
};
|
|
141
|
-
// --- Initialize Tool State Accessors BEFORE Registration ---
|
|
142
|
-
// Pass the defined unified accessor functions to the initializers.
|
|
143
|
-
logger.debug("Initializing state accessors for tools...", context);
|
|
144
|
-
try {
|
|
145
|
-
// Call initializers for all tools that likely need state access (alphabetized).
|
|
146
|
-
// If an initializer doesn't exist, the import would have failed earlier (or build will fail).
|
|
147
|
-
initializeGitAddStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
148
|
-
initializeGitBranchStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
149
|
-
initializeGitCheckoutStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
150
|
-
initializeGitCherryPickStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
151
|
-
initializeGitCleanStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
152
|
-
initializeGitClearWorkingDirStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
153
|
-
// initializeGitCloneStateAccessors - No initializer needed/available
|
|
154
|
-
initializeGitCommitStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
155
|
-
initializeGitDiffStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
156
|
-
initializeGitFetchStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
157
|
-
initializeGitInitStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
158
|
-
initializeGitLogStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
159
|
-
initializeGitMergeStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
160
|
-
initializeGitPullStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
161
|
-
initializeGitPushStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
162
|
-
initializeGitRebaseStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
163
|
-
initializeGitRemoteStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
164
|
-
initializeGitResetStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
165
|
-
initializeGitSetWorkingDirStateAccessors(getWorkingDirectory, setWorkingDirectory, getSessionIdFromContext); // Special case
|
|
166
|
-
initializeGitShowStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
167
|
-
initializeGitStashStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
168
|
-
initializeGitStatusStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
169
|
-
initializeGitTagStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
170
|
-
initializeGitWorktreeStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
171
|
-
initializeGitWrapupInstructionsStateAccessors(getWorkingDirectory, getSessionIdFromContext); // Added this line
|
|
172
|
-
logger.debug("State accessors initialized successfully.", context);
|
|
173
|
-
}
|
|
174
|
-
catch (initError) {
|
|
175
|
-
// Catch errors specifically during initialization phase
|
|
176
|
-
logger.error("Failed during state accessor initialization", {
|
|
177
|
-
...context,
|
|
178
|
-
error: initError instanceof Error ? initError.message : String(initError),
|
|
179
|
-
stack: initError instanceof Error ? initError.stack : undefined,
|
|
180
|
-
});
|
|
181
|
-
throw initError; // Re-throw to prevent server starting incorrectly
|
|
182
|
-
}
|
|
183
78
|
try {
|
|
184
|
-
// Register all defined Git tools (alphabetized). These calls populate the server's
|
|
185
|
-
// internal registry, making them available via MCP methods like 'tools/list'.
|
|
186
79
|
logger.debug("Registering Git tools...", context);
|
|
187
|
-
await registerGitAddTool(server);
|
|
188
|
-
await registerGitBranchTool(server);
|
|
189
|
-
await registerGitCheckoutTool(server);
|
|
190
|
-
await registerGitCherryPickTool(server);
|
|
191
|
-
await registerGitCleanTool(server);
|
|
192
|
-
await registerGitClearWorkingDirTool(server);
|
|
193
|
-
await registerGitCloneTool(server);
|
|
194
|
-
await registerGitCommitTool(server);
|
|
195
|
-
await registerGitDiffTool(server);
|
|
196
|
-
await registerGitFetchTool(server);
|
|
197
|
-
await registerGitInitTool(server);
|
|
198
|
-
await registerGitLogTool(server);
|
|
199
|
-
await registerGitMergeTool(server);
|
|
200
|
-
await registerGitPullTool(server);
|
|
201
|
-
await registerGitPushTool(server);
|
|
202
|
-
await registerGitRebaseTool(server);
|
|
203
|
-
await registerGitRemoteTool(server);
|
|
204
|
-
await registerGitResetTool(server);
|
|
205
|
-
await registerGitSetWorkingDirTool(server);
|
|
206
|
-
await registerGitShowTool(server);
|
|
207
|
-
await registerGitStashTool(server);
|
|
208
|
-
await registerGitStatusTool(server);
|
|
209
|
-
await registerGitTagTool(server);
|
|
210
|
-
await registerGitWorktreeTool(server);
|
|
211
|
-
await registerGitWrapupInstructionsTool(server);
|
|
212
|
-
// Add calls to register other resources/tools here if needed in the future.
|
|
80
|
+
await registerGitAddTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
81
|
+
await registerGitBranchTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
82
|
+
await registerGitCheckoutTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
83
|
+
await registerGitCherryPickTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
84
|
+
await registerGitCleanTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
85
|
+
await registerGitClearWorkingDirTool(server, clearWorkingDirectory, getSessionIdFromContext);
|
|
86
|
+
await registerGitCloneTool(server, getSessionIdFromContext);
|
|
87
|
+
await registerGitCommitTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
88
|
+
await registerGitDiffTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
89
|
+
await registerGitFetchTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
90
|
+
await registerGitInitTool(server, getSessionIdFromContext);
|
|
91
|
+
await registerGitLogTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
92
|
+
await registerGitMergeTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
93
|
+
await registerGitPullTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
94
|
+
await registerGitPushTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
95
|
+
await registerGitRebaseTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
96
|
+
await registerGitRemoteTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
97
|
+
await registerGitResetTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
98
|
+
await registerGitSetWorkingDirTool(server, setWorkingDirectory, getSessionIdFromContext);
|
|
99
|
+
await registerGitShowTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
100
|
+
await registerGitStashTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
101
|
+
await registerGitStatusTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
102
|
+
await registerGitTagTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
103
|
+
await registerGitWorktreeTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
104
|
+
await registerGitWrapupInstructionsTool(server, getWorkingDirectory, getSessionIdFromContext);
|
|
213
105
|
logger.info("Git tools registered successfully", context);
|
|
214
106
|
}
|
|
215
107
|
catch (err) {
|
|
216
|
-
// Registration is critical; log and re-throw errors.
|
|
217
108
|
logger.error("Failed to register resources/tools", {
|
|
218
109
|
...context,
|
|
219
110
|
error: err instanceof Error ? err.message : String(err),
|
|
220
|
-
stack: err instanceof Error ? err.stack : undefined,
|
|
111
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
221
112
|
});
|
|
222
|
-
throw err;
|
|
113
|
+
throw err;
|
|
223
114
|
}
|
|
224
115
|
return server;
|
|
225
116
|
}
|
|
226
|
-
/**
|
|
227
|
-
* Selects, sets up, and starts the appropriate MCP transport layer based on configuration.
|
|
228
|
-
* This function acts as the bridge between the core server logic and the communication channel.
|
|
229
|
-
*
|
|
230
|
-
* MCP Spec Relevance:
|
|
231
|
-
* - Transport Selection: Reads `config.mcpTransportType` ('stdio' or 'http') to determine
|
|
232
|
-
* which transport mechanism defined in the MCP specification to use.
|
|
233
|
-
* - Transport Connection: Calls dedicated functions (`connectStdioTransport` or `startHttpTransport`)
|
|
234
|
-
* which handle the specifics of establishing communication according to the chosen
|
|
235
|
-
* transport's rules (e.g., stdin/stdout handling for 'stdio', HTTP server setup and
|
|
236
|
-
* endpoint handling for 'http').
|
|
237
|
-
* - Server Instance Lifecycle:
|
|
238
|
-
* - For 'stdio', creates a single `McpServer` instance for the lifetime of the process.
|
|
239
|
-
* - For 'http', passes the `createMcpServerInstance` factory function to `startHttpTransport`,
|
|
240
|
-
* allowing the HTTP transport to create a new, isolated server instance for each client session,
|
|
241
|
-
* aligning with the stateful session management described in the Streamable HTTP spec.
|
|
242
|
-
*
|
|
243
|
-
* @returns {Promise<McpServer | void>} Resolves with the McpServer instance for 'stdio', or void for 'http'.
|
|
244
|
-
* @throws {Error} If the configured transport type is unsupported or if transport setup fails.
|
|
245
|
-
*/
|
|
246
117
|
async function startTransport() {
|
|
247
|
-
// Determine the transport type from the validated configuration.
|
|
248
118
|
const transportType = config.mcpTransportType;
|
|
249
119
|
const context = requestContextService.createRequestContext({
|
|
250
120
|
operation: "startTransport",
|
|
251
121
|
transport: transportType,
|
|
252
122
|
});
|
|
253
123
|
logger.info(`Starting transport: ${transportType}`, context);
|
|
254
|
-
|
|
124
|
+
const server = await createMcpServerInstance();
|
|
255
125
|
if (transportType === "http") {
|
|
256
|
-
|
|
257
|
-
// For HTTP, the transport layer manages its own lifecycle and potentially multiple sessions.
|
|
258
|
-
// We pass the factory function to allow the HTTP transport to create server instances as needed (per session).
|
|
259
|
-
await startHttpTransport(createMcpServerInstance, context);
|
|
260
|
-
// The HTTP server runs indefinitely, listening for connections, so this function returns void.
|
|
126
|
+
await startHttpTransport(server, context);
|
|
261
127
|
return;
|
|
262
128
|
}
|
|
263
|
-
// --- Stdio Transport Setup ---
|
|
264
129
|
if (transportType === "stdio") {
|
|
265
|
-
logger.debug("Creating single McpServer instance for stdio transport...", context);
|
|
266
|
-
// For stdio, there's typically one persistent connection managed by a parent process.
|
|
267
|
-
// Create a single McpServer instance for the entire process lifetime.
|
|
268
|
-
const server = await createMcpServerInstance();
|
|
269
|
-
logger.debug("Delegating to connectStdioTransport...", context);
|
|
270
|
-
// Connect the server instance to the stdio transport handler.
|
|
271
130
|
await connectStdioTransport(server, context);
|
|
272
|
-
// Return the server instance; the caller (main entry point) might hold onto it.
|
|
273
131
|
return server;
|
|
274
132
|
}
|
|
275
|
-
// --- Unsupported Transport ---
|
|
276
|
-
// This case should theoretically not be reached due to config validation, but acts as a safeguard.
|
|
277
133
|
logger.fatal(`Unsupported transport type configured: ${transportType}`, context);
|
|
278
134
|
throw new Error(`Unsupported transport type: ${transportType}. Must be 'stdio' or 'http'.`);
|
|
279
135
|
}
|
|
280
|
-
/**
|
|
281
|
-
* Main application entry point. Initializes and starts the MCP server.
|
|
282
|
-
*
|
|
283
|
-
* MCP Spec Relevance:
|
|
284
|
-
* - Orchestrates the server startup sequence, culminating in a server ready to accept
|
|
285
|
-
* connections and process MCP messages according to the chosen transport's rules.
|
|
286
|
-
* - Implements top-level error handling for critical startup failures, ensuring the
|
|
287
|
-
* process exits appropriately if it cannot initialize correctly.
|
|
288
|
-
*
|
|
289
|
-
* @returns {Promise<void | McpServer>} Resolves upon successful startup (void for http, McpServer for stdio). Rejects on critical failure.
|
|
290
|
-
*/
|
|
291
136
|
export async function initializeAndStartServer() {
|
|
292
137
|
const context = requestContextService.createRequestContext({
|
|
293
138
|
operation: "initializeAndStartServer",
|
|
294
139
|
});
|
|
295
140
|
logger.info("MCP Server initialization sequence started.", context);
|
|
296
141
|
try {
|
|
297
|
-
// Initiate the transport setup based on configuration.
|
|
298
142
|
const result = await startTransport();
|
|
299
143
|
logger.info("MCP Server initialization sequence completed successfully.", context);
|
|
300
144
|
return result;
|
|
301
145
|
}
|
|
302
146
|
catch (err) {
|
|
303
|
-
// Catch any errors that occurred during server instance creation or transport setup.
|
|
304
147
|
logger.fatal("Critical error during MCP server initialization.", {
|
|
305
148
|
...context,
|
|
306
149
|
error: err instanceof Error ? err.message : String(err),
|
|
307
150
|
stack: err instanceof Error ? err.stack : undefined,
|
|
308
151
|
});
|
|
309
|
-
// Use the centralized error handler for consistent critical error reporting.
|
|
310
152
|
ErrorHandler.handleError(err, {
|
|
311
153
|
...context,
|
|
312
154
|
operation: "initializeAndStartServer_Catch",
|
|
313
155
|
critical: true,
|
|
314
156
|
});
|
|
315
|
-
// Exit the process with a non-zero code to indicate failure.
|
|
316
157
|
logger.info("Exiting process due to critical initialization error.", context);
|
|
317
158
|
process.exit(1);
|
|
318
159
|
}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Barrel file for the gitAdd tool.
|
|
3
|
-
*
|
|
3
|
+
* @module src/mcp-server/tools/gitAdd/index
|
|
4
4
|
*/
|
|
5
|
-
export { registerGitAddTool
|
|
6
|
-
// Export types if needed elsewhere, e.g.:
|
|
7
|
-
// export type { GitAddInput, GitAddResult } from './logic.js';
|
|
5
|
+
export { registerGitAddTool } from "./registration.js";
|
|
@@ -1,14 +1,9 @@
|
|
|
1
|
+
import { BaseErrorCode, McpError } from "../../../types-global/errors.js";
|
|
1
2
|
import { execFile } from "child_process";
|
|
2
3
|
import { promisify } from "util";
|
|
3
4
|
import { z } from "zod";
|
|
4
|
-
|
|
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";
|
|
5
|
+
import { logger, sanitization } from "../../../utils/index.js";
|
|
10
6
|
const execFileAsync = promisify(execFile);
|
|
11
|
-
// Define the input schema for the git_add tool using Zod
|
|
12
7
|
export const GitAddInputSchema = z.object({
|
|
13
8
|
path: z
|
|
14
9
|
.string()
|
|
@@ -21,121 +16,50 @@ export const GitAddInputSchema = z.object({
|
|
|
21
16
|
.default(".")
|
|
22
17
|
.describe("Files or patterns to stage, defaults to all changes ('.')"),
|
|
23
18
|
});
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
* @throws {McpError} Throws an McpError if path resolution or validation fails, or if the git command fails unexpectedly.
|
|
31
|
-
*/
|
|
32
|
-
export async function addGitFiles(input, context) {
|
|
19
|
+
export const GitAddOutputSchema = z.object({
|
|
20
|
+
success: z.boolean().describe("Indicates whether the operation was successful."),
|
|
21
|
+
statusMessage: z.string().describe("A message describing the result of the operation."),
|
|
22
|
+
filesStaged: z.union([z.string(), z.array(z.string())]).describe("The files or patterns that were staged."),
|
|
23
|
+
});
|
|
24
|
+
export async function addGitFiles(params, context) {
|
|
33
25
|
const operation = "addGitFiles";
|
|
34
|
-
logger.debug(`Executing ${operation}`, { ...context,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (input.path && input.path !== ".") {
|
|
39
|
-
targetPath = input.path;
|
|
40
|
-
logger.debug(`Using provided path: ${targetPath}`, {
|
|
41
|
-
...context,
|
|
42
|
-
operation,
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
else {
|
|
46
|
-
const workingDir = context.getWorkingDirectory();
|
|
47
|
-
if (!workingDir) {
|
|
48
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, "No path provided and no working directory set for the session.", { context, operation });
|
|
49
|
-
}
|
|
50
|
-
targetPath = workingDir;
|
|
51
|
-
logger.debug(`Using session working directory: ${targetPath}`, {
|
|
52
|
-
...context,
|
|
53
|
-
operation,
|
|
54
|
-
sessionId: context.sessionId,
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
// Sanitize the resolved path
|
|
58
|
-
const sanitizedPathInfo = sanitization.sanitizePath(targetPath, {
|
|
59
|
-
allowAbsolute: true,
|
|
60
|
-
});
|
|
61
|
-
logger.debug("Sanitized repository path", {
|
|
62
|
-
...context,
|
|
63
|
-
operation,
|
|
64
|
-
sanitizedPathInfo,
|
|
65
|
-
});
|
|
66
|
-
targetPath = sanitizedPathInfo.sanitizedPath; // Use the sanitized path going forward
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
logger.error("Path resolution or sanitization failed", {
|
|
70
|
-
...context,
|
|
71
|
-
operation,
|
|
72
|
-
error,
|
|
73
|
-
});
|
|
74
|
-
if (error instanceof McpError) {
|
|
75
|
-
throw error;
|
|
76
|
-
}
|
|
77
|
-
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
26
|
+
logger.debug(`Executing ${operation}`, { ...context, params });
|
|
27
|
+
const workingDir = context.getWorkingDirectory();
|
|
28
|
+
if (params.path === "." && !workingDir) {
|
|
29
|
+
throw new McpError(BaseErrorCode.VALIDATION_ERROR, "No session working directory set. Please specify a 'path' or use 'git_set_working_dir' first.");
|
|
78
30
|
}
|
|
79
|
-
const
|
|
31
|
+
const targetPath = sanitization.sanitizePath(params.path === "." ? workingDir : params.path, { allowAbsolute: true }).sanitizedPath;
|
|
32
|
+
const filesToStage = Array.isArray(params.files) ? params.files : [params.files];
|
|
80
33
|
if (filesToStage.length === 0) {
|
|
81
|
-
filesToStage.push(".");
|
|
82
|
-
}
|
|
83
|
-
try {
|
|
84
|
-
const args = ["-C", targetPath, "add", "--"];
|
|
85
|
-
filesToStage.forEach((file) => {
|
|
86
|
-
// Sanitize each file path. Although execFile is safer,
|
|
87
|
-
// this prevents arguments like "-v" from being treated as flags by git.
|
|
88
|
-
const sanitizedFile = file.startsWith("-") ? `./${file}` : file;
|
|
89
|
-
args.push(sanitizedFile);
|
|
90
|
-
});
|
|
91
|
-
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
92
|
-
...context,
|
|
93
|
-
operation,
|
|
94
|
-
});
|
|
95
|
-
const { stdout, stderr } = await execFileAsync("git", args);
|
|
96
|
-
if (stderr) {
|
|
97
|
-
// Log stderr as warning, as 'git add' can produce warnings but still succeed.
|
|
98
|
-
logger.warning(`Git add command produced stderr`, {
|
|
99
|
-
...context,
|
|
100
|
-
operation,
|
|
101
|
-
stderr,
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
const filesAddedDesc = Array.isArray(filesToStage)
|
|
105
|
-
? filesToStage.join(", ")
|
|
106
|
-
: filesToStage;
|
|
107
|
-
const successMessage = `Successfully staged: ${filesAddedDesc}`;
|
|
108
|
-
logger.info(successMessage, {
|
|
109
|
-
...context,
|
|
110
|
-
operation,
|
|
111
|
-
path: targetPath,
|
|
112
|
-
files: filesToStage,
|
|
113
|
-
});
|
|
114
|
-
const reminder = "Remember to write clear, concise commit messages using the Conventional Commits format (e.g., 'feat(scope): subject').";
|
|
115
|
-
// Use statusMessage and add reminder
|
|
116
|
-
return {
|
|
117
|
-
success: true,
|
|
118
|
-
statusMessage: `${successMessage}. ${reminder}`,
|
|
119
|
-
filesStaged: filesToStage,
|
|
120
|
-
};
|
|
34
|
+
filesToStage.push(".");
|
|
121
35
|
}
|
|
122
|
-
|
|
123
|
-
|
|
36
|
+
const args = ["-C", targetPath, "add", "--", ...filesToStage.map(file => file.startsWith("-") ? `./${file}` : file)];
|
|
37
|
+
logger.debug(`Executing command: git ${args.join(" ")}`, {
|
|
38
|
+
...context,
|
|
39
|
+
operation,
|
|
40
|
+
});
|
|
41
|
+
const { stderr } = await execFileAsync("git", args);
|
|
42
|
+
if (stderr) {
|
|
43
|
+
logger.warning(`Git add command produced stderr`, {
|
|
124
44
|
...context,
|
|
125
45
|
operation,
|
|
126
|
-
|
|
127
|
-
error: error.message,
|
|
128
|
-
stderr: error.stderr,
|
|
46
|
+
stderr,
|
|
129
47
|
});
|
|
130
|
-
const errorMessage = error.stderr || error.message || "";
|
|
131
|
-
if (errorMessage.toLowerCase().includes("not a git repository")) {
|
|
132
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`, { context, operation, originalError: error });
|
|
133
|
-
}
|
|
134
|
-
if (errorMessage.toLowerCase().includes("did not match any files")) {
|
|
135
|
-
// Still throw an error, but return structured info in the catch block of the registration
|
|
136
|
-
throw new McpError(BaseErrorCode.NOT_FOUND, `Specified files/patterns did not match any files in ${targetPath}: ${filesToStage.join(", ")}`, { context, operation, originalError: error, filesStaged: filesToStage });
|
|
137
|
-
}
|
|
138
|
-
// Throw generic error for other cases
|
|
139
|
-
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to stage files for path: ${targetPath}. Error: ${errorMessage}`, { context, operation, originalError: error, filesStaged: filesToStage });
|
|
140
48
|
}
|
|
49
|
+
const filesAddedDesc = Array.isArray(filesToStage)
|
|
50
|
+
? filesToStage.join(", ")
|
|
51
|
+
: filesToStage;
|
|
52
|
+
const successMessage = `Successfully staged: ${filesAddedDesc}`;
|
|
53
|
+
logger.info(successMessage, {
|
|
54
|
+
...context,
|
|
55
|
+
operation,
|
|
56
|
+
path: targetPath,
|
|
57
|
+
files: filesToStage,
|
|
58
|
+
});
|
|
59
|
+
const reminder = "Remember to write clear, concise commit messages using the Conventional Commits format (e.g., 'feat(scope): subject').";
|
|
60
|
+
return {
|
|
61
|
+
success: true,
|
|
62
|
+
statusMessage: `${successMessage}. ${reminder}`,
|
|
63
|
+
filesStaged: filesToStage,
|
|
64
|
+
};
|
|
141
65
|
}
|