@cyanheads/git-mcp-server 2.0.6 → 2.0.8

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
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![TypeScript](https://img.shields.io/badge/TypeScript-^5.8.3-blue.svg)](https://www.typescriptlang.org/)
4
4
  [![Model Context Protocol](https://img.shields.io/badge/MCP%20SDK-^1.11.0-green.svg)](https://modelcontextprotocol.io/)
5
- [![Version](https://img.shields.io/badge/Version-2.0.6-blue.svg)](./CHANGELOG.md)
5
+ [![Version](https://img.shields.io/badge/Version-2.0.8-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)
@@ -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. | `3000` |
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`, otherwise it defaults to the server's CWD._
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
 
@@ -44,7 +44,7 @@ export async function addGitFiles(input, context // Add getter to context
44
44
  logger.debug(`Using session working directory: ${targetPath}`, { ...context, operation, sessionId: context.sessionId });
45
45
  }
46
46
  // Sanitize the resolved path
47
- const sanitizedPath = sanitization.sanitizePath(targetPath);
47
+ const sanitizedPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
48
48
  logger.debug('Sanitized repository path', { ...context, operation, sanitizedPath });
49
49
  targetPath = sanitizedPath; // Use the sanitized path going forward
50
50
  }
@@ -56,7 +56,7 @@ export async function gitBranchLogic(input, context) {
56
56
  else {
57
57
  logger.debug(`Using provided path: ${targetPath}`, { ...context, operation });
58
58
  }
59
- targetPath = sanitization.sanitizePath(targetPath);
59
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
60
60
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
61
61
  }
62
62
  catch (error) {
@@ -41,7 +41,7 @@ export async function checkoutGit(input, context) {
41
41
  }
42
42
  targetPath = workingDir;
43
43
  }
44
- targetPath = sanitization.sanitizePath(targetPath);
44
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
45
45
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
46
46
  }
47
47
  catch (error) {
@@ -47,7 +47,7 @@ export async function gitCherryPickLogic(input, context) {
47
47
  else {
48
48
  logger.debug(`Using provided path: ${targetPath}`, { ...context, operation });
49
49
  }
50
- targetPath = sanitization.sanitizePath(targetPath);
50
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
51
51
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
52
52
  }
53
53
  catch (error) {
@@ -55,7 +55,7 @@ export async function gitCleanLogic(input, context) {
55
55
  else {
56
56
  logger.debug(`Using provided path: ${targetPath}`, { ...context, operation });
57
57
  }
58
- targetPath = sanitization.sanitizePath(targetPath);
58
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
59
59
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
60
60
  }
61
61
  catch (error) {
@@ -33,7 +33,7 @@ export async function gitCloneLogic(input, context) {
33
33
  let sanitizedRepoUrl;
34
34
  try {
35
35
  // Sanitize the target path (must be absolute)
36
- sanitizedTargetPath = sanitization.sanitizePath(input.targetPath);
36
+ sanitizedTargetPath = sanitization.sanitizePath(input.targetPath, { allowAbsolute: true });
37
37
  logger.debug('Sanitized target path', { ...context, operation, sanitizedTargetPath });
38
38
  // Basic sanitization/validation for URL (Zod already checks format)
39
39
  // Further sanitization might be needed depending on how it's used in the shell command
@@ -50,7 +50,7 @@ export async function commitGitChanges(input, context // Add getter to context
50
50
  logger.debug(`Using session working directory: ${targetPath}`, { ...context, operation, sessionId: context.sessionId });
51
51
  }
52
52
  // Sanitize the resolved path
53
- const sanitizedPath = sanitization.sanitizePath(targetPath);
53
+ const sanitizedPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
54
54
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath });
55
55
  targetPath = sanitizedPath; // Use the sanitized path going forward
56
56
  }
@@ -48,7 +48,7 @@ export async function diffGitChanges(input, context) {
48
48
  }
49
49
  targetPath = workingDir;
50
50
  }
51
- targetPath = sanitization.sanitizePath(targetPath);
51
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
52
52
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
53
53
  }
54
54
  catch (error) {
@@ -41,7 +41,7 @@ export async function fetchGitRemote(input, context) {
41
41
  }
42
42
  targetPath = workingDir;
43
43
  }
44
- targetPath = sanitization.sanitizePath(targetPath);
44
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
45
45
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
46
46
  }
47
47
  catch (error) {
@@ -31,7 +31,7 @@ export async function gitInitLogic(input, context) {
31
31
  let targetPath;
32
32
  try {
33
33
  // Sanitize the provided absolute path
34
- targetPath = sanitization.sanitizePath(input.path);
34
+ targetPath = sanitization.sanitizePath(input.path, { allowAbsolute: true });
35
35
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
36
36
  // Ensure the target directory exists before trying to init inside it
37
37
  // git init creates the directory if it doesn't exist, but we might want to ensure the parent exists
@@ -55,11 +55,11 @@ export const registerGitInitTool = async (server) => {
55
55
  let resolvedPath;
56
56
  try {
57
57
  if (path.isAbsolute(inputPath)) {
58
- resolvedPath = sanitization.sanitizePath(inputPath);
58
+ resolvedPath = sanitization.sanitizePath(inputPath, { allowAbsolute: true });
59
59
  logger.debug(`Using absolute path: ${resolvedPath}`, requestContext);
60
60
  }
61
61
  else if (sessionWorkingDirectory) {
62
- resolvedPath = sanitization.sanitizePath(path.resolve(sessionWorkingDirectory, inputPath));
62
+ resolvedPath = sanitization.sanitizePath(path.resolve(sessionWorkingDirectory, inputPath), { allowAbsolute: true });
63
63
  logger.debug(`Resolved relative path '${inputPath}' to absolute path: ${resolvedPath} using session CWD`, requestContext);
64
64
  }
65
65
  else {
@@ -56,7 +56,7 @@ export async function logGitHistory(input, context) {
56
56
  }
57
57
  targetPath = workingDir;
58
58
  }
59
- targetPath = sanitization.sanitizePath(targetPath);
59
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
60
60
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
61
61
  }
62
62
  catch (error) {
@@ -44,7 +44,7 @@ export async function pullGitChanges(input, context) {
44
44
  logger.debug(`Using session working directory: ${targetPath}`, { ...context, operation, sessionId: context.sessionId });
45
45
  }
46
46
  // Sanitize the resolved path
47
- targetPath = sanitization.sanitizePath(targetPath);
47
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
48
48
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
49
49
  }
50
50
  catch (error) {
@@ -45,7 +45,7 @@ export async function pushGitChanges(input, context) {
45
45
  }
46
46
  targetPath = workingDir;
47
47
  }
48
- targetPath = sanitization.sanitizePath(targetPath);
48
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
49
49
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
50
50
  }
51
51
  catch (error) {
@@ -55,7 +55,7 @@ export async function gitRebaseLogic(input, context) {
55
55
  else {
56
56
  logger.debug(`Using provided path: ${targetPath}`, { ...context, operation });
57
57
  }
58
- targetPath = sanitization.sanitizePath(targetPath);
58
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
59
59
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
60
60
  }
61
61
  catch (error) {
@@ -42,7 +42,7 @@ export async function gitRemoteLogic(input, context) {
42
42
  else {
43
43
  logger.debug(`Using provided path: ${targetPath}`, { ...context, operation });
44
44
  }
45
- targetPath = sanitization.sanitizePath(targetPath); // Sanitize the final resolved path
45
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true }); // Sanitize the final resolved path
46
46
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
47
47
  }
48
48
  catch (error) {
@@ -44,7 +44,7 @@ export async function resetGitState(input, context) {
44
44
  }
45
45
  targetPath = workingDir;
46
46
  }
47
- targetPath = sanitization.sanitizePath(targetPath);
47
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
48
48
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
49
49
  }
50
50
  catch (error) {
@@ -26,9 +26,9 @@ export async function gitSetWorkingDirLogic(input, context // Assuming context p
26
26
  logger.info('Executing git_set_working_dir logic', { ...context, operation, inputPath: input.path });
27
27
  let sanitizedPath;
28
28
  try {
29
- // Sanitize the path. By default, sanitizePath allows absolute paths.
29
+ // Sanitize the path. Must explicitly allow absolute paths for this tool.
30
30
  // It normalizes and checks for traversal issues.
31
- sanitizedPath = sanitization.sanitizePath(input.path);
31
+ sanitizedPath = sanitization.sanitizePath(input.path, { allowAbsolute: true });
32
32
  logger.debug(`Sanitized path: ${sanitizedPath}`, { ...context, operation });
33
33
  }
34
34
  catch (error) {
@@ -41,7 +41,7 @@ export async function gitShowLogic(input, context) {
41
41
  else {
42
42
  logger.debug(`Using provided path: ${targetPath}`, { ...context, operation });
43
43
  }
44
- targetPath = sanitization.sanitizePath(targetPath);
44
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
45
45
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
46
46
  }
47
47
  catch (error) {
@@ -47,7 +47,7 @@ export async function gitStashLogic(input, context) {
47
47
  else {
48
48
  logger.debug(`Using provided path: ${targetPath}`, { ...context, operation });
49
49
  }
50
- targetPath = sanitization.sanitizePath(targetPath);
50
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
51
51
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
52
52
  }
53
53
  catch (error) {
@@ -160,7 +160,7 @@ export async function getGitStatus(input, context // Add getter to context
160
160
  logger.debug(`Using session working directory: ${targetPath}`, { ...context, operation, sessionId: context.sessionId });
161
161
  }
162
162
  // Sanitize the resolved path
163
- const sanitizedPath = sanitization.sanitizePath(targetPath);
163
+ const sanitizedPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
164
164
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath });
165
165
  targetPath = sanitizedPath; // Use the sanitized path going forward
166
166
  }
@@ -55,7 +55,7 @@ export async function gitTagLogic(input, context) {
55
55
  else {
56
56
  logger.debug(`Using provided path: ${targetPath}`, { ...context, operation });
57
57
  }
58
- targetPath = sanitization.sanitizePath(targetPath);
58
+ targetPath = sanitization.sanitizePath(targetPath, { allowAbsolute: true });
59
59
  logger.debug('Sanitized path', { ...context, operation, sanitizedPath: targetPath });
60
60
  }
61
61
  catch (error) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cyanheads/git-mcp-server",
3
- "version": "2.0.6",
3
+ "version": "2.0.8",
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,7 +38,7 @@
38
38
  "dependencies": {
39
39
  "@modelcontextprotocol/sdk": "^1.11.0",
40
40
  "@types/jsonwebtoken": "^9.0.9",
41
- "@types/node": "^22.15.9",
41
+ "@types/node": "^22.15.15",
42
42
  "@types/sanitize-html": "^2.16.0",
43
43
  "@types/validator": "^13.15.0",
44
44
  "chrono-node": "^2.8.0",