@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,22 +1,39 @@
1
- import { exec } from 'child_process';
2
- import fs from 'fs/promises';
3
- import { promisify } from 'util';
4
- import { z } from 'zod';
1
+ import { exec } from "child_process";
2
+ import fs from "fs/promises";
3
+ import { promisify } from "util";
4
+ import { z } from "zod";
5
5
  // Import utils from barrel (logger from ../utils/internal/logger.js)
6
- import { logger } from '../../../utils/index.js';
6
+ import { logger } from "../../../utils/index.js";
7
7
  // Import utils from barrel (RequestContext from ../utils/internal/requestContext.js)
8
- import { BaseErrorCode, McpError } from '../../../types-global/errors.js'; // Keep direct import for types-global
8
+ import { BaseErrorCode, McpError } from "../../../types-global/errors.js"; // Keep direct import for types-global
9
9
  // Import utils from barrel (sanitization from ../utils/security/sanitization.js)
10
- import { sanitization } from '../../../utils/index.js';
10
+ import { sanitization } from "../../../utils/index.js";
11
11
  const execAsync = promisify(exec);
12
12
  // Define the input schema for the git_clone tool using Zod
13
13
  export const GitCloneInputSchema = z.object({
14
- repositoryUrl: z.string().url("Invalid repository URL format.").describe("The URL of the repository to clone (e.g., https://github.com/cyanheads/git-mcp-server, git@github.com:cyanheads/git-mcp-server.git)."),
15
- targetPath: z.string().min(1).describe("The absolute path to the directory where the repository should be cloned."),
16
- branch: z.string().optional().describe("Specify a specific branch to checkout after cloning."),
17
- depth: z.number().int().positive().optional().describe("Create a shallow clone with a history truncated to the specified number of commits."),
14
+ repositoryUrl: z
15
+ .string()
16
+ .url("Invalid repository URL format.")
17
+ .describe("The URL of the repository to clone (e.g., https://github.com/cyanheads/git-mcp-server, git@github.com:cyanheads/git-mcp-server.git)."),
18
+ targetPath: z
19
+ .string()
20
+ .min(1)
21
+ .describe("The absolute path to the directory where the repository should be cloned."),
22
+ branch: z
23
+ .string()
24
+ .optional()
25
+ .describe("Specify a specific branch to checkout after cloning."),
26
+ depth: z
27
+ .number()
28
+ .int()
29
+ .positive()
30
+ .optional()
31
+ .describe("Create a shallow clone with a history truncated to the specified number of commits."),
18
32
  // recursive: z.boolean().default(false).describe("After the clone is created, initialize all submodules within, using their default settings."), // Consider adding later
19
- quiet: z.boolean().default(false).describe("Operate quietly. Progress is not reported to the standard error stream."),
33
+ quiet: z
34
+ .boolean()
35
+ .default(false)
36
+ .describe("Operate quietly. Progress is not reported to the standard error stream."),
20
37
  });
21
38
  /**
22
39
  * Executes the 'git clone' command to clone a repository.
@@ -27,19 +44,29 @@ export const GitCloneInputSchema = z.object({
27
44
  * @throws {McpError} Throws an McpError if path/URL validation fails or the git command fails unexpectedly.
28
45
  */
29
46
  export async function gitCloneLogic(input, context) {
30
- const operation = 'gitCloneLogic';
47
+ const operation = "gitCloneLogic";
31
48
  logger.debug(`Executing ${operation}`, { ...context, input });
32
49
  let sanitizedTargetPath;
33
50
  let sanitizedRepoUrl;
34
51
  try {
35
52
  // Sanitize the target path (must be absolute)
36
- sanitizedTargetPath = sanitization.sanitizePath(input.targetPath, { allowAbsolute: true }).sanitizedPath;
37
- logger.debug('Sanitized target path', { ...context, operation, sanitizedTargetPath });
53
+ sanitizedTargetPath = sanitization.sanitizePath(input.targetPath, {
54
+ allowAbsolute: true,
55
+ }).sanitizedPath;
56
+ logger.debug("Sanitized target path", {
57
+ ...context,
58
+ operation,
59
+ sanitizedTargetPath,
60
+ });
38
61
  // Basic sanitization/validation for URL (Zod already checks format)
39
62
  // Further sanitization might be needed depending on how it's used in the shell command
40
63
  // For now, rely on Zod's URL validation and careful command construction.
41
64
  sanitizedRepoUrl = input.repositoryUrl; // Assume Zod validation is sufficient for now
42
- logger.debug('Validated repository URL', { ...context, operation, sanitizedRepoUrl });
65
+ logger.debug("Validated repository URL", {
66
+ ...context,
67
+ operation,
68
+ sanitizedRepoUrl,
69
+ });
43
70
  // Check if target directory already exists and is not empty
44
71
  try {
45
72
  const stats = await fs.stat(sanitizedTargetPath);
@@ -56,9 +83,13 @@ export async function gitCloneLogic(input, context) {
56
83
  catch (error) {
57
84
  if (error instanceof McpError)
58
85
  throw error; // Re-throw our specific validation errors
59
- if (error.code !== 'ENOENT') {
86
+ if (error.code !== "ENOENT") {
60
87
  // If error is not "does not exist", it's unexpected
61
- logger.error(`Error checking target directory ${sanitizedTargetPath}`, { ...context, operation, error: error.message });
88
+ logger.error(`Error checking target directory ${sanitizedTargetPath}`, {
89
+ ...context,
90
+ operation,
91
+ error: error.message,
92
+ });
62
93
  throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to check target directory: ${error.message}`, { context, operation });
63
94
  }
64
95
  // ENOENT is expected - directory doesn't exist, which is fine for clone
@@ -66,7 +97,11 @@ export async function gitCloneLogic(input, context) {
66
97
  }
67
98
  }
68
99
  catch (error) {
69
- logger.error('Path/URL validation or sanitization failed', { ...context, operation, error });
100
+ logger.error("Path/URL validation or sanitization failed", {
101
+ ...context,
102
+ operation,
103
+ error,
104
+ });
70
105
  if (error instanceof McpError)
71
106
  throw error;
72
107
  throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid input: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
@@ -76,7 +111,7 @@ export async function gitCloneLogic(input, context) {
76
111
  // Use placeholders and pass args safely if possible, but exec requires string command. Be careful with quoting.
77
112
  let command = `git clone`;
78
113
  if (input.quiet) {
79
- command += ' --quiet';
114
+ command += " --quiet";
80
115
  }
81
116
  if (input.branch) {
82
117
  command += ` --branch "${input.branch.replace(/"/g, '\\"')}"`;
@@ -91,10 +126,18 @@ export async function gitCloneLogic(input, context) {
91
126
  const { stdout, stderr } = await execAsync(command, { timeout: 300000 }); // 5 minutes timeout
92
127
  if (stderr && !input.quiet) {
93
128
  // Stderr often contains progress info, log as info if quiet is false
94
- logger.info(`Git clone command produced stderr (progress/info)`, { ...context, operation, stderr });
129
+ logger.info(`Git clone command produced stderr (progress/info)`, {
130
+ ...context,
131
+ operation,
132
+ stderr,
133
+ });
95
134
  }
96
135
  if (stdout && !input.quiet) {
97
- logger.info(`Git clone command produced stdout`, { ...context, operation, stdout });
136
+ logger.info(`Git clone command produced stdout`, {
137
+ ...context,
138
+ operation,
139
+ stdout,
140
+ });
98
141
  }
99
142
  // Verify the target directory exists after clone
100
143
  let repoDirExists = false;
@@ -108,29 +151,45 @@ export async function gitCloneLogic(input, context) {
108
151
  throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Clone command finished but target directory ${sanitizedTargetPath} not found.`, { context, operation });
109
152
  }
110
153
  const successMessage = `Repository cloned successfully into ${sanitizedTargetPath}`;
111
- logger.info(`${operation} executed successfully`, { ...context, operation, path: sanitizedTargetPath });
154
+ logger.info(successMessage, {
155
+ ...context,
156
+ operation,
157
+ path: sanitizedTargetPath,
158
+ });
112
159
  return {
113
160
  success: true,
114
161
  message: successMessage,
115
162
  path: sanitizedTargetPath,
116
- repoDirExists: repoDirExists
163
+ repoDirExists: repoDirExists,
117
164
  };
118
165
  }
119
166
  catch (error) {
120
- const errorMessage = error.stderr || error.message || '';
121
- logger.error(`Failed to execute git clone command`, { ...context, operation, path: sanitizedTargetPath, error: errorMessage, stderr: error.stderr, stdout: error.stdout });
167
+ const errorMessage = error.stderr || error.message || "";
168
+ logger.error(`Failed to execute git clone command`, {
169
+ ...context,
170
+ operation,
171
+ path: sanitizedTargetPath,
172
+ error: errorMessage,
173
+ stderr: error.stderr,
174
+ stdout: error.stdout,
175
+ });
122
176
  // Handle specific error cases
123
- if (errorMessage.toLowerCase().includes('repository not found') || errorMessage.toLowerCase().includes('could not read from remote repository')) {
177
+ if (errorMessage.toLowerCase().includes("repository not found") ||
178
+ errorMessage
179
+ .toLowerCase()
180
+ .includes("could not read from remote repository")) {
124
181
  throw new McpError(BaseErrorCode.NOT_FOUND, `Repository not found or access denied: ${sanitizedRepoUrl}. Error: ${errorMessage}`, { context, operation, originalError: error });
125
182
  }
126
- if (errorMessage.toLowerCase().includes('already exists and is not an empty directory')) {
183
+ if (errorMessage
184
+ .toLowerCase()
185
+ .includes("already exists and is not an empty directory")) {
127
186
  // This should have been caught by our pre-check, but handle defensively
128
187
  throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Target directory already exists and is not empty: ${sanitizedTargetPath}. Error: ${errorMessage}`, { context, operation, originalError: error });
129
188
  }
130
- if (errorMessage.toLowerCase().includes('permission denied')) {
189
+ if (errorMessage.toLowerCase().includes("permission denied")) {
131
190
  throw new McpError(BaseErrorCode.FORBIDDEN, `Permission denied during clone operation for path: ${sanitizedTargetPath}. Error: ${errorMessage}`, { context, operation, originalError: error });
132
191
  }
133
- if (errorMessage.toLowerCase().includes('timeout')) {
192
+ if (errorMessage.toLowerCase().includes("timeout")) {
134
193
  throw new McpError(BaseErrorCode.TIMEOUT, `Git clone operation timed out for repository: ${sanitizedRepoUrl}. Error: ${errorMessage}`, { context, operation, originalError: error });
135
194
  }
136
195
  // Generic internal error for other failures
@@ -1,13 +1,13 @@
1
1
  // Import utils from barrel (ErrorHandler from ../utils/internal/errorHandler.js)
2
- import { ErrorHandler } from '../../../utils/index.js';
2
+ import { ErrorHandler } from "../../../utils/index.js";
3
3
  // Import utils from barrel (logger from ../utils/internal/logger.js)
4
- import { logger } from '../../../utils/index.js';
4
+ import { logger } from "../../../utils/index.js";
5
5
  // Import utils from barrel (requestContextService from ../utils/internal/requestContext.js)
6
- import { requestContextService } from '../../../utils/index.js';
7
- import { GitCloneInputSchema, gitCloneLogic } from './logic.js';
8
- import { BaseErrorCode } from '../../../types-global/errors.js'; // Keep direct import for types-global
9
- const TOOL_NAME = 'git_clone';
10
- const TOOL_DESCRIPTION = 'Clones a Git repository from a given URL into a specified absolute directory path. Supports cloning specific branches and setting clone depth.';
6
+ import { requestContextService } from "../../../utils/index.js";
7
+ import { GitCloneInputSchema, gitCloneLogic, } from "./logic.js";
8
+ import { BaseErrorCode } from "../../../types-global/errors.js"; // Keep direct import for types-global
9
+ const TOOL_NAME = "git_clone";
10
+ const TOOL_DESCRIPTION = "Clones a Git repository from a given URL into a specified absolute directory path. Supports cloning specific branches and setting clone depth.";
11
11
  /**
12
12
  * Registers the git_clone tool with the MCP server.
13
13
  *
@@ -16,22 +16,25 @@ const TOOL_DESCRIPTION = 'Clones a Git repository from a given URL into a specif
16
16
  * @throws {Error} If registration fails.
17
17
  */
18
18
  export const registerGitCloneTool = async (server) => {
19
- const operation = 'registerGitCloneTool';
19
+ const operation = "registerGitCloneTool";
20
20
  const context = requestContextService.createRequestContext({ operation });
21
21
  await ErrorHandler.tryCatch(async () => {
22
22
  server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitCloneInputSchema.shape, // Provide the Zod schema shape
23
23
  async (validatedArgs, callContext) => {
24
- const toolOperation = 'tool:git_clone';
25
- const requestContext = requestContextService.createRequestContext({ operation: toolOperation, parentContext: callContext });
24
+ const toolOperation = "tool:git_clone";
25
+ const requestContext = requestContextService.createRequestContext({
26
+ operation: toolOperation,
27
+ parentContext: callContext,
28
+ });
26
29
  logger.info(`Executing tool: ${TOOL_NAME}`, requestContext);
27
30
  return await ErrorHandler.tryCatch(async () => {
28
31
  // Call the core logic function
29
32
  const cloneResult = await gitCloneLogic(validatedArgs, requestContext);
30
33
  // Format the result as a JSON string within TextContent
31
34
  const resultContent = {
32
- type: 'text',
35
+ type: "text",
33
36
  text: JSON.stringify(cloneResult, null, 2), // Pretty-print JSON
34
- contentType: 'application/json',
37
+ contentType: "application/json",
35
38
  };
36
39
  logger.info(`Tool ${TOOL_NAME} executed successfully, returning JSON`, requestContext);
37
40
  return { content: [resultContent] };
@@ -2,6 +2,6 @@
2
2
  * @fileoverview Barrel file for the gitCommit tool.
3
3
  * Exports the registration function and state accessor initialization function.
4
4
  */
5
- export { initializeGitCommitStateAccessors, registerGitCommitTool } from './registration.js';
5
+ export { initializeGitCommitStateAccessors, registerGitCommitTool, } from "./registration.js";
6
6
  // Export types if needed elsewhere, e.g.:
7
7
  // export type { GitCommitInput, GitCommitResult } from './logic.js';