@cyanheads/git-mcp-server 2.0.4 → 2.0.7
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 +7 -4
- package/dist/mcp-server/server.js +22 -22
- package/dist/mcp-server/tools/gitCommit/logic.js +38 -2
- package/dist/mcp-server/tools/gitCommit/registration.js +3 -6
- package/dist/mcp-server/tools/gitLog/logic.js +3 -2
- package/dist/mcp-server/tools/gitLog/registration.js +1 -1
- package/package.json +3 -3
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)
|
|
@@ -16,7 +16,9 @@ Built on the [`cyanheads/mcp-ts-template`](https://github.com/cyanheads/mcp-ts-t
|
|
|
16
16
|
## Table of Contents
|
|
17
17
|
|
|
18
18
|
| [Overview](#overview) | [Features](#features) | [Installation](#installation) |
|
|
19
|
+
|
|
19
20
|
| [Configuration](#configuration) | [Project Structure](#project-structure) |
|
|
21
|
+
|
|
20
22
|
| [Tools](#tools) | [Resources](#resources) | [Development](#development) | [License](#license) |
|
|
21
23
|
|
|
22
24
|
## Overview
|
|
@@ -68,7 +70,7 @@ Leverages the robust utilities provided by the `mcp-ts-template`:
|
|
|
68
70
|
|
|
69
71
|
1. Install the package globally:
|
|
70
72
|
```bash
|
|
71
|
-
npm install git-mcp-server
|
|
73
|
+
npm install @cyanheads/git-mcp-server
|
|
72
74
|
```
|
|
73
75
|
|
|
74
76
|
### Install from Source
|
|
@@ -97,11 +99,12 @@ Configure the server using environment variables. Create a `.env` file in the pr
|
|
|
97
99
|
| Variable | Description | Default |
|
|
98
100
|
| --------------------- | ------------------------------------------------------------------------------------------------------------------------------------- | ----------- |
|
|
99
101
|
| `MCP_TRANSPORT_TYPE` | Transport mechanism: `stdio` or `http`. | `stdio` |
|
|
100
|
-
| `MCP_HTTP_PORT` | Port for the HTTP server (if `MCP_TRANSPORT_TYPE=http`). Retries next ports if busy. | `
|
|
102
|
+
| `MCP_HTTP_PORT` | Port for the HTTP server (if `MCP_TRANSPORT_TYPE=http`). Retries next ports if busy. | `3010` |
|
|
101
103
|
| `MCP_HTTP_HOST` | Host address for the HTTP server (if `MCP_TRANSPORT_TYPE=http`). | `127.0.0.1` |
|
|
102
104
|
| `MCP_ALLOWED_ORIGINS` | Comma-separated list of allowed origins for CORS (if `MCP_TRANSPORT_TYPE=http`). | (none) |
|
|
103
105
|
| `MCP_LOG_LEVEL` | Logging level (`debug`, `info`, `notice`, `warning`, `error`, `crit`, `alert`, `emerg`). Inherited from template. | `info` |
|
|
104
106
|
| `GIT_SIGN_COMMITS` | Set to `"true"` to enable signing attempts for commits made by the `git_commit` tool. Requires server-side Git/key setup (see below). | `false` |
|
|
107
|
+
| `MCP_AUTH_SECRET_KEY` | Secret key for signing/verifying authentication tokens (required if auth is enabled in the future). | `''` |
|
|
105
108
|
|
|
106
109
|
### MCP Client Settings
|
|
107
110
|
|
|
@@ -176,7 +179,7 @@ The Git MCP Server provides a suite of tools for interacting with Git repositori
|
|
|
176
179
|
| `git_status` | Gets repository status (branch, staged, modified, untracked files). | `path?` |
|
|
177
180
|
| `git_tag` | Manages tags (list, create annotated/lightweight, delete). | `path?`, `mode`, `tagName?`, `message?`, `commitRef?`, `annotate?` |
|
|
178
181
|
|
|
179
|
-
_Note: The `path` parameter for most tools defaults to the session's working directory if set via `git_set_working_dir
|
|
182
|
+
_Note: The `path` parameter for most tools defaults to the session's working directory if set via `git_set_working_dir`._
|
|
180
183
|
|
|
181
184
|
## Resources
|
|
182
185
|
|
|
@@ -17,30 +17,30 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
17
17
|
import { config, environment } from '../config/index.js';
|
|
18
18
|
// Import core utilities: ErrorHandler, logger, requestContextService.
|
|
19
19
|
import { ErrorHandler, logger, requestContextService } from '../utils/index.js'; // Added RequestContext
|
|
20
|
-
// Import registration AND state initialization functions for ALL Git tools (
|
|
20
|
+
// Import registration AND state initialization functions for ALL Git tools (alphabetized)
|
|
21
21
|
import { registerGitAddTool, initializeGitAddStateAccessors } from './tools/gitAdd/index.js';
|
|
22
22
|
import { registerGitBranchTool, initializeGitBranchStateAccessors } from './tools/gitBranch/index.js';
|
|
23
|
-
import { registerGitCheckoutTool, initializeGitCheckoutStateAccessors } from './tools/gitCheckout/index.js';
|
|
24
|
-
import { registerGitCherryPickTool, initializeGitCherryPickStateAccessors } from './tools/gitCherryPick/index.js';
|
|
25
|
-
import { registerGitCleanTool, initializeGitCleanStateAccessors } from './tools/gitClean/index.js';
|
|
26
|
-
import { registerGitClearWorkingDirTool, initializeGitClearWorkingDirStateAccessors } from './tools/gitClearWorkingDir/index.js';
|
|
27
|
-
import { registerGitCloneTool } from './tools/gitClone/index.js'; //
|
|
28
|
-
import { registerGitCommitTool, initializeGitCommitStateAccessors } from './tools/gitCommit/index.js';
|
|
29
|
-
import { registerGitDiffTool, initializeGitDiffStateAccessors } from './tools/gitDiff/index.js';
|
|
23
|
+
import { registerGitCheckoutTool, initializeGitCheckoutStateAccessors } from './tools/gitCheckout/index.js';
|
|
24
|
+
import { registerGitCherryPickTool, initializeGitCherryPickStateAccessors } from './tools/gitCherryPick/index.js';
|
|
25
|
+
import { registerGitCleanTool, initializeGitCleanStateAccessors } from './tools/gitClean/index.js';
|
|
26
|
+
import { registerGitClearWorkingDirTool, initializeGitClearWorkingDirStateAccessors } from './tools/gitClearWorkingDir/index.js';
|
|
27
|
+
import { registerGitCloneTool } from './tools/gitClone/index.js'; // No initializer needed/available
|
|
28
|
+
import { registerGitCommitTool, initializeGitCommitStateAccessors } from './tools/gitCommit/index.js';
|
|
29
|
+
import { registerGitDiffTool, initializeGitDiffStateAccessors } from './tools/gitDiff/index.js';
|
|
30
30
|
import { registerGitFetchTool, initializeGitFetchStateAccessors } from './tools/gitFetch/index.js';
|
|
31
|
-
import { registerGitInitTool, initializeGitInitStateAccessors } from './tools/gitInit/index.js';
|
|
32
|
-
import { registerGitLogTool, initializeGitLogStateAccessors } from './tools/gitLog/index.js';
|
|
33
|
-
import { registerGitMergeTool, initializeGitMergeStateAccessors } from './tools/gitMerge/index.js';
|
|
31
|
+
import { registerGitInitTool, initializeGitInitStateAccessors } from './tools/gitInit/index.js';
|
|
32
|
+
import { registerGitLogTool, initializeGitLogStateAccessors } from './tools/gitLog/index.js';
|
|
33
|
+
import { registerGitMergeTool, initializeGitMergeStateAccessors } from './tools/gitMerge/index.js';
|
|
34
34
|
import { registerGitPullTool, initializeGitPullStateAccessors } from './tools/gitPull/index.js';
|
|
35
35
|
import { registerGitPushTool, initializeGitPushStateAccessors } from './tools/gitPush/index.js';
|
|
36
|
-
import { registerGitRebaseTool, initializeGitRebaseStateAccessors } from './tools/gitRebase/index.js';
|
|
37
|
-
import { registerGitRemoteTool, initializeGitRemoteStateAccessors } from './tools/gitRemote/index.js';
|
|
38
|
-
import { registerGitResetTool, initializeGitResetStateAccessors } from './tools/gitReset/index.js';
|
|
36
|
+
import { registerGitRebaseTool, initializeGitRebaseStateAccessors } from './tools/gitRebase/index.js';
|
|
37
|
+
import { registerGitRemoteTool, initializeGitRemoteStateAccessors } from './tools/gitRemote/index.js';
|
|
38
|
+
import { registerGitResetTool, initializeGitResetStateAccessors } from './tools/gitReset/index.js';
|
|
39
39
|
import { registerGitSetWorkingDirTool, initializeGitSetWorkingDirStateAccessors } from './tools/gitSetWorkingDir/index.js';
|
|
40
|
-
import { registerGitShowTool, initializeGitShowStateAccessors } from './tools/gitShow/index.js';
|
|
41
|
-
import { registerGitStashTool, initializeGitStashStateAccessors } from './tools/gitStash/index.js';
|
|
42
|
-
import { registerGitStatusTool, initializeGitStatusStateAccessors } from './tools/gitStatus/index.js';
|
|
43
|
-
import { registerGitTagTool, initializeGitTagStateAccessors } from './tools/gitTag/index.js';
|
|
40
|
+
import { registerGitShowTool, initializeGitShowStateAccessors } from './tools/gitShow/index.js';
|
|
41
|
+
import { registerGitStashTool, initializeGitStashStateAccessors } from './tools/gitStash/index.js';
|
|
42
|
+
import { registerGitStatusTool, initializeGitStatusStateAccessors } from './tools/gitStatus/index.js';
|
|
43
|
+
import { registerGitTagTool, initializeGitTagStateAccessors } from './tools/gitTag/index.js';
|
|
44
44
|
// Import transport setup functions AND state accessors
|
|
45
45
|
import { startHttpTransport, getHttpSessionWorkingDirectory, setHttpSessionWorkingDirectory } from './transports/httpTransport.js';
|
|
46
46
|
import { connectStdioTransport, getStdioWorkingDirectory, setStdioWorkingDirectory } from './transports/stdioTransport.js';
|
|
@@ -132,7 +132,7 @@ async function createMcpServerInstance() {
|
|
|
132
132
|
// Pass the defined unified accessor functions to the initializers.
|
|
133
133
|
logger.debug('Initializing state accessors for tools...', context);
|
|
134
134
|
try {
|
|
135
|
-
// Call initializers for all tools that likely need state access.
|
|
135
|
+
// Call initializers for all tools that likely need state access (alphabetized).
|
|
136
136
|
// If an initializer doesn't exist, the import would have failed earlier (or build will fail).
|
|
137
137
|
initializeGitAddStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
138
138
|
initializeGitBranchStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
@@ -140,11 +140,11 @@ async function createMcpServerInstance() {
|
|
|
140
140
|
initializeGitCherryPickStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
141
141
|
initializeGitCleanStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
142
142
|
initializeGitClearWorkingDirStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
143
|
-
// initializeGitCloneStateAccessors
|
|
143
|
+
// initializeGitCloneStateAccessors - No initializer needed/available
|
|
144
144
|
initializeGitCommitStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
145
145
|
initializeGitDiffStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
146
146
|
initializeGitFetchStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
147
|
-
initializeGitInitStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
147
|
+
initializeGitInitStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
148
148
|
initializeGitLogStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
149
149
|
initializeGitMergeStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
150
150
|
initializeGitPullStateAccessors(getWorkingDirectory, getSessionIdFromContext);
|
|
@@ -169,7 +169,7 @@ async function createMcpServerInstance() {
|
|
|
169
169
|
throw initError; // Re-throw to prevent server starting incorrectly
|
|
170
170
|
}
|
|
171
171
|
try {
|
|
172
|
-
// Register all defined Git tools. These calls populate the server's
|
|
172
|
+
// Register all defined Git tools (alphabetized). These calls populate the server's
|
|
173
173
|
// internal registry, making them available via MCP methods like 'tools/list'.
|
|
174
174
|
logger.debug('Registering Git tools...', context);
|
|
175
175
|
await registerGitAddTool(server);
|
|
@@ -20,6 +20,7 @@ export const GitCommitInputSchema = z.object({
|
|
|
20
20
|
allowEmpty: z.boolean().default(false).describe('Allow creating empty commits'),
|
|
21
21
|
amend: z.boolean().default(false).describe('Amend the previous commit instead of creating a new one'),
|
|
22
22
|
forceUnsignedOnFailure: z.boolean().default(false).describe('If true and signing is enabled but fails, attempt the commit without signing instead of failing.'),
|
|
23
|
+
filesToStage: z.array(z.string().min(1)).optional().describe('Optional array of specific file paths (relative to the repository root) to stage automatically before committing. If provided, only these files will be staged.'),
|
|
23
24
|
});
|
|
24
25
|
/**
|
|
25
26
|
* Executes the 'git commit' command and returns structured JSON output.
|
|
@@ -61,6 +62,24 @@ export async function commitGitChanges(input, context // Add getter to context
|
|
|
61
62
|
throw new McpError(BaseErrorCode.VALIDATION_ERROR, `Invalid path: ${error instanceof Error ? error.message : String(error)}`, { context, operation, originalError: error });
|
|
62
63
|
}
|
|
63
64
|
try {
|
|
65
|
+
// --- Stage specific files if requested ---
|
|
66
|
+
if (input.filesToStage && input.filesToStage.length > 0) {
|
|
67
|
+
logger.debug(`Attempting to stage specific files: ${input.filesToStage.join(', ')}`, { ...context, operation });
|
|
68
|
+
try {
|
|
69
|
+
// Correctly pass targetPath as rootDir in options object
|
|
70
|
+
const sanitizedFiles = input.filesToStage.map(file => sanitization.sanitizePath(file, { rootDir: targetPath })); // Sanitize relative to repo root
|
|
71
|
+
const filesToAddString = sanitizedFiles.map(file => `"${file}"`).join(' '); // Quote paths for safety
|
|
72
|
+
const addCommand = `git -C "${targetPath}" add -- ${filesToAddString}`;
|
|
73
|
+
logger.debug(`Executing git add command: ${addCommand}`, { ...context, operation });
|
|
74
|
+
await execAsync(addCommand);
|
|
75
|
+
logger.info(`Successfully staged specified files: ${sanitizedFiles.join(', ')}`, { ...context, operation });
|
|
76
|
+
}
|
|
77
|
+
catch (addError) {
|
|
78
|
+
logger.error('Failed to stage specified files', { ...context, operation, files: input.filesToStage, error: addError.message, stderr: addError.stderr });
|
|
79
|
+
throw new McpError(BaseErrorCode.INTERNAL_ERROR, `Failed to stage files before commit: ${addError.stderr || addError.message}`, { context, operation, originalError: addError });
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// --- End staging files ---
|
|
64
83
|
// Escape message for shell safety
|
|
65
84
|
const escapeShellArg = (arg) => {
|
|
66
85
|
// Escape backslashes first, then other special chars
|
|
@@ -177,11 +196,28 @@ export async function commitGitChanges(input, context // Add getter to context
|
|
|
177
196
|
const finalStatusMsg = commitResult?.statusMessage || (commitHash
|
|
178
197
|
? `Commit successful: ${commitHash}`
|
|
179
198
|
: `Commit successful (stdout: ${stdout.trim()})`);
|
|
180
|
-
|
|
199
|
+
let committedFiles = [];
|
|
200
|
+
if (commitHash) {
|
|
201
|
+
try {
|
|
202
|
+
// Get the list of files included in this specific commit
|
|
203
|
+
const showCommand = `git -C "${targetPath}" show --pretty="" --name-only ${commitHash}`;
|
|
204
|
+
logger.debug(`Executing git show command: ${showCommand}`, { ...context, operation });
|
|
205
|
+
const { stdout: showStdout } = await execAsync(showCommand);
|
|
206
|
+
committedFiles = showStdout.trim().split('\n').filter(Boolean); // Split by newline, remove empty lines
|
|
207
|
+
logger.debug(`Retrieved committed files list for ${commitHash}`, { ...context, operation, count: committedFiles.length });
|
|
208
|
+
}
|
|
209
|
+
catch (showError) {
|
|
210
|
+
// Log a warning but don't fail the overall operation if we can't get the file list
|
|
211
|
+
logger.warning('Failed to retrieve committed files list', { ...context, operation, commitHash, error: showError.message, stderr: showError.stderr });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
logger.info(`${operation} executed successfully`, { ...context, operation, path: targetPath, commitHash, signed: !commitResult, committedFilesCount: committedFiles.length }); // Log if it was signed (not fallback)
|
|
181
215
|
return {
|
|
182
216
|
success: true,
|
|
183
217
|
statusMessage: finalStatusMsg, // Use potentially modified message
|
|
184
|
-
commitHash: commitHash
|
|
218
|
+
commitHash: commitHash,
|
|
219
|
+
commitMessage: input.message, // Include the original commit message
|
|
220
|
+
committedFiles: committedFiles // Include the list of files
|
|
185
221
|
};
|
|
186
222
|
}
|
|
187
223
|
catch (error) { // This catch block now primarily handles non-signing errors or errors from the fallback attempt
|
|
@@ -41,12 +41,9 @@ feat(auth): implement password reset endpoint
|
|
|
41
41
|
Closes #123 (if applicable).
|
|
42
42
|
\`\`\`
|
|
43
43
|
|
|
44
|
-
**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
**Commit Signing:** If the server is configured with the \`GIT_SIGN_COMMITS=true\` environment variable, this tool adds the \`-S\` flag to the \`git commit\` command, requesting a signature. Signing requires proper GPG or SSH key setup and Git configuration on the server machine.
|
|
49
|
-
- **Fallback:** If signing is enabled but fails (e.g., key not found, agent issue), the commit will **fail by default**. However, if the optional \`forceUnsignedOnFailure: true\` parameter is provided in the tool call, the tool will attempt the commit again *without* the \`-S\` flag, resulting in an unsigned commit.`;
|
|
44
|
+
**Tool Options & Behavior:**
|
|
45
|
+
- Commit related changes logically. Use the optional \`filesToStage\` parameter to auto-stage specific files before committing.
|
|
46
|
+
- The \`path\` defaults to the session's working directory unless overridden. If \`GIT_SIGN_COMMITS=true\` is set, commits are signed (\`-S\`), with an optional \`forceUnsignedOnFailure\` fallback.`;
|
|
50
47
|
/**
|
|
51
48
|
* Registers the git_commit tool with the MCP server.
|
|
52
49
|
* Uses the high-level server.tool() method for registration, schema validation, and routing.
|
|
@@ -120,11 +120,12 @@ export async function logGitHistory(input, context) {
|
|
|
120
120
|
logger.warning(`Git log stderr: ${stderr.trim()}`, { ...context, operation });
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
-
// If raw output was requested, return it directly
|
|
123
|
+
// If raw output was requested, return it directly in the message field, omitting commits
|
|
124
124
|
if (isRawOutput) {
|
|
125
125
|
const message = `Raw log output (showSignature=true):\n${stdout}`;
|
|
126
126
|
logger.info(`${operation} completed successfully (raw output).`, { ...context, operation, path: targetPath });
|
|
127
|
-
|
|
127
|
+
// Return without the 'commits' field
|
|
128
|
+
return { success: true, message: message };
|
|
128
129
|
}
|
|
129
130
|
// Otherwise, parse the structured output
|
|
130
131
|
const commits = [];
|
|
@@ -20,7 +20,7 @@ export function initializeGitLogStateAccessors(getWdFn, getSidFn) {
|
|
|
20
20
|
logger.info('State accessors initialized for git_log tool registration.');
|
|
21
21
|
}
|
|
22
22
|
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 list of commit objects by default.
|
|
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.";
|
|
24
24
|
/**
|
|
25
25
|
* Registers the git_log tool with the MCP server.
|
|
26
26
|
*
|
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.7",
|
|
4
4
|
"description": "An MCP (Model Context Protocol) server providing tools to interact with Git repositories. Enables LLMs and AI agents to perform Git operations like clone, commit, push, pull, branch, diff, log, status, and more via the MCP standard.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -38,8 +38,8 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@modelcontextprotocol/sdk": "^1.11.0",
|
|
40
40
|
"@types/jsonwebtoken": "^9.0.9",
|
|
41
|
-
"@types/node": "^22.15.
|
|
42
|
-
"@types/sanitize-html": "^2.
|
|
41
|
+
"@types/node": "^22.15.9",
|
|
42
|
+
"@types/sanitize-html": "^2.16.0",
|
|
43
43
|
"@types/validator": "^13.15.0",
|
|
44
44
|
"chrono-node": "^2.8.0",
|
|
45
45
|
"dotenv": "^16.5.0",
|