@cyanheads/git-mcp-server 2.0.1 → 2.0.3
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 +55 -89
- package/{build → dist}/config/index.js +16 -18
- package/{build → dist}/index.js +80 -30
- package/dist/mcp-server/server.js +296 -0
- package/{build → dist}/mcp-server/tools/gitAdd/logic.js +9 -6
- package/{build → dist}/mcp-server/tools/gitAdd/registration.js +7 -4
- package/{build → dist}/mcp-server/tools/gitBranch/logic.js +23 -12
- package/{build → dist}/mcp-server/tools/gitBranch/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitCheckout/logic.js +92 -44
- package/{build → dist}/mcp-server/tools/gitCheckout/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitCherryPick/logic.js +10 -7
- package/{build → dist}/mcp-server/tools/gitCherryPick/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitClean/logic.js +9 -6
- package/{build → dist}/mcp-server/tools/gitClean/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitClearWorkingDir/logic.js +3 -2
- package/{build → dist}/mcp-server/tools/gitClearWorkingDir/registration.js +7 -4
- package/{build → dist}/mcp-server/tools/gitClone/logic.js +8 -5
- package/{build → dist}/mcp-server/tools/gitClone/registration.js +7 -4
- package/dist/mcp-server/tools/gitCommit/logic.js +207 -0
- package/{build → dist}/mcp-server/tools/gitCommit/registration.js +22 -15
- package/{build → dist}/mcp-server/tools/gitDiff/logic.js +9 -6
- package/{build → dist}/mcp-server/tools/gitDiff/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitFetch/logic.js +10 -7
- package/{build → dist}/mcp-server/tools/gitFetch/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitInit/index.js +2 -2
- package/{build → dist}/mcp-server/tools/gitInit/logic.js +9 -6
- package/dist/mcp-server/tools/gitInit/registration.js +98 -0
- package/{build → dist}/mcp-server/tools/gitLog/logic.js +53 -16
- package/{build → dist}/mcp-server/tools/gitLog/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitMerge/logic.js +9 -6
- package/{build → dist}/mcp-server/tools/gitMerge/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitPull/logic.js +11 -8
- package/{build → dist}/mcp-server/tools/gitPull/registration.js +7 -4
- package/{build → dist}/mcp-server/tools/gitPush/logic.js +12 -9
- package/{build → dist}/mcp-server/tools/gitPush/registration.js +7 -4
- package/{build → dist}/mcp-server/tools/gitRebase/logic.js +9 -6
- package/{build → dist}/mcp-server/tools/gitRebase/registration.js +8 -5
- package/{build → dist}/mcp-server/tools/gitRemote/logic.js +4 -5
- package/{build → dist}/mcp-server/tools/gitRemote/registration.js +2 -4
- package/{build → dist}/mcp-server/tools/gitReset/logic.js +5 -6
- package/{build → dist}/mcp-server/tools/gitReset/registration.js +2 -4
- package/{build → dist}/mcp-server/tools/gitSetWorkingDir/logic.js +5 -6
- package/{build → dist}/mcp-server/tools/gitSetWorkingDir/registration.js +22 -13
- package/{build → dist}/mcp-server/tools/gitShow/logic.js +5 -6
- package/{build → dist}/mcp-server/tools/gitShow/registration.js +3 -5
- package/{build → dist}/mcp-server/tools/gitStash/logic.js +5 -6
- package/{build → dist}/mcp-server/tools/gitStash/registration.js +3 -5
- package/{build → dist}/mcp-server/tools/gitStatus/logic.js +5 -6
- package/{build → dist}/mcp-server/tools/gitStatus/registration.js +2 -4
- package/{build → dist}/mcp-server/tools/gitTag/logic.js +3 -4
- package/{build → dist}/mcp-server/tools/gitTag/registration.js +2 -4
- package/dist/mcp-server/transports/authentication/authMiddleware.js +145 -0
- package/dist/mcp-server/transports/httpTransport.js +432 -0
- package/dist/mcp-server/transports/stdioTransport.js +87 -0
- package/{build → dist}/types-global/errors.js +2 -2
- package/dist/utils/index.js +12 -0
- package/{build/utils → dist/utils/internal}/errorHandler.js +18 -8
- package/dist/utils/internal/index.js +3 -0
- package/dist/utils/internal/logger.js +254 -0
- package/{build/utils → dist/utils/internal}/requestContext.js +2 -3
- package/dist/utils/metrics/index.js +1 -0
- package/{build/utils → dist/utils/metrics}/tokenCounter.js +3 -3
- package/dist/utils/parsing/dateParser.js +62 -0
- package/dist/utils/parsing/index.js +2 -0
- package/{build/utils → dist/utils/parsing}/jsonParser.js +3 -2
- package/{build/utils → dist/utils/security}/idGenerator.js +4 -5
- package/dist/utils/security/index.js +3 -0
- package/{build/utils → dist/utils/security}/rateLimiter.js +7 -10
- package/{build/utils → dist/utils/security}/sanitization.js +4 -3
- package/package.json +20 -16
- package/build/mcp-server/server.js +0 -572
- package/build/mcp-server/tools/gitCommit/logic.js +0 -129
- package/build/mcp-server/tools/gitInit/registration.js +0 -44
- package/build/types-global/mcp.js +0 -59
- package/build/types-global/tool.js +0 -1
- package/build/utils/index.js +0 -11
- package/build/utils/logger.js +0 -266
- /package/{build → dist}/mcp-server/tools/gitAdd/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitBranch/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitCheckout/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitCherryPick/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitClean/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitClearWorkingDir/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitClone/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitCommit/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitDiff/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitFetch/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitLog/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitMerge/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitPull/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitPush/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitRebase/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitRemote/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitReset/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitSetWorkingDir/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitShow/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitStash/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitStatus/index.js +0 -0
- /package/{build → dist}/mcp-server/tools/gitTag/index.js +0 -0
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main entry point for the MCP (Model Context Protocol) server.
|
|
3
|
+
* This file orchestrates the server's lifecycle:
|
|
4
|
+
* 1. Initializes the core McpServer instance with its identity and capabilities.
|
|
5
|
+
* 2. Registers available resources and tools, making them discoverable and usable by clients.
|
|
6
|
+
* 3. Selects and starts the appropriate communication transport (stdio or Streamable HTTP)
|
|
7
|
+
* based on configuration.
|
|
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
|
+
*/
|
|
15
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
16
|
+
// Import validated configuration and environment details.
|
|
17
|
+
import { config, environment } from '../config/index.js';
|
|
18
|
+
// Import core utilities: ErrorHandler, logger, requestContextService.
|
|
19
|
+
import { ErrorHandler, logger, requestContextService } from '../utils/index.js'; // Added RequestContext
|
|
20
|
+
// Import registration AND state initialization functions for ALL Git tools (assuming pattern)
|
|
21
|
+
import { registerGitAddTool, initializeGitAddStateAccessors } from './tools/gitAdd/index.js';
|
|
22
|
+
import { registerGitBranchTool, initializeGitBranchStateAccessors } from './tools/gitBranch/index.js';
|
|
23
|
+
import { registerGitCheckoutTool, initializeGitCheckoutStateAccessors } from './tools/gitCheckout/index.js'; // Assumed initializer
|
|
24
|
+
import { registerGitCherryPickTool, initializeGitCherryPickStateAccessors } from './tools/gitCherryPick/index.js'; // Assumed initializer
|
|
25
|
+
import { registerGitCleanTool, initializeGitCleanStateAccessors } from './tools/gitClean/index.js'; // Assumed initializer
|
|
26
|
+
import { registerGitClearWorkingDirTool, initializeGitClearWorkingDirStateAccessors } from './tools/gitClearWorkingDir/index.js'; // Assumed initializer
|
|
27
|
+
import { registerGitCloneTool } from './tools/gitClone/index.js'; // Removed initializer import
|
|
28
|
+
import { registerGitCommitTool, initializeGitCommitStateAccessors } from './tools/gitCommit/index.js'; // Assumed initializer
|
|
29
|
+
import { registerGitDiffTool, initializeGitDiffStateAccessors } from './tools/gitDiff/index.js'; // Assumed initializer
|
|
30
|
+
import { registerGitFetchTool, initializeGitFetchStateAccessors } from './tools/gitFetch/index.js';
|
|
31
|
+
import { registerGitInitTool, initializeGitInitStateAccessors } from './tools/gitInit/index.js'; // Added initializer import
|
|
32
|
+
import { registerGitLogTool, initializeGitLogStateAccessors } from './tools/gitLog/index.js'; // Assumed initializer
|
|
33
|
+
import { registerGitMergeTool, initializeGitMergeStateAccessors } from './tools/gitMerge/index.js'; // Assumed initializer
|
|
34
|
+
import { registerGitPullTool, initializeGitPullStateAccessors } from './tools/gitPull/index.js';
|
|
35
|
+
import { registerGitPushTool, initializeGitPushStateAccessors } from './tools/gitPush/index.js';
|
|
36
|
+
import { registerGitRebaseTool, initializeGitRebaseStateAccessors } from './tools/gitRebase/index.js'; // Assumed initializer
|
|
37
|
+
import { registerGitRemoteTool, initializeGitRemoteStateAccessors } from './tools/gitRemote/index.js'; // Assumed initializer
|
|
38
|
+
import { registerGitResetTool, initializeGitResetStateAccessors } from './tools/gitReset/index.js'; // Assumed initializer
|
|
39
|
+
import { registerGitSetWorkingDirTool, initializeGitSetWorkingDirStateAccessors } from './tools/gitSetWorkingDir/index.js';
|
|
40
|
+
import { registerGitShowTool, initializeGitShowStateAccessors } from './tools/gitShow/index.js'; // Assumed initializer
|
|
41
|
+
import { registerGitStashTool, initializeGitStashStateAccessors } from './tools/gitStash/index.js'; // Assumed initializer
|
|
42
|
+
import { registerGitStatusTool, initializeGitStatusStateAccessors } from './tools/gitStatus/index.js'; // Assumed initializer
|
|
43
|
+
import { registerGitTagTool, initializeGitTagStateAccessors } from './tools/gitTag/index.js'; // Assumed initializer
|
|
44
|
+
// Import transport setup functions AND state accessors
|
|
45
|
+
import { startHttpTransport, getHttpSessionWorkingDirectory, setHttpSessionWorkingDirectory } from './transports/httpTransport.js';
|
|
46
|
+
import { connectStdioTransport, getStdioWorkingDirectory, setStdioWorkingDirectory } from './transports/stdioTransport.js';
|
|
47
|
+
/**
|
|
48
|
+
* Creates and configures a new instance of the McpServer.
|
|
49
|
+
*
|
|
50
|
+
* This function is central to defining the server's identity and functionality
|
|
51
|
+
* as presented to connecting clients during the MCP initialization phase.
|
|
52
|
+
*
|
|
53
|
+
* MCP Spec Relevance:
|
|
54
|
+
* - Server Identity (`serverInfo`): The `name` and `version` provided here are part
|
|
55
|
+
* of the `ServerInformation` object returned in the `InitializeResult` message,
|
|
56
|
+
* allowing clients to identify the server they are connected to.
|
|
57
|
+
* - Capabilities Declaration: The `capabilities` object declares the features this
|
|
58
|
+
* server supports, enabling clients to tailor their interactions.
|
|
59
|
+
* - `logging: {}`: Indicates the server can receive `logging/setLevel` requests
|
|
60
|
+
* and may send `notifications/message` log messages (handled by the logger utility).
|
|
61
|
+
* - `resources: { listChanged: true }`: Signals that the server supports dynamic
|
|
62
|
+
* resource lists and will send `notifications/resources/list_changed` if the
|
|
63
|
+
* available resources change after initialization. (Currently no resources registered)
|
|
64
|
+
* - `tools: { listChanged: true }`: Signals support for dynamic tool lists and
|
|
65
|
+
* `notifications/tools/list_changed`.
|
|
66
|
+
* - Resource/Tool Registration: This function calls specific registration functions
|
|
67
|
+
* (e.g., `registerGitAdd`) which use SDK methods (`server.resource`, `server.tool`)
|
|
68
|
+
* to make capabilities available for discovery (`resources/list`, `tools/list`) and
|
|
69
|
+
* invocation (`resources/read`, `tools/call`).
|
|
70
|
+
*
|
|
71
|
+
* Design Note: This factory function is used to create server instances. For the 'stdio'
|
|
72
|
+
* transport, it's called once. For the 'http' transport, it's passed to `startHttpTransport`
|
|
73
|
+
* and called *per session* to ensure session isolation.
|
|
74
|
+
*
|
|
75
|
+
* @returns {Promise<McpServer>} A promise resolving with the configured McpServer instance.
|
|
76
|
+
* @throws {Error} If any resource or tool registration fails.
|
|
77
|
+
*/
|
|
78
|
+
// Removed sessionId parameter, it will be retrieved from context within tool handlers
|
|
79
|
+
async function createMcpServerInstance() {
|
|
80
|
+
const context = { operation: 'createMcpServerInstance' };
|
|
81
|
+
logger.info('Initializing MCP server instance', context);
|
|
82
|
+
// Configure the request context service (used for correlating logs/errors).
|
|
83
|
+
requestContextService.configure({
|
|
84
|
+
appName: config.mcpServerName,
|
|
85
|
+
appVersion: config.mcpServerVersion,
|
|
86
|
+
environment,
|
|
87
|
+
});
|
|
88
|
+
// Instantiate the core McpServer using the SDK.
|
|
89
|
+
// Provide server identity (name, version) and declare supported capabilities.
|
|
90
|
+
// Note: Resources capability declared, but none are registered currently.
|
|
91
|
+
logger.debug('Instantiating McpServer with capabilities', { ...context, serverInfo: { name: config.mcpServerName, version: config.mcpServerVersion }, capabilities: { logging: {}, resources: { listChanged: true }, tools: { listChanged: true } } });
|
|
92
|
+
const server = new McpServer({ name: config.mcpServerName, version: config.mcpServerVersion }, // ServerInformation part of InitializeResult
|
|
93
|
+
{ capabilities: { logging: {}, resources: { listChanged: true }, tools: { listChanged: true } } } // Declared capabilities
|
|
94
|
+
);
|
|
95
|
+
// --- Define Unified State Accessor Functions ---
|
|
96
|
+
// These functions abstract away the transport type to get/set session state.
|
|
97
|
+
/** Gets the session ID from the tool's execution context. */
|
|
98
|
+
const getSessionIdFromContext = (toolContext) => {
|
|
99
|
+
// The RequestContext created by the tool registration wrapper should contain the sessionId.
|
|
100
|
+
return toolContext?.sessionId;
|
|
101
|
+
};
|
|
102
|
+
/** Gets the working directory based on transport type and session ID. */
|
|
103
|
+
const getWorkingDirectory = (sessionId) => {
|
|
104
|
+
if (config.mcpTransportType === 'http') {
|
|
105
|
+
if (!sessionId) {
|
|
106
|
+
logger.warning('Attempted to get HTTP working directory without session ID', { ...context, caller: 'getWorkingDirectory' });
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
return getHttpSessionWorkingDirectory(sessionId);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
// For stdio, there's only one implicit session, ID is not needed.
|
|
113
|
+
return getStdioWorkingDirectory();
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
/** Sets the working directory based on transport type and session ID. */
|
|
117
|
+
const setWorkingDirectory = (sessionId, dir) => {
|
|
118
|
+
if (config.mcpTransportType === 'http') {
|
|
119
|
+
if (!sessionId) {
|
|
120
|
+
logger.error('Attempted to set HTTP working directory without session ID', { ...context, caller: 'setWorkingDirectory', dir });
|
|
121
|
+
// Optionally throw an error or just log
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
setHttpSessionWorkingDirectory(sessionId, dir);
|
|
125
|
+
}
|
|
126
|
+
else {
|
|
127
|
+
// For stdio, set the single session's directory.
|
|
128
|
+
setStdioWorkingDirectory(dir);
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
// --- Initialize Tool State Accessors BEFORE Registration ---
|
|
132
|
+
// Pass the defined unified accessor functions to the initializers.
|
|
133
|
+
logger.debug('Initializing state accessors for tools...', context);
|
|
134
|
+
try {
|
|
135
|
+
// Call initializers for all tools that likely need state access.
|
|
136
|
+
// If an initializer doesn't exist, the import would have failed earlier (or build will fail).
|
|
137
|
+
initializeGitAddStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
138
|
+
initializeGitBranchStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
139
|
+
initializeGitCheckoutStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
140
|
+
initializeGitCherryPickStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
141
|
+
initializeGitCleanStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
142
|
+
initializeGitClearWorkingDirStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
143
|
+
// initializeGitCloneStateAccessors(getWorkingDirectory, getSessionIdFromContext); // Removed call
|
|
144
|
+
initializeGitCommitStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
145
|
+
initializeGitDiffStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
146
|
+
initializeGitFetchStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
147
|
+
initializeGitInitStateAccessors(getWorkingDirectory, getSessionIdFromContext); // Added call
|
|
148
|
+
initializeGitLogStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
149
|
+
initializeGitMergeStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
150
|
+
initializeGitPullStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
151
|
+
initializeGitPushStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
152
|
+
initializeGitRebaseStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
153
|
+
initializeGitRemoteStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
154
|
+
initializeGitResetStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
155
|
+
initializeGitSetWorkingDirStateAccessors(getWorkingDirectory, setWorkingDirectory, getSessionIdFromContext); // Special case
|
|
156
|
+
initializeGitShowStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
157
|
+
initializeGitStashStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
158
|
+
initializeGitStatusStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
159
|
+
initializeGitTagStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
160
|
+
logger.debug('State accessors initialized successfully.', context);
|
|
161
|
+
}
|
|
162
|
+
catch (initError) {
|
|
163
|
+
// Catch errors specifically during initialization phase
|
|
164
|
+
logger.error('Failed during state accessor initialization', {
|
|
165
|
+
...context,
|
|
166
|
+
error: initError instanceof Error ? initError.message : String(initError),
|
|
167
|
+
stack: initError instanceof Error ? initError.stack : undefined,
|
|
168
|
+
});
|
|
169
|
+
throw initError; // Re-throw to prevent server starting incorrectly
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
// Register all defined Git tools. These calls populate the server's
|
|
173
|
+
// internal registry, making them available via MCP methods like 'tools/list'.
|
|
174
|
+
logger.debug('Registering Git tools...', context);
|
|
175
|
+
await registerGitAddTool(server);
|
|
176
|
+
await registerGitBranchTool(server);
|
|
177
|
+
await registerGitCheckoutTool(server);
|
|
178
|
+
await registerGitCherryPickTool(server);
|
|
179
|
+
await registerGitCleanTool(server);
|
|
180
|
+
await registerGitClearWorkingDirTool(server);
|
|
181
|
+
await registerGitCloneTool(server);
|
|
182
|
+
await registerGitCommitTool(server);
|
|
183
|
+
await registerGitDiffTool(server);
|
|
184
|
+
await registerGitFetchTool(server);
|
|
185
|
+
await registerGitInitTool(server);
|
|
186
|
+
await registerGitLogTool(server);
|
|
187
|
+
await registerGitMergeTool(server);
|
|
188
|
+
await registerGitPullTool(server);
|
|
189
|
+
await registerGitPushTool(server);
|
|
190
|
+
await registerGitRebaseTool(server);
|
|
191
|
+
await registerGitRemoteTool(server);
|
|
192
|
+
await registerGitResetTool(server);
|
|
193
|
+
await registerGitSetWorkingDirTool(server);
|
|
194
|
+
await registerGitShowTool(server);
|
|
195
|
+
await registerGitStashTool(server);
|
|
196
|
+
await registerGitStatusTool(server);
|
|
197
|
+
await registerGitTagTool(server);
|
|
198
|
+
// Add calls to register other resources/tools here if needed in the future.
|
|
199
|
+
logger.info('Git tools registered successfully', context);
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
// Registration is critical; log and re-throw errors.
|
|
203
|
+
logger.error('Failed to register resources/tools', {
|
|
204
|
+
...context,
|
|
205
|
+
error: err instanceof Error ? err.message : String(err),
|
|
206
|
+
stack: err instanceof Error ? err.stack : undefined, // Include stack for debugging
|
|
207
|
+
});
|
|
208
|
+
throw err; // Propagate error to prevent server starting with incomplete capabilities.
|
|
209
|
+
}
|
|
210
|
+
return server;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Selects, sets up, and starts the appropriate MCP transport layer based on configuration.
|
|
214
|
+
* This function acts as the bridge between the core server logic and the communication channel.
|
|
215
|
+
*
|
|
216
|
+
* MCP Spec Relevance:
|
|
217
|
+
* - Transport Selection: Reads `config.mcpTransportType` ('stdio' or 'http') to determine
|
|
218
|
+
* which transport mechanism defined in the MCP specification to use.
|
|
219
|
+
* - Transport Connection: Calls dedicated functions (`connectStdioTransport` or `startHttpTransport`)
|
|
220
|
+
* which handle the specifics of establishing communication according to the chosen
|
|
221
|
+
* transport's rules (e.g., stdin/stdout handling for 'stdio', HTTP server setup and
|
|
222
|
+
* endpoint handling for 'http').
|
|
223
|
+
* - Server Instance Lifecycle:
|
|
224
|
+
* - For 'stdio', creates a single `McpServer` instance for the lifetime of the process.
|
|
225
|
+
* - For 'http', passes the `createMcpServerInstance` factory function to `startHttpTransport`,
|
|
226
|
+
* allowing the HTTP transport to create a new, isolated server instance for each client session,
|
|
227
|
+
* aligning with the stateful session management described in the Streamable HTTP spec.
|
|
228
|
+
*
|
|
229
|
+
* @returns {Promise<McpServer | void>} Resolves with the McpServer instance for 'stdio', or void for 'http'.
|
|
230
|
+
* @throws {Error} If the configured transport type is unsupported or if transport setup fails.
|
|
231
|
+
*/
|
|
232
|
+
async function startTransport() {
|
|
233
|
+
// Determine the transport type from the validated configuration.
|
|
234
|
+
const transportType = config.mcpTransportType;
|
|
235
|
+
const context = { operation: 'startTransport', transport: transportType };
|
|
236
|
+
logger.info(`Starting transport: ${transportType}`, context);
|
|
237
|
+
// --- HTTP Transport Setup ---
|
|
238
|
+
if (transportType === 'http') {
|
|
239
|
+
logger.debug('Delegating to startHttpTransport...', context);
|
|
240
|
+
// For HTTP, the transport layer manages its own lifecycle and potentially multiple sessions.
|
|
241
|
+
// We pass the factory function to allow the HTTP transport to create server instances as needed (per session).
|
|
242
|
+
await startHttpTransport(createMcpServerInstance, context);
|
|
243
|
+
// The HTTP server runs indefinitely, listening for connections, so this function returns void.
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
// --- Stdio Transport Setup ---
|
|
247
|
+
if (transportType === 'stdio') {
|
|
248
|
+
logger.debug('Creating single McpServer instance for stdio transport...', context);
|
|
249
|
+
// For stdio, there's typically one persistent connection managed by a parent process.
|
|
250
|
+
// Create a single McpServer instance for the entire process lifetime.
|
|
251
|
+
const server = await createMcpServerInstance();
|
|
252
|
+
logger.debug('Delegating to connectStdioTransport...', context);
|
|
253
|
+
// Connect the server instance to the stdio transport handler.
|
|
254
|
+
await connectStdioTransport(server, context);
|
|
255
|
+
// Return the server instance; the caller (main entry point) might hold onto it.
|
|
256
|
+
return server;
|
|
257
|
+
}
|
|
258
|
+
// --- Unsupported Transport ---
|
|
259
|
+
// This case should theoretically not be reached due to config validation, but acts as a safeguard.
|
|
260
|
+
logger.fatal(`Unsupported transport type configured: ${transportType}`, context);
|
|
261
|
+
throw new Error(`Unsupported transport type: ${transportType}. Must be 'stdio' or 'http'.`);
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Main application entry point. Initializes and starts the MCP server.
|
|
265
|
+
*
|
|
266
|
+
* MCP Spec Relevance:
|
|
267
|
+
* - Orchestrates the server startup sequence, culminating in a server ready to accept
|
|
268
|
+
* connections and process MCP messages according to the chosen transport's rules.
|
|
269
|
+
* - Implements top-level error handling for critical startup failures, ensuring the
|
|
270
|
+
* process exits appropriately if it cannot initialize correctly.
|
|
271
|
+
*
|
|
272
|
+
* @returns {Promise<void | McpServer>} Resolves upon successful startup (void for http, McpServer for stdio). Rejects on critical failure.
|
|
273
|
+
*/
|
|
274
|
+
export async function initializeAndStartServer() {
|
|
275
|
+
const context = { operation: 'initializeAndStartServer' };
|
|
276
|
+
logger.info('MCP Server initialization sequence started.', context);
|
|
277
|
+
try {
|
|
278
|
+
// Initiate the transport setup based on configuration.
|
|
279
|
+
const result = await startTransport();
|
|
280
|
+
logger.info('MCP Server initialization sequence completed successfully.', context);
|
|
281
|
+
return result;
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
// Catch any errors that occurred during server instance creation or transport setup.
|
|
285
|
+
logger.fatal('Critical error during MCP server initialization.', {
|
|
286
|
+
...context,
|
|
287
|
+
error: err instanceof Error ? err.message : String(err),
|
|
288
|
+
stack: err instanceof Error ? err.stack : undefined,
|
|
289
|
+
});
|
|
290
|
+
// Use the centralized error handler for consistent critical error reporting.
|
|
291
|
+
ErrorHandler.handleError(err, { ...context, critical: true });
|
|
292
|
+
// Exit the process with a non-zero code to indicate failure.
|
|
293
|
+
logger.info('Exiting process due to critical initialization error.', context);
|
|
294
|
+
process.exit(1);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { promisify } from 'util';
|
|
3
1
|
import { exec } from 'child_process';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
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';
|
|
7
10
|
const execAsync = promisify(exec);
|
|
8
11
|
// Define the input schema for the git_add tool using Zod
|
|
9
12
|
export const GitAddInputSchema = z.object({
|
|
10
|
-
path: z.string().min(1).optional().default('.').describe("Path to the Git repository. Defaults to the
|
|
13
|
+
path: z.string().min(1).optional().default('.').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."),
|
|
11
14
|
files: z.union([
|
|
12
15
|
z.string().min(1),
|
|
13
16
|
z.array(z.string().min(1))
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
2
|
+
import { logger } from '../../../utils/index.js';
|
|
3
|
+
// Import utils from barrel (ErrorHandler from ../utils/internal/errorHandler.js)
|
|
4
|
+
import { ErrorHandler } from '../../../utils/index.js';
|
|
5
|
+
// Import utils from barrel (requestContextService from ../utils/internal/requestContext.js)
|
|
6
|
+
import { requestContextService } from '../../../utils/index.js';
|
|
4
7
|
// Import the result type along with the function and input schema
|
|
5
|
-
import { addGitFiles, GitAddInputSchema } from './logic.js';
|
|
6
8
|
import { BaseErrorCode } from '../../../types-global/errors.js'; // Import BaseErrorCode
|
|
9
|
+
import { addGitFiles, GitAddInputSchema } from './logic.js';
|
|
7
10
|
let _getWorkingDirectory;
|
|
8
11
|
let _getSessionId;
|
|
9
12
|
/**
|
|
@@ -1,17 +1,20 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { promisify } from 'util';
|
|
3
1
|
import { exec } from 'child_process';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
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';
|
|
7
10
|
const execAsync = promisify(exec);
|
|
8
11
|
// Define the BASE input schema for the git_branch tool using Zod
|
|
9
12
|
export const GitBranchBaseSchema = z.object({
|
|
10
|
-
path: z.string().min(1).optional().default('.').describe("Path to the local Git repository.
|
|
13
|
+
path: z.string().min(1).optional().default('.').describe("Path to the local Git repository. Defaults to the directory set via `git_set_working_dir` for the session; set 'git_set_working_dir' if not set."),
|
|
11
14
|
mode: z.enum(['list', 'create', 'delete', 'rename', 'show-current']).describe("The branch operation to perform: 'list', 'create', 'delete', 'rename', 'show-current'."),
|
|
12
|
-
branchName: z.string().min(1).optional().describe("The name of the branch. Required for 'create', 'delete', 'rename' modes."),
|
|
13
|
-
newBranchName: z.string().min(1).optional().describe("The new name for the branch. Required for 'rename' mode."),
|
|
14
|
-
startPoint: z.string().min(1).optional().describe("Optional commit hash, tag, or existing branch name to start the new branch from. Used only in 'create' mode. Defaults to HEAD."),
|
|
15
|
+
branchName: z.string().min(1).optional().describe("The name of the branch (e.g., 'feat/new-login', 'main'). Required for 'create', 'delete', 'rename' modes."),
|
|
16
|
+
newBranchName: z.string().min(1).optional().describe("The new name for the branch (e.g., 'fix/typo-in-readme'). Required for 'rename' mode."),
|
|
17
|
+
startPoint: z.string().min(1).optional().describe("Optional commit hash, tag, or existing branch name (e.g., 'main', 'v1.0.0', 'commit-hash') to start the new branch from. Used only in 'create' mode. Defaults to HEAD."),
|
|
15
18
|
force: z.boolean().default(false).describe("Force the operation. Use -D for delete, -M for rename, -f for create (if branch exists). Use with caution, as forcing operations can lead to data loss."),
|
|
16
19
|
all: z.boolean().default(false).describe("List both local and remote-tracking branches. Used only in 'list' mode."),
|
|
17
20
|
remote: z.boolean().default(false).describe("Act on remote-tracking branches. Used with 'list' (-r) or 'delete' (-r)."),
|
|
@@ -80,12 +83,20 @@ export async function gitBranchLogic(input, context) {
|
|
|
80
83
|
.map(line => {
|
|
81
84
|
const isCurrent = line.startsWith('* ');
|
|
82
85
|
const trimmedLine = line.replace(/^\*?\s+/, ''); // Remove leading '*' and spaces
|
|
86
|
+
// Determine isRemote based on the raw trimmed line BEFORE splitting
|
|
87
|
+
const isRemote = trimmedLine.startsWith('remotes/');
|
|
83
88
|
const parts = trimmedLine.split(/\s+/);
|
|
84
|
-
const name = parts[0];
|
|
85
|
-
const isRemote = name.startsWith('remotes/');
|
|
89
|
+
const name = parts[0]; // This might be 'remotes/origin/main' or just 'main'
|
|
86
90
|
const commitHash = parts[1] || undefined; // Verbose gives hash
|
|
87
91
|
const commitSubject = parts.slice(2).join(' ') || undefined; // Verbose gives subject
|
|
88
|
-
|
|
92
|
+
// Return the correct name (without 'remotes/' prefix if it was remote) and the isRemote flag
|
|
93
|
+
return {
|
|
94
|
+
name: isRemote ? name.split('/').slice(2).join('/') : name, // e.g., 'origin/main' or 'main'
|
|
95
|
+
isCurrent,
|
|
96
|
+
isRemote, // Use the flag determined before splitting
|
|
97
|
+
commitHash,
|
|
98
|
+
commitSubject
|
|
99
|
+
};
|
|
89
100
|
});
|
|
90
101
|
const currentBranch = branches.find(b => b.isCurrent)?.name;
|
|
91
102
|
result = { success: true, mode: 'list', branches, currentBranch };
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
1
|
+
// Import utils from barrel (logger from ../utils/internal/logger.js)
|
|
2
|
+
import { logger } from '../../../utils/index.js';
|
|
3
|
+
// Import utils from barrel (ErrorHandler from ../utils/internal/errorHandler.js)
|
|
4
|
+
import { ErrorHandler } from '../../../utils/index.js';
|
|
5
|
+
// Import utils from barrel (requestContextService from ../utils/internal/requestContext.js)
|
|
6
|
+
import { BaseErrorCode } from '../../../types-global/errors.js'; // Keep direct import for types-global
|
|
7
|
+
import { requestContextService } from '../../../utils/index.js';
|
|
8
|
+
import { GitBranchBaseSchema, gitBranchLogic } from './logic.js';
|
|
6
9
|
let _getWorkingDirectory;
|
|
7
10
|
let _getSessionId;
|
|
8
11
|
/**
|
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { promisify } from 'util';
|
|
3
1
|
import { exec } from 'child_process';
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
|
|
2
|
+
import { promisify } from 'util';
|
|
3
|
+
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';
|
|
7
10
|
const execAsync = promisify(exec);
|
|
8
11
|
// Define the input schema for the git_checkout tool using Zod
|
|
9
12
|
export const GitCheckoutInputSchema = z.object({
|
|
10
|
-
path: z.string().min(1).optional().default('.').describe("Path to the Git repository. Defaults to the session
|
|
11
|
-
branchOrPath: z.string().min(1).describe("The branch name, commit hash, tag, or file path(s) to checkout."),
|
|
12
|
-
newBranch: z.string().optional().describe("Create a new branch named <new_branch> and start it at <branchOrPath>."),
|
|
13
|
+
path: z.string().min(1).optional().default('.').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."),
|
|
14
|
+
branchOrPath: z.string().min(1).describe("The branch name (e.g., 'main'), commit hash, tag, or file path(s) (e.g., './src/file.ts') to checkout."),
|
|
15
|
+
newBranch: z.string().optional().describe("Create a new branch named <new_branch> (e.g., 'feat/new-feature') and start it at <branchOrPath>."),
|
|
13
16
|
force: z.boolean().optional().default(false).describe("Force checkout even if there are uncommitted changes (use with caution, discards local changes)."),
|
|
14
17
|
// Add other relevant git checkout options as needed (e.g., --track, -b for new branch shorthand)
|
|
15
18
|
});
|
|
@@ -72,52 +75,53 @@ export async function checkoutGit(input, context) {
|
|
|
72
75
|
let currentBranch = undefined;
|
|
73
76
|
let newBranchCreated = !!input.newBranch;
|
|
74
77
|
let filesRestored = undefined;
|
|
78
|
+
let isDetachedHead = false;
|
|
79
|
+
let isFileCheckout = false;
|
|
80
|
+
// --- Initial analysis of checkout output ---
|
|
75
81
|
// Extract previous branch if available
|
|
76
82
|
const prevBranchMatch = stderr.match(/Switched to.*? from ['"]?(.*?)['"]?/);
|
|
77
83
|
if (prevBranchMatch) {
|
|
78
84
|
previousBranch = prevBranchMatch[1];
|
|
79
85
|
}
|
|
80
|
-
//
|
|
81
|
-
if (stderr.includes('Switched to branch')) {
|
|
82
|
-
const currentBranchMatch = stderr.match(/Switched to branch ['"]?(.*?)['"]?/);
|
|
83
|
-
if (currentBranchMatch)
|
|
84
|
-
currentBranch = currentBranchMatch[1];
|
|
85
|
-
message = `Switched to branch '${currentBranch || input.branchOrPath}'.`;
|
|
86
|
-
}
|
|
87
|
-
else if (stderr.includes('Switched to a new branch')) {
|
|
86
|
+
// Determine primary outcome from stderr/stdout
|
|
87
|
+
if (stderr.includes('Switched to a new branch')) {
|
|
88
88
|
const currentBranchMatch = stderr.match(/Switched to a new branch ['"]?(.*?)['"]?/);
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
89
|
+
currentBranch = currentBranchMatch ? currentBranchMatch[1] : input.newBranch; // Use matched or input
|
|
90
|
+
message = `Switched to new branch '${currentBranch}'.`;
|
|
91
|
+
newBranchCreated = true;
|
|
92
|
+
}
|
|
93
|
+
else if (stderr.includes('Switched to branch')) {
|
|
94
|
+
const currentBranchMatch = stderr.match(/Switched to branch ['"]?(.*?)['"]?/);
|
|
95
|
+
currentBranch = currentBranchMatch ? currentBranchMatch[1] : input.branchOrPath; // Use matched or input
|
|
96
|
+
message = `Switched to branch '${currentBranch}'.`;
|
|
93
97
|
}
|
|
94
98
|
else if (stderr.includes('Already on')) {
|
|
95
99
|
const currentBranchMatch = stderr.match(/Already on ['"]?(.*?)['"]?/);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
try {
|
|
107
|
-
const statusResult = await execAsync(`git -C "${targetPath}" branch --show-current`);
|
|
108
|
-
currentBranch = statusResult.stdout.trim();
|
|
100
|
+
currentBranch = currentBranchMatch ? currentBranchMatch[1] : input.branchOrPath; // Use matched or input
|
|
101
|
+
message = `Already on '${currentBranch}'.`;
|
|
102
|
+
}
|
|
103
|
+
else if (stderr.includes('Updated N path') || stdout.includes('Updated N path') || stderr.includes('Your branch is up to date with')) { // Checking out files or confirming current state
|
|
104
|
+
// Check if the input looks like file paths rather than a branch/commit
|
|
105
|
+
// This is heuristic - might need refinement if branch names look like paths
|
|
106
|
+
if (input.branchOrPath.includes('/') || input.branchOrPath.includes('.')) {
|
|
107
|
+
isFileCheckout = true;
|
|
108
|
+
message = `Restored or checked path(s): ${input.branchOrPath}`;
|
|
109
|
+
filesRestored = input.branchOrPath.split('\n').map(p => p.trim()).filter(p => p.length > 0);
|
|
109
110
|
}
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
else {
|
|
112
|
+
// Assume it was just confirming the current branch state
|
|
113
|
+
message = stderr.trim() || stdout.trim() || `Checked out ${input.branchOrPath}.`;
|
|
112
114
|
}
|
|
113
115
|
}
|
|
114
116
|
else if (stderr.includes('Previous HEAD position was') && stderr.includes('HEAD is now at')) { // Detached HEAD
|
|
115
117
|
message = `Checked out commit ${input.branchOrPath} (Detached HEAD state).`;
|
|
116
|
-
currentBranch = 'Detached HEAD';
|
|
118
|
+
currentBranch = 'Detached HEAD';
|
|
119
|
+
isDetachedHead = true;
|
|
117
120
|
}
|
|
118
|
-
else if (stderr.includes('Note: switching to')) { //
|
|
121
|
+
else if (stderr.includes('Note: switching to') || stderr.includes('Note: checking out')) { // Other detached HEAD variants
|
|
119
122
|
message = `Checked out ${input.branchOrPath} (Detached HEAD state).`;
|
|
120
123
|
currentBranch = 'Detached HEAD';
|
|
124
|
+
isDetachedHead = true;
|
|
121
125
|
}
|
|
122
126
|
else if (message.includes('fatal:')) {
|
|
123
127
|
success = false;
|
|
@@ -125,19 +129,63 @@ export async function checkoutGit(input, context) {
|
|
|
125
129
|
logger.error(`Git checkout command indicated failure: ${message}`, { ...context, operation, stdout, stderr });
|
|
126
130
|
}
|
|
127
131
|
else if (!message && !stdout && !stderr) {
|
|
128
|
-
message = 'Checkout command executed,
|
|
129
|
-
logger.
|
|
130
|
-
|
|
132
|
+
message = 'Checkout command executed silently.'; // Assume success, will verify branch below
|
|
133
|
+
logger.info(message, { ...context, operation });
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
// Some other message, treat as informational for now
|
|
137
|
+
message = stderr.trim() || stdout.trim();
|
|
138
|
+
logger.info(`Git checkout produced message: ${message}`, { ...context, operation });
|
|
139
|
+
}
|
|
140
|
+
// --- Get definitive current branch IF checkout was successful AND not file checkout/detached HEAD ---
|
|
141
|
+
if (success && !isFileCheckout && !isDetachedHead) {
|
|
131
142
|
try {
|
|
143
|
+
logger.debug('Attempting to get current branch via git branch --show-current', { ...context, operation });
|
|
132
144
|
const statusResult = await execAsync(`git -C "${targetPath}" branch --show-current`);
|
|
133
|
-
|
|
134
|
-
|
|
145
|
+
const definitiveCurrentBranch = statusResult.stdout.trim();
|
|
146
|
+
if (definitiveCurrentBranch) {
|
|
147
|
+
currentBranch = definitiveCurrentBranch;
|
|
148
|
+
logger.info(`Confirmed current branch: ${currentBranch}`, { ...context, operation });
|
|
149
|
+
// Refine message if it wasn't specific before
|
|
150
|
+
if (message.startsWith('Checkout command executed silently') || message.startsWith('Checked out ')) {
|
|
151
|
+
message = `Checked out '${currentBranch}'.`;
|
|
152
|
+
}
|
|
153
|
+
else if (message.startsWith('Already on') && !message.includes(`'${currentBranch}'`)) {
|
|
154
|
+
message = `Already on '${currentBranch}'.`; // Update if initial parse was wrong
|
|
155
|
+
}
|
|
156
|
+
else if (message.startsWith('Switched to branch') && !message.includes(`'${currentBranch}'`)) {
|
|
157
|
+
message = `Switched to branch '${currentBranch}'.`; // Update if initial parse was wrong
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
// Command succeeded but returned empty - might be detached HEAD after all?
|
|
162
|
+
logger.warning('git branch --show-current returned empty, possibly detached HEAD?', { ...context, operation });
|
|
163
|
+
// Keep potentially parsed 'Detached HEAD' or fallback to input if needed
|
|
164
|
+
currentBranch = currentBranch || 'Unknown (possibly detached)';
|
|
165
|
+
if (!message.includes('Detached HEAD'))
|
|
166
|
+
message += ' (Could not confirm branch name).';
|
|
167
|
+
}
|
|
135
168
|
}
|
|
136
169
|
catch (statusError) {
|
|
137
|
-
logger.warning('Could not determine current branch after
|
|
170
|
+
logger.warning('Could not determine current branch after checkout', { ...context, operation, error: statusError.message });
|
|
171
|
+
// Keep potentially parsed 'Detached HEAD' or fallback to input if needed
|
|
172
|
+
currentBranch = currentBranch || 'Unknown (error checking)';
|
|
173
|
+
if (!message.includes('Detached HEAD'))
|
|
174
|
+
message += ' (Error checking branch name).';
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
else if (success && isFileCheckout) {
|
|
178
|
+
// If it was a file checkout, still try to get the branch name for context
|
|
179
|
+
try {
|
|
180
|
+
const statusResult = await execAsync(`git -C "${targetPath}" branch --show-current`);
|
|
181
|
+
currentBranch = statusResult.stdout.trim() || 'Unknown (possibly detached)';
|
|
182
|
+
}
|
|
183
|
+
catch {
|
|
184
|
+
currentBranch = 'Unknown (error checking)';
|
|
138
185
|
}
|
|
186
|
+
logger.info(`Current branch after file checkout: ${currentBranch}`, { ...context, operation });
|
|
139
187
|
}
|
|
140
|
-
logger.info(`${operation} completed`, { ...context, operation, path: targetPath, success, message });
|
|
188
|
+
logger.info(`${operation} completed`, { ...context, operation, path: targetPath, success, message, currentBranch });
|
|
141
189
|
return { success, message, previousBranch, currentBranch, newBranchCreated, filesRestored };
|
|
142
190
|
}
|
|
143
191
|
catch (error) {
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
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 { checkoutGit, GitCheckoutInputSchema } from './logic.js';
|
|
6
9
|
let _getWorkingDirectory;
|
|
7
10
|
let _getSessionId;
|
|
8
11
|
/**
|