@cyanheads/git-mcp-server 2.0.12 → 2.0.14
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 +31 -28
- package/dist/mcp-server/server.js +3 -0
- package/dist/mcp-server/tools/gitDiff/logic.js +85 -20
- package/dist/mcp-server/tools/gitDiff/registration.js +1 -1
- package/dist/mcp-server/tools/gitWrapupInstructions/index.js +3 -0
- package/dist/mcp-server/tools/gitWrapupInstructions/logic.js +29 -0
- package/dist/mcp-server/tools/gitWrapupInstructions/registration.js +42 -0
- package/package.json +8 -8
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.typescriptlang.org/)
|
|
4
4
|
[](https://modelcontextprotocol.io/)
|
|
5
|
-
[](./CHANGELOG.md)
|
|
6
6
|
[](https://opensource.org/licenses/Apache-2.0)
|
|
7
7
|
[](https://github.com/cyanheads/git-mcp-server/issues)
|
|
8
8
|
[](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
|
|
157
|
-
|
|
|
158
|
-
| `git_add`
|
|
159
|
-
| `git_branch`
|
|
160
|
-
| `git_checkout`
|
|
161
|
-
| `git_cherry_pick`
|
|
162
|
-
| `git_clean`
|
|
163
|
-
| `git_clear_working_dir`
|
|
164
|
-
| `git_clone`
|
|
165
|
-
| `git_commit`
|
|
166
|
-
| `git_diff`
|
|
167
|
-
| `git_fetch`
|
|
168
|
-
| `git_init`
|
|
169
|
-
| `git_log`
|
|
170
|
-
| `git_merge`
|
|
171
|
-
| `git_pull`
|
|
172
|
-
| `git_push`
|
|
173
|
-
| `git_rebase`
|
|
174
|
-
| `git_remote`
|
|
175
|
-
| `git_reset`
|
|
176
|
-
| `git_set_working_dir`
|
|
177
|
-
| `git_show`
|
|
178
|
-
| `git_stash`
|
|
179
|
-
| `git_status`
|
|
180
|
-
| `git_tag`
|
|
181
|
-
| `git_worktree`
|
|
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.
|
|
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
|
|
68
|
+
// Construct the standard git diff command
|
|
69
|
+
let standardDiffCommand = `git -C "${targetPath}" diff`;
|
|
67
70
|
if (input.staged) {
|
|
68
|
-
|
|
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
|
-
|
|
76
|
+
standardDiffCommand += ` ${safeCommit1}`;
|
|
74
77
|
}
|
|
75
78
|
if (safeCommit2) {
|
|
76
|
-
|
|
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
|
-
|
|
86
|
+
standardDiffCommand += ` -- "${safeFile}"`; // Use '--' to separate paths from revisions
|
|
82
87
|
}
|
|
83
|
-
logger.debug(`Executing command: ${
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
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,29 @@
|
|
|
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 = `Initiate our standard git wrapup workflow. (1) First, review all changes to our repo using the git_diff tool to understand the precise nature and rationale behind each change (what changed and why did it change?). (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). (3) Then, update the CHANGELOG with concise, descriptive entries detailing all modifications, clearly indicating their purpose (e.g., bug fix, feature implementation, refactoring). (4) Finally, proceed to commit all changes; 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. Be sure to set 'git_set_working_dir' if not already set.`;
|
|
12
|
+
/**
|
|
13
|
+
* Core logic for the git_wrapup_instructions tool.
|
|
14
|
+
* This tool simply returns a predefined set of instructions, potentially augmented.
|
|
15
|
+
*
|
|
16
|
+
* @param {GitWrapupInstructionsInput} input - The validated input, may contain 'updateAgentMetaFiles'.
|
|
17
|
+
* @param {RequestContext} _context - The request context (included for consistency, not used in this simple logic).
|
|
18
|
+
* @returns {Promise<GitWrapupInstructionsResult>} A promise that resolves with the wrap-up instructions.
|
|
19
|
+
*/
|
|
20
|
+
export async function getWrapupInstructions(input, _context // Included for structural consistency, not used by this simple tool
|
|
21
|
+
) {
|
|
22
|
+
let finalInstructions = WRAPUP_INSTRUCTIONS;
|
|
23
|
+
if (input.updateAgentMetaFiles && ['Y', 'y', 'Yes', 'yes'].includes(input.updateAgentMetaFiles)) {
|
|
24
|
+
finalInstructions += ` Extra request: review and update if needed the .clinerules and claude.md files if present.`;
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
instructions: finalInstructions,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -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.
|
|
3
|
+
"version": "2.0.14",
|
|
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,27 +38,27 @@
|
|
|
38
38
|
},
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@modelcontextprotocol/inspector": "^0.13.0",
|
|
41
|
-
"@modelcontextprotocol/sdk": "^1.12.
|
|
41
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
42
42
|
"@types/jsonwebtoken": "^9.0.9",
|
|
43
|
-
"@types/node": "^22.15.
|
|
43
|
+
"@types/node": "^22.15.27",
|
|
44
44
|
"@types/sanitize-html": "^2.16.0",
|
|
45
45
|
"@types/validator": "^13.15.1",
|
|
46
|
-
"chrono-node": "2.8.
|
|
46
|
+
"chrono-node": "2.8.1",
|
|
47
47
|
"dotenv": "^16.5.0",
|
|
48
48
|
"express": "^5.1.0",
|
|
49
49
|
"ignore": "^7.0.4",
|
|
50
50
|
"jsonwebtoken": "^9.0.2",
|
|
51
|
-
"openai": "^
|
|
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.
|
|
57
|
+
"validator": "^13.15.15",
|
|
58
58
|
"winston": "^3.17.0",
|
|
59
59
|
"winston-daily-rotate-file": "^5.0.0",
|
|
60
|
-
"yargs": "^
|
|
61
|
-
"zod": "^3.25.
|
|
60
|
+
"yargs": "^18.0.0",
|
|
61
|
+
"zod": "^3.25.42"
|
|
62
62
|
},
|
|
63
63
|
"keywords": [
|
|
64
64
|
"typescript",
|