@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.
Files changed (99) hide show
  1. package/README.md +55 -89
  2. package/{build → dist}/config/index.js +16 -18
  3. package/{build → dist}/index.js +80 -30
  4. package/dist/mcp-server/server.js +296 -0
  5. package/{build → dist}/mcp-server/tools/gitAdd/logic.js +9 -6
  6. package/{build → dist}/mcp-server/tools/gitAdd/registration.js +7 -4
  7. package/{build → dist}/mcp-server/tools/gitBranch/logic.js +23 -12
  8. package/{build → dist}/mcp-server/tools/gitBranch/registration.js +8 -5
  9. package/{build → dist}/mcp-server/tools/gitCheckout/logic.js +92 -44
  10. package/{build → dist}/mcp-server/tools/gitCheckout/registration.js +8 -5
  11. package/{build → dist}/mcp-server/tools/gitCherryPick/logic.js +10 -7
  12. package/{build → dist}/mcp-server/tools/gitCherryPick/registration.js +8 -5
  13. package/{build → dist}/mcp-server/tools/gitClean/logic.js +9 -6
  14. package/{build → dist}/mcp-server/tools/gitClean/registration.js +8 -5
  15. package/{build → dist}/mcp-server/tools/gitClearWorkingDir/logic.js +3 -2
  16. package/{build → dist}/mcp-server/tools/gitClearWorkingDir/registration.js +7 -4
  17. package/{build → dist}/mcp-server/tools/gitClone/logic.js +8 -5
  18. package/{build → dist}/mcp-server/tools/gitClone/registration.js +7 -4
  19. package/dist/mcp-server/tools/gitCommit/logic.js +207 -0
  20. package/{build → dist}/mcp-server/tools/gitCommit/registration.js +22 -15
  21. package/{build → dist}/mcp-server/tools/gitDiff/logic.js +9 -6
  22. package/{build → dist}/mcp-server/tools/gitDiff/registration.js +8 -5
  23. package/{build → dist}/mcp-server/tools/gitFetch/logic.js +10 -7
  24. package/{build → dist}/mcp-server/tools/gitFetch/registration.js +8 -5
  25. package/{build → dist}/mcp-server/tools/gitInit/index.js +2 -2
  26. package/{build → dist}/mcp-server/tools/gitInit/logic.js +9 -6
  27. package/dist/mcp-server/tools/gitInit/registration.js +98 -0
  28. package/{build → dist}/mcp-server/tools/gitLog/logic.js +53 -16
  29. package/{build → dist}/mcp-server/tools/gitLog/registration.js +8 -5
  30. package/{build → dist}/mcp-server/tools/gitMerge/logic.js +9 -6
  31. package/{build → dist}/mcp-server/tools/gitMerge/registration.js +8 -5
  32. package/{build → dist}/mcp-server/tools/gitPull/logic.js +11 -8
  33. package/{build → dist}/mcp-server/tools/gitPull/registration.js +7 -4
  34. package/{build → dist}/mcp-server/tools/gitPush/logic.js +12 -9
  35. package/{build → dist}/mcp-server/tools/gitPush/registration.js +7 -4
  36. package/{build → dist}/mcp-server/tools/gitRebase/logic.js +9 -6
  37. package/{build → dist}/mcp-server/tools/gitRebase/registration.js +8 -5
  38. package/{build → dist}/mcp-server/tools/gitRemote/logic.js +4 -5
  39. package/{build → dist}/mcp-server/tools/gitRemote/registration.js +2 -4
  40. package/{build → dist}/mcp-server/tools/gitReset/logic.js +5 -6
  41. package/{build → dist}/mcp-server/tools/gitReset/registration.js +2 -4
  42. package/{build → dist}/mcp-server/tools/gitSetWorkingDir/logic.js +5 -6
  43. package/{build → dist}/mcp-server/tools/gitSetWorkingDir/registration.js +22 -13
  44. package/{build → dist}/mcp-server/tools/gitShow/logic.js +5 -6
  45. package/{build → dist}/mcp-server/tools/gitShow/registration.js +3 -5
  46. package/{build → dist}/mcp-server/tools/gitStash/logic.js +5 -6
  47. package/{build → dist}/mcp-server/tools/gitStash/registration.js +3 -5
  48. package/{build → dist}/mcp-server/tools/gitStatus/logic.js +5 -6
  49. package/{build → dist}/mcp-server/tools/gitStatus/registration.js +2 -4
  50. package/{build → dist}/mcp-server/tools/gitTag/logic.js +3 -4
  51. package/{build → dist}/mcp-server/tools/gitTag/registration.js +2 -4
  52. package/dist/mcp-server/transports/authentication/authMiddleware.js +145 -0
  53. package/dist/mcp-server/transports/httpTransport.js +432 -0
  54. package/dist/mcp-server/transports/stdioTransport.js +87 -0
  55. package/{build → dist}/types-global/errors.js +2 -2
  56. package/dist/utils/index.js +12 -0
  57. package/{build/utils → dist/utils/internal}/errorHandler.js +18 -8
  58. package/dist/utils/internal/index.js +3 -0
  59. package/dist/utils/internal/logger.js +254 -0
  60. package/{build/utils → dist/utils/internal}/requestContext.js +2 -3
  61. package/dist/utils/metrics/index.js +1 -0
  62. package/{build/utils → dist/utils/metrics}/tokenCounter.js +3 -3
  63. package/dist/utils/parsing/dateParser.js +62 -0
  64. package/dist/utils/parsing/index.js +2 -0
  65. package/{build/utils → dist/utils/parsing}/jsonParser.js +3 -2
  66. package/{build/utils → dist/utils/security}/idGenerator.js +4 -5
  67. package/dist/utils/security/index.js +3 -0
  68. package/{build/utils → dist/utils/security}/rateLimiter.js +7 -10
  69. package/{build/utils → dist/utils/security}/sanitization.js +4 -3
  70. package/package.json +20 -16
  71. package/build/mcp-server/server.js +0 -572
  72. package/build/mcp-server/tools/gitCommit/logic.js +0 -129
  73. package/build/mcp-server/tools/gitInit/registration.js +0 -44
  74. package/build/types-global/mcp.js +0 -59
  75. package/build/types-global/tool.js +0 -1
  76. package/build/utils/index.js +0 -11
  77. package/build/utils/logger.js +0 -266
  78. /package/{build → dist}/mcp-server/tools/gitAdd/index.js +0 -0
  79. /package/{build → dist}/mcp-server/tools/gitBranch/index.js +0 -0
  80. /package/{build → dist}/mcp-server/tools/gitCheckout/index.js +0 -0
  81. /package/{build → dist}/mcp-server/tools/gitCherryPick/index.js +0 -0
  82. /package/{build → dist}/mcp-server/tools/gitClean/index.js +0 -0
  83. /package/{build → dist}/mcp-server/tools/gitClearWorkingDir/index.js +0 -0
  84. /package/{build → dist}/mcp-server/tools/gitClone/index.js +0 -0
  85. /package/{build → dist}/mcp-server/tools/gitCommit/index.js +0 -0
  86. /package/{build → dist}/mcp-server/tools/gitDiff/index.js +0 -0
  87. /package/{build → dist}/mcp-server/tools/gitFetch/index.js +0 -0
  88. /package/{build → dist}/mcp-server/tools/gitLog/index.js +0 -0
  89. /package/{build → dist}/mcp-server/tools/gitMerge/index.js +0 -0
  90. /package/{build → dist}/mcp-server/tools/gitPull/index.js +0 -0
  91. /package/{build → dist}/mcp-server/tools/gitPush/index.js +0 -0
  92. /package/{build → dist}/mcp-server/tools/gitRebase/index.js +0 -0
  93. /package/{build → dist}/mcp-server/tools/gitRemote/index.js +0 -0
  94. /package/{build → dist}/mcp-server/tools/gitReset/index.js +0 -0
  95. /package/{build → dist}/mcp-server/tools/gitSetWorkingDir/index.js +0 -0
  96. /package/{build → dist}/mcp-server/tools/gitShow/index.js +0 -0
  97. /package/{build → dist}/mcp-server/tools/gitStash/index.js +0 -0
  98. /package/{build → dist}/mcp-server/tools/gitStatus/index.js +0 -0
  99. /package/{build → dist}/mcp-server/tools/gitTag/index.js +0 -0
@@ -0,0 +1,98 @@
1
+ import path from 'path';
2
+ import { z } from 'zod';
3
+ import { BaseErrorCode, McpError } from '../../../types-global/errors.js';
4
+ import { ErrorHandler, logger, requestContextService, sanitization } from '../../../utils/index.js';
5
+ import { GitInitInputSchema, gitInitLogic } from './logic.js';
6
+ const TOOL_NAME = 'git_init';
7
+ const TOOL_DESCRIPTION = 'Initializes a new Git repository at the specified path. If path is relative or omitted, it resolves against the session working directory (if you have set the git_working_dir). Can optionally set the initial branch name and create a bare repository.';
8
+ const RegistrationSchema = GitInitInputSchema.extend({
9
+ path: z.string().min(1).optional().default('.'),
10
+ }).shape;
11
+ // --- Module-level State Accessors ---
12
+ // These will be populated by the initialize function called from server.ts
13
+ let _getWorkingDirectory = () => undefined;
14
+ let _getSessionIdFromContext = () => undefined;
15
+ /**
16
+ * Initializes state accessor functions for the git_init tool.
17
+ * This function is called by the main server setup to provide the tool
18
+ * with a way to access session-specific state (like the working directory)
19
+ * without needing direct access to the server or transport layer internals.
20
+ *
21
+ * @param getWorkingDirectory - Function to retrieve the working directory for a given session ID.
22
+ * @param getSessionIdFromContext - Function to extract the session ID from a tool's execution context.
23
+ */
24
+ export function initializeGitInitStateAccessors(getWorkingDirectory, getSessionIdFromContext) {
25
+ _getWorkingDirectory = getWorkingDirectory;
26
+ _getSessionIdFromContext = getSessionIdFromContext;
27
+ logger.debug(`State accessors initialized for ${TOOL_NAME}`);
28
+ }
29
+ /**
30
+ * Registers the git_init tool with the MCP server.
31
+ *
32
+ * @param {McpServer} server - The McpServer instance to register the tool with.
33
+ * @returns {Promise<void>}
34
+ * @throws {Error} If registration fails.
35
+ */
36
+ export const registerGitInitTool = async (server) => {
37
+ const operation = 'registerGitInitTool';
38
+ const context = requestContextService.createRequestContext({ operation });
39
+ await ErrorHandler.tryCatch(async () => {
40
+ server.tool(TOOL_NAME, TOOL_DESCRIPTION, RegistrationSchema, async (validatedArgs, callContext) => {
41
+ const toolOperation = 'tool:git_init';
42
+ const requestContext = requestContextService.createRequestContext({ operation: toolOperation, parentContext: callContext });
43
+ // Use the initialized accessor to get the session ID
44
+ const sessionId = _getSessionIdFromContext(requestContext); // Pass the created context
45
+ if (!sessionId && !path.isAbsolute(validatedArgs.path)) {
46
+ // If path is relative, we NEED a session ID to resolve against a potential working dir
47
+ logger.error('Session ID is missing in context, cannot resolve relative path', requestContext);
48
+ throw new McpError(BaseErrorCode.INTERNAL_ERROR, 'Session context is unavailable for relative path resolution.', { context: requestContext, operation: toolOperation });
49
+ }
50
+ logger.info(`Executing tool: ${TOOL_NAME}`, requestContext);
51
+ return await ErrorHandler.tryCatch(async () => {
52
+ // Use the initialized accessor to get the working directory
53
+ const sessionWorkingDirectory = _getWorkingDirectory(sessionId);
54
+ const inputPath = validatedArgs.path;
55
+ let resolvedPath;
56
+ try {
57
+ if (path.isAbsolute(inputPath)) {
58
+ resolvedPath = sanitization.sanitizePath(inputPath);
59
+ logger.debug(`Using absolute path: ${resolvedPath}`, requestContext);
60
+ }
61
+ else if (sessionWorkingDirectory) {
62
+ resolvedPath = sanitization.sanitizePath(path.resolve(sessionWorkingDirectory, inputPath));
63
+ logger.debug(`Resolved relative path '${inputPath}' to absolute path: ${resolvedPath} using session CWD`, requestContext);
64
+ }
65
+ else {
66
+ // This case should now only be hit if the path is relative AND there's no session CWD set.
67
+ logger.error(`Relative path '${inputPath}' provided but no session working directory is set.`, requestContext);
68
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Relative path '${inputPath}' provided but no session working directory is set. Please provide an absolute path or set a working directory using git_set_working_dir.`, { context: requestContext, operation: toolOperation });
69
+ }
70
+ }
71
+ catch (error) {
72
+ logger.error('Path resolution or sanitization failed', { ...requestContext, operation: toolOperation, error });
73
+ if (error instanceof McpError)
74
+ throw error;
75
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path processing: ${error instanceof Error ? error.message : String(error)}`, { context: requestContext, operation: toolOperation, originalError: error });
76
+ }
77
+ const logicArgs = {
78
+ ...validatedArgs,
79
+ path: resolvedPath,
80
+ };
81
+ const initResult = await gitInitLogic(logicArgs, requestContext);
82
+ const resultContent = {
83
+ type: 'text',
84
+ text: JSON.stringify(initResult, null, 2), // Pretty-print JSON
85
+ contentType: 'application/json',
86
+ };
87
+ logger.info(`Tool ${TOOL_NAME} executed successfully, returning JSON`, requestContext);
88
+ return { content: [resultContent] };
89
+ }, {
90
+ operation: toolOperation,
91
+ context: requestContext,
92
+ input: validatedArgs,
93
+ errorCode: BaseErrorCode.INTERNAL_ERROR,
94
+ });
95
+ });
96
+ logger.info(`Tool registered: ${TOOL_NAME}`, context);
97
+ }, { operation, context, critical: true });
98
+ };
@@ -1,9 +1,12 @@
1
- import { z } from 'zod';
2
- import { promisify } from 'util';
3
1
  import { exec } from 'child_process';
4
- import { logger } from '../../../utils/logger.js';
5
- import { McpError, BaseErrorCode } from '../../../types-global/errors.js';
6
- import { sanitization } from '../../../utils/sanitization.js';
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 structure for a single commit entry
9
12
  export const CommitEntrySchema = z.object({
@@ -16,13 +19,14 @@ export const CommitEntrySchema = z.object({
16
19
  });
17
20
  // Define the input schema for the git_log tool using Zod
18
21
  export const GitLogInputSchema = z.object({
19
- path: z.string().min(1).optional().default('.').describe("Path to the Git repository. Defaults to the session's working directory if set."),
22
+ 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."),
20
23
  maxCount: z.number().int().positive().optional().describe("Limit the number of commits to output."),
21
24
  author: z.string().optional().describe("Limit commits to those matching the specified author pattern."),
22
25
  since: z.string().optional().describe("Show commits more recent than a specific date (e.g., '2 weeks ago', '2023-01-01')."),
23
26
  until: z.string().optional().describe("Show commits older than a specific date."),
24
- branchOrFile: z.string().optional().describe("Show logs for a specific branch, tag, or file path."),
25
- // Note: We use a fixed pretty format for reliable parsing. Custom formats are not directly supported via input.
27
+ branchOrFile: z.string().optional().describe("Show logs for a specific branch (e.g., 'main'), tag, or file path (e.g., 'src/utils/logger.ts')."),
28
+ showSignature: z.boolean().optional().default(false).describe("Show signature verification status for commits. Returns raw output instead of parsed JSON."),
29
+ // Note: We use a fixed pretty format for reliable parsing unless showSignature is true.
26
30
  });
27
31
  // Delimiters for parsing the custom format
28
32
  const FIELD_SEP = '\x1f'; // Unit Separator
@@ -62,11 +66,29 @@ export async function logGitHistory(input, context) {
62
66
  throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
63
67
  }
64
68
  try {
65
- // Construct the git log command
66
- // Use a specific format for reliable parsing
67
- let command = `git -C "${targetPath}" log ${GIT_LOG_FORMAT}`;
68
- if (input.maxCount) {
69
- command += ` -n ${input.maxCount}`;
69
+ let command;
70
+ let isRawOutput = false; // Flag to indicate if we should parse or return raw
71
+ if (input.showSignature) {
72
+ isRawOutput = true;
73
+ command = `git -C "${targetPath}" log --show-signature`;
74
+ logger.info('Show signature requested, returning raw output.', { ...context, operation });
75
+ // Append other filters if provided
76
+ if (input.maxCount)
77
+ command += ` -n ${input.maxCount}`;
78
+ if (input.author)
79
+ command += ` --author="${input.author.replace(/[`"$&;*()|<>]/g, '')}"`;
80
+ if (input.since)
81
+ command += ` --since="${input.since.replace(/[`"$&;*()|<>]/g, '')}"`;
82
+ if (input.until)
83
+ command += ` --until="${input.until.replace(/[`"$&;*()|<>]/g, '')}"`;
84
+ if (input.branchOrFile)
85
+ command += ` ${input.branchOrFile.replace(/[`"$&;*()|<>]/g, '')}`;
86
+ }
87
+ else {
88
+ // Construct the git log command with the fixed format for parsing
89
+ command = `git -C "${targetPath}" log ${GIT_LOG_FORMAT}`;
90
+ if (input.maxCount)
91
+ command += ` -n ${input.maxCount}`;
70
92
  }
71
93
  if (input.author) {
72
94
  // Basic sanitization for author string
@@ -90,13 +112,28 @@ export async function logGitHistory(input, context) {
90
112
  const { stdout, stderr } = await execAsync(command, { maxBuffer: 1024 * 1024 * 10 }); // 10MB buffer
91
113
  if (stderr) {
92
114
  // Log stderr as warning, as git log might sometimes use it for non-fatal info
93
- logger.warning(`Git log stderr: ${stderr}`, { ...context, operation });
115
+ // Exception: If showing signature, stderr about allowedSignersFile is expected, treat as info
116
+ if (isRawOutput && stderr.includes('allowedSignersFile needs to be configured')) {
117
+ logger.info(`Git log stderr (signature verification note): ${stderr.trim()}`, { ...context, operation });
118
+ }
119
+ else {
120
+ logger.warning(`Git log stderr: ${stderr.trim()}`, { ...context, operation });
121
+ }
122
+ }
123
+ // If raw output was requested, return it directly
124
+ if (isRawOutput) {
125
+ const message = `Raw log output (showSignature=true):\n${stdout}`;
126
+ logger.info(`${operation} completed successfully (raw output).`, { ...context, operation, path: targetPath });
127
+ return { success: true, commits: [], message: message };
94
128
  }
95
- // Parse the output
129
+ // Otherwise, parse the structured output
96
130
  const commits = [];
97
131
  const commitRecords = stdout.split(RECORD_SEP).filter(record => record.trim() !== ''); // Split records and remove empty ones
98
132
  for (const record of commitRecords) {
99
- const fields = record.split(FIELD_SEP);
133
+ const trimmedRecord = record.trim(); // Trim leading/trailing whitespace (like newlines)
134
+ if (!trimmedRecord)
135
+ continue; // Skip empty records after trimming
136
+ const fields = trimmedRecord.split(FIELD_SEP); // Split the trimmed record
100
137
  if (fields.length >= 5) { // Need at least hash, name, email, timestamp, subject
101
138
  try {
102
139
  const commitEntry = {
@@ -1,8 +1,11 @@
1
- import { ErrorHandler } from '../../../utils/errorHandler.js';
2
- import { logger } from '../../../utils/logger.js';
3
- import { requestContextService } from '../../../utils/requestContext.js';
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';
4
8
  import { GitLogInputSchema, logGitHistory } from './logic.js';
5
- import { BaseErrorCode } from '../../../types-global/errors.js';
6
9
  let _getWorkingDirectory;
7
10
  let _getSessionId;
8
11
  /**
@@ -17,7 +20,7 @@ export function initializeGitLogStateAccessors(getWdFn, getSidFn) {
17
20
  logger.info('State accessors initialized for git_log tool registration.');
18
21
  }
19
22
  const TOOL_NAME = 'git_log';
20
- const TOOL_DESCRIPTION = "Shows commit logs for the repository. Supports limiting count, filtering by author, date range, and specific branch/file. Returns a list of commit objects.";
23
+ const TOOL_DESCRIPTION = "Shows commit logs for the repository. Supports limiting count, filtering by author, date range, and specific branch/file. Returns a list of commit objects by default. Can optionally show signature verification status (`showSignature: true`), which returns raw text output instead of JSON.";
21
24
  /**
22
25
  * Registers the git_log tool with the MCP server.
23
26
  *
@@ -1,14 +1,17 @@
1
- import { z } from 'zod';
2
- import { promisify } from 'util';
3
1
  import { exec } from 'child_process';
4
- import { logger } from '../../../utils/logger.js';
5
- import { McpError, BaseErrorCode } from '../../../types-global/errors.js';
6
- import { sanitization } from '../../../utils/sanitization.js';
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)
7
9
  import path from 'path'; // Import path module
10
+ import { sanitization } from '../../../utils/index.js';
8
11
  const execAsync = promisify(exec);
9
12
  // Define the input schema for the git_merge tool
10
13
  export const GitMergeInputSchema = z.object({
11
- path: z.string().min(1).optional().default('.').describe("Path to the Git repository. Defaults to the session's working directory if set."),
14
+ 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
15
  branch: z.string().min(1).describe('The name of the branch to merge into the current branch.'),
13
16
  commitMessage: z.string().optional().describe('Commit message to use for the merge commit (if required, e.g., not fast-forward).'),
14
17
  noFf: z.boolean().default(false).describe('Create a merge commit even when the merge resolves as a fast-forward (`--no-ff`).'),
@@ -1,8 +1,11 @@
1
- import { logger } from '../../../utils/logger.js';
2
- import { ErrorHandler } from '../../../utils/errorHandler.js';
3
- import { requestContextService } from '../../../utils/requestContext.js';
4
- import { gitMergeLogic, GitMergeInputSchema } from './logic.js';
5
- import { BaseErrorCode } from '../../../types-global/errors.js';
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 { GitMergeInputSchema, gitMergeLogic } 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 { logger } from '../../../utils/logger.js';
5
- import { McpError, BaseErrorCode } from '../../../types-global/errors.js';
6
- import { sanitization } from '../../../utils/sanitization.js';
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_pull tool using Zod
9
12
  export const GitPullInputSchema = z.object({
10
- path: z.string().min(1).optional().default('.').describe("Path to the Git repository. Defaults to the session's working directory if set."),
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
  remote: z.string().optional().describe("The remote repository to pull from (e.g., 'origin'). Defaults to the tracked upstream or 'origin'."),
12
- branch: z.string().optional().describe("The remote branch to pull. Defaults to the current branch's upstream."),
15
+ branch: z.string().optional().describe("The remote branch to pull (e.g., 'main'). Defaults to the current branch's upstream."),
13
16
  rebase: z.boolean().optional().default(false).describe("Use 'git pull --rebase' instead of merge."),
14
17
  ffOnly: z.boolean().optional().default(false).describe("Use '--ff-only' to only allow fast-forward merges."),
15
18
  // Add other relevant git pull options as needed (e.g., --prune, --tags, --depth)
@@ -125,7 +128,7 @@ export async function pullGitChanges(input, context) {
125
128
  throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`, { context, operation, originalError: error });
126
129
  }
127
130
  if (errorMessage.includes('resolve host') || errorMessage.includes('Could not read from remote repository')) {
128
- throw new McpError(BaseErrorCode.NETWORK_ERROR, `Failed to connect to remote repository. Error: ${errorMessage}`, { context, operation, originalError: error });
131
+ throw new McpError(BaseErrorCode.SERVICE_UNAVAILABLE, `Failed to connect to remote repository. Error: ${errorMessage}`, { context, operation, originalError: error });
129
132
  }
130
133
  if (errorMessage.includes('merge conflict') || errorMessage.includes('fix conflicts')) {
131
134
  // This might be caught here if execAsync throws due to non-zero exit code during conflict
@@ -1,8 +1,11 @@
1
- import { ErrorHandler } from '../../../utils/errorHandler.js';
2
- import { logger } from '../../../utils/logger.js';
3
- import { requestContextService } from '../../../utils/requestContext.js';
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';
4
8
  import { GitPullInputSchema, pullGitChanges } from './logic.js';
5
- import { BaseErrorCode } from '../../../types-global/errors.js';
6
9
  let _getWorkingDirectory;
7
10
  let _getSessionId;
8
11
  /**
@@ -1,16 +1,19 @@
1
- import { z } from 'zod';
2
- import { promisify } from 'util';
3
1
  import { exec } from 'child_process';
4
- import { logger } from '../../../utils/logger.js';
5
- import { McpError, BaseErrorCode } from '../../../types-global/errors.js';
6
- import { sanitization } from '../../../utils/sanitization.js';
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_push tool using Zod
9
12
  export const GitPushInputSchema = z.object({
10
- path: z.string().min(1).optional().default('.').describe("Path to the Git repository. Defaults to the session's working directory if set."),
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
  remote: z.string().optional().describe("The remote repository to push to (e.g., 'origin'). Defaults to the tracked upstream or 'origin'."),
12
- branch: z.string().optional().describe("The local branch to push. Defaults to the current branch."),
13
- remoteBranch: z.string().optional().describe("The remote branch to push to. Defaults to the same name as the local branch."),
15
+ branch: z.string().optional().describe("The local branch to push (e.g., 'main', 'feat/new-login'). Defaults to the current branch."),
16
+ remoteBranch: z.string().optional().describe("The remote branch to push to (e.g., 'main', 'develop'). Defaults to the same name as the local branch."),
14
17
  force: z.boolean().optional().default(false).describe("Force the push (use with caution: `--force-with-lease` is generally safer)."),
15
18
  forceWithLease: z.boolean().optional().default(false).describe("Force the push only if the remote ref is the expected value (`--force-with-lease`). Safer than --force."),
16
19
  setUpstream: z.boolean().optional().default(false).describe("Set the upstream tracking configuration (`-u` or `--set-upstream`)."),
@@ -169,7 +172,7 @@ export async function pushGitChanges(input, context) {
169
172
  throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`, { context, operation, originalError: error });
170
173
  }
171
174
  if (errorMessage.includes('resolve host') || errorMessage.includes('Could not read from remote repository') || errorMessage.includes('Connection timed out')) {
172
- throw new McpError(BaseErrorCode.NETWORK_ERROR, `Failed to connect to remote repository. Error: ${errorMessage}`, { context, operation, originalError: error });
175
+ throw new McpError(BaseErrorCode.SERVICE_UNAVAILABLE, `Failed to connect to remote repository. Error: ${errorMessage}`, { context, operation, originalError: error });
173
176
  }
174
177
  if (errorMessage.includes('rejected') || errorMessage.includes('failed to push some refs')) {
175
178
  // This might be caught here if execAsync throws due to non-zero exit code on rejection
@@ -1,8 +1,11 @@
1
- import { ErrorHandler } from '../../../utils/errorHandler.js';
2
- import { logger } from '../../../utils/logger.js';
3
- import { requestContextService } from '../../../utils/requestContext.js';
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';
4
8
  import { GitPushInputSchema, pushGitChanges } from './logic.js';
5
- import { BaseErrorCode } from '../../../types-global/errors.js';
6
9
  let _getWorkingDirectory;
7
10
  let _getSessionId;
8
11
  /**
@@ -1,13 +1,16 @@
1
- import { z } from 'zod';
2
- import { promisify } from 'util';
3
1
  import { exec } from 'child_process';
4
- import { logger } from '../../../utils/logger.js';
5
- import { McpError, BaseErrorCode } from '../../../types-global/errors.js';
6
- import { sanitization } from '../../../utils/sanitization.js';
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_rebase tool using Zod
9
12
  export const GitRebaseBaseSchema = z.object({
10
- path: z.string().min(1).optional().default('.').describe("Path to the local Git repository. If omitted, defaults to the path set by `git_set_working_dir` for the current session, or the server's CWD if no session path is set."),
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(['start', 'continue', 'abort', 'skip']).default('start').describe("Rebase operation mode: 'start' (initiate rebase), 'continue', 'abort', 'skip' (manage ongoing rebase)."),
12
15
  upstream: z.string().min(1).optional().describe("The upstream branch or commit to rebase onto. Required for 'start' mode unless 'interactive' is true with default base."),
13
16
  branch: z.string().min(1).optional().describe("The branch to rebase. Defaults to the current branch if omitted."),
@@ -1,8 +1,11 @@
1
- import { logger } from '../../../utils/logger.js';
2
- import { ErrorHandler } from '../../../utils/errorHandler.js';
3
- import { requestContextService } from '../../../utils/requestContext.js';
4
- import { gitRebaseLogic, GitRebaseBaseSchema } from './logic.js';
5
- import { BaseErrorCode } from '../../../types-global/errors.js';
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 { GitRebaseBaseSchema, gitRebaseLogic } from './logic.js';
6
9
  let _getWorkingDirectory;
7
10
  let _getSessionId;
8
11
  /**
@@ -1,16 +1,15 @@
1
1
  import { exec } from 'child_process';
2
2
  import { promisify } from 'util';
3
3
  import { z } from 'zod';
4
- import { BaseErrorCode, McpError } from '../../../types-global/errors.js';
5
- import { logger } from '../../../utils/logger.js';
6
- import { sanitization } from '../../../utils/sanitization.js';
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)
7
6
  const execAsync = promisify(exec);
8
7
  // Define the input schema for the git_remote tool using Zod
9
8
  export const GitRemoteInputSchema = z.object({
10
- path: z.string().min(1).optional().default('.').describe("Path to the Git repository. Defaults to the session's working directory if set."),
9
+ 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
10
  mode: z.enum(['list', 'add', 'remove', 'show']).describe("Operation mode: 'list', 'add', 'remove', 'show'"),
12
11
  name: z.string().min(1).optional().describe("Remote name (required for 'add', 'remove', 'show')"),
13
- url: z.string().url().optional().describe("Remote URL (required for 'add')"),
12
+ url: z.string().optional().describe("Remote URL (required for 'add')"), // Removed .url() validation
14
13
  });
15
14
  /**
16
15
  * Executes git remote commands based on the specified mode.
@@ -1,7 +1,5 @@
1
- import { BaseErrorCode } from '../../../types-global/errors.js';
2
- import { ErrorHandler } from '../../../utils/errorHandler.js';
3
- import { logger } from '../../../utils/logger.js';
4
- import { requestContextService } from '../../../utils/requestContext.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)
5
3
  import { GitRemoteInputSchema, gitRemoteLogic } from './logic.js';
6
4
  let _getWorkingDirectory;
7
5
  let _getSessionId;
@@ -1,15 +1,14 @@
1
- import { z } from 'zod';
2
- import { promisify } from 'util';
3
1
  import { exec } from 'child_process';
4
- import { logger } from '../../../utils/logger.js';
5
- import { McpError, BaseErrorCode } from '../../../types-global/errors.js';
6
- import { sanitization } from '../../../utils/sanitization.js';
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)
7
6
  const execAsync = promisify(exec);
8
7
  // Define the reset modes
9
8
  const ResetModeEnum = z.enum(['soft', 'mixed', 'hard', 'merge', 'keep']);
10
9
  // Define the input schema for the git_reset tool using Zod
11
10
  export const GitResetInputSchema = z.object({
12
- path: z.string().min(1).optional().default('.').describe("Path to the Git repository. Defaults to the session's working directory if set."),
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."),
13
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'."),
14
13
  commit: z.string().optional().describe("Commit, branch, or ref to reset to. Defaults to HEAD (useful for unstaging with 'mixed' mode)."),
15
14
  // 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
@@ -1,8 +1,6 @@
1
- import { ErrorHandler } from '../../../utils/errorHandler.js';
2
- import { logger } from '../../../utils/logger.js';
3
- import { requestContextService } from '../../../utils/requestContext.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)
4
3
  import { GitResetInputSchema, resetGitState } from './logic.js';
5
- import { BaseErrorCode } from '../../../types-global/errors.js';
6
4
  let _getWorkingDirectory;
7
5
  let _getSessionId;
8
6
  /**
@@ -1,14 +1,13 @@
1
- import { z } from 'zod';
2
1
  import { exec } from 'child_process';
3
- import { promisify } from 'util';
4
2
  import fs from 'fs/promises';
5
- import { McpError, BaseErrorCode } from '../../../types-global/errors.js';
6
- import { logger } from '../../../utils/logger.js';
7
- import { sanitization } from '../../../utils/sanitization.js';
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)
8
7
  const execAsync = promisify(exec);
9
8
  // Define the Zod schema for input validation
10
9
  export const GitSetWorkingDirInputSchema = z.object({
11
- path: z.string().min(1, "Path cannot be empty.").describe("The absolute path to set as the default working directory for the current session."),
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."),
12
11
  validateGitRepo: z.boolean().default(true).describe("Whether to validate that the path is a Git repository"),
13
12
  });
14
13
  /**
@@ -1,19 +1,21 @@
1
- import { ErrorHandler } from '../../../utils/errorHandler.js';
2
- import { logger } from '../../../utils/logger.js';
3
- import { requestContextService } from '../../../utils/requestContext.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)
4
3
  import { GitSetWorkingDirInputSchema, gitSetWorkingDirLogic } from './logic.js';
5
- import { BaseErrorCode } from '../../../types-global/errors.js';
4
+ let _getWorkingDirectory; // Added getter
6
5
  let _setWorkingDirectory;
7
6
  let _getSessionId;
8
7
  /**
9
8
  * Initializes the state accessors needed by the tool registration.
10
9
  * This should be called once during server setup.
11
- * @param setFn - Function to set the working directory for a session.
12
- * @param getFn - Function to get the session ID from context.
10
+ * @param getWdFn - Function to get the working directory for a session.
11
+ * @param setWdFn - Function to set the working directory for a session.
12
+ * @param getSidFn - Function to get the session ID from context.
13
13
  */
14
- export function initializeGitSetWorkingDirStateAccessors(setFn, getFn) {
15
- _setWorkingDirectory = setFn;
16
- _getSessionId = getFn;
14
+ export function initializeGitSetWorkingDirStateAccessors(getWdFn, // Added getter parameter
15
+ setWdFn, getSidFn) {
16
+ _getWorkingDirectory = getWdFn; // Store getter
17
+ _setWorkingDirectory = setWdFn;
18
+ _getSessionId = getSidFn;
17
19
  logger.info('State accessors initialized for git_set_working_dir tool registration.');
18
20
  }
19
21
  const TOOL_NAME = 'git_set_working_dir';
@@ -25,8 +27,9 @@ const TOOL_DESCRIPTION = "Sets the default working directory for the current ses
25
27
  * @throws {Error} If state accessors are not initialized.
26
28
  */
27
29
  export async function registerGitSetWorkingDirTool(server) {
28
- if (!_setWorkingDirectory || !_getSessionId) {
29
- throw new Error('State accessors for git_set_working_dir must be initialized before registration.');
30
+ // Check all required accessors
31
+ if (!_getWorkingDirectory || !_setWorkingDirectory || !_getSessionId) {
32
+ throw new Error('State accessors (getWD, setWD, getSID) for git_set_working_dir must be initialized before registration.');
30
33
  }
31
34
  try {
32
35
  server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitSetWorkingDirInputSchema.shape, // Pass the shape for SDK validation
@@ -40,11 +43,17 @@ export async function registerGitSetWorkingDirTool(server) {
40
43
  const setWorkingDirectoryForSession = (path) => {
41
44
  _setWorkingDirectory(sessionId, path); // Non-null assertion
42
45
  };
43
- // Enhance context with session ID and the setter function
46
+ // Define the session-specific getter function (needed by logic?)
47
+ // If the logic needs the current WD, pass the getter too. Assuming it might.
48
+ const getWorkingDirectoryForSession = () => {
49
+ return _getWorkingDirectory(sessionId); // Non-null assertion
50
+ };
51
+ // Enhance context with session ID and the getter/setter functions
44
52
  const logicContext = {
45
53
  ...requestContext,
46
54
  sessionId: sessionId,
47
- setWorkingDirectory: setWorkingDirectoryForSession,
55
+ getWorkingDirectory: getWorkingDirectoryForSession, // Pass getter
56
+ setWorkingDirectory: setWorkingDirectoryForSession, // Pass setter
48
57
  };
49
58
  return await ErrorHandler.tryCatch(async () => {
50
59
  // Call the core logic function with validated args and enhanced context