@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.
Files changed (97) hide show
  1. package/README.md +8 -11
  2. package/dist/config/index.js +7 -7
  3. package/dist/index.js +35 -21
  4. package/dist/mcp-server/server.js +72 -56
  5. package/dist/mcp-server/tools/gitAdd/index.js +1 -1
  6. package/dist/mcp-server/tools/gitAdd/logic.js +88 -39
  7. package/dist/mcp-server/tools/gitAdd/registration.js +17 -14
  8. package/dist/mcp-server/tools/gitBranch/index.js +1 -1
  9. package/dist/mcp-server/tools/gitBranch/logic.js +213 -85
  10. package/dist/mcp-server/tools/gitBranch/registration.js +16 -13
  11. package/dist/mcp-server/tools/gitCheckout/index.js +1 -1
  12. package/dist/mcp-server/tools/gitCheckout/logic.js +85 -145
  13. package/dist/mcp-server/tools/gitCheckout/registration.js +16 -14
  14. package/dist/mcp-server/tools/gitCherryPick/index.js +1 -1
  15. package/dist/mcp-server/tools/gitCherryPick/logic.js +100 -41
  16. package/dist/mcp-server/tools/gitCherryPick/registration.js +21 -14
  17. package/dist/mcp-server/tools/gitClean/index.js +1 -1
  18. package/dist/mcp-server/tools/gitClean/logic.js +93 -41
  19. package/dist/mcp-server/tools/gitClean/registration.js +19 -16
  20. package/dist/mcp-server/tools/gitClearWorkingDir/index.js +1 -1
  21. package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +14 -11
  22. package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +19 -13
  23. package/dist/mcp-server/tools/gitClone/index.js +1 -1
  24. package/dist/mcp-server/tools/gitClone/logic.js +89 -30
  25. package/dist/mcp-server/tools/gitClone/registration.js +15 -12
  26. package/dist/mcp-server/tools/gitCommit/index.js +1 -1
  27. package/dist/mcp-server/tools/gitCommit/logic.js +198 -76
  28. package/dist/mcp-server/tools/gitCommit/registration.js +23 -20
  29. package/dist/mcp-server/tools/gitDiff/index.js +1 -1
  30. package/dist/mcp-server/tools/gitDiff/logic.js +124 -44
  31. package/dist/mcp-server/tools/gitDiff/registration.js +16 -14
  32. package/dist/mcp-server/tools/gitFetch/index.js +1 -1
  33. package/dist/mcp-server/tools/gitFetch/logic.js +78 -49
  34. package/dist/mcp-server/tools/gitFetch/registration.js +16 -14
  35. package/dist/mcp-server/tools/gitInit/index.js +1 -1
  36. package/dist/mcp-server/tools/gitInit/logic.js +88 -34
  37. package/dist/mcp-server/tools/gitInit/registration.js +32 -18
  38. package/dist/mcp-server/tools/gitLog/index.js +1 -1
  39. package/dist/mcp-server/tools/gitLog/logic.js +133 -47
  40. package/dist/mcp-server/tools/gitLog/registration.js +16 -14
  41. package/dist/mcp-server/tools/gitMerge/index.js +1 -1
  42. package/dist/mcp-server/tools/gitMerge/logic.js +102 -61
  43. package/dist/mcp-server/tools/gitMerge/registration.js +17 -14
  44. package/dist/mcp-server/tools/gitPull/index.js +1 -1
  45. package/dist/mcp-server/tools/gitPull/logic.js +90 -69
  46. package/dist/mcp-server/tools/gitPull/registration.js +16 -14
  47. package/dist/mcp-server/tools/gitPush/index.js +1 -1
  48. package/dist/mcp-server/tools/gitPush/logic.js +116 -100
  49. package/dist/mcp-server/tools/gitPush/registration.js +16 -14
  50. package/dist/mcp-server/tools/gitRebase/index.js +1 -1
  51. package/dist/mcp-server/tools/gitRebase/logic.js +121 -82
  52. package/dist/mcp-server/tools/gitRebase/registration.js +21 -14
  53. package/dist/mcp-server/tools/gitRemote/index.js +1 -1
  54. package/dist/mcp-server/tools/gitRemote/logic.js +108 -52
  55. package/dist/mcp-server/tools/gitRemote/registration.js +14 -11
  56. package/dist/mcp-server/tools/gitReset/index.js +1 -1
  57. package/dist/mcp-server/tools/gitReset/logic.js +65 -37
  58. package/dist/mcp-server/tools/gitReset/registration.js +14 -12
  59. package/dist/mcp-server/tools/gitSetWorkingDir/index.js +1 -1
  60. package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +74 -34
  61. package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +18 -11
  62. package/dist/mcp-server/tools/gitShow/index.js +1 -1
  63. package/dist/mcp-server/tools/gitShow/logic.js +78 -35
  64. package/dist/mcp-server/tools/gitShow/registration.js +17 -12
  65. package/dist/mcp-server/tools/gitStash/index.js +1 -1
  66. package/dist/mcp-server/tools/gitStash/logic.js +143 -58
  67. package/dist/mcp-server/tools/gitStash/registration.js +19 -12
  68. package/dist/mcp-server/tools/gitStatus/index.js +1 -1
  69. package/dist/mcp-server/tools/gitStatus/logic.js +100 -58
  70. package/dist/mcp-server/tools/gitStatus/registration.js +15 -12
  71. package/dist/mcp-server/tools/gitTag/index.js +1 -1
  72. package/dist/mcp-server/tools/gitTag/logic.js +124 -51
  73. package/dist/mcp-server/tools/gitTag/registration.js +14 -11
  74. package/dist/mcp-server/tools/gitWorktree/index.js +1 -1
  75. package/dist/mcp-server/tools/gitWorktree/logic.js +204 -95
  76. package/dist/mcp-server/tools/gitWorktree/registration.js +14 -11
  77. package/dist/mcp-server/tools/gitWrapupInstructions/index.js +1 -1
  78. package/dist/mcp-server/tools/gitWrapupInstructions/logic.js +23 -11
  79. package/dist/mcp-server/tools/gitWrapupInstructions/registration.js +14 -12
  80. package/dist/mcp-server/transports/httpTransport.js +187 -79
  81. package/dist/mcp-server/transports/stdioTransport.js +14 -8
  82. package/dist/types-global/errors.js +9 -4
  83. package/dist/utils/index.js +4 -4
  84. package/dist/utils/internal/errorHandler.js +62 -40
  85. package/dist/utils/internal/index.js +3 -3
  86. package/dist/utils/internal/logger.js +97 -54
  87. package/dist/utils/internal/requestContext.js +7 -5
  88. package/dist/utils/metrics/index.js +1 -1
  89. package/dist/utils/metrics/tokenCounter.js +18 -14
  90. package/dist/utils/parsing/dateParser.js +5 -5
  91. package/dist/utils/parsing/index.js +2 -2
  92. package/dist/utils/parsing/jsonParser.js +20 -11
  93. package/dist/utils/security/idGenerator.js +8 -10
  94. package/dist/utils/security/index.js +3 -3
  95. package/dist/utils/security/rateLimiter.js +16 -14
  96. package/dist/utils/security/sanitization.js +139 -82
  97. package/package.json +45 -23
@@ -1,6 +1,6 @@
1
- import { BaseErrorCode } from '../../../types-global/errors.js'; // Direct import for types-global
2
- import { ErrorHandler, logger, requestContextService } from '../../../utils/index.js'; // ErrorHandler (./utils/internal/errorHandler.js), logger (./utils/internal/logger.js), requestContextService (./utils/internal/requestContext.js)
3
- import { GitRemoteInputSchema, gitRemoteLogic } from './logic.js';
1
+ import { BaseErrorCode } from "../../../types-global/errors.js"; // Direct import for types-global
2
+ import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js"; // ErrorHandler (./utils/internal/errorHandler.js), logger (./utils/internal/logger.js), requestContextService (./utils/internal/requestContext.js)
3
+ import { GitRemoteInputSchema, gitRemoteLogic, } from "./logic.js";
4
4
  let _getWorkingDirectory;
5
5
  let _getSessionId;
6
6
  /**
@@ -11,10 +11,10 @@ let _getSessionId;
11
11
  export function initializeGitRemoteStateAccessors(getWdFn, getSidFn) {
12
12
  _getWorkingDirectory = getWdFn;
13
13
  _getSessionId = getSidFn;
14
- logger.info('State accessors initialized for git_remote tool registration.');
14
+ logger.info("State accessors initialized for git_remote tool registration.");
15
15
  }
16
- const TOOL_NAME = 'git_remote';
17
- const TOOL_DESCRIPTION = 'Manages remote repositories (list, add, remove, show).';
16
+ const TOOL_NAME = "git_remote";
17
+ const TOOL_DESCRIPTION = "Manages remote repositories (list, add, remove, show).";
18
18
  /**
19
19
  * Registers the git_remote tool with the MCP server.
20
20
  *
@@ -24,15 +24,18 @@ const TOOL_DESCRIPTION = 'Manages remote repositories (list, add, remove, show).
24
24
  */
25
25
  export const registerGitRemoteTool = async (server) => {
26
26
  if (!_getWorkingDirectory || !_getSessionId) {
27
- throw new Error('State accessors for git_remote must be initialized before registration.');
27
+ throw new Error("State accessors for git_remote must be initialized before registration.");
28
28
  }
29
- const operation = 'registerGitRemoteTool';
29
+ const operation = "registerGitRemoteTool";
30
30
  const context = requestContextService.createRequestContext({ operation });
31
31
  await ErrorHandler.tryCatch(async () => {
32
32
  server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitRemoteInputSchema.shape, // Provide the Zod schema shape
33
33
  async (validatedArgs, callContext) => {
34
34
  const toolOperation = `tool:${TOOL_NAME}:${validatedArgs.mode}`; // Include mode in operation
35
- const requestContext = requestContextService.createRequestContext({ operation: toolOperation, parentContext: callContext });
35
+ const requestContext = requestContextService.createRequestContext({
36
+ operation: toolOperation,
37
+ parentContext: callContext,
38
+ });
36
39
  const sessionId = _getSessionId(requestContext);
37
40
  const getWorkingDirectoryForSession = () => {
38
41
  return _getWorkingDirectory(sessionId);
@@ -48,9 +51,9 @@ export const registerGitRemoteTool = async (server) => {
48
51
  const remoteResult = await gitRemoteLogic(validatedArgs, logicContext);
49
52
  // Format the result as a JSON string within TextContent
50
53
  const resultContent = {
51
- type: 'text',
54
+ type: "text",
52
55
  text: JSON.stringify(remoteResult, null, 2), // Pretty-print JSON
53
- contentType: 'application/json',
56
+ contentType: "application/json",
54
57
  };
55
58
  // Log based on the success flag in the result
56
59
  if (remoteResult.success) {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @fileoverview Barrel file for the gitReset tool.
3
3
  */
4
- export { registerGitResetTool, initializeGitResetStateAccessors } from './registration.js';
4
+ export { registerGitResetTool, initializeGitResetStateAccessors, } from "./registration.js";
5
5
  // Export types if needed elsewhere, e.g.:
6
6
  // export type { GitResetInput, GitResetResult } from './logic.js';
@@ -1,16 +1,26 @@
1
- import { exec } from 'child_process';
2
- import { promisify } from 'util';
3
- import { z } from 'zod';
4
- import { BaseErrorCode, McpError } from '../../../types-global/errors.js'; // Direct import for types-global
5
- import { logger, sanitization } from '../../../utils/index.js'; // logger (./utils/internal/logger.js), RequestContext (./utils/internal/requestContext.js), sanitization (./utils/security/sanitization.js)
1
+ import { exec } from "child_process";
2
+ import { promisify } from "util";
3
+ import { z } from "zod";
4
+ import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Direct import for types-global
5
+ import { logger, sanitization } from "../../../utils/index.js"; // logger (./utils/internal/logger.js), RequestContext (./utils/internal/requestContext.js), sanitization (./utils/security/sanitization.js)
6
6
  const execAsync = promisify(exec);
7
7
  // Define the reset modes
8
- const ResetModeEnum = z.enum(['soft', 'mixed', 'hard', 'merge', 'keep']);
8
+ const ResetModeEnum = z.enum(["soft", "mixed", "hard", "merge", "keep"]);
9
9
  // Define the input schema for the git_reset tool using Zod
10
10
  export const GitResetInputSchema = z.object({
11
- 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."),
12
- mode: ResetModeEnum.optional().default('mixed').describe("Reset mode: 'soft' (reset HEAD only), 'mixed' (reset HEAD and index, default), 'hard' (reset HEAD, index, and working tree - USE WITH CAUTION), 'merge', 'keep'."),
13
- commit: z.string().optional().describe("Commit, branch, or ref to reset to. Defaults to HEAD (useful for unstaging with 'mixed' mode)."),
11
+ path: z
12
+ .string()
13
+ .min(1)
14
+ .optional()
15
+ .default(".")
16
+ .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."),
17
+ mode: ResetModeEnum.optional()
18
+ .default("mixed")
19
+ .describe("Reset mode: 'soft' (reset HEAD only), 'mixed' (reset HEAD and index, default), 'hard' (reset HEAD, index, and working tree - USE WITH CAUTION), 'merge', 'keep'."),
20
+ commit: z
21
+ .string()
22
+ .optional()
23
+ .describe("Commit, branch, or ref to reset to. Defaults to HEAD (useful for unstaging with 'mixed' mode)."),
14
24
  // file: z.string().optional().describe("If specified, reset only this file in the index (unstaging). Mode must be 'mixed' or omitted."), // Git reset [<mode>] [<tree-ish>] [--] <paths>… is complex, handle separately if needed
15
25
  });
16
26
  /**
@@ -22,7 +32,7 @@ export const GitResetInputSchema = z.object({
22
32
  * @throws {McpError} Throws an McpError if path resolution, validation, or the git command fails unexpectedly.
23
33
  */
24
34
  export async function resetGitState(input, context) {
25
- const operation = 'resetGitState';
35
+ const operation = "resetGitState";
26
36
  logger.debug(`Executing ${operation}`, { ...context, input });
27
37
  // Validate input combinations (e.g., file path usage) if refinement wasn't used
28
38
  // if (input.file && input.mode && input.mode !== 'mixed') {
@@ -34,7 +44,7 @@ export async function resetGitState(input, context) {
34
44
  let targetPath;
35
45
  try {
36
46
  // Resolve and sanitize the target path
37
- if (input.path && input.path !== '.') {
47
+ if (input.path && input.path !== ".") {
38
48
  targetPath = input.path;
39
49
  }
40
50
  else {
@@ -44,17 +54,27 @@ export async function resetGitState(input, context) {
44
54
  }
45
55
  targetPath = workingDir;
46
56
  }
47
- targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true }).sanitizedPath;
48
- logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
57
+ targetPath = sanitization.sanitizePath(targetPath, {
58
+ allowAbsolute: true,
59
+ }).sanitizedPath;
60
+ logger.debug("Sanitized path", {
61
+ ...context,
62
+ operation,
63
+ sanitizedPath: targetPath,
64
+ });
49
65
  }
50
66
  catch (error) {
51
- logger.error('Path resolution or sanitization failed', { ...context, operation, error });
67
+ logger.error("Path resolution or sanitization failed", {
68
+ ...context,
69
+ operation,
70
+ error,
71
+ });
52
72
  if (error instanceof McpError)
53
73
  throw error;
54
74
  throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
55
75
  }
56
76
  // Basic sanitization for commit ref
57
- const safeCommit = input.commit?.replace(/[`$&;*()|<>]/g, '');
77
+ const safeCommit = input.commit?.replace(/[`$&;*()|<>]/g, "");
58
78
  try {
59
79
  // Construct the git reset command
60
80
  let command = `git -C "${targetPath}" reset`;
@@ -71,42 +91,50 @@ export async function resetGitState(input, context) {
71
91
  logger.debug(`Executing command: ${command}`, { ...context, operation });
72
92
  // Execute command. Reset output is often minimal on success, but stderr might indicate issues.
73
93
  const { stdout, stderr } = await execAsync(command);
74
- logger.info(`Git reset stdout: ${stdout}`, { ...context, operation });
94
+ logger.debug(`Git reset stdout: ${stdout}`, { ...context, operation });
75
95
  if (stderr) {
76
96
  // Log stderr as info, as it often contains the primary status message
77
- logger.info(`Git reset stderr: ${stderr}`, { ...context, operation });
97
+ logger.debug(`Git reset stderr: ${stderr}`, { ...context, operation });
78
98
  }
79
99
  // Analyze output (primarily stderr for reset)
80
- let message = stderr.trim() || stdout.trim() || `Reset successful (mode: ${input.mode || 'mixed'}).`; // Default success message
81
- let changesSummary = undefined;
82
- if (stderr.includes('Unstaged changes after reset')) {
83
- message = `Reset successful (mode: ${input.mode || 'mixed'}).`;
84
- changesSummary = stderr; // Include the list of unstaged changes
85
- }
86
- else if (stderr.match(/HEAD is now at [a-f0-9]+ /)) {
87
- message = stderr.trim(); // Use the direct message from git
88
- }
89
- else if (!stderr && !stdout) {
90
- // If no output, assume success but provide context
91
- message = `Reset successful (mode: ${input.mode || 'mixed'}, commit: ${input.commit || 'HEAD'}). No specific output.`;
92
- }
93
- logger.info(`${operation} completed successfully. ${message}`, { ...context, operation, path: targetPath });
100
+ const message = stderr.trim() ||
101
+ stdout.trim() ||
102
+ `Reset successful (mode: ${input.mode || "mixed"}).`; // Default success message
103
+ const changesSummary = stderr.includes("Unstaged changes after reset")
104
+ ? stderr
105
+ : undefined;
106
+ logger.info("git reset executed successfully", {
107
+ ...context,
108
+ operation,
109
+ path: targetPath,
110
+ message,
111
+ changesSummary,
112
+ });
94
113
  return { success: true, message, changesSummary };
95
114
  }
96
115
  catch (error) {
97
- logger.error(`Failed to execute git reset command`, { ...context, operation, path: targetPath, error: error.message, stderr: error.stderr, stdout: error.stdout });
98
- const errorMessage = error.stderr || error.stdout || error.message || '';
116
+ logger.error(`Failed to execute git reset command`, {
117
+ ...context,
118
+ operation,
119
+ path: targetPath,
120
+ error: error.message,
121
+ stderr: error.stderr,
122
+ stdout: error.stdout,
123
+ });
124
+ const errorMessage = error.stderr || error.stdout || error.message || "";
99
125
  // Handle specific error cases
100
- if (errorMessage.toLowerCase().includes('not a git repository')) {
126
+ if (errorMessage.toLowerCase().includes("not a git repository")) {
101
127
  throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`, { context, operation, originalError: error });
102
128
  }
103
- if (errorMessage.includes('fatal: bad revision') || errorMessage.includes('unknown revision')) {
129
+ if (errorMessage.includes("fatal: bad revision") ||
130
+ errorMessage.includes("unknown revision")) {
104
131
  throw new McpError(BaseErrorCode.NOT_FOUND, `Invalid commit reference specified: '${input.commit}'. Error: ${errorMessage}`, { context, operation, originalError: error });
105
132
  }
106
- if (errorMessage.includes('Cannot reset paths') && errorMessage.includes('mode')) {
133
+ if (errorMessage.includes("Cannot reset paths") &&
134
+ errorMessage.includes("mode")) {
107
135
  throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid mode ('${input.mode}') used with file paths. Error: ${errorMessage}`, { context, operation, originalError: error });
108
136
  }
109
- if (errorMessage.includes('unmerged paths')) {
137
+ if (errorMessage.includes("unmerged paths")) {
110
138
  throw new McpError(BaseErrorCode.CONFLICT, `Cannot reset due to unmerged files. Please resolve conflicts first. Error: ${errorMessage}`, { context, operation, originalError: error });
111
139
  }
112
140
  // Generic internal error for other failures
@@ -1,6 +1,6 @@
1
- import { BaseErrorCode } from '../../../types-global/errors.js'; // Direct import for types-global
2
- import { ErrorHandler, logger, requestContextService } from '../../../utils/index.js'; // ErrorHandler (./utils/internal/errorHandler.js), logger (./utils/internal/logger.js), requestContextService & RequestContext (./utils/internal/requestContext.js)
3
- import { GitResetInputSchema, resetGitState } from './logic.js';
1
+ import { BaseErrorCode } from "../../../types-global/errors.js"; // Direct import for types-global
2
+ import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js"; // ErrorHandler (./utils/internal/errorHandler.js), logger (./utils/internal/logger.js), requestContextService & RequestContext (./utils/internal/requestContext.js)
3
+ import { GitResetInputSchema, resetGitState, } from "./logic.js";
4
4
  let _getWorkingDirectory;
5
5
  let _getSessionId;
6
6
  /**
@@ -12,9 +12,9 @@ let _getSessionId;
12
12
  export function initializeGitResetStateAccessors(getWdFn, getSidFn) {
13
13
  _getWorkingDirectory = getWdFn;
14
14
  _getSessionId = getSidFn;
15
- logger.info('State accessors initialized for git_reset tool registration.');
15
+ logger.info("State accessors initialized for git_reset tool registration.");
16
16
  }
17
- const TOOL_NAME = 'git_reset';
17
+ const TOOL_NAME = "git_reset";
18
18
  const TOOL_DESCRIPTION = "Resets the current HEAD to a specified state. Supports different modes ('soft', 'mixed', 'hard', 'merge', 'keep') to control how the index and working tree are affected. Can reset to a specific commit. USE 'hard' MODE WITH EXTREME CAUTION as it discards local changes.";
19
19
  /**
20
20
  * Registers the git_reset tool with the MCP server.
@@ -24,15 +24,18 @@ const TOOL_DESCRIPTION = "Resets the current HEAD to a specified state. Supports
24
24
  */
25
25
  export async function registerGitResetTool(server) {
26
26
  if (!_getWorkingDirectory || !_getSessionId) {
27
- throw new Error('State accessors for git_reset must be initialized before registration.');
27
+ throw new Error("State accessors for git_reset must be initialized before registration.");
28
28
  }
29
- const operation = 'registerGitResetTool';
29
+ const operation = "registerGitResetTool";
30
30
  const context = requestContextService.createRequestContext({ operation });
31
31
  await ErrorHandler.tryCatch(async () => {
32
32
  server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitResetInputSchema.shape, // Provide the Zod schema shape
33
33
  async (validatedArgs, callContext) => {
34
- const toolOperation = 'tool:git_reset';
35
- const requestContext = requestContextService.createRequestContext({ operation: toolOperation, parentContext: callContext });
34
+ const toolOperation = "tool:git_reset";
35
+ const requestContext = requestContextService.createRequestContext({
36
+ operation: toolOperation,
37
+ parentContext: callContext,
38
+ });
36
39
  const sessionId = _getSessionId(requestContext);
37
40
  const getWorkingDirectoryForSession = () => {
38
41
  return _getWorkingDirectory(sessionId);
@@ -48,10 +51,10 @@ export async function registerGitResetTool(server) {
48
51
  const resetResult = await resetGitState(validatedArgs, logicContext);
49
52
  // Format the result as a JSON string within TextContent
50
53
  const resultContent = {
51
- type: 'text',
54
+ type: "text",
52
55
  // Stringify the entire GitResetResult object
53
56
  text: JSON.stringify(resetResult, null, 2), // Pretty-print JSON
54
- contentType: 'application/json',
57
+ contentType: "application/json",
55
58
  };
56
59
  logger.info(`Tool ${TOOL_NAME} executed successfully: ${resetResult.message}`, logicContext);
57
60
  // Success is determined by the logic function and included in the result object
@@ -66,4 +69,3 @@ export async function registerGitResetTool(server) {
66
69
  logger.info(`Tool registered: ${TOOL_NAME}`, context);
67
70
  }, { operation, context, critical: true });
68
71
  }
69
- ;
@@ -2,6 +2,6 @@
2
2
  * @fileoverview Barrel file for the git_set_working_dir tool.
3
3
  * Exports the registration function and potentially other related components.
4
4
  */
5
- export { registerGitSetWorkingDirTool, initializeGitSetWorkingDirStateAccessors } from './registration.js';
5
+ export { registerGitSetWorkingDirTool, initializeGitSetWorkingDirStateAccessors, } from "./registration.js";
6
6
  // Export types if needed elsewhere, e.g.:
7
7
  // export type { GitSetWorkingDirInput, GitSetWorkingDirResult } from './logic.js';
@@ -1,15 +1,25 @@
1
- import { exec } from 'child_process';
2
- import fs from 'fs/promises';
3
- import { promisify } from 'util';
4
- import { z } from 'zod';
5
- import { BaseErrorCode, McpError } from '../../../types-global/errors.js'; // Direct import for types-global
6
- import { logger, sanitization } from '../../../utils/index.js'; // RequestContext (./utils/internal/requestContext.js), logger (./utils/internal/logger.js), sanitization (./utils/security/sanitization.js)
1
+ import { exec } from "child_process";
2
+ import fs from "fs/promises";
3
+ import { promisify } from "util";
4
+ import { z } from "zod";
5
+ import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Direct import for types-global
6
+ import { logger, sanitization } from "../../../utils/index.js"; // RequestContext (./utils/internal/requestContext.js), logger (./utils/internal/logger.js), sanitization (./utils/security/sanitization.js)
7
7
  const execAsync = promisify(exec);
8
8
  // Define the Zod schema for input validation
9
9
  export const GitSetWorkingDirInputSchema = z.object({
10
- path: z.string().min(1, "Path cannot be empty.").describe("The absolute path to set as the default working directory for the current session. Set this before using other git_* tools."),
11
- validateGitRepo: z.boolean().default(true).describe("Whether to validate that the path is a Git repository"),
12
- initializeIfNotPresent: z.boolean().optional().default(false).describe("If true and the directory is not a Git repository, attempt to initialize it with 'git init'.")
10
+ path: z
11
+ .string()
12
+ .min(1, "Path cannot be empty.")
13
+ .describe("The absolute path to set as the default working directory for the current session. Set this before using other git_* tools."),
14
+ validateGitRepo: z
15
+ .boolean()
16
+ .default(true)
17
+ .describe("Whether to validate that the path is a Git repository"),
18
+ initializeIfNotPresent: z
19
+ .boolean()
20
+ .optional()
21
+ .default(false)
22
+ .describe("If true and the directory is not a Git repository, attempt to initialize it with 'git init'."),
13
23
  });
14
24
  /**
15
25
  * Logic for the git_set_working_dir tool.
@@ -21,19 +31,20 @@ export const GitSetWorkingDirInputSchema = z.object({
21
31
  * @returns {Promise<GitSetWorkingDirResult>} The result of the operation.
22
32
  * @throws {McpError} Throws McpError for validation failures or operational errors.
23
33
  */
24
- export async function gitSetWorkingDirLogic(input, context // Assuming context provides session info and setter
25
- ) {
26
- const operation = 'gitSetWorkingDirLogic';
27
- logger.info('Executing git_set_working_dir logic', { ...context, operation, inputPath: input.path });
34
+ export async function gitSetWorkingDirLogic(input, context) {
35
+ const operation = "gitSetWorkingDirLogic";
36
+ logger.debug(`Executing ${operation}`, { ...context, input });
28
37
  let sanitizedPath;
29
38
  try {
30
39
  // Sanitize the path. Must explicitly allow absolute paths for this tool.
31
40
  // It normalizes and checks for traversal issues.
32
- sanitizedPath = sanitization.sanitizePath(input.path, { allowAbsolute: true }).sanitizedPath;
41
+ sanitizedPath = sanitization.sanitizePath(input.path, {
42
+ allowAbsolute: true,
43
+ }).sanitizedPath;
33
44
  logger.debug(`Sanitized path: ${sanitizedPath}`, { ...context, operation });
34
45
  }
35
46
  catch (error) {
36
- logger.error('Path sanitization failed', error, { ...context, operation });
47
+ logger.error("Path sanitization failed", error, { ...context, operation });
37
48
  if (error instanceof McpError)
38
49
  throw error;
39
50
  throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path provided: ${error.message}`, { context, operation });
@@ -46,41 +57,60 @@ export async function gitSetWorkingDirLogic(input, context // Assuming context p
46
57
  }
47
58
  }
48
59
  catch (error) {
49
- if (error.code === 'ENOENT') {
60
+ if (error.code === "ENOENT") {
50
61
  throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Directory does not exist: ${sanitizedPath}`, { context, operation });
51
62
  }
52
- logger.error('Failed to stat directory', error, { ...context, operation, path: sanitizedPath });
63
+ logger.error("Failed to stat directory", error, {
64
+ ...context,
65
+ operation,
66
+ path: sanitizedPath,
67
+ });
53
68
  throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to access path: ${error.message}`, { context, operation });
54
69
  }
55
70
  let isGitRepo = false;
56
71
  let initializedRepo = false;
57
72
  try {
58
- const { stdout } = await execAsync('git rev-parse --is-inside-work-tree', { cwd: sanitizedPath });
59
- if (stdout.trim() === 'true') {
73
+ const { stdout } = await execAsync("git rev-parse --is-inside-work-tree", {
74
+ cwd: sanitizedPath,
75
+ });
76
+ if (stdout.trim() === "true") {
60
77
  isGitRepo = true;
61
- logger.debug('Path is already a Git repository', { ...context, operation, path: sanitizedPath });
78
+ logger.debug("Path is already a Git repository", {
79
+ ...context,
80
+ operation,
81
+ path: sanitizedPath,
82
+ });
62
83
  }
63
84
  }
64
85
  catch (error) {
65
- logger.debug('Path is not a Git repository (rev-parse failed or returned non-true)', { ...context, operation, path: sanitizedPath, error: error.message });
86
+ logger.debug("Path is not a Git repository (rev-parse failed or returned non-true)", {
87
+ ...context,
88
+ operation,
89
+ path: sanitizedPath,
90
+ error: error.message,
91
+ });
66
92
  isGitRepo = false;
67
93
  }
68
94
  if (!isGitRepo && input.initializeIfNotPresent) {
69
95
  logger.info(`Path is not a Git repository. Attempting to initialize (initializeIfNotPresent=true) with initial branch 'main'.`, { ...context, operation, path: sanitizedPath });
70
96
  try {
71
- await execAsync('git init --initial-branch=main', { cwd: sanitizedPath });
97
+ await execAsync("git init --initial-branch=main", { cwd: sanitizedPath });
72
98
  initializedRepo = true;
73
99
  isGitRepo = true; // Now it is a git repo
74
100
  logger.info('Successfully initialized Git repository with initial branch "main".', { ...context, operation, path: sanitizedPath });
75
101
  }
76
102
  catch (initError) {
77
- logger.error('Failed to initialize Git repository', initError, { ...context, operation, path: sanitizedPath });
103
+ logger.error("Failed to initialize Git repository", initError, {
104
+ ...context,
105
+ operation,
106
+ path: sanitizedPath,
107
+ });
78
108
  throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to initialize Git repository at ${sanitizedPath}: ${initError.message}`, { context, operation });
79
109
  }
80
110
  }
81
111
  // After potential initialization, if validateGitRepo is true, it must now be a Git repo.
82
112
  if (input.validateGitRepo && !isGitRepo) {
83
- logger.warning('Path is not a valid Git repository and initialization was not performed or failed.', { ...context, operation, path: sanitizedPath });
113
+ logger.warning("Path is not a valid Git repository and initialization was not performed or failed.", { ...context, operation, path: sanitizedPath });
84
114
  throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Path is not a valid Git repository: ${sanitizedPath}.`, { context, operation });
85
115
  }
86
116
  // --- Update Session State ---
@@ -88,25 +118,35 @@ export async function gitSetWorkingDirLogic(input, context // Assuming context p
88
118
  // We assume the context provides a way to set the working directory for the current session.
89
119
  try {
90
120
  context.setWorkingDirectory(sanitizedPath);
91
- logger.info(`Working directory set for session ${context.sessionId || 'stdio'} to: ${sanitizedPath}`, { ...context, operation });
121
+ const message = `Working directory set for session ${context.sessionId || "stdio"} to: ${sanitizedPath}`;
122
+ logger.info(message, { ...context, operation });
92
123
  }
93
124
  catch (error) {
94
- logger.error('Failed to set working directory in session state', error, { ...context, operation });
125
+ logger.error("Failed to set working directory in session state", error, {
126
+ ...context,
127
+ operation,
128
+ });
95
129
  // This indicates an internal logic error in how state is passed/managed.
96
- throw new McpError(BaseErrorCode.INTERNAL_ERROR, 'Failed to update session state.', { context, operation });
130
+ throw new McpError(BaseErrorCode.INTERNAL_ERROR, "Failed to update session state.", { context, operation });
97
131
  }
98
132
  let message = `Working directory set to: ${sanitizedPath}`;
99
133
  if (initializedRepo) {
100
- message += ' (New Git repository initialized).';
134
+ message += " (New Git repository initialized).";
101
135
  }
102
- else if (isGitRepo && input.validateGitRepo) { // Only state "Existing" if validation was on and it passed
103
- message += ' (Existing Git repository).';
136
+ else if (isGitRepo && input.validateGitRepo) {
137
+ // Only state "Existing" if validation was on and it passed
138
+ message += " (Existing Git repository).";
104
139
  }
105
- else if (isGitRepo && !input.validateGitRepo) { // It is a git repo, but we weren't asked to validate it
106
- message += ' (Is a Git repository, validation skipped).';
140
+ else if (isGitRepo && !input.validateGitRepo) {
141
+ // It is a git repo, but we weren't asked to validate it
142
+ message += " (Is a Git repository, validation skipped).";
107
143
  }
108
- else if (!isGitRepo && !input.validateGitRepo && !input.initializeIfNotPresent) { // Not a git repo, validation off, no init request
109
- message += ' (Not a Git repository, validation skipped, no initialization requested).';
144
+ else if (!isGitRepo &&
145
+ !input.validateGitRepo &&
146
+ !input.initializeIfNotPresent) {
147
+ // Not a git repo, validation off, no init request
148
+ message +=
149
+ " (Not a Git repository, validation skipped, no initialization requested).";
110
150
  }
111
151
  return {
112
152
  success: true,
@@ -1,6 +1,6 @@
1
- import { BaseErrorCode } from '../../../types-global/errors.js'; // Direct import for types-global
2
- import { ErrorHandler, logger, requestContextService } from '../../../utils/index.js'; // ErrorHandler (./utils/internal/errorHandler.js), logger (./utils/internal/logger.js), requestContextService & RequestContext (./utils/internal/requestContext.js)
3
- import { GitSetWorkingDirInputSchema, gitSetWorkingDirLogic } from './logic.js';
1
+ import { BaseErrorCode } from "../../../types-global/errors.js"; // Direct import for types-global
2
+ import { ErrorHandler, logger, requestContextService, } from "../../../utils/index.js"; // ErrorHandler (./utils/internal/errorHandler.js), logger (./utils/internal/logger.js), requestContextService & RequestContext (./utils/internal/requestContext.js)
3
+ import { GitSetWorkingDirInputSchema, gitSetWorkingDirLogic, } from "./logic.js";
4
4
  let _getWorkingDirectory; // Added getter
5
5
  let _setWorkingDirectory;
6
6
  let _getSessionId;
@@ -16,9 +16,9 @@ setWdFn, getSidFn) {
16
16
  _getWorkingDirectory = getWdFn; // Store getter
17
17
  _setWorkingDirectory = setWdFn;
18
18
  _getSessionId = getSidFn;
19
- logger.info('State accessors initialized for git_set_working_dir tool registration.');
19
+ logger.info("State accessors initialized for git_set_working_dir tool registration.");
20
20
  }
21
- const TOOL_NAME = 'git_set_working_dir';
21
+ const TOOL_NAME = "git_set_working_dir";
22
22
  const TOOL_DESCRIPTION = "Sets the default working directory for the current session. Subsequent Git tool calls within this session can use '.' for the `path` parameter, which will resolve to this directory. Optionally validates if the path is a Git repository (`validateGitRepo: true`). Can optionally initialize a Git repository with 'git init' if it's not already one and `initializeIfNotPresent: true` is set. Returns the result as a JSON object. IMPORTANT: The provided path must be absolute.";
23
23
  /**
24
24
  * Registers the git_set_working_dir tool with the MCP server.
@@ -29,14 +29,18 @@ const TOOL_DESCRIPTION = "Sets the default working directory for the current ses
29
29
  export async function registerGitSetWorkingDirTool(server) {
30
30
  // Check all required accessors
31
31
  if (!_getWorkingDirectory || !_setWorkingDirectory || !_getSessionId) {
32
- throw new Error('State accessors (getWD, setWD, getSID) for git_set_working_dir must be initialized before registration.');
32
+ throw new Error("State accessors (getWD, setWD, getSID) for git_set_working_dir must be initialized before registration.");
33
33
  }
34
34
  try {
35
35
  server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitSetWorkingDirInputSchema.shape, // Pass the shape for SDK validation
36
36
  async (validatedArgs, callContext) => {
37
- const operation = 'tool:git_set_working_dir';
37
+ // Use callContext provided by SDK
38
+ const operation = "tool:git_set_working_dir";
38
39
  // Create a request context, potentially inheriting from callContext if it provides relevant info
39
- const requestContext = requestContextService.createRequestContext({ operation, parentContext: callContext });
40
+ const requestContext = requestContextService.createRequestContext({
41
+ operation,
42
+ parentContext: callContext,
43
+ });
40
44
  // Get session ID using the accessor function
41
45
  const sessionId = _getSessionId(requestContext); // Non-null assertion as we checked initialization
42
46
  // Define the session-specific setter function
@@ -60,11 +64,14 @@ export async function registerGitSetWorkingDirTool(server) {
60
64
  const result = await gitSetWorkingDirLogic(validatedArgs, logicContext);
61
65
  // Format the successful result for the MCP client
62
66
  const responseContent = {
63
- type: 'text',
67
+ type: "text",
64
68
  text: JSON.stringify(result, null, 2), // Pretty-print JSON result
65
- contentType: 'application/json',
69
+ contentType: "application/json",
66
70
  };
67
- logger.info(`Tool ${TOOL_NAME} executed successfully`, { ...logicContext, result });
71
+ logger.info(`Tool ${TOOL_NAME} executed successfully`, {
72
+ ...logicContext,
73
+ result,
74
+ });
68
75
  return { content: [responseContent] };
69
76
  }, {
70
77
  operation,
@@ -2,6 +2,6 @@
2
2
  * @fileoverview Barrel file for the git_show tool.
3
3
  * Exports the registration function and state accessor initialization function.
4
4
  */
5
- export { registerGitShowTool, initializeGitShowStateAccessors } from './registration.js';
5
+ export { registerGitShowTool, initializeGitShowStateAccessors, } from "./registration.js";
6
6
  // Export types if needed elsewhere, e.g.:
7
7
  // export type { GitShowInput, GitShowResult } from './logic.js';