@rethunk/mcp-multi-root-git 2.3.3 → 2.4.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.
- package/AGENTS.md +30 -3
- package/CHANGELOG.md +61 -0
- package/HUMANS.md +106 -2
- package/README.md +15 -1
- package/dist/server/batch-commit-tool.js +244 -19
- package/dist/server/coverage.js +22 -0
- package/dist/server/git-diff-tool.js +132 -0
- package/dist/server/git-fetch-tool.js +131 -0
- package/dist/server/git-push-tool.js +8 -1
- package/dist/server/git-show-tool.js +148 -0
- package/dist/server/git-stash-tool.js +131 -0
- package/dist/server/git-tag-tool.js +162 -0
- package/dist/server/git.js +18 -2
- package/dist/server/roots.js +8 -4
- package/dist/server/test-harness.js +77 -6
- package/dist/server/tool-parameter-schemas.js +94 -0
- package/dist/server/tools.js +11 -0
- package/docs/install.md +19 -2
- package/docs/mcp-tools.md +235 -5
- package/package.json +15 -8
- package/schemas/batch_commit.json +125 -0
- package/schemas/git_cherry_pick.json +63 -0
- package/schemas/git_diff.json +62 -0
- package/schemas/git_diff_summary.json +75 -0
- package/schemas/git_fetch.json +52 -0
- package/schemas/git_inventory.json +74 -0
- package/schemas/git_log.json +75 -0
- package/schemas/git_merge.json +79 -0
- package/schemas/git_parity.json +74 -0
- package/schemas/git_push.json +50 -0
- package/schemas/git_reset_soft.json +42 -0
- package/schemas/git_show.json +39 -0
- package/schemas/git_stash_apply.json +44 -0
- package/schemas/git_stash_list.json +30 -0
- package/schemas/git_status.json +49 -0
- package/schemas/git_tag.json +50 -0
- package/schemas/git_worktree_add.json +52 -0
- package/schemas/git_worktree_list.json +30 -0
- package/schemas/git_worktree_remove.json +48 -0
- package/schemas/index.json +108 -0
- package/schemas/list_presets.json +44 -0
- package/tool-parameters.schema.json +1125 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { registerBatchCommitTool } from "./batch-commit-tool.js";
|
|
3
|
+
import { registerGitCherryPickTool } from "./git-cherry-pick-tool.js";
|
|
4
|
+
import { registerGitDiffSummaryTool } from "./git-diff-summary-tool.js";
|
|
5
|
+
import { registerGitDiffTool } from "./git-diff-tool.js";
|
|
6
|
+
import { registerGitFetchTool } from "./git-fetch-tool.js";
|
|
7
|
+
import { registerGitInventoryTool } from "./git-inventory-tool.js";
|
|
8
|
+
import { registerGitLogTool } from "./git-log-tool.js";
|
|
9
|
+
import { registerGitMergeTool } from "./git-merge-tool.js";
|
|
10
|
+
import { registerGitParityTool } from "./git-parity-tool.js";
|
|
11
|
+
import { registerGitPushTool } from "./git-push-tool.js";
|
|
12
|
+
import { registerGitResetSoftTool } from "./git-reset-soft-tool.js";
|
|
13
|
+
import { registerGitShowTool } from "./git-show-tool.js";
|
|
14
|
+
import { registerGitStashApplyTool, registerGitStashListTool } from "./git-stash-tool.js";
|
|
15
|
+
import { registerGitStatusTool } from "./git-status-tool.js";
|
|
16
|
+
import { registerGitTagTool } from "./git-tag-tool.js";
|
|
17
|
+
import { registerGitWorktreeAddTool, registerGitWorktreeListTool, registerGitWorktreeRemoveTool, } from "./git-worktree-tool.js";
|
|
18
|
+
import { registerListPresetsTool } from "./list-presets-tool.js";
|
|
19
|
+
export const READ_ONLY_ABSOLUTE_ROOT_TOOLS = [
|
|
20
|
+
"git_status",
|
|
21
|
+
"git_inventory",
|
|
22
|
+
"git_parity",
|
|
23
|
+
"list_presets",
|
|
24
|
+
"git_log",
|
|
25
|
+
"git_diff_summary",
|
|
26
|
+
];
|
|
27
|
+
export const READ_ONLY_SINGLE_REPO_TOOLS = [
|
|
28
|
+
"git_diff",
|
|
29
|
+
"git_show",
|
|
30
|
+
"git_worktree_list",
|
|
31
|
+
"git_stash_list",
|
|
32
|
+
];
|
|
33
|
+
export const MUTATING_TOOLS = [
|
|
34
|
+
"git_fetch",
|
|
35
|
+
"batch_commit",
|
|
36
|
+
"git_push",
|
|
37
|
+
"git_merge",
|
|
38
|
+
"git_cherry_pick",
|
|
39
|
+
"git_reset_soft",
|
|
40
|
+
"git_tag",
|
|
41
|
+
"git_worktree_add",
|
|
42
|
+
"git_worktree_remove",
|
|
43
|
+
"git_stash_apply",
|
|
44
|
+
];
|
|
45
|
+
export const ALL_PARAMETER_SCHEMA_TOOLS = [
|
|
46
|
+
...READ_ONLY_ABSOLUTE_ROOT_TOOLS,
|
|
47
|
+
...READ_ONLY_SINGLE_REPO_TOOLS,
|
|
48
|
+
...MUTATING_TOOLS,
|
|
49
|
+
];
|
|
50
|
+
function captureParameterTools(register) {
|
|
51
|
+
const tools = [];
|
|
52
|
+
const server = {
|
|
53
|
+
sessions: [],
|
|
54
|
+
addTool(tool) {
|
|
55
|
+
tools.push({ name: tool.name, parameters: tool.parameters });
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
register(server);
|
|
59
|
+
return tools;
|
|
60
|
+
}
|
|
61
|
+
export function captureToolParameterSchemas() {
|
|
62
|
+
const tools = captureParameterTools((server) => {
|
|
63
|
+
registerGitStatusTool(server);
|
|
64
|
+
registerGitInventoryTool(server);
|
|
65
|
+
registerGitParityTool(server);
|
|
66
|
+
registerListPresetsTool(server);
|
|
67
|
+
registerGitLogTool(server);
|
|
68
|
+
registerGitDiffSummaryTool(server);
|
|
69
|
+
registerGitDiffTool(server);
|
|
70
|
+
registerGitShowTool(server);
|
|
71
|
+
registerGitWorktreeListTool(server);
|
|
72
|
+
registerGitStashListTool(server);
|
|
73
|
+
registerGitFetchTool(server);
|
|
74
|
+
registerBatchCommitTool(server);
|
|
75
|
+
registerGitPushTool(server);
|
|
76
|
+
registerGitMergeTool(server);
|
|
77
|
+
registerGitCherryPickTool(server);
|
|
78
|
+
registerGitResetSoftTool(server);
|
|
79
|
+
registerGitTagTool(server);
|
|
80
|
+
registerGitWorktreeAddTool(server);
|
|
81
|
+
registerGitWorktreeRemoveTool(server);
|
|
82
|
+
registerGitStashApplyTool(server);
|
|
83
|
+
});
|
|
84
|
+
return Object.fromEntries(tools.map((tool) => [tool.name, z.toJSONSchema(tool.parameters)]));
|
|
85
|
+
}
|
|
86
|
+
export function buildToolParameterSchemaDocument() {
|
|
87
|
+
return {
|
|
88
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
89
|
+
title: "@rethunk/mcp-multi-root-git tool parameter schemas",
|
|
90
|
+
description: "JSON Schema snapshots generated from registered FastMCP tool parameter schemas.",
|
|
91
|
+
generatedBy: "scripts/generate-tool-parameters-schema.ts",
|
|
92
|
+
tools: captureToolParameterSchemas(),
|
|
93
|
+
};
|
|
94
|
+
}
|
package/dist/server/tools.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { registerBatchCommitTool } from "./batch-commit-tool.js";
|
|
2
2
|
import { registerGitCherryPickTool } from "./git-cherry-pick-tool.js";
|
|
3
3
|
import { registerGitDiffSummaryTool } from "./git-diff-summary-tool.js";
|
|
4
|
+
import { registerGitDiffTool } from "./git-diff-tool.js";
|
|
5
|
+
import { registerGitFetchTool } from "./git-fetch-tool.js";
|
|
4
6
|
import { registerGitInventoryTool } from "./git-inventory-tool.js";
|
|
5
7
|
import { registerGitLogTool } from "./git-log-tool.js";
|
|
6
8
|
import { registerGitMergeTool } from "./git-merge-tool.js";
|
|
7
9
|
import { registerGitParityTool } from "./git-parity-tool.js";
|
|
8
10
|
import { registerGitPushTool } from "./git-push-tool.js";
|
|
9
11
|
import { registerGitResetSoftTool } from "./git-reset-soft-tool.js";
|
|
12
|
+
import { registerGitShowTool } from "./git-show-tool.js";
|
|
13
|
+
import { registerGitStashApplyTool, registerGitStashListTool } from "./git-stash-tool.js";
|
|
10
14
|
import { registerGitStatusTool } from "./git-status-tool.js";
|
|
15
|
+
import { registerGitTagTool } from "./git-tag-tool.js";
|
|
11
16
|
import { registerGitWorktreeAddTool, registerGitWorktreeListTool, registerGitWorktreeRemoveTool, } from "./git-worktree-tool.js";
|
|
12
17
|
import { registerListPresetsTool } from "./list-presets-tool.js";
|
|
13
18
|
import { registerPresetsResource } from "./presets-resource.js";
|
|
@@ -19,15 +24,21 @@ export function registerRethunkGitTools(server) {
|
|
|
19
24
|
registerListPresetsTool(server);
|
|
20
25
|
registerGitLogTool(server);
|
|
21
26
|
registerGitDiffSummaryTool(server);
|
|
27
|
+
registerGitDiffTool(server);
|
|
28
|
+
registerGitShowTool(server);
|
|
22
29
|
registerGitWorktreeListTool(server);
|
|
30
|
+
registerGitStashListTool(server);
|
|
31
|
+
registerGitFetchTool(server);
|
|
23
32
|
// Mutating tools
|
|
24
33
|
registerBatchCommitTool(server);
|
|
25
34
|
registerGitPushTool(server);
|
|
26
35
|
registerGitMergeTool(server);
|
|
27
36
|
registerGitCherryPickTool(server);
|
|
28
37
|
registerGitResetSoftTool(server);
|
|
38
|
+
registerGitTagTool(server);
|
|
29
39
|
registerGitWorktreeAddTool(server);
|
|
30
40
|
registerGitWorktreeRemoveTool(server);
|
|
41
|
+
registerGitStashApplyTool(server);
|
|
31
42
|
// Resources
|
|
32
43
|
registerPresetsResource(server);
|
|
33
44
|
}
|
package/docs/install.md
CHANGED
|
@@ -10,6 +10,7 @@ This package is an MCP **stdio** server. The client starts the process and passe
|
|
|
10
10
|
- [GitHub Packages](#github-packages)
|
|
11
11
|
- [Ways to run the binary](#ways-to-run-the-binary)
|
|
12
12
|
- [Configuration shape (stdio)](#configuration-shape-stdio)
|
|
13
|
+
- [Environment variables](#environment-variables)
|
|
13
14
|
- [Cursor](#cursor)
|
|
14
15
|
- [Visual Studio Code (GitHub Copilot)](#visual-studio-code-github-copilot)
|
|
15
16
|
- [Claude Desktop](#claude-desktop)
|
|
@@ -86,11 +87,27 @@ Register the server under a stable name (this documentation uses **`rethunk-git`
|
|
|
86
87
|
|
|
87
88
|
Omit any `cwd` / `workingDirectory` field unless your client requires it for unrelated reasons. This server resolves repos from MCP **roots**, not from the process cwd.
|
|
88
89
|
|
|
90
|
+
## Environment variables
|
|
91
|
+
|
|
92
|
+
| Variable | Default | Purpose |
|
|
93
|
+
|----------|---------|---------|
|
|
94
|
+
| `GIT_SUBPROCESS_PARALLELISM` | `4` | Number of parallel git subprocesses for inventory operations and `git_status` submodule rows. Valid range: 1 to 2×CPU count (auto-clamped to prevent runaway spawning). Increase on high-core machines to accelerate large fleet scans; decrease if system resources are constrained. |
|
|
95
|
+
|
|
96
|
+
Set these in the environment where the MCP client launches the server (e.g. in your shell, in the MCP client config as `env`, or in a startup script).
|
|
97
|
+
|
|
98
|
+
Example: Running the server with 8 parallel git processes on a 4-core machine:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
GIT_SUBPROCESS_PARALLELISM=8 npx -y @rethunk/mcp-multi-root-git
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
On a 4-core machine (CPU count = 4), the max parallelism is clamped to 8 (2×4). The value requested is used if valid.
|
|
105
|
+
|
|
89
106
|
## Cursor
|
|
90
107
|
|
|
91
108
|
**User scope (all workspaces):** `~/.cursor/mcp.json` on macOS/Linux, or `%USERPROFILE%\.cursor\mcp.json` on Windows.
|
|
92
109
|
|
|
93
|
-
**Project scope:** `.cursor/mcp.json` in the workspace (
|
|
110
|
+
**Project scope:** `.cursor/mcp.json` in the workspace (use when a team wants repo-local MCP wiring).
|
|
94
111
|
|
|
95
112
|
Cursor uses a top-level **`mcpServers`** object:
|
|
96
113
|
|
|
@@ -185,7 +202,7 @@ For contributors working inside a clone of [mcp-multi-root-git](https://github.c
|
|
|
185
202
|
1. **Dependencies, build, and CI parity:** **[HUMANS.md](../HUMANS.md)** — *Development* (`bun install`, `bun run build`, `bun run check`). Do not duplicate that workflow here.
|
|
186
203
|
2. **Run the dev server** (no `dist/` required): from the repo root, **`bun src/server.ts`** (stdio MCP).
|
|
187
204
|
|
|
188
|
-
**MCP registration for a local checkout:** point your client at that command (or at **`dist/server.js`** via `node` after `bun run build` — see HUMANS). **Cursor:**
|
|
205
|
+
**MCP registration for a local checkout:** point your client at that command (or at **`dist/server.js`** via `node` after `bun run build` — see HUMANS). **Cursor:** add the command to either your user-scope or project-scope `mcp.json`, and open the workspace at the repository root so relative args resolve.
|
|
189
206
|
|
|
190
207
|
**Reload** the MCP connection after changing server code.
|
|
191
208
|
|
package/docs/mcp-tools.md
CHANGED
|
@@ -13,18 +13,24 @@ MCP clients expose tools as `{serverName}_{toolName}`. With the server registere
|
|
|
13
13
|
|----------|-----------------------------------|---------|
|
|
14
14
|
| `git_status` | `rethunk-git_git_status` | `git status --short -b` per MCP root and optional submodules (`includeSubmodules`); parallel submodule status. Args include `absoluteGitRoots`, `allWorkspaceRoots`, `rootIndex`, `workspaceRoot`, `format`. **Read-only.** |
|
|
15
15
|
| `git_inventory` | `rethunk-git_git_inventory` | Status + ahead/behind per path; default upstream each repo’s `@{u}`; pass **both** `remote` and `branch` for fixed tracking. `nestedRoots`, `preset`, `presetMerge`, `maxRoots`, `format`, plus workspace pick args (`absoluteGitRoots` cannot combine with `preset`/`nestedRoots`). **Read-only.** |
|
|
16
|
-
| `git_parity` | `rethunk-git_git_parity` | Compare `git rev-parse HEAD` for path pairs. `pairs`, `preset`, `presetMerge`, `format`, plus workspace pick args. **Read-only.** |
|
|
16
|
+
| `git_parity` | `rethunk-git_git_parity` | Compare `git rev-parse HEAD` for path pairs. `pairs`, `preset`, `presetMerge`, `format`, plus workspace pick args (`absoluteGitRoots` for sibling clone batches). **Read-only.** |
|
|
17
17
|
| `list_presets` | `rethunk-git_list_presets` | List preset names/counts from `.rethunk/git-mcp-presets.json`; invalid JSON/schema surface as errors. Workspace pick + `format` only (includes `absoluteGitRoots`). **Read-only.** |
|
|
18
18
|
| `git_log` | `rethunk-git_git_log` | Path-filtered, time-windowed `git log` across one or more workspace roots. Returns commit history with author, date, subject, and shortstat. Args: `since`, `paths`, `grep`, `author`, `maxCommits`, `branch`, plus workspace pick args (`absoluteGitRoots` for sibling clones) + `format`. **Read-only.** |
|
|
19
19
|
| `git_diff_summary` | `rethunk-git_git_diff_summary` | Structured, token-efficient diff viewer. Returns per-file diffs with additions/deletions counts, truncated to configurable line limits, with lock files/dist/vendor excluded by default. Args: `range`, `fileFilter`, `maxLinesPerFile`, `maxFiles`, `excludePatterns`, plus workspace pick args (optional single-entry `absoluteGitRoots`) + `format`. **Read-only.** |
|
|
20
|
+
| `git_diff` | `rethunk-git_git_diff` | Raw diff text for a single repo. Supports unstaged, staged, or `base..head` ranges, optionally scoped to one path. Args: `base?`, `head?`, `path?`, `staged?`, plus single-repo workspace pick + `format`. **Read-only.** |
|
|
21
|
+
| `git_show` | `rethunk-git_git_show` | Inspect one commit or ref. Returns commit message plus diff, or file content at `path` for that ref. Args: `ref`, `path?`, plus single-repo workspace pick + `format`. **Read-only.** |
|
|
20
22
|
| `git_worktree_list` | `rethunk-git_git_worktree_list` | List all worktrees (`git worktree list --porcelain`). Workspace pick + `format`. **Read-only.** |
|
|
23
|
+
| `git_stash_list` | `rethunk-git_git_stash_list` | List `git stash` entries for one repo. Args: single-repo workspace pick + `format`. **Read-only.** |
|
|
24
|
+
| `git_fetch` | `rethunk-git_git_fetch` | Fetch from a remote without modifying the working tree. Updates refs only and reports updated/new refs. Args: `remote?`, `branch?`, `prune?`, `tags?`, plus single-repo workspace pick + `format`. **Mutating — refs only.** |
|
|
21
25
|
| `git_push` | `rethunk-git_git_push` | Push the current branch to its upstream. Optional `remote`, `branch`, `setUpstream` (passes `-u`). Refuses on detached HEAD; never force-pushes. Workspace pick + `format`. **Mutating.** |
|
|
26
|
+
| `git_tag` | `rethunk-git_git_tag` | Create/delete annotated or lightweight tags for one repo. Args: `tag`, `message?`, `ref?`, `delete?`, plus single-repo workspace pick + `format`. **Mutating.** |
|
|
22
27
|
| `git_worktree_add` | `rethunk-git_git_worktree_add` | Create a new linked worktree, creating the branch from `baseRef` if it does not yet exist. Refuses on protected branch names. Args: `path`, `branch`, `baseRef?`, plus workspace pick + `format`. **Mutating.** |
|
|
23
28
|
| `git_worktree_remove` | `rethunk-git_git_worktree_remove` | Remove a registered worktree; refuses to remove the main worktree. Optional `force: true` for dirty trees. Args: `path`, `force?`, plus workspace pick + `format`. **Mutating.** |
|
|
24
29
|
| `git_reset_soft` | `rethunk-git_git_reset_soft` | Soft-reset the current branch to a ref (`HEAD~N`, SHA, branch). Rewound changes land in the staging index; requires a clean working tree. Args: `ref`, plus workspace pick + `format`. **Mutating — not idempotent.** |
|
|
25
|
-
| `batch_commit` | `rethunk-git_batch_commit` | Create multiple sequential git commits in a single call. Each entry stages the listed files then commits with the given message. Stops on first failure. Optional `push: "after"` pushes
|
|
30
|
+
| `batch_commit` | `rethunk-git_batch_commit` | Create multiple sequential git commits in a single call. Each entry stages the listed files or line-ranged file hunks, then commits with the given message. Stops on first failure. Optional `push: "after"` pushes once every commit lands; optional `dryRun: true` previews staged content without writing commits. Args: `commits` (array of `{message, files}`), `push?`, `dryRun?`, plus workspace pick args + `format`. **Mutating — not idempotent.** |
|
|
26
31
|
| `git_merge` | `rethunk-git_git_merge` | Merge one or more source branches into a destination. Default strategy `auto` cascades fast-forward → rebase → merge-commit per source, preferring linear history. Refuses on dirty tree; stops on first conflict with structured path report. Optional `deleteMergedBranches` / `deleteMergedWorktrees` cascade cleanup, always skipping protected names (main/master/dev/develop/stable/trunk/prod/production/release\*/hotfix\*). Args: `sources`, `into?`, `strategy?`, `message?`, cleanup flags + workspace pick + `format`. **Mutating.** |
|
|
27
32
|
| `git_cherry_pick` | `rethunk-git_git_cherry_pick` | Play commits from one or more sources onto a destination. Sources may be SHAs, `A..B` ranges, or branch names (expanded to `onto..<branch>`, oldest-first). Uses `--empty=drop` so patch-equivalent re-applies add nothing. Refuses on dirty tree; stops on first conflict, aborting cleanly. Same cleanup flags as `git_merge` (branch-kind sources only, protected names skipped). Args: `sources`, `onto?`, cleanup flags + workspace pick + `format`. **Mutating.** |
|
|
33
|
+
| `git_stash_apply` | `rethunk-git_git_stash_apply` | Apply or pop a stash entry for one repo. Args: `index?`, `pop?`, plus single-repo workspace pick + `format`. **Mutating.** |
|
|
28
34
|
|
|
29
35
|
Pass **`format: "json"`** on any tool for structured JSON instead of markdown (default).
|
|
30
36
|
|
|
@@ -32,6 +38,8 @@ Pass **`format: "json"`** on any tool for structured JSON instead of markdown (d
|
|
|
32
38
|
|
|
33
39
|
Tool JSON bodies are minified and contain only the payload — no `rethunkGitMcp` envelope. Current `MCP_JSON_FORMAT_VERSION` is **`"3"`**; server + format version are discoverable via MCP `initialize`. Payload keys (`groups`, `inventories`, `parity`, `roots`) are stable within a given format version. Preset-related responses may include **`presetSchemaVersion`**.
|
|
34
40
|
|
|
41
|
+
The package also ships **`tool-parameters.schema.json`**, generated from the registered Zod parameter schemas via `bun run schema:tools`, plus the published **`schemas/`** directory (`schemas/index.json` + one JSON Schema per tool) via `bun run schema:individual`. Connected MCP clients should still prefer live schema discovery from `initialize` / tool listing; the shipped artifacts are for offline inspection, drift checks, and code generation.
|
|
42
|
+
|
|
35
43
|
### v2/v3 field omission (consumer contract)
|
|
36
44
|
|
|
37
45
|
To keep responses compact, **optional fields are omitted when they would be empty, `null`, or `false`** — they are not emitted as `null`. Consumers must test for *presence*, not compare to `null`.
|
|
@@ -113,6 +121,23 @@ v2 field-omission rules still apply: `filesChanged`, `insertions`, `deletions` o
|
|
|
113
121
|
| `absolute_git_roots_empty` | `absoluteGitRoots` produced zero git toplevels after resolution. |
|
|
114
122
|
| `absolute_git_roots_single_repo_only` | A single-repo tool received `absoluteGitRoots` resolving to more than one distinct git toplevel. |
|
|
115
123
|
|
|
124
|
+
### `git_parity` — `absoluteGitRoots` example
|
|
125
|
+
|
|
126
|
+
Use **`absoluteGitRoots`** when the same parity pair should be checked across sibling clones that are not all MCP workspace roots:
|
|
127
|
+
|
|
128
|
+
```json
|
|
129
|
+
{
|
|
130
|
+
"format": "json",
|
|
131
|
+
"absoluteGitRoots": [
|
|
132
|
+
"/usr/local/src/com.github/Rethunk-AI/mcp-multi-root-git",
|
|
133
|
+
"/usr/local/src/com.github/Rethunk-AI/rethunk-github-mcp"
|
|
134
|
+
],
|
|
135
|
+
"pairs": [{ "left": "packages/shared", "right": "apps/web/shared", "label": "shared" }]
|
|
136
|
+
}
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
The response contains one **`parity[]`** entry per resolved git toplevel. `absoluteGitRoots` cannot be combined with `preset`; pass inline `pairs` for sibling-clone batches.
|
|
140
|
+
|
|
116
141
|
### `git_diff_summary` — parameters
|
|
117
142
|
|
|
118
143
|
| Parameter | Type | Default | Notes |
|
|
@@ -160,12 +185,151 @@ v2 field-omission rules still apply: `filesChanged`, `insertions`, `deletions` o
|
|
|
160
185
|
|
|
161
186
|
---
|
|
162
187
|
|
|
188
|
+
### `git_diff` — parameters
|
|
189
|
+
|
|
190
|
+
| Parameter | Type | Default | Notes |
|
|
191
|
+
|-----------|------|---------|-------|
|
|
192
|
+
| `base` | string | — | Base ref for a revision diff. When omitted with no `staged`, the tool shows unstaged changes. |
|
|
193
|
+
| `head` | string | `HEAD` | Head ref for a revision diff. Used only when `base` is provided. |
|
|
194
|
+
| `path` | string | — | Optional single file path to scope the diff. |
|
|
195
|
+
| `staged` | boolean | `false` | When `true`, runs `git diff --staged`. Ignored when `base` is provided. |
|
|
196
|
+
| `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
|
|
197
|
+
|
|
198
|
+
### `git_diff` — JSON shape (`format: "json"`)
|
|
199
|
+
|
|
200
|
+
```json
|
|
201
|
+
{
|
|
202
|
+
"range": "HEAD~1..HEAD (src/server.ts)",
|
|
203
|
+
"diff": "diff --git a/src/server.ts b/src/server.ts\n..."
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### `git_diff` — error codes
|
|
208
|
+
|
|
209
|
+
| Code | Meaning |
|
|
210
|
+
|------|---------|
|
|
211
|
+
| `unsafe_range_token` | `base` or `head` contains characters outside the argv-safe subset. |
|
|
212
|
+
| `git_diff_failed` | `git diff` exited non-zero. |
|
|
213
|
+
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
217
|
+
### `git_show` — parameters
|
|
218
|
+
|
|
219
|
+
| Parameter | Type | Notes |
|
|
220
|
+
|-----------|------|-------|
|
|
221
|
+
| `ref` | string | Commit, branch, tag, or other git rev-spec to inspect. |
|
|
222
|
+
| `path` | string | Optional single path. When provided, the response shows that path's content at `ref` instead of the full commit diff. |
|
|
223
|
+
| `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
|
|
224
|
+
|
|
225
|
+
### `git_show` — JSON shape (`format: "json"`)
|
|
226
|
+
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"ref": "HEAD",
|
|
230
|
+
"message": "feat: add tool",
|
|
231
|
+
"path": "src/server.ts",
|
|
232
|
+
"diff": "diff --git a/src/server.ts b/src/server.ts\n..."
|
|
233
|
+
}
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
`path` is omitted when not requested. `diff` is omitted when `git show` returns only a commit message.
|
|
237
|
+
|
|
238
|
+
### `git_show` — error codes
|
|
239
|
+
|
|
240
|
+
| Code | Meaning |
|
|
241
|
+
|------|---------|
|
|
242
|
+
| `git_show_failed` | `git show` exited non-zero (e.g. unknown ref). |
|
|
243
|
+
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
### `git_stash_list` — parameters
|
|
248
|
+
|
|
249
|
+
| Parameter | Type | Notes |
|
|
250
|
+
|-----------|------|-------|
|
|
251
|
+
| `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
|
|
252
|
+
|
|
253
|
+
### `git_stash_list` — JSON shape (`format: "json"`)
|
|
254
|
+
|
|
255
|
+
```json
|
|
256
|
+
{
|
|
257
|
+
"stashes": [
|
|
258
|
+
{ "index": 0, "message": "WIP on main: abc1234 feat: add tool", "sha": "abc1234" }
|
|
259
|
+
]
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### `git_stash_list` — error codes
|
|
264
|
+
|
|
265
|
+
| Code | Meaning |
|
|
266
|
+
|------|---------|
|
|
267
|
+
| `stash_list_failed` | `git stash list` failed unexpectedly. |
|
|
268
|
+
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### `git_fetch` — parameters
|
|
273
|
+
|
|
274
|
+
| Parameter | Type | Default | Notes |
|
|
275
|
+
|-----------|------|---------|-------|
|
|
276
|
+
| `remote` | string | `"origin"` | Remote to fetch from. |
|
|
277
|
+
| `branch` | string | — | Optional branch/ref to fetch from that remote. |
|
|
278
|
+
| `prune` | boolean | `false` | Pass `--prune` to remove deleted remote-tracking refs. |
|
|
279
|
+
| `tags` | boolean | `false` | Pass `--tags` to also fetch all tags. |
|
|
280
|
+
| `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
|
|
281
|
+
|
|
282
|
+
### `git_fetch` — JSON shape (`format: "json"`)
|
|
283
|
+
|
|
284
|
+
```json
|
|
285
|
+
{
|
|
286
|
+
"ok": true,
|
|
287
|
+
"remote": "origin",
|
|
288
|
+
"updatedRefs": ["abc1234..def5678 main -> origin/main"],
|
|
289
|
+
"newRefs": ["[new tag] v2.0.0 -> v2.0.0"],
|
|
290
|
+
"output": "From origin\n..."
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
Fetch failures are reported as `ok: false` with the captured git output in `output`.
|
|
295
|
+
|
|
296
|
+
### `git_fetch` — error codes
|
|
297
|
+
|
|
298
|
+
| Code | Meaning |
|
|
299
|
+
|------|---------|
|
|
300
|
+
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
### `batch_commit` — atomic staging semantics
|
|
305
|
+
|
|
306
|
+
**Critical for AI agents:** Each call to `batch_commit` is **self-contained and atomic per-commit entry**.
|
|
307
|
+
|
|
308
|
+
- **All files in a single entry are staged together.** When you list `files: ["src/foo.ts", "src/bar.ts"]` in one commit entry, both are staged atomically as a unit with a single `git add` before the commit is created.
|
|
309
|
+
- **Each commit entry is processed sequentially within the call.** The tool stages files, commits, then moves to the next entry. All entries within a single `batch_commit` call happen in one atomic MCP transaction.
|
|
310
|
+
- **A single `batch_commit` call cannot be split across multiple MCP calls.** Do NOT attempt incremental staging like "call 1 with file A, then call 2 with file B hoping they stage together." Each call is independent — call 1's commit lands immediately; call 2's changes are a separate transaction.
|
|
311
|
+
- **Failed entry stops the batch.** If staging or commit fails on entry N, the tool aborts and skips remaining entries. However, **entries that succeeded before the failure remain committed** — they are not rolled back.
|
|
312
|
+
- **Include all files for a logical change in a single `batch_commit` call.** Group related files in each commit entry, list them all in the `files` array, and include all necessary entries in the `commits` array.
|
|
313
|
+
|
|
314
|
+
Example: to commit two related changes atomically, pass both entries in one call:
|
|
315
|
+
```json
|
|
316
|
+
{
|
|
317
|
+
"commits": [
|
|
318
|
+
{ "message": "feat: add foo module", "files": ["src/foo.ts", "tests/foo.test.ts"] },
|
|
319
|
+
{ "message": "feat: integrate foo into bar", "files": ["src/bar.ts", "docs/foo.md"] }
|
|
320
|
+
]
|
|
321
|
+
}
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Do NOT do this: make two separate calls hoping to stage files incrementally. That breaks the contract.
|
|
325
|
+
|
|
163
326
|
### `batch_commit` — parameters
|
|
164
327
|
|
|
165
328
|
| Parameter | Type | Notes |
|
|
166
329
|
|-----------|------|-------|
|
|
167
|
-
| `commits` | `{message: string, files: string[]}[]` | Commits to create in order. 1–50 entries. Each `files` entry is a path relative to the git root
|
|
330
|
+
| `commits` | `{message: string, files: (string \| {path: string, lines: {from: number, to: number}})[]}[]` | Commits to create in order. 1–50 entries. Each `files` entry is either a path relative to the git root or a `{path, lines}` object for hunk-level staging. All paths must stay within the git toplevel. |
|
|
168
331
|
| `push` | `"never"` \| `"after"` | Default `"never"`. `"after"` pushes the current branch to its upstream **once all commits succeed**. Never auto-sets upstream — branches without an upstream fail with `push_no_upstream`. Commits are **not** rolled back on push failure. Enum reserved for future modes such as `"force-with-lease"`. |
|
|
332
|
+
| `dryRun` | boolean | Default `false`. When `true`, stages each entry, reports what would be committed (`staged`, `diffStat`), then unstages everything without writing commits. |
|
|
169
333
|
| `workspaceRoot` | string | Explicit root; highest priority. |
|
|
170
334
|
| `rootIndex` | int | Pick one of several MCP roots (0-based). |
|
|
171
335
|
| `format` | `"markdown"` \| `"json"` | Output format. Default: `"markdown"`. |
|
|
@@ -200,6 +364,8 @@ v2 field-omission rules still apply: `filesChanged`, `insertions`, `deletions` o
|
|
|
200
364
|
|
|
201
365
|
On first failure `ok` is `false`, `committed` reflects only the entries that succeeded before the error, and the failing entry includes `error` and `detail` fields. Remaining entries are skipped and not included in `results`.
|
|
202
366
|
|
|
367
|
+
When `dryRun: true`, the top-level response includes `dryRun: true`; successful `results[*]` entries omit `sha` and instead include `staged` and `diffStat`.
|
|
368
|
+
|
|
203
369
|
The `push` object is present only when `push: "after"` was requested **and** every commit landed. On push failure the top-level `ok` stays `true` (the commits themselves succeeded) while `push.ok` is `false` and `push.error` carries the code.
|
|
204
370
|
|
|
205
371
|
### `batch_commit` — error codes (per-result `error` field)
|
|
@@ -352,6 +518,8 @@ Repo state is cleaned (`git cherry-pick --abort`) before returning — no partia
|
|
|
352
518
|
|
|
353
519
|
### `git_push` — parameters
|
|
354
520
|
|
|
521
|
+
For already-committed work, call **`git_push`** directly instead of creating an empty commit or falling back to shell. Continue to prefer **`batch_commit`** with **`push: "after"`** when commits and push happen in the same MCP call.
|
|
522
|
+
|
|
355
523
|
| Parameter | Type | Notes |
|
|
356
524
|
|-----------|------|-------|
|
|
357
525
|
| `remote` | string | Remote to push to. Defaults to the remote inferred from the upstream tracking ref, or `origin` when `setUpstream` is true. |
|
|
@@ -378,6 +546,39 @@ Repo state is cleaned (`git cherry-pick --abort`) before returning — no partia
|
|
|
378
546
|
|
|
379
547
|
---
|
|
380
548
|
|
|
549
|
+
### `git_tag` — parameters
|
|
550
|
+
|
|
551
|
+
| Parameter | Type | Default | Notes |
|
|
552
|
+
|-----------|------|---------|-------|
|
|
553
|
+
| `tag` | string | — | Tag name to create or delete. |
|
|
554
|
+
| `message` | string | — | When provided, creates an annotated tag; otherwise creates a lightweight tag. |
|
|
555
|
+
| `ref` | string | `HEAD` | Commit/ref to tag. Ignored when `delete: true`. |
|
|
556
|
+
| `delete` | boolean | `false` | Delete the named tag instead of creating it. |
|
|
557
|
+
| `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
|
|
558
|
+
|
|
559
|
+
### `git_tag` — JSON shape (`format: "json"`)
|
|
560
|
+
|
|
561
|
+
```json
|
|
562
|
+
{ "tag": "v2.3.5", "type": "annotated", "sha": "a1b2c3d4e5f6..." }
|
|
563
|
+
```
|
|
564
|
+
|
|
565
|
+
For deletions, `type` is `"deleted"` and `sha` is an empty string.
|
|
566
|
+
|
|
567
|
+
### `git_tag` — error codes
|
|
568
|
+
|
|
569
|
+
| Code | Meaning |
|
|
570
|
+
|------|---------|
|
|
571
|
+
| `tag_empty` | `tag` trimmed to an empty string. |
|
|
572
|
+
| `tag_unsafe` | `tag` contains disallowed characters. |
|
|
573
|
+
| `ref_unsafe` | `ref` contains disallowed characters. |
|
|
574
|
+
| `ref_not_found` | `ref` did not resolve to a commit. |
|
|
575
|
+
| `tag_create_failed` | `git tag` failed while creating the tag. |
|
|
576
|
+
| `tag_delete_failed` | `git tag -d` failed. |
|
|
577
|
+
| `tag_verification_failed` | The tag could not be read back after creation. |
|
|
578
|
+
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
579
|
+
|
|
580
|
+
---
|
|
581
|
+
|
|
381
582
|
### `git_reset_soft` — parameters
|
|
382
583
|
|
|
383
584
|
| Parameter | Type | Notes |
|
|
@@ -403,6 +604,35 @@ Repo state is cleaned (`git cherry-pick --abort`) before returning — no partia
|
|
|
403
604
|
|
|
404
605
|
---
|
|
405
606
|
|
|
607
|
+
### `git_stash_apply` — parameters
|
|
608
|
+
|
|
609
|
+
| Parameter | Type | Default | Notes |
|
|
610
|
+
|-----------|------|---------|-------|
|
|
611
|
+
| `index` | int | `0` | Stash index to apply/pop (`stash@{index}`). |
|
|
612
|
+
| `pop` | boolean | `false` | When `true`, runs `git stash pop` instead of `git stash apply`. |
|
|
613
|
+
| `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
|
|
614
|
+
|
|
615
|
+
### `git_stash_apply` — JSON shape (`format: "json"`)
|
|
616
|
+
|
|
617
|
+
```json
|
|
618
|
+
{
|
|
619
|
+
"applied": true,
|
|
620
|
+
"stashIndex": 0,
|
|
621
|
+
"popped": false,
|
|
622
|
+
"output": "On branch main\nChanges not staged for commit:\n..."
|
|
623
|
+
}
|
|
624
|
+
```
|
|
625
|
+
|
|
626
|
+
`output` is omitted when git produced no stdout/stderr text.
|
|
627
|
+
|
|
628
|
+
### `git_stash_apply` — error codes
|
|
629
|
+
|
|
630
|
+
| Code | Meaning |
|
|
631
|
+
|------|---------|
|
|
632
|
+
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
633
|
+
|
|
634
|
+
---
|
|
635
|
+
|
|
406
636
|
### `git_worktree_list` — JSON shape (`format: "json"`)
|
|
407
637
|
|
|
408
638
|
```json
|
|
@@ -494,7 +724,7 @@ Order applied when resolving which directory(ies) tools run against:
|
|
|
494
724
|
2. **`rootIndex`** (0-based) — one `file://` MCP root when several exist.
|
|
495
725
|
3. **`allWorkspaceRoots`: true** — every `file://` root; markdown output emits one `# {tool}` header with per-root subsections (`git_inventory` uses `### {gitTop}`; `git_status` uses `### MCP root: ...`), or combined JSON.
|
|
496
726
|
4. **`preset`** set and multiple roots — first root whose git toplevel defines that preset (respecting **`workspaceRootHint`** on the preset entry when present).
|
|
497
|
-
5. Otherwise the first `file://` root
|
|
727
|
+
5. Otherwise the first `file://` root reported by the MCP client through **`roots/list`**.
|
|
498
728
|
6. **`process.cwd()`** if no file roots (e.g. CI with explicit `workspaceRoot`).
|
|
499
729
|
|
|
500
|
-
Roots come from
|
|
730
|
+
Roots come from active MCP sessions (**`FastMCP` with `roots: { enabled: true }`** in code); there is no fixed `cwd` in server config. This is what allows one globally installed server to follow the workspace opened in VS Code, Claude Code, Cursor, or any other roots-capable MCP client.
|
package/package.json
CHANGED
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rethunk/mcp-multi-root-git",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
4
|
"description": "MCP stdio server: multi-root git status, inventory, and HEAD parity checks. Generic tools usable by any workspace.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
|
-
"packageManager": "bun@1.3.
|
|
7
|
+
"packageManager": "bun@1.3.13",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=22.0.0"
|
|
10
10
|
},
|
|
11
11
|
"files": [
|
|
12
12
|
"dist",
|
|
13
13
|
"git-mcp-presets.schema.json",
|
|
14
|
+
"tool-parameters.schema.json",
|
|
15
|
+
"schemas",
|
|
14
16
|
"docs/install.md",
|
|
15
17
|
"docs/mcp-tools.md",
|
|
16
18
|
"AGENTS.md",
|
|
@@ -31,9 +33,14 @@
|
|
|
31
33
|
"build": "rimraf dist && tsc",
|
|
32
34
|
"check": "biome check .",
|
|
33
35
|
"check:fix": "biome check --write .",
|
|
36
|
+
"coverage:check": "bun scripts/check-coverage.ts",
|
|
37
|
+
"schema:tools": "bun scripts/generate-tool-parameters-schema.ts",
|
|
38
|
+
"schema:tools:check": "bun scripts/generate-tool-parameters-schema.ts --check",
|
|
39
|
+
"schema:individual": "bun scripts/generate-individual-schemas.ts",
|
|
40
|
+
"publish:preflight": "bun scripts/publish-preflight.ts",
|
|
34
41
|
"test": "bun test src/",
|
|
35
42
|
"test:coverage": "bun test src/ --coverage",
|
|
36
|
-
"prepublishOnly": "bun run build && bun run check && bun run test",
|
|
43
|
+
"prepublishOnly": "bun run schema:tools:check && bun run schema:individual && bun run build && bun run check && bun run test",
|
|
37
44
|
"setup-hooks": "git config core.hooksPath .githooks"
|
|
38
45
|
},
|
|
39
46
|
"keywords": [
|
|
@@ -57,13 +64,13 @@
|
|
|
57
64
|
"access": "public"
|
|
58
65
|
},
|
|
59
66
|
"dependencies": {
|
|
60
|
-
"fastmcp": "^
|
|
61
|
-
"zod": "^4.3
|
|
67
|
+
"fastmcp": "^4.0.1",
|
|
68
|
+
"zod": "^4.4.3"
|
|
62
69
|
},
|
|
63
70
|
"devDependencies": {
|
|
64
|
-
"@biomejs/biome": "^2.4.
|
|
65
|
-
"@types/node": "^
|
|
71
|
+
"@biomejs/biome": "^2.4.14",
|
|
72
|
+
"@types/node": "^25.6.0",
|
|
66
73
|
"rimraf": "^6.1.3",
|
|
67
|
-
"typescript": "^6.0.
|
|
74
|
+
"typescript": "^6.0.3"
|
|
68
75
|
}
|
|
69
76
|
}
|