@rethunk/mcp-multi-root-git 2.4.0 → 2.6.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 +4 -4
- package/CHANGELOG.md +55 -1
- package/dist/server/batch-commit-tool.js +49 -6
- package/dist/server/git-cherry-pick-tool.js +32 -8
- package/dist/server/git-diff-summary-tool.js +46 -22
- package/dist/server/git-diff-tool.js +4 -1
- package/dist/server/git-fetch-tool.js +8 -1
- package/dist/server/git-inventory-tool.js +2 -2
- package/dist/server/git-log-tool.js +42 -0
- package/dist/server/git-parity-tool.js +2 -2
- package/dist/server/git-refs.js +84 -5
- package/dist/server/git-show-tool.js +16 -2
- package/dist/server/git-stash-tool.js +17 -16
- package/dist/server/git-tag-tool.js +3 -3
- package/dist/server/git.js +33 -5
- package/dist/server.js +7 -0
- package/docs/mcp-tools.md +246 -13
- package/package.json +14 -3
- package/schemas/batch_commit.json +1 -1
- package/schemas/git_cherry_pick.json +7 -1
- package/schemas/git_diff.json +0 -14
- package/schemas/git_diff_summary.json +0 -6
- package/schemas/git_log.json +3 -1
- package/schemas/git_show.json +1 -0
- package/tool-parameters.schema.json +12 -23
package/docs/mcp-tools.md
CHANGED
|
@@ -15,9 +15,9 @@ MCP clients expose tools as `{serverName}_{toolName}`. With the server registere
|
|
|
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
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
|
-
| `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
|
|
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` (`markdown`/`json`/`oneline`). **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
|
|
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: `workspaceRoot`, `rootIndex`, `format`, `base?`, `head?`, `path?`, `staged?`. No `absoluteGitRoots` or `allWorkspaceRoots`. **Read-only.** |
|
|
21
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.** |
|
|
22
22
|
| `git_worktree_list` | `rethunk-git_git_worktree_list` | List all worktrees (`git worktree list --porcelain`). Workspace pick + `format`. **Read-only.** |
|
|
23
23
|
| `git_stash_list` | `rethunk-git_git_stash_list` | List `git stash` entries for one repo. Args: single-repo workspace pick + `format`. **Read-only.** |
|
|
@@ -29,14 +29,237 @@ MCP clients expose tools as `{serverName}_{toolName}`. With the server registere
|
|
|
29
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.** |
|
|
30
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.** |
|
|
31
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.** |
|
|
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.** |
|
|
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); branch deletion uses patch-id equivalence by default so cherry-pick workflows (where SHA differs but diff is identical) clean up correctly. Pass `strictMergedRefEquality: true` for strict `git branch -d` ancestry semantics. Args: `sources`, `onto?`, cleanup flags, `strictMergedRefEquality?` + workspace pick + `format`. **Mutating.** |
|
|
33
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.** |
|
|
34
34
|
|
|
35
35
|
Pass **`format: "json"`** on any tool for structured JSON instead of markdown (default).
|
|
36
36
|
|
|
37
|
+
---
|
|
38
|
+
|
|
39
|
+
### `git_status` — parameters
|
|
40
|
+
|
|
41
|
+
| Parameter | Type | Default | Notes |
|
|
42
|
+
|-----------|------|---------|-------|
|
|
43
|
+
| `includeSubmodules` | boolean | `true` | When `true`, reads `.gitmodules` at each git toplevel and runs `git status --short -b` for each checked-out submodule in parallel. Set `false` to skip submodule discovery entirely. |
|
|
44
|
+
| `workspaceRoot` | string | — | Explicit root; highest priority. |
|
|
45
|
+
| `rootIndex` | int | — | Pick one of several MCP roots (0-based). |
|
|
46
|
+
| `allWorkspaceRoots` | boolean | `false` | Fan out across all MCP roots. |
|
|
47
|
+
| `absoluteGitRoots` | string[] | — | Explicit list of absolute paths; replaces normal workspace pick. Max 256. Read-only tools only. |
|
|
48
|
+
| `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
|
|
49
|
+
|
|
50
|
+
### `git_status` — JSON shape (`format: "json"`)
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"groups": [{
|
|
55
|
+
"mcpRoot": "/abs/workspace",
|
|
56
|
+
"repos": [
|
|
57
|
+
{ "label": ".", "path": "/abs/workspace", "statusText": "## main...origin/main\nM src/foo.ts", "ok": true },
|
|
58
|
+
{ "label": "sub", "path": "/abs/workspace/sub", "statusText": "## main...origin/main", "ok": true }
|
|
59
|
+
]
|
|
60
|
+
}]
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
One `groups` entry per resolved root. Each `repos` entry has `label` (relative path, `"."` for the root), `path` (absolute), `statusText` (full `git status --short -b` output), and `ok` (`false` when git failed or the submodule is not checked out).
|
|
65
|
+
|
|
66
|
+
### `git_status` — error codes
|
|
67
|
+
|
|
68
|
+
Error payloads appear as top-level JSON or inline in individual repo rows (`ok: false`):
|
|
69
|
+
|
|
70
|
+
| Code / statusText | Context | Meaning |
|
|
71
|
+
|-------------------|---------|---------|
|
|
72
|
+
| `git_not_found` | top-level | `git` binary not on `PATH`. |
|
|
73
|
+
| `not_a_git_repository` | repo row `statusText` | Root is not inside a git repository. |
|
|
74
|
+
| `(submodule path escapes repository — rejected)` | repo row `statusText` | `.gitmodules` path resolves outside the git toplevel (security guard). |
|
|
75
|
+
| `(no .git — submodule not checked out?)` | repo row `statusText` | Submodule directory exists but has no `.git` — not initialized. |
|
|
76
|
+
| `root_index_out_of_range` | top-level | `rootIndex` exceeds the number of MCP file roots. |
|
|
77
|
+
| `absolute_git_roots_exclusive` | top-level | `absoluteGitRoots` combined with `workspaceRoot`, `rootIndex`, or `allWorkspaceRoots: true`. |
|
|
78
|
+
| `invalid_absolute_git_root` | top-level | An `absoluteGitRoots` entry is not a git-recognized directory. |
|
|
79
|
+
| `absolute_git_roots_too_many` | top-level | More than 256 entries in `absoluteGitRoots`. |
|
|
80
|
+
| `absolute_git_roots_empty` | top-level | `absoluteGitRoots` resolved to zero toplevels. |
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
### `git_inventory` — parameters
|
|
85
|
+
|
|
86
|
+
| Parameter | Type | Default | Notes |
|
|
87
|
+
|-----------|------|---------|-------|
|
|
88
|
+
| `nestedRoots` | string[] | — | Relative paths (from the workspace git toplevel) to treat as independent git repos to inventory. Each must be a valid git work tree; invalid paths produce skip entries rather than errors. Cannot combine with `absoluteGitRoots` or `preset`. |
|
|
89
|
+
| `preset` | string | — | Preset name from `.rethunk/git-mcp-presets.json`. Loads `nestedRoots` from the preset's entry. Cannot combine with `absoluteGitRoots`. |
|
|
90
|
+
| `presetMerge` | boolean | `false` | When `true`, merge inline `nestedRoots` with preset roots instead of replacing. |
|
|
91
|
+
| `remote` | string | — | Fixed remote for ahead/behind tracking. Must be paired with `branch`. |
|
|
92
|
+
| `branch` | string | — | Fixed branch for ahead/behind tracking. Must be paired with `remote`. When both are absent the tool uses each repo's `@{u}` upstream. |
|
|
93
|
+
| `maxRoots` | int | `64` | Max nested roots to process (1–256). Roots beyond the limit are omitted; `nestedRootsTruncated: true` and `nestedRootsOmittedCount` are set on the group. |
|
|
94
|
+
| `workspaceRoot` | string | — | Explicit root; highest priority. |
|
|
95
|
+
| `rootIndex` | int | — | Pick one of several MCP roots (0-based). |
|
|
96
|
+
| `allWorkspaceRoots` | boolean | `false` | Fan out across all MCP roots. |
|
|
97
|
+
| `absoluteGitRoots` | string[] | — | Explicit list; replaces normal workspace pick. Cannot combine with `nestedRoots` or `preset`. |
|
|
98
|
+
| `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
|
|
99
|
+
|
|
100
|
+
### `git_inventory` — JSON shape (`format: "json"`)
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"inventories": [{
|
|
105
|
+
"workspace_root": "/abs/path",
|
|
106
|
+
"entries": [{
|
|
107
|
+
"label": ".",
|
|
108
|
+
"path": "/abs/path",
|
|
109
|
+
"upstreamMode": "auto",
|
|
110
|
+
"branchStatus": "## main...origin/main",
|
|
111
|
+
"headAbbrev": "a1b2c3d",
|
|
112
|
+
"upstreamRef": "origin/main",
|
|
113
|
+
"ahead": "2",
|
|
114
|
+
"behind": "0"
|
|
115
|
+
}],
|
|
116
|
+
"nestedRootsTruncated": true,
|
|
117
|
+
"nestedRootsOmittedCount": 3,
|
|
118
|
+
"upstream": { "mode": "fixed", "remote": "origin", "branch": "main" }
|
|
119
|
+
}]
|
|
120
|
+
}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`nestedRootsTruncated` / `nestedRootsOmittedCount` present only when `maxRoots` cut the list. `upstream` object present only when `remote`+`branch` were supplied (fixed mode). `presetSchemaVersion` present only when a preset was loaded. See *v2/v3 field omission* for `entries[*]` optional fields.
|
|
124
|
+
|
|
125
|
+
### `git_inventory` — error codes
|
|
126
|
+
|
|
127
|
+
| Code | Meaning |
|
|
128
|
+
|------|---------|
|
|
129
|
+
| `git_not_found` | `git` binary not on `PATH`. |
|
|
130
|
+
| `remote_branch_mismatch` | Only one of `remote` / `branch` was provided; supply both or neither. |
|
|
131
|
+
| `invalid_remote_or_branch` | `remote` or `branch` contains characters outside the safe token set. |
|
|
132
|
+
| `absolute_git_roots_nested_or_preset_conflict` | `absoluteGitRoots` combined with `nestedRoots` or `preset`. |
|
|
133
|
+
| `absolute_git_roots_exclusive` | `absoluteGitRoots` combined with `workspaceRoot`, `rootIndex`, or `allWorkspaceRoots: true`. |
|
|
134
|
+
| `absolute_git_roots_preset_conflict` | `absoluteGitRoots` combined with a `preset` argument. |
|
|
135
|
+
| `invalid_absolute_git_root` | An `absoluteGitRoots` entry is not a git-recognized directory. |
|
|
136
|
+
| `absolute_git_roots_too_many` | More than 256 entries in `absoluteGitRoots`. |
|
|
137
|
+
| `absolute_git_roots_empty` | `absoluteGitRoots` resolved to zero toplevels. |
|
|
138
|
+
| `root_index_out_of_range` | `rootIndex` exceeds the number of MCP file roots. |
|
|
139
|
+
| `preset_not_found` | Named preset does not exist in the preset file. |
|
|
140
|
+
| `invalid_json` | Preset file contains invalid JSON. |
|
|
141
|
+
| `invalid_schema` | Preset file fails schema validation. |
|
|
142
|
+
|
|
143
|
+
Skip entries (individual repos that could not be inventoried) appear inline in `entries[*]` with `skipReason` rather than as top-level errors.
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
### `git_parity` — parameters
|
|
148
|
+
|
|
149
|
+
| Parameter | Type | Notes |
|
|
150
|
+
|-----------|------|-------|
|
|
151
|
+
| `pairs` | `{left: string, right: string, label?: string}[]` | Path pairs to compare. `left` and `right` are relative to the workspace git toplevel. `label` is optional display name; defaults to `"left / right"`. At least one pair required (via inline `pairs` or `preset`). |
|
|
152
|
+
| `preset` | string | Preset name from `.rethunk/git-mcp-presets.json`. Loads `parityPairs` from the preset's entry. |
|
|
153
|
+
| `presetMerge` | boolean | Default `false`. When `true`, merge inline `pairs` with preset pairs instead of replacing. |
|
|
154
|
+
| `workspaceRoot` | string | Explicit root; highest priority. |
|
|
155
|
+
| `rootIndex` | int | Pick one of several MCP roots (0-based). |
|
|
156
|
+
| `allWorkspaceRoots` | boolean | `false` | Fan out across all MCP roots. |
|
|
157
|
+
| `absoluteGitRoots` | string[] | Explicit list of absolute paths; replaces normal workspace pick. Useful for checking parity across sibling clones. |
|
|
158
|
+
| `format` | `"markdown"` \| `"json"` | Output format. Default: `"markdown"`. |
|
|
159
|
+
|
|
160
|
+
### `git_parity` — JSON shape (`format: "json"`)
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"parity": [{
|
|
165
|
+
"workspace_root": "/abs/path",
|
|
166
|
+
"status": "MISMATCH",
|
|
167
|
+
"pairs": [
|
|
168
|
+
{
|
|
169
|
+
"label": "shared",
|
|
170
|
+
"leftPath": "/abs/path/left",
|
|
171
|
+
"rightPath": "/abs/path/right",
|
|
172
|
+
"match": true,
|
|
173
|
+
"sha": "a1b2c3d4e5f6…"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"label": "config",
|
|
177
|
+
"leftPath": "/abs/path/cfg-a",
|
|
178
|
+
"rightPath": "/abs/path/cfg-b",
|
|
179
|
+
"match": false,
|
|
180
|
+
"leftSha": "a1b2c3d…",
|
|
181
|
+
"rightSha": "f9e8d7c…"
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
}]
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
`status` is `"OK"` when every pair matches, `"MISMATCH"` when any pair differs or errors. On a match, `sha` carries the common HEAD SHA. On a mismatch, `leftSha` / `rightSha` carry the differing SHAs. On error (path escape or `git rev-parse HEAD` failure), `error` carries a description string and both SHA fields are absent. `presetSchemaVersion` is present when a preset was loaded.
|
|
189
|
+
|
|
190
|
+
### `git_parity` — error codes
|
|
191
|
+
|
|
192
|
+
| Code | Meaning |
|
|
193
|
+
|------|---------|
|
|
194
|
+
| `git_not_found` | `git` binary not on `PATH`. |
|
|
195
|
+
| `no_pairs` | Neither inline `pairs` nor a preset with `parityPairs` was supplied. |
|
|
196
|
+
| `absolute_git_roots_exclusive` | `absoluteGitRoots` combined with `workspaceRoot`, `rootIndex`, or `allWorkspaceRoots: true`. |
|
|
197
|
+
| `absolute_git_roots_preset_conflict` | `absoluteGitRoots` combined with a `preset` argument. |
|
|
198
|
+
| `invalid_absolute_git_root` | An `absoluteGitRoots` entry is not a git-recognized directory. |
|
|
199
|
+
| `absolute_git_roots_too_many` | More than 256 entries in `absoluteGitRoots`. |
|
|
200
|
+
| `absolute_git_roots_empty` | `absoluteGitRoots` resolved to zero toplevels. |
|
|
201
|
+
| `root_index_out_of_range` | `rootIndex` exceeds the number of MCP file roots. |
|
|
202
|
+
| `preset_not_found` | Named preset does not exist in the preset file. |
|
|
203
|
+
| `invalid_json` | Preset file contains invalid JSON. |
|
|
204
|
+
| `invalid_schema` | Preset file fails schema validation. |
|
|
205
|
+
|
|
206
|
+
Path-escape and `rev-parse` failures are reported inline in `pairs[*].error`, not as top-level error codes.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
### `list_presets` — parameters
|
|
211
|
+
|
|
212
|
+
| Parameter | Type | Notes |
|
|
213
|
+
|-----------|------|-------|
|
|
214
|
+
| `workspaceRoot` | string | Explicit root; highest priority. |
|
|
215
|
+
| `rootIndex` | int | Pick one of several MCP roots (0-based). |
|
|
216
|
+
| `allWorkspaceRoots` | boolean | Default `false`. Fan out across all MCP roots. |
|
|
217
|
+
| `absoluteGitRoots` | string[] | Explicit list; replaces normal workspace pick. |
|
|
218
|
+
| `format` | `"markdown"` \| `"json"` | Output format. Default: `"markdown"`. |
|
|
219
|
+
|
|
220
|
+
### `list_presets` — JSON shape (`format: "json"`)
|
|
221
|
+
|
|
222
|
+
```json
|
|
223
|
+
{
|
|
224
|
+
"roots": [{
|
|
225
|
+
"workspaceRoot": "/abs/workspace",
|
|
226
|
+
"gitTop": "/abs/workspace",
|
|
227
|
+
"presetFile": "/abs/workspace/.rethunk/git-mcp-presets.json",
|
|
228
|
+
"fileExists": true,
|
|
229
|
+
"presetSchemaVersion": "1",
|
|
230
|
+
"presets": [
|
|
231
|
+
{
|
|
232
|
+
"name": "monorepo",
|
|
233
|
+
"nestedRootsCount": 5,
|
|
234
|
+
"parityPairsCount": 2,
|
|
235
|
+
"workspaceRootHint": "/abs/workspace"
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
}]
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
`gitTop` is `null` when the workspace root is not inside a git repository. `presetSchemaVersion` is omitted when absent. `workspaceRootHint` is omitted when not set. When `fileExists: false` the `presets` array is empty and no `error` is present. When the file exists but fails to load, `error` contains a structured error object and `presets` is empty.
|
|
243
|
+
|
|
244
|
+
### `list_presets` — error codes
|
|
245
|
+
|
|
246
|
+
| Code | Meaning |
|
|
247
|
+
|------|---------|
|
|
248
|
+
| `git_not_found` | `git` binary not on `PATH`. |
|
|
249
|
+
| `not_a_git_repository` | Root is not inside a git repository (reported inline in the `roots[*].error` field, not top-level). |
|
|
250
|
+
| `invalid_json` | Preset file is not valid JSON (inline in `roots[*].error`). |
|
|
251
|
+
| `invalid_schema` | Preset file fails schema validation (inline in `roots[*].error`). |
|
|
252
|
+
| `absolute_git_roots_exclusive` | `absoluteGitRoots` combined with `workspaceRoot`, `rootIndex`, or `allWorkspaceRoots: true`. |
|
|
253
|
+
| `invalid_absolute_git_root` | An `absoluteGitRoots` entry is not a git-recognized directory. |
|
|
254
|
+
| `absolute_git_roots_too_many` | More than 256 entries in `absoluteGitRoots`. |
|
|
255
|
+
| `absolute_git_roots_empty` | `absoluteGitRoots` resolved to zero toplevels. |
|
|
256
|
+
| `root_index_out_of_range` | `rootIndex` exceeds the number of MCP file roots. |
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
37
260
|
## JSON responses
|
|
38
261
|
|
|
39
|
-
Tool JSON bodies are minified and contain only the payload — no `rethunkGitMcp` envelope. Current `MCP_JSON_FORMAT_VERSION` is **`"3"
|
|
262
|
+
Tool JSON bodies are minified and contain only the payload — no `rethunkGitMcp` envelope. Current `MCP_JSON_FORMAT_VERSION` is **`"3"`** (exported constant in `src/server.ts`); the version string is surfaced in the FastMCP `instructions` field and is therefore discoverable via the MCP `initialize` response. Payload keys (`groups`, `inventories`, `parity`, `roots`) are stable within a given format version. Preset-related responses may include **`presetSchemaVersion`**.
|
|
40
263
|
|
|
41
264
|
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
265
|
|
|
@@ -59,7 +282,7 @@ To keep responses compact, **optional fields are omitted when they would be empt
|
|
|
59
282
|
|
|
60
283
|
- Error payloads carry an `error` code string and any structured context (e.g. `preset`, `presetFile`). The old free-text `message` field is **removed** for self-describing codes (`git_not_found`, `remote_branch_mismatch`, `invalid_remote_or_branch`, `no_pairs`, `preset_not_found` *missing* case). It is retained only where it carries parse output (the `invalid_json` preset branch).
|
|
61
284
|
|
|
62
|
-
**When to bump `MCP_JSON_FORMAT_VERSION` or change payload shape:** [AGENTS.md](../AGENTS.md) — *Changing contracts*.
|
|
285
|
+
**When to bump `MCP_JSON_FORMAT_VERSION` or change payload shape:** [AGENTS.md](../AGENTS.md) — *Changing contracts*. The constant lives in `src/server.ts` and is surfaced via the server `instructions` field (discoverable from the MCP `initialize` response).
|
|
63
286
|
|
|
64
287
|
### `git_log` — parameters
|
|
65
288
|
|
|
@@ -74,7 +297,7 @@ To keep responses compact, **optional fields are omitted when they would be empt
|
|
|
74
297
|
| `workspaceRoot` | string | — | Explicit root; highest priority. |
|
|
75
298
|
| `rootIndex` | int | — | Pick one of several MCP roots (0-based). |
|
|
76
299
|
| `allWorkspaceRoots` | boolean | `false` | Fan out across all MCP roots. |
|
|
77
|
-
| `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
|
|
300
|
+
| `format` | `"markdown"` \| `"json"` \| `"oneline"` | `"markdown"` | Output format. `oneline` returns `<sha7> <subject>` per line with no headers (single root) or `### repo (branch)` separators per group (multi-root). Lowest-token option for post-commit verification. |
|
|
78
301
|
|
|
79
302
|
### `git_log` — JSON shape (`format: "json"`)
|
|
80
303
|
|
|
@@ -112,6 +335,7 @@ v2 field-omission rules still apply: `filesChanged`, `insertions`, `deletions` o
|
|
|
112
335
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
113
336
|
| `invalid_since` | The `since` string contains shell metacharacters and was rejected. |
|
|
114
337
|
| `invalid_paths` | One of the `paths` entries contains shell metacharacters and was rejected. |
|
|
338
|
+
| `unsafe_ref_token` | `branch` contains characters outside the argv-safe subset. |
|
|
115
339
|
| `git_log_failed` | `git log` exited non-zero (e.g. unknown branch ref). |
|
|
116
340
|
| `root_index_out_of_range` | `rootIndex` exceeds the number of MCP file roots. |
|
|
117
341
|
| `absolute_git_roots_exclusive` | `absoluteGitRoots` was combined with `workspaceRoot`, `rootIndex`, or `allWorkspaceRoots: true`. |
|
|
@@ -193,7 +417,11 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
|
|
|
193
417
|
| `head` | string | `HEAD` | Head ref for a revision diff. Used only when `base` is provided. |
|
|
194
418
|
| `path` | string | — | Optional single file path to scope the diff. |
|
|
195
419
|
| `staged` | boolean | `false` | When `true`, runs `git diff --staged`. Ignored when `base` is provided. |
|
|
196
|
-
| `workspaceRoot
|
|
420
|
+
| `workspaceRoot` | string | — | Explicit root; highest priority. |
|
|
421
|
+
| `rootIndex` | int | — | Pick one of several MCP roots (0-based). |
|
|
422
|
+
| `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
|
|
423
|
+
|
|
424
|
+
`git_diff` is a single-repo tool. It does not accept `absoluteGitRoots` or `allWorkspaceRoots`; use `workspaceRoot` or `rootIndex` to select the target repo.
|
|
197
425
|
|
|
198
426
|
### `git_diff` — JSON shape (`format: "json"`)
|
|
199
427
|
|
|
@@ -239,6 +467,8 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
|
|
|
239
467
|
|
|
240
468
|
| Code | Meaning |
|
|
241
469
|
|------|---------|
|
|
470
|
+
| `unsafe_ref_token` | `ref` contains characters outside the argv-safe subset. |
|
|
471
|
+
| `path_escapes_repo` | `path` resolves outside the git toplevel. |
|
|
242
472
|
| `git_show_failed` | `git show` exited non-zero (e.g. unknown ref). |
|
|
243
473
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
244
474
|
|
|
@@ -297,6 +527,8 @@ Fetch failures are reported as `ok: false` with the captured git output in `outp
|
|
|
297
527
|
|
|
298
528
|
| Code | Meaning |
|
|
299
529
|
|------|---------|
|
|
530
|
+
| `unsafe_remote_token` | `remote` contains characters outside the argv-safe subset. |
|
|
531
|
+
| `unsafe_ref_token` | `branch` contains characters outside the argv-safe subset. |
|
|
300
532
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
301
533
|
|
|
302
534
|
---
|
|
@@ -327,7 +559,7 @@ Do NOT do this: make two separate calls hoping to stage files incrementally. Tha
|
|
|
327
559
|
|
|
328
560
|
| Parameter | Type | Notes |
|
|
329
561
|
|-----------|------|-------|
|
|
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
|
|
562
|
+
| `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) a path relative to the git root, staged with `git add`; (b) a `{path, lines: {from, to}}` object for hunk-level staging — only unified-diff hunks overlapping the given 1-indexed line range are staged; or (c) a path to a **deleted tracked file** (missing on disk but tracked in HEAD), which is staged as a removal via `git rm --cached` — combining `{path, lines}` with a deleted file is an error. All paths must stay within the git toplevel. |
|
|
331
563
|
| `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
564
|
| `dryRun` | boolean | Default `false`. When `true`, stages each entry, reports what would be committed (`staged`, `diffStat`), then unstages everything without writing commits. |
|
|
333
565
|
| `workspaceRoot` | string | Explicit root; highest priority. |
|
|
@@ -373,7 +605,7 @@ The `push` object is present only when `push: "after"` was requested **and** eve
|
|
|
373
605
|
| Code | Meaning |
|
|
374
606
|
|------|---------|
|
|
375
607
|
| `path_escapes_repository` | One of the listed file paths resolves outside the git toplevel. |
|
|
376
|
-
| `stage_failed` | `git add`
|
|
608
|
+
| `stage_failed` | Staging failed. `git add` error for modified/new files; `git rm --cached` error for deleted files (e.g. path never tracked in HEAD); `{path, lines}` on a deleted file. |
|
|
377
609
|
| `commit_failed` | `git commit` failed (e.g. nothing staged, hooks rejected). |
|
|
378
610
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
379
611
|
|
|
@@ -457,8 +689,9 @@ On conflict: top-level `ok` is `false`, the conflicting entry has `ok: false` wi
|
|
|
457
689
|
|-----------|------|-------|
|
|
458
690
|
| `sources` | `string[]` | Source specs. 1–50 entries. Each entry is one of: a full/short SHA, an `A..B` / `A...B` range, or a branch name. Branch names expand to `onto..<branch>` (oldest-first). |
|
|
459
691
|
| `onto` | string | Destination branch. Defaults to the currently checked-out branch. Rejected when HEAD is detached. |
|
|
460
|
-
| `deleteMergedBranches` | boolean | Default `false`. After all commits apply, delete each **branch-kind** source locally
|
|
692
|
+
| `deleteMergedBranches` | boolean | Default `false`. After all commits apply, delete each **branch-kind** source locally. Deletion uses **patch-id equivalence** by default — correct for cherry-pick workflows where SHA differs but diff is identical. Protected names always skipped; never touches remote refs. |
|
|
461
693
|
| `deleteMergedWorktrees` | boolean | Default `false`. After success, remove any local worktree attached to a branch-kind source (`git worktree remove`). Protected tails always skipped. |
|
|
694
|
+
| `strictMergedRefEquality` | boolean | Default `false`. When `true`, branch deletion uses strict SHA-reachability (`git branch -d` ancestry semantics) instead of patch-id equivalence. Use when you need git's exact ancestry guarantee rather than content equivalence. |
|
|
462
695
|
| `workspaceRoot`, `rootIndex`, `format` | — | Standard workspace pick + output format. |
|
|
463
696
|
|
|
464
697
|
### `git_cherry_pick` — JSON shape (`format: "json"`)
|
|
@@ -568,9 +801,9 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
|
|
|
568
801
|
|
|
569
802
|
| Code | Meaning |
|
|
570
803
|
|------|---------|
|
|
571
|
-
| `
|
|
572
|
-
| `
|
|
573
|
-
| `
|
|
804
|
+
| `empty_tag_name` | `tag` trimmed to an empty string. |
|
|
805
|
+
| `unsafe_tag_token` | `tag` contains disallowed characters. |
|
|
806
|
+
| `unsafe_ref_token` | `ref` contains disallowed characters. |
|
|
574
807
|
| `ref_not_found` | `ref` did not resolve to a commit. |
|
|
575
808
|
| `tag_create_failed` | `git tag` failed while creating the tag. |
|
|
576
809
|
| `tag_delete_failed` | `git tag -d` failed. |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rethunk/mcp-multi-root-git",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.6.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,
|
|
@@ -37,6 +37,7 @@
|
|
|
37
37
|
"schema:tools": "bun scripts/generate-tool-parameters-schema.ts",
|
|
38
38
|
"schema:tools:check": "bun scripts/generate-tool-parameters-schema.ts --check",
|
|
39
39
|
"schema:individual": "bun scripts/generate-individual-schemas.ts",
|
|
40
|
+
"schema:individual:check": "bun scripts/generate-individual-schemas.ts --check",
|
|
40
41
|
"publish:preflight": "bun scripts/publish-preflight.ts",
|
|
41
42
|
"test": "bun test src/",
|
|
42
43
|
"test:coverage": "bun test src/ --coverage",
|
|
@@ -68,9 +69,19 @@
|
|
|
68
69
|
"zod": "^4.4.3"
|
|
69
70
|
},
|
|
70
71
|
"devDependencies": {
|
|
71
|
-
"@biomejs/biome": "^2.4.
|
|
72
|
-
"@types/node": "^25.
|
|
72
|
+
"@biomejs/biome": "^2.4.15",
|
|
73
|
+
"@types/node": "^25.9.1",
|
|
73
74
|
"rimraf": "^6.1.3",
|
|
74
75
|
"typescript": "^6.0.3"
|
|
76
|
+
},
|
|
77
|
+
"overrides": {
|
|
78
|
+
"@hono/node-server": "^1.19.14",
|
|
79
|
+
"hono": "^4.12.18",
|
|
80
|
+
"fast-uri": "^3.1.2",
|
|
81
|
+
"postcss": "^8.5.10",
|
|
82
|
+
"vite": "^7.3.2",
|
|
83
|
+
"ip-address": "^10.1.1",
|
|
84
|
+
"axios": "^1.15.2",
|
|
85
|
+
"follow-redirects": "^1.15.12"
|
|
75
86
|
}
|
|
76
87
|
}
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
}
|
|
89
89
|
]
|
|
90
90
|
},
|
|
91
|
-
"description": "Paths to stage, relative to the git root. Each can be a string path or { path, lines } for hunk-level staging."
|
|
91
|
+
"description": "Paths to stage, relative to the git root. Each can be a string path or { path, lines } for hunk-level staging. Deleted files (missing on disk but tracked in HEAD) are staged as removals via `git rm --cached`. Combining { path, lines } with a deleted file is an error."
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
94
|
"required": [
|
|
@@ -50,6 +50,11 @@
|
|
|
50
50
|
"default": false,
|
|
51
51
|
"description": "After success, remove any local worktree attached to a branch-kind source (`git worktree remove`). Protected tails always skipped.",
|
|
52
52
|
"type": "boolean"
|
|
53
|
+
},
|
|
54
|
+
"strictMergedRefEquality": {
|
|
55
|
+
"default": false,
|
|
56
|
+
"description": "When false (default), branch deletion uses patch-id equivalence: a source branch is deleted when every commit it contains has a content-equivalent commit on the destination (same diff, different SHA — the normal cherry-pick outcome). Set to true to require strict ref ancestry (`git branch -d` semantics), which will refuse deletion after a cherry-pick because the SHA differs.",
|
|
57
|
+
"type": "boolean"
|
|
53
58
|
}
|
|
54
59
|
},
|
|
55
60
|
"required": [
|
|
@@ -57,7 +62,8 @@
|
|
|
57
62
|
"format",
|
|
58
63
|
"sources",
|
|
59
64
|
"deleteMergedBranches",
|
|
60
|
-
"deleteMergedWorktrees"
|
|
65
|
+
"deleteMergedWorktrees",
|
|
66
|
+
"strictMergedRefEquality"
|
|
61
67
|
],
|
|
62
68
|
"additionalProperties": false
|
|
63
69
|
}
|
package/schemas/git_diff.json
CHANGED
|
@@ -14,19 +14,6 @@
|
|
|
14
14
|
"minimum": 0,
|
|
15
15
|
"maximum": 9007199254740991
|
|
16
16
|
},
|
|
17
|
-
"allWorkspaceRoots": {
|
|
18
|
-
"default": false,
|
|
19
|
-
"description": "Fan out across all MCP file roots.",
|
|
20
|
-
"type": "boolean"
|
|
21
|
-
},
|
|
22
|
-
"absoluteGitRoots": {
|
|
23
|
-
"description": "Absolute paths to git repo roots. Use for many sibling clones under a non-git parent directory.",
|
|
24
|
-
"maxItems": 256,
|
|
25
|
-
"type": "array",
|
|
26
|
-
"items": {
|
|
27
|
-
"type": "string"
|
|
28
|
-
}
|
|
29
|
-
},
|
|
30
17
|
"format": {
|
|
31
18
|
"default": "markdown",
|
|
32
19
|
"type": "string",
|
|
@@ -54,7 +41,6 @@
|
|
|
54
41
|
}
|
|
55
42
|
},
|
|
56
43
|
"required": [
|
|
57
|
-
"allWorkspaceRoots",
|
|
58
44
|
"format",
|
|
59
45
|
"staged"
|
|
60
46
|
],
|
|
@@ -14,11 +14,6 @@
|
|
|
14
14
|
"minimum": 0,
|
|
15
15
|
"maximum": 9007199254740991
|
|
16
16
|
},
|
|
17
|
-
"allWorkspaceRoots": {
|
|
18
|
-
"default": false,
|
|
19
|
-
"description": "Fan out across all MCP file roots.",
|
|
20
|
-
"type": "boolean"
|
|
21
|
-
},
|
|
22
17
|
"absoluteGitRoots": {
|
|
23
18
|
"description": "Absolute paths to git repo roots. Use for many sibling clones under a non-git parent directory.",
|
|
24
19
|
"maxItems": 256,
|
|
@@ -66,7 +61,6 @@
|
|
|
66
61
|
}
|
|
67
62
|
},
|
|
68
63
|
"required": [
|
|
69
|
-
"allWorkspaceRoots",
|
|
70
64
|
"format",
|
|
71
65
|
"maxLinesPerFile",
|
|
72
66
|
"maxFiles"
|
package/schemas/git_log.json
CHANGED
|
@@ -29,10 +29,12 @@
|
|
|
29
29
|
},
|
|
30
30
|
"format": {
|
|
31
31
|
"default": "markdown",
|
|
32
|
+
"description": "`markdown` (default): headed sections per root. `json`: structured groups array. `oneline`: `<sha7> <subject>` per line, no headers (single-root) or `### repo (branch)` separator per group (multi-root). Lowest-token option for post-commit verification.",
|
|
32
33
|
"type": "string",
|
|
33
34
|
"enum": [
|
|
34
35
|
"markdown",
|
|
35
|
-
"json"
|
|
36
|
+
"json",
|
|
37
|
+
"oneline"
|
|
36
38
|
]
|
|
37
39
|
},
|
|
38
40
|
"since": {
|
package/schemas/git_show.json
CHANGED
|
@@ -266,10 +266,12 @@
|
|
|
266
266
|
},
|
|
267
267
|
"format": {
|
|
268
268
|
"default": "markdown",
|
|
269
|
+
"description": "`markdown` (default): headed sections per root. `json`: structured groups array. `oneline`: `<sha7> <subject>` per line, no headers (single-root) or `### repo (branch)` separator per group (multi-root). Lowest-token option for post-commit verification.",
|
|
269
270
|
"type": "string",
|
|
270
271
|
"enum": [
|
|
271
272
|
"markdown",
|
|
272
|
-
"json"
|
|
273
|
+
"json",
|
|
274
|
+
"oneline"
|
|
273
275
|
]
|
|
274
276
|
},
|
|
275
277
|
"since": {
|
|
@@ -324,11 +326,6 @@
|
|
|
324
326
|
"minimum": 0,
|
|
325
327
|
"maximum": 9007199254740991
|
|
326
328
|
},
|
|
327
|
-
"allWorkspaceRoots": {
|
|
328
|
-
"default": false,
|
|
329
|
-
"description": "Fan out across all MCP file roots.",
|
|
330
|
-
"type": "boolean"
|
|
331
|
-
},
|
|
332
329
|
"absoluteGitRoots": {
|
|
333
330
|
"description": "Absolute paths to git repo roots. Use for many sibling clones under a non-git parent directory.",
|
|
334
331
|
"maxItems": 256,
|
|
@@ -376,7 +373,6 @@
|
|
|
376
373
|
}
|
|
377
374
|
},
|
|
378
375
|
"required": [
|
|
379
|
-
"allWorkspaceRoots",
|
|
380
376
|
"format",
|
|
381
377
|
"maxLinesPerFile",
|
|
382
378
|
"maxFiles"
|
|
@@ -397,19 +393,6 @@
|
|
|
397
393
|
"minimum": 0,
|
|
398
394
|
"maximum": 9007199254740991
|
|
399
395
|
},
|
|
400
|
-
"allWorkspaceRoots": {
|
|
401
|
-
"default": false,
|
|
402
|
-
"description": "Fan out across all MCP file roots.",
|
|
403
|
-
"type": "boolean"
|
|
404
|
-
},
|
|
405
|
-
"absoluteGitRoots": {
|
|
406
|
-
"description": "Absolute paths to git repo roots. Use for many sibling clones under a non-git parent directory.",
|
|
407
|
-
"maxItems": 256,
|
|
408
|
-
"type": "array",
|
|
409
|
-
"items": {
|
|
410
|
-
"type": "string"
|
|
411
|
-
}
|
|
412
|
-
},
|
|
413
396
|
"format": {
|
|
414
397
|
"default": "markdown",
|
|
415
398
|
"type": "string",
|
|
@@ -437,7 +420,6 @@
|
|
|
437
420
|
}
|
|
438
421
|
},
|
|
439
422
|
"required": [
|
|
440
|
-
"allWorkspaceRoots",
|
|
441
423
|
"format",
|
|
442
424
|
"staged"
|
|
443
425
|
],
|
|
@@ -467,6 +449,7 @@
|
|
|
467
449
|
},
|
|
468
450
|
"ref": {
|
|
469
451
|
"type": "string",
|
|
452
|
+
"minLength": 1,
|
|
470
453
|
"description": "Commit reference (SHA, branch, tag, or any git rev-spec)."
|
|
471
454
|
},
|
|
472
455
|
"path": {
|
|
@@ -674,7 +657,7 @@
|
|
|
674
657
|
}
|
|
675
658
|
]
|
|
676
659
|
},
|
|
677
|
-
"description": "Paths to stage, relative to the git root. Each can be a string path or { path, lines } for hunk-level staging."
|
|
660
|
+
"description": "Paths to stage, relative to the git root. Each can be a string path or { path, lines } for hunk-level staging. Deleted files (missing on disk but tracked in HEAD) are staged as removals via `git rm --cached`. Combining { path, lines } with a deleted file is an error."
|
|
678
661
|
}
|
|
679
662
|
},
|
|
680
663
|
"required": [
|
|
@@ -884,6 +867,11 @@
|
|
|
884
867
|
"default": false,
|
|
885
868
|
"description": "After success, remove any local worktree attached to a branch-kind source (`git worktree remove`). Protected tails always skipped.",
|
|
886
869
|
"type": "boolean"
|
|
870
|
+
},
|
|
871
|
+
"strictMergedRefEquality": {
|
|
872
|
+
"default": false,
|
|
873
|
+
"description": "When false (default), branch deletion uses patch-id equivalence: a source branch is deleted when every commit it contains has a content-equivalent commit on the destination (same diff, different SHA — the normal cherry-pick outcome). Set to true to require strict ref ancestry (`git branch -d` semantics), which will refuse deletion after a cherry-pick because the SHA differs.",
|
|
874
|
+
"type": "boolean"
|
|
887
875
|
}
|
|
888
876
|
},
|
|
889
877
|
"required": [
|
|
@@ -891,7 +879,8 @@
|
|
|
891
879
|
"format",
|
|
892
880
|
"sources",
|
|
893
881
|
"deleteMergedBranches",
|
|
894
|
-
"deleteMergedWorktrees"
|
|
882
|
+
"deleteMergedWorktrees",
|
|
883
|
+
"strictMergedRefEquality"
|
|
895
884
|
],
|
|
896
885
|
"additionalProperties": false
|
|
897
886
|
},
|