@cyanheads/git-mcp-server 2.1.8 → 2.2.0

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 (79) hide show
  1. package/README.md +4 -4
  2. package/dist/mcp-server/server.js +69 -228
  3. package/dist/mcp-server/tools/gitAdd/index.js +2 -4
  4. package/dist/mcp-server/tools/gitAdd/logic.js +17 -74
  5. package/dist/mcp-server/tools/gitAdd/registration.js +38 -59
  6. package/dist/mcp-server/tools/gitBranch/index.js +3 -5
  7. package/dist/mcp-server/tools/gitBranch/logic.js +118 -296
  8. package/dist/mcp-server/tools/gitBranch/registration.js +52 -66
  9. package/dist/mcp-server/tools/gitCheckout/index.js +2 -3
  10. package/dist/mcp-server/tools/gitCheckout/logic.js +47 -122
  11. package/dist/mcp-server/tools/gitCheckout/registration.js +53 -72
  12. package/dist/mcp-server/tools/gitCherryPick/index.js +3 -5
  13. package/dist/mcp-server/tools/gitCherryPick/logic.js +55 -162
  14. package/dist/mcp-server/tools/gitCherryPick/registration.js +52 -67
  15. package/dist/mcp-server/tools/gitClean/index.js +3 -5
  16. package/dist/mcp-server/tools/gitClean/logic.js +44 -143
  17. package/dist/mcp-server/tools/gitClean/registration.js +52 -92
  18. package/dist/mcp-server/tools/gitClearWorkingDir/index.js +3 -5
  19. package/dist/mcp-server/tools/gitClearWorkingDir/logic.js +19 -26
  20. package/dist/mcp-server/tools/gitClearWorkingDir/registration.js +55 -73
  21. package/dist/mcp-server/tools/gitClone/index.js +2 -4
  22. package/dist/mcp-server/tools/gitClone/logic.js +50 -171
  23. package/dist/mcp-server/tools/gitClone/registration.js +51 -42
  24. package/dist/mcp-server/tools/gitCommit/index.js +2 -4
  25. package/dist/mcp-server/tools/gitCommit/logic.js +90 -295
  26. package/dist/mcp-server/tools/gitCommit/registration.js +52 -73
  27. package/dist/mcp-server/tools/gitDiff/index.js +2 -3
  28. package/dist/mcp-server/tools/gitDiff/logic.js +78 -254
  29. package/dist/mcp-server/tools/gitDiff/registration.js +53 -68
  30. package/dist/mcp-server/tools/gitFetch/index.js +2 -3
  31. package/dist/mcp-server/tools/gitFetch/logic.js +47 -129
  32. package/dist/mcp-server/tools/gitFetch/registration.js +54 -66
  33. package/dist/mcp-server/tools/gitInit/index.js +3 -5
  34. package/dist/mcp-server/tools/gitInit/logic.js +46 -152
  35. package/dist/mcp-server/tools/gitInit/registration.js +52 -104
  36. package/dist/mcp-server/tools/gitLog/index.js +2 -3
  37. package/dist/mcp-server/tools/gitLog/logic.js +75 -257
  38. package/dist/mcp-server/tools/gitLog/registration.js +54 -66
  39. package/dist/mcp-server/tools/gitMerge/index.js +3 -5
  40. package/dist/mcp-server/tools/gitMerge/logic.js +52 -179
  41. package/dist/mcp-server/tools/gitMerge/registration.js +52 -71
  42. package/dist/mcp-server/tools/gitPull/index.js +2 -3
  43. package/dist/mcp-server/tools/gitPull/logic.js +48 -146
  44. package/dist/mcp-server/tools/gitPull/registration.js +53 -75
  45. package/dist/mcp-server/tools/gitPush/index.js +2 -3
  46. package/dist/mcp-server/tools/gitPush/logic.js +73 -181
  47. package/dist/mcp-server/tools/gitPush/registration.js +53 -75
  48. package/dist/mcp-server/tools/gitRebase/index.js +3 -5
  49. package/dist/mcp-server/tools/gitRebase/logic.js +73 -202
  50. package/dist/mcp-server/tools/gitRebase/registration.js +52 -70
  51. package/dist/mcp-server/tools/gitRemote/index.js +3 -5
  52. package/dist/mcp-server/tools/gitRemote/logic.js +85 -193
  53. package/dist/mcp-server/tools/gitRemote/registration.js +52 -65
  54. package/dist/mcp-server/tools/gitReset/index.js +2 -3
  55. package/dist/mcp-server/tools/gitReset/logic.js +37 -121
  56. package/dist/mcp-server/tools/gitReset/registration.js +53 -60
  57. package/dist/mcp-server/tools/gitSetWorkingDir/index.js +3 -5
  58. package/dist/mcp-server/tools/gitSetWorkingDir/logic.js +45 -133
  59. package/dist/mcp-server/tools/gitSetWorkingDir/registration.js +55 -85
  60. package/dist/mcp-server/tools/gitShow/index.js +3 -5
  61. package/dist/mcp-server/tools/gitShow/logic.js +33 -122
  62. package/dist/mcp-server/tools/gitShow/registration.js +52 -74
  63. package/dist/mcp-server/tools/gitStash/index.js +3 -5
  64. package/dist/mcp-server/tools/gitStash/logic.js +70 -214
  65. package/dist/mcp-server/tools/gitStash/registration.js +52 -77
  66. package/dist/mcp-server/tools/gitStatus/index.js +2 -4
  67. package/dist/mcp-server/tools/gitStatus/logic.js +82 -229
  68. package/dist/mcp-server/tools/gitStatus/registration.js +52 -66
  69. package/dist/mcp-server/tools/gitTag/index.js +3 -5
  70. package/dist/mcp-server/tools/gitTag/logic.js +66 -188
  71. package/dist/mcp-server/tools/gitTag/registration.js +54 -73
  72. package/dist/mcp-server/tools/gitWorktree/index.js +3 -5
  73. package/dist/mcp-server/tools/gitWorktree/logic.js +112 -322
  74. package/dist/mcp-server/tools/gitWorktree/registration.js +54 -58
  75. package/dist/mcp-server/tools/gitWrapupInstructions/index.js +5 -3
  76. package/dist/mcp-server/tools/gitWrapupInstructions/logic.js +26 -38
  77. package/dist/mcp-server/tools/gitWrapupInstructions/registration.js +54 -70
  78. package/dist/mcp-server/transports/httpTransport.js +2 -3
  79. package/package.json +8 -8
@@ -1,284 +1,102 @@
1
+ /**
2
+ * @fileoverview Defines the core logic, schemas, and types for the git_log tool.
3
+ * @module src/mcp-server/tools/gitLog/logic
4
+ */
1
5
  import { execFile } from "child_process";
2
6
  import { promisify } from "util";
3
7
  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";
8
+ import { logger, sanitization } from "../../../utils/index.js";
9
+ import { McpError, BaseErrorCode } from "../../../types-global/errors.js";
10
10
  const execFileAsync = promisify(execFile);
11
- // Define the structure for a single commit entry
12
- export const CommitEntrySchema = z.object({
11
+ // 1. DEFINE the Zod input schema.
12
+ export const GitLogInputSchema = z.object({
13
+ path: z.string().default(".").describe("Path to the Git repository."),
14
+ maxCount: z.number().int().positive().optional().describe("Limit the number of commits to output."),
15
+ author: z.string().optional().describe("Limit commits to those by a specific author."),
16
+ since: z.string().optional().describe("Show commits more recent than a specific date (e.g., '2 weeks ago')."),
17
+ until: z.string().optional().describe("Show commits older than a specific date."),
18
+ branchOrFile: z.string().optional().describe("Show logs for a specific branch, tag, or file path."),
19
+ showSignature: z.boolean().default(false).describe("Show signature verification status for commits."),
20
+ });
21
+ // 2. DEFINE the Zod response schema.
22
+ const CommitEntrySchema = z.object({
13
23
  hash: z.string().describe("Full commit hash"),
14
24
  authorName: z.string().describe("Author's name"),
15
25
  authorEmail: z.string().email().describe("Author's email"),
16
- timestamp: z
17
- .number()
18
- .int()
19
- .positive()
20
- .describe("Commit timestamp (Unix epoch seconds)"),
26
+ timestamp: z.number().int().positive().describe("Commit timestamp (Unix epoch seconds)"),
21
27
  subject: z.string().describe("Commit subject line"),
22
- body: z.string().optional().describe("Commit body (optional)"),
28
+ body: z.string().optional().describe("Commit body"),
23
29
  });
24
- // Define the input schema for the git_log tool using Zod
25
- export const GitLogInputSchema = z.object({
26
- path: z
27
- .string()
28
- .min(1)
29
- .optional()
30
- .default(".")
31
- .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."),
32
- maxCount: z
33
- .number()
34
- .int()
35
- .positive()
36
- .optional()
37
- .describe("Limit the number of commits to output."),
38
- author: z
39
- .string()
40
- .optional()
41
- .describe("Limit commits to those matching the specified author pattern."),
42
- since: z
43
- .string()
44
- .optional()
45
- .describe("Show commits more recent than a specific date (e.g., '2 weeks ago', '2023-01-01')."),
46
- until: z
47
- .string()
48
- .optional()
49
- .describe("Show commits older than a specific date."),
50
- branchOrFile: z
51
- .string()
52
- .optional()
53
- .describe("Show logs for a specific branch (e.g., 'main'), tag, or file path (e.g., 'src/utils/logger.ts')."),
54
- showSignature: z
55
- .boolean()
56
- .optional()
57
- .default(false)
58
- .describe("Show signature verification status for commits. Returns raw output instead of parsed JSON."),
59
- // Note: We use a fixed pretty format for reliable parsing unless showSignature is true.
30
+ export const GitLogOutputSchema = z.object({
31
+ success: z.boolean().describe("Indicates if the command was successful."),
32
+ message: z.string().describe("A summary message of the result."),
33
+ commits: z.array(CommitEntrySchema).optional().describe("A list of commits."),
34
+ rawOutput: z.string().optional().describe("Raw output from the git log command, used when showSignature is true."),
60
35
  });
61
- // Delimiters for parsing the custom format
62
- const FIELD_SEP = "\x1f"; // Unit Separator
63
- const RECORD_SEP = "\x1e"; // Record Separator
64
- const GIT_LOG_FORMAT = `--pretty=format:%H${FIELD_SEP}%an${FIELD_SEP}%ae${FIELD_SEP}%at${FIELD_SEP}%s${FIELD_SEP}%b${RECORD_SEP}`; // %H=hash, %an=author name, %ae=author email, %at=timestamp, %s=subject, %b=body
36
+ const FIELD_SEP = "\x1f";
37
+ const RECORD_SEP = "\x1e";
38
+ const GIT_LOG_FORMAT = `--pretty=format:%H${FIELD_SEP}%an${FIELD_SEP}%ae${FIELD_SEP}%at${FIELD_SEP}%s${FIELD_SEP}%b${RECORD_SEP}`;
65
39
  /**
66
- * Executes the 'git log' command with a specific format and returns structured JSON output.
67
- *
68
- * @param {GitLogInput} input - The validated input object.
69
- * @param {RequestContext} context - The request context for logging and error handling.
70
- * @returns {Promise<GitLogResult>} A promise that resolves with the structured log result (either flat or grouped).
71
- * @throws {McpError} Throws an McpError if path resolution, validation, or the git command fails unexpectedly.
40
+ * 4. IMPLEMENT the core logic function.
41
+ * @throws {McpError} If the logic encounters an unrecoverable issue.
72
42
  */
73
- export async function logGitHistory(input, context) {
74
- // Return type updated to the union
43
+ export async function logGitHistory(params, context) {
75
44
  const operation = "logGitHistory";
76
- logger.debug(`Executing ${operation}`, { ...context, input });
77
- let targetPath;
78
- try {
79
- // Resolve and sanitize the target path
80
- if (input.path && input.path !== ".") {
81
- targetPath = input.path;
82
- }
83
- else {
84
- const workingDir = context.getWorkingDirectory();
85
- if (!workingDir) {
86
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, "No path provided and no working directory set for the session.", { context, operation });
87
- }
88
- targetPath = workingDir;
89
- }
90
- targetPath = sanitization.sanitizePath(targetPath, {
91
- allowAbsolute: true,
92
- }).sanitizedPath;
93
- logger.debug("Sanitized path", {
94
- ...context,
95
- operation,
96
- sanitizedPath: targetPath,
97
- });
45
+ logger.debug(`Executing ${operation}`, { ...context, params });
46
+ const workingDir = context.getWorkingDirectory();
47
+ if (params.path === "." && !workingDir) {
48
+ throw new McpError(BaseErrorCode.VALIDATION_ERROR, "No session working directory set. Please specify a 'path' or use 'git_set_working_dir' first.");
98
49
  }
99
- catch (error) {
100
- logger.error("Path resolution or sanitization failed", {
101
- ...context,
102
- operation,
103
- error,
104
- });
105
- if (error instanceof McpError)
106
- throw error;
107
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
50
+ const targetPath = sanitization.sanitizePath(params.path === "." ? workingDir : params.path, { allowAbsolute: true }).sanitizedPath;
51
+ const args = ["-C", targetPath, "log"];
52
+ if (params.showSignature) {
53
+ args.push("--show-signature");
54
+ }
55
+ else {
56
+ args.push(GIT_LOG_FORMAT);
108
57
  }
58
+ if (params.maxCount)
59
+ args.push(`-n${params.maxCount}`);
60
+ if (params.author)
61
+ args.push(`--author=${params.author}`);
62
+ if (params.since)
63
+ args.push(`--since=${params.since}`);
64
+ if (params.until)
65
+ args.push(`--until=${params.until}`);
66
+ if (params.branchOrFile)
67
+ args.push(params.branchOrFile);
109
68
  try {
110
- const args = ["-C", targetPath, "log"];
111
- let isRawOutput = false; // Flag to indicate if we should parse or return raw
112
- if (input.showSignature) {
113
- isRawOutput = true;
114
- args.push("--show-signature");
115
- logger.info("Show signature requested, returning raw output.", {
116
- ...context,
117
- operation,
118
- });
119
- }
120
- else {
121
- args.push(GIT_LOG_FORMAT);
122
- }
123
- if (input.maxCount) {
124
- args.push(`-n${input.maxCount}`);
125
- }
126
- if (input.author) {
127
- args.push(`--author=${input.author}`);
128
- }
129
- if (input.since) {
130
- args.push(`--since=${input.since}`);
131
- }
132
- if (input.until) {
133
- args.push(`--until=${input.until}`);
134
- }
135
- if (input.branchOrFile) {
136
- args.push(input.branchOrFile);
137
- }
138
- logger.debug(`Executing command: git ${args.join(" ")}`, {
139
- ...context,
140
- operation,
141
- });
142
- // Increase maxBuffer if logs can be large
143
- const { stdout, stderr } = await execFileAsync("git", args, {
144
- maxBuffer: 1024 * 1024 * 10,
145
- }); // 10MB buffer
146
- if (stderr) {
147
- // Log stderr as warning, as git log might sometimes use it for non-fatal info
148
- // Exception: If showing signature, stderr about allowedSignersFile is expected, treat as info
149
- if (isRawOutput &&
150
- stderr.includes("allowedSignersFile needs to be configured")) {
151
- logger.info(`Git log stderr (signature verification note): ${stderr.trim()}`, { ...context, operation });
152
- }
153
- else {
154
- logger.warning(`Git log stderr: ${stderr.trim()}`, {
155
- ...context,
156
- operation,
157
- });
158
- }
159
- }
160
- // If raw output was requested, return it directly in the message field, omitting commits
161
- if (isRawOutput) {
162
- const message = `Raw log output (showSignature=true):\n${stdout}`;
163
- logger.info(`${operation} completed successfully (raw output).`, {
164
- ...context,
165
- operation,
166
- path: targetPath,
167
- });
168
- // Return without the 'commits' or 'groupedCommits' field
169
- return { success: true, message: message };
170
- }
171
- // --- Parse the structured output into a flat list first ---
172
- const flatCommits = [];
173
- const commitRecords = stdout
174
- .split(RECORD_SEP)
175
- .filter((record) => record.trim() !== ""); // Split records and remove empty ones
176
- for (const record of commitRecords) {
177
- const trimmedRecord = record.trim(); // Trim leading/trailing whitespace (like newlines)
178
- if (!trimmedRecord)
179
- continue; // Skip empty records after trimming
180
- const fields = trimmedRecord.split(FIELD_SEP); // Split the trimmed record
181
- if (fields.length >= 5) {
182
- // Need at least hash, name, email, timestamp, subject
183
- try {
184
- const commitEntry = {
185
- hash: fields[0],
186
- authorName: fields[1],
187
- authorEmail: fields[2],
188
- timestamp: parseInt(fields[3], 10), // Unix timestamp
189
- subject: fields[4],
190
- body: fields[5] || undefined, // Body might be empty
191
- };
192
- // Validate parsed entry
193
- CommitEntrySchema.parse(commitEntry);
194
- flatCommits.push(commitEntry);
195
- }
196
- catch (parseError) {
197
- logger.warning(`Failed to parse commit record field`, {
198
- ...context,
199
- operation,
200
- fieldIndex: fields.findIndex((_, i) => i > 5),
201
- recordFragment: record.substring(0, 100),
202
- parseError,
203
- });
204
- // Decide whether to skip the commit or throw an error
205
- }
206
- }
207
- else {
208
- logger.warning(`Skipping commit record due to unexpected number of fields (${fields.length})`, { ...context, operation, recordFragment: record.substring(0, 100) });
209
- }
210
- }
211
- // --- Group the flat list by author ---
212
- const groupedCommitsMap = new Map();
213
- for (const commit of flatCommits) {
214
- const authorKey = `${commit.authorName} <${commit.authorEmail}>`;
215
- const groupedInfo = {
216
- hash: commit.hash,
217
- timestamp: commit.timestamp,
218
- subject: commit.subject,
219
- body: commit.body,
69
+ logger.debug(`Executing command: git ${args.join(" ")}`, { ...context, operation });
70
+ const { stdout } = await execFileAsync("git", args, { maxBuffer: 1024 * 1024 * 10 });
71
+ if (params.showSignature) {
72
+ return { success: true, message: "Raw log output with signature status.", rawOutput: stdout };
73
+ }
74
+ const commitRecords = stdout.split(RECORD_SEP).filter(r => r.trim());
75
+ const commits = commitRecords.map(record => {
76
+ const fields = record.trim().split(FIELD_SEP);
77
+ return {
78
+ hash: fields[0],
79
+ authorName: fields[1],
80
+ authorEmail: fields[2],
81
+ timestamp: parseInt(fields[3], 10),
82
+ subject: fields[4],
83
+ body: fields[5] || undefined,
220
84
  };
221
- if (groupedCommitsMap.has(authorKey)) {
222
- groupedCommitsMap.get(authorKey).commits.push(groupedInfo);
223
- }
224
- else {
225
- groupedCommitsMap.set(authorKey, {
226
- authorName: commit.authorName,
227
- authorEmail: commit.authorEmail,
228
- commits: [groupedInfo],
229
- });
230
- }
231
- }
232
- const groupedCommits = Array.from(groupedCommitsMap.values());
233
- // --- Prepare final result ---
234
- const commitCount = flatCommits.length;
235
- const message = commitCount > 0
236
- ? `${commitCount} commit(s) found.`
237
- : "No commits found matching criteria.";
238
- logger.info(message, {
239
- ...context,
240
- operation,
241
- path: targetPath,
242
- commitCount: commitCount,
243
- authorGroupCount: groupedCommits.length,
244
85
  });
245
- return { success: true, groupedCommits, message }; // Return the grouped structure
86
+ return { success: true, message: `Found ${commits.length} commit(s).`, commits };
246
87
  }
247
88
  catch (error) {
248
- logger.error(`Failed to execute git log command`, {
249
- ...context,
250
- operation,
251
- path: targetPath,
252
- error: error.message,
253
- stderr: error.stderr,
254
- stdout: error.stdout,
255
- });
256
- const errorMessage = error.stderr || error.stdout || error.message || "";
257
- // Handle specific error cases
89
+ const errorMessage = error.stderr || error.message || "";
90
+ logger.error(`Failed to execute git log command`, { ...context, operation, errorMessage });
258
91
  if (errorMessage.toLowerCase().includes("not a git repository")) {
259
- throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`, { context, operation, originalError: error });
92
+ throw new McpError(BaseErrorCode.NOT_FOUND, `Path is not a Git repository: ${targetPath}`);
260
93
  }
261
- if (errorMessage.includes("fatal: bad revision")) {
262
- throw new McpError(BaseErrorCode.NOT_FOUND, `Invalid branch, tag, or revision specified: '${input.branchOrFile}'. Error: ${errorMessage}`, { context, operation, originalError: error });
94
+ if (errorMessage.includes("bad revision")) {
95
+ throw new McpError(BaseErrorCode.NOT_FOUND, `Invalid branch, tag, or revision specified: '${params.branchOrFile}'.`);
263
96
  }
264
- if (errorMessage.includes("fatal: ambiguous argument")) {
265
- throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Ambiguous argument provided (e.g., branch/tag/file conflict): '${input.branchOrFile}'. Error: ${errorMessage}`, { context, operation, originalError: error });
266
- }
267
- // Check if it's just that no commits were found
268
97
  if (errorMessage.includes("does not have any commits yet")) {
269
- logger.info("Repository has no commits yet.", {
270
- ...context,
271
- operation,
272
- path: targetPath,
273
- });
274
- // Return the grouped structure even for no commits
275
- return {
276
- success: true,
277
- groupedCommits: [],
278
- message: "Repository has no commits yet.",
279
- };
98
+ return { success: true, message: "Repository has no commits yet.", commits: [] };
280
99
  }
281
- // Generic internal error for other failures
282
- throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to get git log for path: ${targetPath}. Error: ${errorMessage}`, { context, operation, originalError: error });
100
+ throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Git log failed: ${errorMessage}`);
283
101
  }
284
102
  }
@@ -1,76 +1,64 @@
1
- // Import utils from barrel (ErrorHandler from ../utils/internal/errorHandler.js)
2
- import { ErrorHandler } from "../../../utils/index.js";
3
- // Import utils from barrel (logger from ../utils/internal/logger.js)
4
- import { logger } from "../../../utils/index.js";
5
- // Import utils from barrel (requestContextService, RequestContext from ../utils/internal/requestContext.js)
6
- import { BaseErrorCode } from "../../../types-global/errors.js"; // Keep direct import for types-global
7
- import { requestContextService } from "../../../utils/index.js";
8
- import { GitLogInputSchema, logGitHistory, } from "./logic.js";
9
- let _getWorkingDirectory;
10
- let _getSessionId;
11
1
  /**
12
- * Initializes the state accessors needed by the tool registration.
13
- * This should be called once during server setup.
14
- * @param getWdFn - Function to get the working directory for a session.
15
- * @param getSidFn - Function to get the session ID from context.
2
+ * @fileoverview Handles registration and error handling for the git_log tool.
3
+ * @module src/mcp-server/tools/gitLog/registration
16
4
  */
17
- export function initializeGitLogStateAccessors(getWdFn, getSidFn) {
18
- _getWorkingDirectory = getWdFn;
19
- _getSessionId = getSidFn;
20
- logger.info("State accessors initialized for git_log tool registration.");
21
- }
5
+ import { ErrorHandler, logger, requestContextService } from "../../../utils/index.js";
6
+ import { McpError, BaseErrorCode } from "../../../types-global/errors.js";
7
+ import { logGitHistory, GitLogInputSchema, GitLogOutputSchema, } from "./logic.js";
22
8
  const TOOL_NAME = "git_log";
23
- const TOOL_DESCRIPTION = "Shows commit logs for the repository. Supports limiting count, filtering by author, date range, and specific branch/file. Returns a JSON object containing a list of commit objects (`commits` array) by default. If `showSignature: true` is used, it returns a JSON object where the `commits` array is empty and the raw signature verification output is included in the `message` field.";
9
+ const TOOL_DESCRIPTION = "Shows commit logs for the repository. Supports limiting count, filtering by author, date range, and specific branch/file. Returns a JSON object containing a list of commit objects (`commits` array) by default. If `showSignature: true` is used, it returns a JSON object where the `commits` array is empty and the raw signature verification output is included in the `rawOutput` field.";
24
10
  /**
25
- * Registers the git_log tool with the MCP server.
26
- *
27
- * @param {McpServer} server - The MCP server instance.
28
- * @throws {Error} If state accessors are not initialized.
11
+ * Registers the git_log tool with the MCP server instance.
12
+ * @param server The MCP server instance.
13
+ * @param getWorkingDirectory Function to get the session's working directory.
14
+ * @param getSessionId Function to get the session ID from context.
29
15
  */
30
- export async function registerGitLogTool(server) {
31
- if (!_getWorkingDirectory || !_getSessionId) {
32
- throw new Error("State accessors for git_log must be initialized before registration.");
33
- }
16
+ export const registerGitLogTool = async (server, getWorkingDirectory, getSessionId) => {
34
17
  const operation = "registerGitLogTool";
35
18
  const context = requestContextService.createRequestContext({ operation });
36
- await ErrorHandler.tryCatch(async () => {
37
- server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitLogInputSchema.shape, // Provide the Zod schema shape
38
- async (validatedArgs, callContext) => {
39
- const toolOperation = "tool:git_log";
40
- const requestContext = requestContextService.createRequestContext({
41
- operation: toolOperation,
42
- parentContext: callContext,
19
+ server.registerTool(TOOL_NAME, {
20
+ title: "Git Log",
21
+ description: TOOL_DESCRIPTION,
22
+ inputSchema: GitLogInputSchema.shape,
23
+ outputSchema: GitLogOutputSchema.shape,
24
+ annotations: {
25
+ readOnlyHint: true,
26
+ destructiveHint: false,
27
+ idempotentHint: true,
28
+ openWorldHint: false,
29
+ },
30
+ }, async (params, callContext) => {
31
+ const handlerContext = requestContextService.createRequestContext({
32
+ toolName: TOOL_NAME,
33
+ parentContext: callContext,
34
+ });
35
+ try {
36
+ const sessionId = getSessionId(handlerContext);
37
+ const result = await logGitHistory(params, {
38
+ ...handlerContext,
39
+ getWorkingDirectory: () => getWorkingDirectory(sessionId),
43
40
  });
44
- const sessionId = _getSessionId(requestContext);
45
- const getWorkingDirectoryForSession = () => {
46
- return _getWorkingDirectory(sessionId);
47
- };
48
- const logicContext = {
49
- ...requestContext,
50
- sessionId: sessionId,
51
- getWorkingDirectory: getWorkingDirectoryForSession,
41
+ return {
42
+ structuredContent: result,
43
+ content: [{ type: "text", text: `Success: ${JSON.stringify(result, null, 2)}` }],
52
44
  };
53
- logger.info(`Executing tool: ${TOOL_NAME}`, logicContext);
54
- return await ErrorHandler.tryCatch(async () => {
55
- // Call the core logic function
56
- const logResult = await logGitHistory(validatedArgs, logicContext);
57
- // Format the result (array of commits) as a JSON string within TextContent
58
- const resultContent = {
59
- type: "text",
60
- // Stringify the entire GitLogResult object which includes the commits array and success flag
61
- text: JSON.stringify(logResult, null, 2), // Pretty-print JSON
62
- contentType: "application/json",
63
- };
64
- logger.info(`Tool ${TOOL_NAME} executed successfully, returning JSON`, logicContext);
65
- // Success is determined by the logic function and included in the result object
66
- return { content: [resultContent] };
67
- }, {
68
- operation: toolOperation,
69
- context: logicContext,
70
- input: validatedArgs,
71
- errorCode: BaseErrorCode.INTERNAL_ERROR, // Default if unexpected error in logic
45
+ }
46
+ catch (error) {
47
+ logger.error(`Error in ${TOOL_NAME} handler`, { error, ...handlerContext });
48
+ const handledError = ErrorHandler.handleError(error, {
49
+ operation: `tool:${TOOL_NAME}`,
50
+ context: handlerContext,
51
+ input: params,
72
52
  });
73
- });
74
- logger.info(`Tool registered: ${TOOL_NAME}`, context);
75
- }, { operation, context, critical: true });
76
- }
53
+ const mcpError = handledError instanceof McpError
54
+ ? handledError
55
+ : new McpError(BaseErrorCode.INTERNAL_ERROR, "An unexpected error occurred.", { originalError: handledError });
56
+ return {
57
+ isError: true,
58
+ content: [{ type: "text", text: mcpError.message }],
59
+ structuredContent: mcpError.details,
60
+ };
61
+ }
62
+ });
63
+ logger.info(`Tool '${TOOL_NAME}' registered successfully.`, context);
64
+ };
@@ -1,7 +1,5 @@
1
1
  /**
2
- * @fileoverview Barrel file for the git_merge tool.
3
- * Exports the registration function and state accessor initialization function.
2
+ * @fileoverview Barrel file for the gitMerge tool.
3
+ * @module src/mcp-server/tools/gitMerge/index
4
4
  */
5
- export { registerGitMergeTool, initializeGitMergeStateAccessors, } from "./registration.js";
6
- // Export types if needed elsewhere, e.g.:
7
- // export type { GitMergeInput, GitMergeResult } from './logic.js';
5
+ export { registerGitMergeTool } from "./registration.js";