@cyanheads/git-mcp-server 2.0.12 → 2.0.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Git MCP Server
2
2
 
3
3
  [![TypeScript](https://img.shields.io/badge/TypeScript-^5.8.3-blue.svg)](https://www.typescriptlang.org/)
4
- [![Model Context Protocol](https://img.shields.io/badge/MCP%20SDK-^1.12.0-green.svg)](https://modelcontextprotocol.io/)
5
- [![Version](https://img.shields.io/badge/Version-2.0.12-blue.svg)](./CHANGELOG.md)
4
+ [![Model Context Protocol](https://img.shields.io/badge/MCP%20SDK-^1.12.1-green.svg)](https://modelcontextprotocol.io/)
5
+ [![Version](https://img.shields.io/badge/Version-2.0.15-blue.svg)](./CHANGELOG.md)
6
6
  [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
7
7
  [![Status](https://img.shields.io/badge/Status-Stable-green.svg)](https://github.com/cyanheads/git-mcp-server/issues)
8
8
  [![GitHub](https://img.shields.io/github/stars/cyanheads/git-mcp-server?style=social)](https://github.com/cyanheads/git-mcp-server)
@@ -129,6 +129,8 @@ Add to your MCP client settings (e.g., `cline_mcp_settings.json`):
129
129
  }
130
130
  ```
131
131
 
132
+ **Note**: You can see [mcp.json](mcp.json) for an example MCP client configuration file that includes the Git MCP Server.\*
133
+
132
134
  ## Project Structure
133
135
 
134
136
  The codebase follows a modular structure within the `src/` directory:
@@ -153,38 +155,39 @@ For a detailed file tree, run `npm run tree` or see [docs/tree.md](docs/tree.md)
153
155
 
154
156
  The Git MCP Server provides a suite of tools for interacting with Git repositories, callable via the Model Context Protocol.
155
157
 
156
- | Tool Name | Description | Key Arguments |
157
- | :---------------------- | :------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------- |
158
- | `git_add` | Stages specified files or patterns. | `path?`, `files?` |
159
- | `git_branch` | Manages branches (list, create, delete, rename, show current). | `path?`, `mode`, `branchName?`, `newBranchName?`, `startPoint?`, `force?`, `all?`, `remote?` |
160
- | `git_checkout` | Switches branches or restores working tree files. | `path?`, `branchOrPath`, `newBranch?`, `force?` |
161
- | `git_cherry_pick` | Applies changes introduced by existing commits. | `path?`, `commitRef`, `mainline?`, `strategy?`, `noCommit?`, `signoff?` |
162
- | `git_clean` | Removes untracked files. **Requires `force: true`**. | `path?`, `force`, `dryRun?`, `directories?`, `ignored?` |
163
- | `git_clear_working_dir` | Clears the session-specific working directory. | (none) |
164
- | `git_clone` | Clones a repository into a specified absolute path. | `repositoryUrl`, `targetPath`, `branch?`, `depth?`, `quiet?` |
165
- | `git_commit` | Commits staged changes. Supports author override, signing control. | `path?`, `message`, `author?`, `allowEmpty?`, `amend?`, `forceUnsignedOnFailure?` |
166
- | `git_diff` | Shows changes between commits, working tree, etc. | `path?`, `commit1?`, `commit2?`, `staged?`, `file?` |
167
- | `git_fetch` | Downloads objects and refs from other repositories. | `path?`, `remote?`, `prune?`, `tags?`, `all?` |
168
- | `git_init` | Initializes a new Git repository at the specified absolute path. Defaults to 'main' for initial branch. | `path`, `initialBranch?`, `bare?`, `quiet?` |
169
- | `git_log` | Shows commit logs. | `path?`, `maxCount?`, `author?`, `since?`, `until?`, `branchOrFile?` |
170
- | `git_merge` | Merges the specified branch into the current branch. | `path?`, `branch`, `commitMessage?`, `noFf?`, `squash?`, `abort?` |
171
- | `git_pull` | Fetches from and integrates with another repository or local branch. | `path?`, `remote?`, `branch?`, `rebase?`, `ffOnly?` |
172
- | `git_push` | Updates remote refs using local refs. | `path?`, `remote?`, `branch?`, `remoteBranch?`, `force?`, `forceWithLease?`, `setUpstream?`, `tags?`, `delete?` |
173
- | `git_rebase` | Reapplies commits on top of another base tip. | `path?`, `mode?`, `upstream?`, `branch?`, `interactive?`, `strategy?`, `strategyOption?`, `onto?` |
174
- | `git_remote` | Manages remote repositories (list, add, remove, show). | `path?`, `mode`, `name?`, `url?` |
175
- | `git_reset` | Resets current HEAD to a specified state. Supports soft, mixed, hard modes. **USE 'hard' WITH CAUTION**. | `path?`, `mode?`, `commit?` |
176
- | `git_set_working_dir` | Sets the default working directory. Can optionally initialize repo if not present. Requires absolute path. | `path`, `validateGitRepo?`, `initializeIfNotPresent?` |
177
- | `git_show` | Shows information about Git objects (commits, tags, etc.). | `path?`, `ref`, `filePath?` |
178
- | `git_stash` | Manages stashed changes (list, apply, pop, drop, save). | `path?`, `mode`, `stashRef?`, `message?` |
179
- | `git_status` | Gets repository status (branch, staged, modified, untracked files). | `path?` |
180
- | `git_tag` | Manages tags (list, create annotated/lightweight, delete). | `path?`, `mode`, `tagName?`, `message?`, `commitRef?`, `annotate?` |
181
- | `git_worktree` | Manages Git worktrees (list, add, remove, move, prune). | `path?`, `mode`, `worktreePath?`, `commitish?`, `newBranch?`, `force?`, `detach?`, `newPath?`, `verbose?`, `dryRun?`, `expire?` |
158
+ | Tool Name | Description | Key Arguments |
159
+ | :------------------------ | :--------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------ |
160
+ | `git_add` | Stages specified files or patterns. | `path?`, `files?` |
161
+ | `git_branch` | Manages branches (list, create, delete, rename, show current). | `path?`, `mode`, `branchName?`, `newBranchName?`, `startPoint?`, `force?`, `all?`, `remote?` |
162
+ | `git_checkout` | Switches branches or restores working tree files. | `path?`, `branchOrPath`, `newBranch?`, `force?` |
163
+ | `git_cherry_pick` | Applies changes introduced by existing commits. | `path?`, `commitRef`, `mainline?`, `strategy?`, `noCommit?`, `signoff?` |
164
+ | `git_clean` | Removes untracked files. **Requires `force: true`**. | `path?`, `force`, `dryRun?`, `directories?`, `ignored?` |
165
+ | `git_clear_working_dir` | Clears the session-specific working directory. | (none) |
166
+ | `git_clone` | Clones a repository into a specified absolute path. | `repositoryUrl`, `targetPath`, `branch?`, `depth?`, `quiet?` |
167
+ | `git_commit` | Commits staged changes. Supports author override, signing control. | `path?`, `message`, `author?`, `allowEmpty?`, `amend?`, `forceUnsignedOnFailure?` |
168
+ | `git_diff` | Shows changes between commits, working tree, etc. | `path?`, `commit1?`, `commit2?`, `staged?`, `file?`, `includeUntracked?` |
169
+ | `git_fetch` | Downloads objects and refs from other repositories. | `path?`, `remote?`, `prune?`, `tags?`, `all?` |
170
+ | `git_init` | Initializes a new Git repository at the specified absolute path. Defaults to 'main' for initial branch. | `path`, `initialBranch?`, `bare?`, `quiet?` |
171
+ | `git_log` | Shows commit logs. | `path?`, `maxCount?`, `author?`, `since?`, `until?`, `branchOrFile?` |
172
+ | `git_merge` | Merges the specified branch into the current branch. | `path?`, `branch`, `commitMessage?`, `noFf?`, `squash?`, `abort?` |
173
+ | `git_pull` | Fetches from and integrates with another repository or local branch. | `path?`, `remote?`, `branch?`, `rebase?`, `ffOnly?` |
174
+ | `git_push` | Updates remote refs using local refs. | `path?`, `remote?`, `branch?`, `remoteBranch?`, `force?`, `forceWithLease?`, `setUpstream?`, `tags?`, `delete?` |
175
+ | `git_rebase` | Reapplies commits on top of another base tip. | `path?`, `mode?`, `upstream?`, `branch?`, `interactive?`, `strategy?`, `strategyOption?`, `onto?` |
176
+ | `git_remote` | Manages remote repositories (list, add, remove, show). | `path?`, `mode`, `name?`, `url?` |
177
+ | `git_reset` | Resets current HEAD to a specified state. Supports soft, mixed, hard modes. **USE 'hard' WITH CAUTION**. | `path?`, `mode?`, `commit?` |
178
+ | `git_set_working_dir` | Sets the default working directory. Can optionally initialize repo if not present. Requires absolute path. | `path`, `validateGitRepo?`, `initializeIfNotPresent?` |
179
+ | `git_show` | Shows information about Git objects (commits, tags, etc.). | `path?`, `ref`, `filePath?` |
180
+ | `git_stash` | Manages stashed changes (list, apply, pop, drop, save). | `path?`, `mode`, `stashRef?`, `message?` |
181
+ | `git_status` | Gets repository status (branch, staged, modified, untracked files). | `path?` |
182
+ | `git_tag` | Manages tags (list, create annotated/lightweight, delete). | `path?`, `mode`, `tagName?`, `message?`, `commitRef?`, `annotate?` |
183
+ | `git_worktree` | Manages Git worktrees (list, add, remove, move, prune). | `path?`, `mode`, `worktreePath?`, `commitish?`, `newBranch?`, `force?`, `detach?`, `newPath?`, `verbose?`, `dryRun?`, `expire?` |
184
+ | `git_wrapup_instructions` | Provides a standard Git wrap-up workflow. | `acknowledgement`, `updateAgentMetaFiles?` |
182
185
 
183
186
  _Note: The `path` parameter for most tools defaults to the session's working directory if set via `git_set_working_dir`._
184
187
 
185
188
  ## Resources
186
189
 
187
- **MCP Resources are not implemented in this version (v2.0.12).**
190
+ **MCP Resources are not implemented in this version (v2.0.14).**
188
191
 
189
192
  This version focuses on the refactored Git tools implementation based on the latest `mcp-ts-template` and MCP SDK v1.12.0. Resource capabilities, previously available, have been temporarily removed during this major update.
190
193
 
@@ -42,6 +42,7 @@ import { initializeGitStashStateAccessors, registerGitStashTool } from './tools/
42
42
  import { initializeGitStatusStateAccessors, registerGitStatusTool } from './tools/gitStatus/index.js';
43
43
  import { initializeGitTagStateAccessors, registerGitTagTool } from './tools/gitTag/index.js';
44
44
  import { initializeGitWorktreeStateAccessors, registerGitWorktreeTool } from './tools/gitWorktree/index.js';
45
+ import { registerGitWrapupInstructionsTool } from './tools/gitWrapupInstructions/index.js';
45
46
  // Import transport setup functions AND state accessors
46
47
  import { getHttpSessionWorkingDirectory, setHttpSessionWorkingDirectory, startHttpTransport } from './transports/httpTransport.js';
47
48
  import { connectStdioTransport, getStdioWorkingDirectory, setStdioWorkingDirectory } from './transports/stdioTransport.js';
@@ -159,6 +160,7 @@ async function createMcpServerInstance() {
159
160
  initializeGitStatusStateAccessors(getWorkingDirectory, getSessionIdFromContext);
160
161
  initializeGitTagStateAccessors(getWorkingDirectory, getSessionIdFromContext);
161
162
  initializeGitWorktreeStateAccessors(getWorkingDirectory, getSessionIdFromContext);
163
+ // No state accessor initialization needed for gitWrapupInstructionsTool
162
164
  logger.debug('State accessors initialized successfully.', context);
163
165
  }
164
166
  catch (initError) {
@@ -198,6 +200,7 @@ async function createMcpServerInstance() {
198
200
  await registerGitStatusTool(server);
199
201
  await registerGitTagTool(server);
200
202
  await registerGitWorktreeTool(server);
203
+ await registerGitWrapupInstructionsTool(server);
201
204
  // Add calls to register other resources/tools here if needed in the future.
202
205
  logger.info('Git tools registered successfully', context);
203
206
  }
@@ -15,6 +15,7 @@ const GitDiffInputBaseSchema = z.object({
15
15
  commit2: z.string().optional().describe("Second commit, branch, or ref for comparison. If omitted, compares commit1 against the working tree or index."),
16
16
  staged: z.boolean().optional().default(false).describe("Show diff of changes staged for the next commit (compares index against HEAD). Overrides commit1/commit2 if true."),
17
17
  file: z.string().optional().describe("Limit the diff output to a specific file path."),
18
+ includeUntracked: z.boolean().optional().default(false).describe("Include untracked files in the diff output (shows their full content as new files). This is a non-standard extension."),
18
19
  // Add options like --name-only, --stat, context lines (-U<n>) if needed
19
20
  });
20
21
  // Export the shape for registration
@@ -61,42 +62,106 @@ export async function diffGitChanges(input, context) {
61
62
  const safeCommit1 = input.commit1?.replace(/[`$&;*()|<>]/g, '');
62
63
  const safeCommit2 = input.commit2?.replace(/[`$&;*()|<>]/g, '');
63
64
  const safeFile = input.file?.replace(/[`$&;*()|<>]/g, '');
65
+ let untrackedFilesDiff = '';
66
+ let untrackedFilesCount = 0;
64
67
  try {
65
- // Construct the git diff command
66
- let command = `git -C "${targetPath}" diff`;
68
+ // Construct the standard git diff command
69
+ let standardDiffCommand = `git -C "${targetPath}" diff`;
67
70
  if (input.staged) {
68
- command += ' --staged'; // Or --cached
71
+ standardDiffCommand += ' --staged'; // Or --cached
69
72
  }
70
73
  else {
71
74
  // Add commit references if not doing staged diff
72
75
  if (safeCommit1) {
73
- command += ` ${safeCommit1}`;
76
+ standardDiffCommand += ` ${safeCommit1}`;
74
77
  }
75
78
  if (safeCommit2) {
76
- command += ` ${safeCommit2}`;
79
+ standardDiffCommand += ` ${safeCommit2}`;
77
80
  }
78
81
  }
79
- // Add file path limiter if provided
82
+ // Add file path limiter if provided for standard diff
83
+ // Note: `input.file` will not apply to the untracked files part unless we explicitly filter them.
84
+ // For simplicity, `includeUntracked` will show all untracked files if `input.file` is also set.
80
85
  if (safeFile) {
81
- command += ` -- "${safeFile}"`; // Use '--' to separate paths from revisions
86
+ standardDiffCommand += ` -- "${safeFile}"`; // Use '--' to separate paths from revisions
82
87
  }
83
- logger.debug(`Executing command: ${command}`, { ...context, operation });
84
- // Execute command. Diff output is primarily on stdout.
85
- // Increase maxBuffer as diffs can be large.
86
- const { stdout, stderr } = await execAsync(command, { maxBuffer: 1024 * 1024 * 20 }); // 20MB buffer
87
- if (stderr) {
88
- // Log stderr as warning, as it might contain non-fatal info
89
- logger.warning(`Git diff stderr: ${stderr}`, { ...context, operation });
88
+ logger.debug(`Executing standard diff command: ${standardDiffCommand}`, { ...context, operation });
89
+ const { stdout: standardStdout, stderr: standardStderr } = await execAsync(standardDiffCommand, { maxBuffer: 1024 * 1024 * 20 });
90
+ if (standardStderr) {
91
+ logger.warning(`Git diff (standard) stderr: ${standardStderr}`, { ...context, operation });
92
+ }
93
+ let combinedDiffOutput = standardStdout;
94
+ // Handle untracked files if requested
95
+ if (input.includeUntracked) {
96
+ logger.debug('Including untracked files.', { ...context, operation });
97
+ const listUntrackedCommand = `git -C "${targetPath}" ls-files --others --exclude-standard`;
98
+ try {
99
+ const { stdout: untrackedFilesStdOut } = await execAsync(listUntrackedCommand);
100
+ const untrackedFiles = untrackedFilesStdOut.trim().split('\n').filter(f => f); // Filter out empty lines
101
+ if (untrackedFiles.length > 0) {
102
+ logger.info(`Found ${untrackedFiles.length} untracked files.`, { ...context, operation, untrackedFiles });
103
+ let individualUntrackedDiffs = '';
104
+ for (const untrackedFile of untrackedFiles) {
105
+ // Sanitize each untracked file path before using in command
106
+ const safeUntrackedFile = untrackedFile.replace(/[`$&;*()|<>]/g, '');
107
+ // Skip if file path becomes empty after sanitization (unlikely but safe)
108
+ if (!safeUntrackedFile)
109
+ continue;
110
+ const untrackedDiffCommand = `git -C "${targetPath}" diff --no-index /dev/null "${safeUntrackedFile}"`;
111
+ logger.debug(`Executing diff for untracked file: ${untrackedDiffCommand}`, { ...context, operation, file: safeUntrackedFile });
112
+ try {
113
+ const { stdout: untrackedFileDiffOut } = await execAsync(untrackedDiffCommand);
114
+ individualUntrackedDiffs += untrackedFileDiffOut;
115
+ untrackedFilesCount++;
116
+ }
117
+ catch (untrackedError) {
118
+ // For `git diff --no-index`, a non-zero exit code (usually 1) means differences were found.
119
+ // The actual diff output will be in untrackedError.stdout.
120
+ if (untrackedError.stdout) {
121
+ individualUntrackedDiffs += untrackedError.stdout;
122
+ untrackedFilesCount++;
123
+ // Log stderr if it exists, as it might contain actual error messages despite stdout having the diff
124
+ if (untrackedError.stderr) {
125
+ logger.warning(`Stderr while diffing untracked file ${safeUntrackedFile} (diff captured from stdout): ${untrackedError.stderr}`, { ...context, operation, file: safeUntrackedFile });
126
+ }
127
+ }
128
+ else {
129
+ // If stdout is empty, then it's a more genuine failure.
130
+ logger.warning(`Failed to diff untracked file: ${safeUntrackedFile}. Error: ${untrackedError.message}`, { ...context, operation, file: safeUntrackedFile, errorDetails: { stderr: untrackedError.stderr, stdout: untrackedError.stdout, code: untrackedError.code } });
131
+ individualUntrackedDiffs += `\n--- Diff for untracked file ${safeUntrackedFile} failed: ${untrackedError.message}\n`;
132
+ }
133
+ }
134
+ }
135
+ if (individualUntrackedDiffs) {
136
+ // Add a separator if standard diff also had output
137
+ if (combinedDiffOutput.trim()) {
138
+ combinedDiffOutput += '\n';
139
+ }
140
+ combinedDiffOutput += individualUntrackedDiffs;
141
+ }
142
+ }
143
+ else {
144
+ logger.info('No untracked files found.', { ...context, operation });
145
+ }
146
+ }
147
+ catch (lsFilesError) {
148
+ logger.warning(`Failed to list untracked files. Error: ${lsFilesError.message}`, { ...context, operation, error: lsFilesError.stderr || lsFilesError.stdout });
149
+ // Proceed without untracked files if listing fails
150
+ }
151
+ }
152
+ const isNoChanges = combinedDiffOutput.trim() === '';
153
+ const finalDiffOutput = isNoChanges ? 'No changes found.' : combinedDiffOutput;
154
+ let message = isNoChanges ? 'No changes found.' : 'Diff generated successfully.';
155
+ if (untrackedFilesCount > 0) {
156
+ message += ` Included ${untrackedFilesCount} untracked file(s).`;
90
157
  }
91
- const rawDiffOutput = stdout;
92
- const isNoChanges = rawDiffOutput.trim() === '';
93
- const finalDiffOutput = isNoChanges ? 'No changes found.' : rawDiffOutput;
94
- const message = isNoChanges ? 'No changes found.' : 'Diff generated successfully.';
95
158
  logger.info(`${operation} completed successfully. ${message}`, { ...context, operation, path: targetPath });
96
- return { success: true, diff: finalDiffOutput, message };
159
+ return { success: true, diff: finalDiffOutput, message, untrackedFilesProcessed: untrackedFilesCount };
97
160
  }
98
161
  catch (error) {
99
- logger.error(`Failed to execute git diff command`, { ...context, operation, path: targetPath, error: error.message, stderr: error.stderr, stdout: error.stdout });
162
+ // This catch block now primarily handles errors from the *standard* diff command
163
+ // or catastrophic failures before/after untracked file processing.
164
+ logger.error(`Failed to execute git diff operation`, { ...context, operation, path: targetPath, error: error.message, stderr: error.stderr, stdout: error.stdout });
100
165
  const errorMessage = error.stderr || error.stdout || error.message || '';
101
166
  // Handle specific error cases
102
167
  if (errorMessage.toLowerCase().includes('not a git repository')) {
@@ -21,7 +21,7 @@ export function initializeGitDiffStateAccessors(getWdFn, getSidFn) {
21
21
  logger.info('State accessors initialized for git_diff tool registration.');
22
22
  }
23
23
  const TOOL_NAME = 'git_diff';
24
- const TOOL_DESCRIPTION = "Shows changes between commits, commit and working tree, etc. Can show staged changes or diff specific files. Returns the diff output as plain text.";
24
+ const TOOL_DESCRIPTION = "Shows changes between commits, commit and working tree, etc. Can show staged changes or diff specific files. An optional 'includeUntracked' parameter (boolean) can be used to also show the content of untracked files. Returns the diff output as plain text.";
25
25
  /**
26
26
  * Registers the git_diff tool with the MCP server.
27
27
  *
@@ -0,0 +1,3 @@
1
+ export { registerGitWrapupInstructionsTool } from './registration.js';
2
+ // This tool does not require session-specific state accessors like getWorkingDirectory,
3
+ // so no initialize...StateAccessors function is needed or exported here.
@@ -0,0 +1,36 @@
1
+ import { z } from 'zod';
2
+ // Define the input schema
3
+ export const GitWrapupInstructionsInputSchema = z.object({
4
+ acknowledgement: z.enum(['Y', 'y', 'Yes', 'yes'], {
5
+ required_error: 'Acknowledgement is required.',
6
+ description: 'Acknowledgement that you have permission (implicit allowed, explicit preferred) from the user to initiate this tool. Must be "Y" or "Yes" (case-insensitive).',
7
+ }),
8
+ updateAgentMetaFiles: z.enum(['Y', 'y', 'Yes', 'yes']).optional().describe("If set to 'Y' or 'Yes', include an extra instruction to review and update agent-specific meta files like .clinerules or claude.md if present. Only use this if the user explicitly requested it."),
9
+ });
10
+ // The predefined instructions string.
11
+ const WRAPUP_INSTRUCTIONS = `
12
+ Perform all actions for our git wrapup workflow:
13
+ 1. Use the git_diff tool to understand the precise nature and rationale behind each change (what changed and why did it change?) within the code base. Use the 'includeUntracked' parameter to view all changes, including untracked files. This will help you understand the context and purpose of the modifications made.
14
+ 2. For substantial code updates, review and update the README to ensure it is up to date with our current codebase (make a note to the user of any discrepancies you noticed, gathered from everything you've seen of our codebase so far).
15
+ 3. Update the CHANGELOG with concise, descriptive entries detailing all modifications, clearly indicating their purpose (e.g., bug fix, feature implementation, refactoring). Include specific metrics or identifiers where applicable, such as issue numbers or pull request links, to provide context and traceability for each change. This will help maintain a clear history of changes and their impacts on the project.
16
+ 4. Proceed to commit all changes; based on your review of the git_diff and readme, group these changes into logical, atomic commits, each accompanied by a clear and descriptive message adhering to Conventional Commits standards (e.g. "docs(readme): updated readme to include xyz."). Note the 'git_commit' tool allows you to also stage the files while commiting. Ensure commit messages accurately convey the scope and impact of the changes, incorporating specific metrics or identifiers where applicable.
17
+ Note: Be sure to set 'git_set_working_dir' if not already set.
18
+ `;
19
+ /**
20
+ * Core logic for the git_wrapup_instructions tool.
21
+ * This tool simply returns a predefined set of instructions, potentially augmented.
22
+ *
23
+ * @param {GitWrapupInstructionsInput} input - The validated input, may contain 'updateAgentMetaFiles'.
24
+ * @param {RequestContext} _context - The request context (included for consistency, not used in this simple logic).
25
+ * @returns {Promise<GitWrapupInstructionsResult>} A promise that resolves with the wrap-up instructions.
26
+ */
27
+ export async function getWrapupInstructions(input, _context // Included for structural consistency, not used by this simple tool
28
+ ) {
29
+ let finalInstructions = WRAPUP_INSTRUCTIONS;
30
+ if (input.updateAgentMetaFiles && ['Y', 'y', 'Yes', 'yes'].includes(input.updateAgentMetaFiles)) {
31
+ finalInstructions += ` Extra request: review and update if needed the .clinerules and claude.md files if present.`;
32
+ }
33
+ return {
34
+ instructions: finalInstructions,
35
+ };
36
+ }
@@ -0,0 +1,42 @@
1
+ import { BaseErrorCode } from '../../../types-global/errors.js';
2
+ import { ErrorHandler, logger, requestContextService } from '../../../utils/index.js';
3
+ import { getWrapupInstructions, GitWrapupInstructionsInputSchema, } from './logic.js';
4
+ const TOOL_NAME = 'git_wrapup_instructions';
5
+ const TOOL_DESCRIPTION = 'Provides a standard Git wrap-up workflow. This involves reviewing changes with `git_diff`, updating documentation (README, CHANGELOG), and making logical, descriptive commits using the `git_commit` tool.';
6
+ /**
7
+ * Registers the git_wrapup_instructions tool with the MCP server.
8
+ *
9
+ * @param {McpServer} server - The McpServer instance to register the tool with.
10
+ * @returns {Promise<void>}
11
+ * @throws {Error} If registration fails.
12
+ */
13
+ export const registerGitWrapupInstructionsTool = async (server) => {
14
+ const operation = 'registerGitWrapupInstructionsTool';
15
+ const context = requestContextService.createRequestContext({ operation });
16
+ await ErrorHandler.tryCatch(async () => {
17
+ server.tool(TOOL_NAME, TOOL_DESCRIPTION, GitWrapupInstructionsInputSchema.shape, // Empty schema shape
18
+ async (validatedArgs, callContext) => {
19
+ const toolOperation = 'tool:git_wrapup_instructions';
20
+ // Pass callContext as parentContext for consistent context chaining
21
+ const requestContext = requestContextService.createRequestContext({ operation: toolOperation, parentContext: callContext });
22
+ logger.info(`Executing tool: ${TOOL_NAME}`, requestContext);
23
+ return await ErrorHandler.tryCatch(async () => {
24
+ const result = await getWrapupInstructions(validatedArgs, requestContext // Pass the created requestContext
25
+ );
26
+ const resultContent = {
27
+ type: 'text',
28
+ text: JSON.stringify(result, null, 2),
29
+ contentType: 'application/json',
30
+ };
31
+ logger.info(`Tool ${TOOL_NAME} executed successfully, returning JSON`, requestContext);
32
+ return { content: [resultContent] };
33
+ }, {
34
+ operation: toolOperation,
35
+ context: requestContext,
36
+ input: validatedArgs,
37
+ errorCode: BaseErrorCode.INTERNAL_ERROR,
38
+ });
39
+ });
40
+ logger.info(`Tool registered: ${TOOL_NAME}`, context);
41
+ }, { operation, context, critical: true });
42
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/git-mcp-server",
3
- "version": "2.0.12",
3
+ "version": "2.0.15",
4
4
  "description": "An MCP (Model Context Protocol) server enabling LLMs and AI agents to interact with Git repositories. Provides tools for comprehensive Git operations including clone, commit, branch, diff, log, status, push, pull, merge, rebase, worktree, tag management, and more, via the MCP standard. STDIO & HTTP.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -38,9 +38,9 @@
38
38
  },
39
39
  "dependencies": {
40
40
  "@modelcontextprotocol/inspector": "^0.13.0",
41
- "@modelcontextprotocol/sdk": "^1.12.0",
41
+ "@modelcontextprotocol/sdk": "^1.12.1",
42
42
  "@types/jsonwebtoken": "^9.0.9",
43
- "@types/node": "^22.15.21",
43
+ "@types/node": "^22.15.27",
44
44
  "@types/sanitize-html": "^2.16.0",
45
45
  "@types/validator": "^13.15.1",
46
46
  "chrono-node": "2.8.0",
@@ -48,17 +48,17 @@
48
48
  "express": "^5.1.0",
49
49
  "ignore": "^7.0.4",
50
50
  "jsonwebtoken": "^9.0.2",
51
- "openai": "^4.103.0",
51
+ "openai": "^5.0.1",
52
52
  "partial-json": "^0.1.7",
53
53
  "sanitize-html": "^2.17.0",
54
54
  "tiktoken": "^1.0.21",
55
55
  "ts-node": "^10.9.2",
56
56
  "typescript": "^5.8.3",
57
- "validator": "^13.15.0",
57
+ "validator": "^13.15.15",
58
58
  "winston": "^3.17.0",
59
59
  "winston-daily-rotate-file": "^5.0.0",
60
- "yargs": "^17.7.2",
61
- "zod": "^3.25.28"
60
+ "yargs": "^18.0.0",
61
+ "zod": "^3.25.42"
62
62
  },
63
63
  "keywords": [
64
64
  "typescript",