@rethunk/mcp-multi-root-git 2.9.0 → 3.1.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.
Files changed (56) hide show
  1. package/AGENTS.md +4 -6
  2. package/CHANGELOG.md +65 -0
  3. package/HUMANS.md +4 -4
  4. package/dist/server/batch-commit-tool.js +24 -15
  5. package/dist/server/coverage.js +5 -1
  6. package/dist/server/error-codes.js +6 -9
  7. package/dist/server/git-blame-tool.js +60 -16
  8. package/dist/server/git-branch-list-tool.js +1 -7
  9. package/dist/server/git-cherry-pick-tool.js +1 -1
  10. package/dist/server/git-diff-summary-tool.js +3 -3
  11. package/dist/server/git-diff-tool.js +1 -4
  12. package/dist/server/git-fetch-tool.js +3 -12
  13. package/dist/server/git-inventory-tool.js +4 -4
  14. package/dist/server/git-log-tool.js +2 -2
  15. package/dist/server/git-merge-tool.js +1 -1
  16. package/dist/server/git-parity-tool.js +2 -2
  17. package/dist/server/git-push-tool.js +1 -1
  18. package/dist/server/git-reflog-tool.js +1 -7
  19. package/dist/server/git-reset-soft-tool.js +1 -1
  20. package/dist/server/git-show-tool.js +2 -11
  21. package/dist/server/git-stash-tool.js +3 -12
  22. package/dist/server/git-status-tool.js +2 -2
  23. package/dist/server/git-tag-tool.js +1 -7
  24. package/dist/server/git-worktree-tool.js +3 -7
  25. package/dist/server/git.js +2 -8
  26. package/dist/server/list-presets-tool.js +2 -8
  27. package/dist/server/roots.js +41 -80
  28. package/dist/server/schemas.js +11 -19
  29. package/dist/server/tool-parameter-schemas.js +3 -3
  30. package/dist/server.js +1 -1
  31. package/docs/mcp-tools.md +117 -136
  32. package/package.json +6 -5
  33. package/schemas/batch_commit.json +7 -19
  34. package/schemas/git_blame.json +12 -10
  35. package/schemas/git_branch_list.json +2 -8
  36. package/schemas/git_cherry_pick.json +5 -17
  37. package/schemas/git_diff.json +7 -13
  38. package/schemas/git_diff_summary.json +2 -16
  39. package/schemas/git_fetch.json +1 -7
  40. package/schemas/git_inventory.json +18 -23
  41. package/schemas/git_log.json +24 -29
  42. package/schemas/git_merge.json +3 -15
  43. package/schemas/git_parity.json +18 -23
  44. package/schemas/git_push.json +1 -13
  45. package/schemas/git_reflog.json +1 -7
  46. package/schemas/git_reset_soft.json +2 -14
  47. package/schemas/git_show.json +1 -7
  48. package/schemas/git_stash_apply.json +3 -9
  49. package/schemas/git_stash_list.json +1 -7
  50. package/schemas/git_status.json +18 -23
  51. package/schemas/git_tag.json +1 -7
  52. package/schemas/git_worktree_add.json +4 -16
  53. package/schemas/git_worktree_list.json +1 -7
  54. package/schemas/git_worktree_remove.json +2 -14
  55. package/schemas/list_presets.json +18 -23
  56. package/tool-parameters.schema.json +122 -297
package/docs/mcp-tools.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # MCP tools and resources (canonical reference)
2
2
 
3
- Single source of truth for **registered tool ids**, **client naming**, **JSON output shape**, **resource URI**, and **workspace root resolution**.
3
+ Single source of truth for **registered tool ids**, **client naming**, **JSON output shape**, **resource URI**, and **root resolution**.
4
4
  **Install and MCP clients (only canonical location):** [install.md](install.md). **Preset file, dev, CI, publishing:** [HUMANS.md](../HUMANS.md). **Implementation layout (`src/server/` + entry [`server.ts`](../src/server.ts)), contract bumps:** [AGENTS.md](../AGENTS.md).
5
5
 
6
6
  ## Naming
@@ -11,29 +11,29 @@ MCP clients expose tools as `{serverName}_{toolName}`. With the server registere
11
11
 
12
12
  | Short id | Client id (server `rethunk-git`) | Purpose |
13
13
  |----------|-----------------------------------|---------|
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
- | `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 (`absoluteGitRoots` for sibling clone batches). **Read-only.** |
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` (`markdown`/`json`/`oneline`). **Read-only.** |
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, scoped to one or more paths with configurable context width. Args: `workspaceRoot`, `rootIndex`, `format`, `base?`, `head?`, `path?`, `paths?`, `unified?`, `staged?`. No `absoluteGitRoots` or `allWorkspaceRoots`. **Read-only.** |
21
- | `git_show` | `rethunk-git_git_show` | Inspect one commit or ref. Returns commit message plus diff (or `--stat` diffstat), or file content at `path` for that ref. Args: `ref`, `path?`, `paths?`, `stat?`, plus single-repo workspace pick + `format`. **Read-only.** |
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_blame` | `rethunk-git_git_blame` | Line-by-line authorship for a file: commit SHA, author, date, summary per line. Args: `path` (required), `ref?`, `startLine?`, `endLine?`, plus single-repo workspace pick + `format`. **Read-only.** |
25
- | `git_branch_list` | `rethunk-git_git_branch_list` | List local branches (sha, current marker, upstream); optional `includeRemotes` adds remote-tracking branches. Args: `includeRemotes?`, plus single-repo workspace pick + `format`. **Read-only.** |
26
- | `git_reflog` | `rethunk-git_git_reflog` | Show the reflog for a ref (default `HEAD`) — recent HEAD movements with selector, SHA, and message. Args: `ref?`, `maxEntries?`, plus single-repo workspace pick + `format`. **Read-only.** |
27
- | `git_fetch` | `rethunk-git_git_fetch` | Fetch from a remote without modifying the working tree. Updates refs only and reports updated/new refs, plus structured `updated`/`created`/`pruned` deltas on git ≥ 2.41. Args: `remote?`, `branch?`, `prune?`, `tags?`, plus single-repo workspace pick + `format`. **Mutating — refs only.** |
28
- | `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.** |
29
- | `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.** |
30
- | `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.** |
31
- | `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.** |
32
- | `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.** |
33
- | `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.** |
34
- | `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.** |
35
- | `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.** |
36
- | `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.** |
14
+ | `git_status` | `rethunk-git_git_status` | `git status --short -b` per MCP root and optional submodules (`includeSubmodules`); parallel submodule status. Args: `includeSubmodules`, `root`, `format`. **Read-only.** |
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 `root` (array form 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 `root`. **Read-only.** |
17
+ | `list_presets` | `rethunk-git_list_presets` | List preset names/counts from `.rethunk/git-mcp-presets.json`; invalid JSON/schema surface as errors. `root` + `format` only. **Read-only.** |
18
+ | `git_log` | `rethunk-git_git_log` | Path-filtered, time-windowed `git log` across one or more roots. Returns commit history with author, date, subject, and shortstat. Args: `since`, `paths`, `grep`, `author`, `maxCommits`, `branch`, plus `root` + `format` (`markdown`/`json`/`oneline`). **Read-only.** |
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 `workspaceRoot` + `format`. **Read-only.** |
20
+ | `git_diff` | `rethunk-git_git_diff` | Raw diff text for a single repo. Supports unstaged, staged, or `base..head` ranges, scoped to one or more paths with configurable context width. Args: `workspaceRoot`, `format`, `base?`, `head?`, `path?`, `paths?`, `unified?`, `staged?`. **Read-only.** |
21
+ | `git_show` | `rethunk-git_git_show` | Inspect one commit or ref. Returns commit message plus diff (or `--stat` diffstat), or file content at `path` for that ref. Args: `ref`, `path?`, `paths?`, `stat?`, plus `workspaceRoot` + `format`. **Read-only.** |
22
+ | `git_worktree_list` | `rethunk-git_git_worktree_list` | List all worktrees (`git worktree list --porcelain`). `workspaceRoot` + `format`. **Read-only.** |
23
+ | `git_stash_list` | `rethunk-git_git_stash_list` | List `git stash` entries for one repo. Args: `workspaceRoot` + `format`. **Read-only.** |
24
+ | `git_blame` | `rethunk-git_git_blame` | File authorship grouped into contiguous same-commit line runs (SHA, author, date, summary once per run). Args: `path` (required), `ref?`, `startLine?`, `endLine?`, `maxLines?`, plus `workspaceRoot` + `format`. **Read-only.** |
25
+ | `git_branch_list` | `rethunk-git_git_branch_list` | List local branches (sha, current marker, upstream); optional `includeRemotes` adds remote-tracking branches. Args: `includeRemotes?`, plus `workspaceRoot` + `format`. **Read-only.** |
26
+ | `git_reflog` | `rethunk-git_git_reflog` | Show the reflog for a ref (default `HEAD`) — recent HEAD movements with selector, SHA, and message. Args: `ref?`, `maxEntries?`, plus `workspaceRoot` + `format`. **Read-only.** |
27
+ | `git_fetch` | `rethunk-git_git_fetch` | Fetch from a remote without modifying the working tree. Updates refs only and reports updated/new refs, plus structured `updated`/`created`/`pruned` deltas on git ≥ 2.41. Args: `remote?`, `branch?`, `prune?`, `tags?`, plus `workspaceRoot` + `format`. **Mutating — refs only.** |
28
+ | `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. `workspaceRoot` + `format`. **Mutating.** |
29
+ | `git_tag` | `rethunk-git_git_tag` | Create/delete annotated or lightweight tags for one repo. Args: `tag`, `message?`, `ref?`, `delete?`, plus `workspaceRoot` + `format`. **Mutating.** |
30
+ | `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 `workspaceRoot` + `format`. **Mutating.** |
31
+ | `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 `workspaceRoot` + `format`. **Mutating.** |
32
+ | `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 `workspaceRoot` + `format`. **Mutating — not idempotent.** |
33
+ | `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 `workspaceRoot` + `format`. **Mutating — not idempotent.** |
34
+ | `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 + `workspaceRoot` + `format`. **Mutating.** |
35
+ | `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?` + `workspaceRoot` + `format`. **Mutating.** |
36
+ | `git_stash_apply` | `rethunk-git_git_stash_apply` | Apply or pop a stash entry for one repo. Args: `index?`, `pop?`, plus `workspaceRoot` + `format`. **Mutating.** |
37
37
 
38
38
  Pass **`format: "json"`** on any tool for structured JSON instead of markdown (default).
39
39
 
@@ -44,10 +44,7 @@ Pass **`format: "json"`** on any tool for structured JSON instead of markdown (d
44
44
  | Parameter | Type | Default | Notes |
45
45
  |-----------|------|---------|-------|
46
46
  | `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. |
47
- | `workspaceRoot` | string | — | Explicit root; highest priority. |
48
- | `rootIndex` | int | — | Pick one of several MCP roots (0-based). |
49
- | `allWorkspaceRoots` | boolean | `false` | Fan out across all MCP roots. |
50
- | `absoluteGitRoots` | string[] | — | Explicit list of absolute paths; replaces normal workspace pick. Max 256. Read-only tools only. |
47
+ | `root` | string \| string[] \| `"*"` | — | Repo path (string), explicit list of repo paths (array, max 256), or `"*"` for every MCP root. Default: first MCP root / cwd. |
51
48
  | `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
52
49
 
53
50
  ### `git_status` — JSON shape (`format: "json"`)
@@ -76,11 +73,9 @@ Error payloads appear as top-level JSON or inline in individual repo rows (`ok:
76
73
  | `not_a_git_repository` | repo row `statusText` | Root is not inside a git repository. |
77
74
  | `(submodule path escapes repository — rejected)` | repo row `statusText` | `.gitmodules` path resolves outside the git toplevel (security guard). |
78
75
  | `(no .git — submodule not checked out?)` | repo row `statusText` | Submodule directory exists but has no `.git` — not initialized. |
79
- | `root_index_out_of_range` | top-level | `rootIndex` exceeds the number of MCP file roots. |
80
- | `absolute_git_roots_exclusive` | top-level | `absoluteGitRoots` combined with `workspaceRoot`, `rootIndex`, or `allWorkspaceRoots: true`. |
81
- | `invalid_absolute_git_root` | top-level | An `absoluteGitRoots` entry is not a git-recognized directory. |
82
- | `absolute_git_roots_too_many` | top-level | More than 256 entries in `absoluteGitRoots`. |
83
- | `absolute_git_roots_empty` | top-level | `absoluteGitRoots` resolved to zero toplevels. |
76
+ | `invalid_root_path` | top-level | A `root` array entry is empty or not a git-recognized directory. |
77
+ | `root_list_too_many` | top-level | More than 256 entries in the `root` array. |
78
+ | `root_list_empty` | top-level | The `root` array resolved to zero git toplevels. |
84
79
 
85
80
  ---
86
81
 
@@ -88,16 +83,13 @@ Error payloads appear as top-level JSON or inline in individual repo rows (`ok:
88
83
 
89
84
  | Parameter | Type | Default | Notes |
90
85
  |-----------|------|---------|-------|
91
- | `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`. |
92
- | `preset` | string | — | Preset name from `.rethunk/git-mcp-presets.json`. Loads `nestedRoots` from the preset's entry. Cannot combine with `absoluteGitRoots`. |
86
+ | `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 an array `root` or `preset`. |
87
+ | `preset` | string | — | Preset name from `.rethunk/git-mcp-presets.json`. Loads `nestedRoots` from the preset's entry. Cannot combine with an array `root`. |
93
88
  | `presetMerge` | boolean | `false` | When `true`, merge inline `nestedRoots` with preset roots instead of replacing. |
94
89
  | `remote` | string | — | Fixed remote for ahead/behind tracking. Must be paired with `branch`. |
95
90
  | `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. |
96
91
  | `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. |
97
- | `workspaceRoot` | string | — | Explicit root; highest priority. |
98
- | `rootIndex` | int | — | Pick one of several MCP roots (0-based). |
99
- | `allWorkspaceRoots` | boolean | `false` | Fan out across all MCP roots. |
100
- | `absoluteGitRoots` | string[] | — | Explicit list; replaces normal workspace pick. Cannot combine with `nestedRoots` or `preset`. |
92
+ | `root` | string \| string[] \| `"*"` | — | Repo path (string), explicit list of repo paths (array, max 256), or `"*"` for every MCP root. Default: first MCP root / cwd. | Array form cannot combine with `nestedRoots` or `preset`.
101
93
  | `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
102
94
 
103
95
  ### `git_inventory` — JSON shape (`format: "json"`)
@@ -123,7 +115,7 @@ Error payloads appear as top-level JSON or inline in individual repo rows (`ok:
123
115
  }
124
116
  ```
125
117
 
126
- `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.
118
+ `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 *Field omission* for `entries[*]` optional fields.
127
119
 
128
120
  ### `git_inventory` — error codes
129
121
 
@@ -132,13 +124,11 @@ Error payloads appear as top-level JSON or inline in individual repo rows (`ok:
132
124
  | `git_not_found` | `git` binary not on `PATH`. |
133
125
  | `remote_branch_mismatch` | Only one of `remote` / `branch` was provided; supply both or neither. |
134
126
  | `invalid_remote_or_branch` | `remote` or `branch` contains characters outside the safe token set. |
135
- | `absolute_git_roots_nested_or_preset_conflict` | `absoluteGitRoots` combined with `nestedRoots` or `preset`. |
136
- | `absolute_git_roots_exclusive` | `absoluteGitRoots` combined with `workspaceRoot`, `rootIndex`, or `allWorkspaceRoots: true`. |
137
- | `absolute_git_roots_preset_conflict` | `absoluteGitRoots` combined with a `preset` argument. |
138
- | `invalid_absolute_git_root` | An `absoluteGitRoots` entry is not a git-recognized directory. |
139
- | `absolute_git_roots_too_many` | More than 256 entries in `absoluteGitRoots`. |
140
- | `absolute_git_roots_empty` | `absoluteGitRoots` resolved to zero toplevels. |
141
- | `root_index_out_of_range` | `rootIndex` exceeds the number of MCP file roots. |
127
+ | `root_list_nested_or_preset_conflict` | `root` array combined with `nestedRoots` or `preset`. |
128
+ | `root_list_preset_conflict` | `root` array combined with a `preset` argument. |
129
+ | `invalid_root_path` | A `root` array entry is empty or not a git-recognized directory. |
130
+ | `root_list_too_many` | More than 256 entries in the `root` array. |
131
+ | `root_list_empty` | The `root` array resolved to zero git toplevels. |
142
132
  | `preset_not_found` | Named preset does not exist in the preset file. |
143
133
  | `invalid_json` | Preset file contains invalid JSON. |
144
134
  | `invalid_schema` | Preset file fails schema validation. |
@@ -154,10 +144,7 @@ Skip entries (individual repos that could not be inventoried) appear inline in `
154
144
  | `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`). |
155
145
  | `preset` | string | Preset name from `.rethunk/git-mcp-presets.json`. Loads `parityPairs` from the preset's entry. |
156
146
  | `presetMerge` | boolean | Default `false`. When `true`, merge inline `pairs` with preset pairs instead of replacing. |
157
- | `workspaceRoot` | string | Explicit root; highest priority. |
158
- | `rootIndex` | int | Pick one of several MCP roots (0-based). |
159
- | `allWorkspaceRoots` | boolean | `false` | Fan out across all MCP roots. |
160
- | `absoluteGitRoots` | string[] | Explicit list of absolute paths; replaces normal workspace pick. Useful for checking parity across sibling clones. |
147
+ | `root` | string \| string[] \| `"*"` | Repo path (string), explicit list of repo paths (array, max 256), or `"*"` for every MCP root. Default: first MCP root / cwd. |
161
148
  | `format` | `"markdown"` \| `"json"` | Output format. Default: `"markdown"`. |
162
149
 
163
150
  ### `git_parity` — JSON shape (`format: "json"`)
@@ -196,12 +183,10 @@ Skip entries (individual repos that could not be inventoried) appear inline in `
196
183
  |------|---------|
197
184
  | `git_not_found` | `git` binary not on `PATH`. |
198
185
  | `no_pairs` | Neither inline `pairs` nor a preset with `parityPairs` was supplied. |
199
- | `absolute_git_roots_exclusive` | `absoluteGitRoots` combined with `workspaceRoot`, `rootIndex`, or `allWorkspaceRoots: true`. |
200
- | `absolute_git_roots_preset_conflict` | `absoluteGitRoots` combined with a `preset` argument. |
201
- | `invalid_absolute_git_root` | An `absoluteGitRoots` entry is not a git-recognized directory. |
202
- | `absolute_git_roots_too_many` | More than 256 entries in `absoluteGitRoots`. |
203
- | `absolute_git_roots_empty` | `absoluteGitRoots` resolved to zero toplevels. |
204
- | `root_index_out_of_range` | `rootIndex` exceeds the number of MCP file roots. |
186
+ | `root_list_preset_conflict` | `root` array combined with a `preset` argument. |
187
+ | `invalid_root_path` | A `root` array entry is empty or not a git-recognized directory. |
188
+ | `root_list_too_many` | More than 256 entries in the `root` array. |
189
+ | `root_list_empty` | The `root` array resolved to zero git toplevels. |
205
190
  | `preset_not_found` | Named preset does not exist in the preset file. |
206
191
  | `invalid_json` | Preset file contains invalid JSON. |
207
192
  | `invalid_schema` | Preset file fails schema validation. |
@@ -214,10 +199,7 @@ Path-escape and `rev-parse` failures are reported inline in `pairs[*].error`, no
214
199
 
215
200
  | Parameter | Type | Notes |
216
201
  |-----------|------|-------|
217
- | `workspaceRoot` | string | Explicit root; highest priority. |
218
- | `rootIndex` | int | Pick one of several MCP roots (0-based). |
219
- | `allWorkspaceRoots` | boolean | Default `false`. Fan out across all MCP roots. |
220
- | `absoluteGitRoots` | string[] | Explicit list; replaces normal workspace pick. |
202
+ | `root` | string \| string[] \| `"*"` | Repo path (string), explicit list of repo paths (array, max 256), or `"*"` for every MCP root. Default: first MCP root / cwd. |
221
203
  | `format` | `"markdown"` \| `"json"` | Output format. Default: `"markdown"`. |
222
204
 
223
205
  ### `list_presets` — JSON shape (`format: "json"`)
@@ -252,21 +234,21 @@ Path-escape and `rev-parse` failures are reported inline in `pairs[*].error`, no
252
234
  | `not_a_git_repository` | Root is not inside a git repository (reported inline in the `roots[*].error` field, not top-level). |
253
235
  | `invalid_json` | Preset file is not valid JSON (inline in `roots[*].error`). |
254
236
  | `invalid_schema` | Preset file fails schema validation (inline in `roots[*].error`). |
255
- | `absolute_git_roots_exclusive` | `absoluteGitRoots` combined with `workspaceRoot`, `rootIndex`, or `allWorkspaceRoots: true`. |
256
- | `invalid_absolute_git_root` | An `absoluteGitRoots` entry is not a git-recognized directory. |
257
- | `absolute_git_roots_too_many` | More than 256 entries in `absoluteGitRoots`. |
258
- | `absolute_git_roots_empty` | `absoluteGitRoots` resolved to zero toplevels. |
259
- | `root_index_out_of_range` | `rootIndex` exceeds the number of MCP file roots. |
237
+ | `invalid_root_path` | A `root` array entry is empty or not a git-recognized directory. |
238
+ | `root_list_too_many` | More than 256 entries in the `root` array. |
239
+ | `root_list_empty` | The `root` array resolved to zero git toplevels. |
260
240
 
261
241
  ---
262
242
 
263
243
  ## JSON responses
264
244
 
265
- 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`**.
245
+ Tool JSON bodies are minified and contain only the payload — no `rethunkGitMcp` envelope. Current `MCP_JSON_FORMAT_VERSION` is **`"4"`** (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`**.
246
+
247
+ v4 changes from v3: `git_blame` output is run-length grouped (`lines[]` with per-line commit metadata → `groups[]` with metadata once per contiguous same-commit run); `git_diff_summary` per-file `truncated` is omitted when `false`.
266
248
 
267
249
  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.
268
250
 
269
- ### v2/v3 field omission (consumer contract)
251
+ ### Field omission (consumer contract, v2+)
270
252
 
271
253
  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`.
272
254
 
@@ -297,9 +279,7 @@ To keep responses compact, **optional fields are omitted when they would be empt
297
279
  | `author` | string | — | Filter by author name or email (`--author=`). |
298
280
  | `maxCommits` | int | `50` | Max commits per root. Hard cap: `500`. |
299
281
  | `branch` | string | `HEAD` | Ref/branch to log from. |
300
- | `workspaceRoot` | string | — | Explicit root; highest priority. |
301
- | `rootIndex` | int | — | Pick one of several MCP roots (0-based). |
302
- | `allWorkspaceRoots` | boolean | `false` | Fan out across all MCP roots. |
282
+ | `root` | string \| string[] \| `"*"` | — | Repo path (string), explicit list of repo paths (array, max 256), or `"*"` for every MCP root. Default: first MCP root / cwd. |
303
283
  | `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. |
304
284
 
305
285
  ### `git_log` — JSON shape (`format: "json"`)
@@ -340,22 +320,19 @@ v2 field-omission rules still apply: `filesChanged`, `insertions`, `deletions` o
340
320
  | `invalid_paths` | One of the `paths` entries contains shell metacharacters and was rejected. |
341
321
  | `unsafe_ref_token` | `branch` contains characters outside the argv-safe subset. |
342
322
  | `git_log_failed` | `git log` exited non-zero (e.g. unknown branch ref). |
343
- | `root_index_out_of_range` | `rootIndex` exceeds the number of MCP file roots. |
344
- | `absolute_git_roots_exclusive` | `absoluteGitRoots` was combined with `workspaceRoot`, `rootIndex`, or `allWorkspaceRoots: true`. |
345
- | `absolute_git_roots_preset_conflict` | `absoluteGitRoots` was combined with a `preset` argument (root resolution). |
346
- | `invalid_absolute_git_root` | An `absoluteGitRoots` entry is empty, not inside a git worktree, or not a directory git recognizes. |
347
- | `absolute_git_roots_too_many` | More than 256 entries in `absoluteGitRoots`. |
348
- | `absolute_git_roots_empty` | `absoluteGitRoots` produced zero git toplevels after resolution. |
349
- | `absolute_git_roots_single_repo_only` | A single-repo tool received `absoluteGitRoots` resolving to more than one distinct git toplevel. |
323
+ | `root_list_preset_conflict` | `root` array was combined with a `preset` argument (root resolution). |
324
+ | `invalid_root_path` | A `root` array entry is empty, not inside a git worktree, or not a directory git recognizes. |
325
+ | `root_list_too_many` | More than 256 entries in the `root` array. |
326
+ | `root_list_empty` | The `root` array produced zero git toplevels after resolution. |
350
327
 
351
- ### `git_parity` — `absoluteGitRoots` example
328
+ ### `git_parity` — `root` array example
352
329
 
353
- Use **`absoluteGitRoots`** when the same parity pair should be checked across sibling clones that are not all MCP workspace roots:
330
+ Pass a `root` array when the same parity pair should be checked across sibling clones that are not all MCP workspace roots:
354
331
 
355
332
  ```json
356
333
  {
357
334
  "format": "json",
358
- "absoluteGitRoots": [
335
+ "root": [
359
336
  "/usr/local/src/com.github/Rethunk-AI/mcp-multi-root-git",
360
337
  "/usr/local/src/com.github/Rethunk-AI/rethunk-github-mcp"
361
338
  ],
@@ -363,7 +340,7 @@ Use **`absoluteGitRoots`** when the same parity pair should be checked across si
363
340
  }
364
341
  ```
365
342
 
366
- The response contains one **`parity[]`** entry per resolved git toplevel. `absoluteGitRoots` cannot be combined with `preset`; pass inline `pairs` for sibling-clone batches.
343
+ The response contains one **`parity[]`** entry per resolved git toplevel. An array `root` cannot be combined with `preset`; pass inline `pairs` for sibling-clone batches.
367
344
 
368
345
  ### `git_diff_summary` — parameters
369
346
 
@@ -374,8 +351,7 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
374
351
  | `maxLinesPerFile` | int | `50` | Max diff lines to include per file (1–2000). |
375
352
  | `maxFiles` | int | `30` | Max files to include in output (1–500). |
376
353
  | `excludePatterns` | string[] | lock files, dist, vendor | Glob patterns to exclude. Defaults to `*.lock`, `*.lockb`, `bun.lock`, `package-lock.json`, `yarn.lock`, `pnpm-lock.yaml`, `*.min.js`, `*.min.css`, `vendor/**`, `node_modules/**`, `dist/**`. Pass an empty array to disable. |
377
- | `workspaceRoot` | string | — | Explicit root; highest priority. |
378
- | `rootIndex` | int | — | Pick one of several MCP roots (0-based). |
354
+ | `workspaceRoot` | string | — | Repo path. Default: first MCP root / cwd. |
379
355
  | `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
380
356
 
381
357
  ### `git_diff_summary` — JSON shape (`format: "json"`)
@@ -391,7 +367,6 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
391
367
  "status": "modified",
392
368
  "additions": 8,
393
369
  "deletions": 3,
394
- "truncated": false,
395
370
  "diff": "@@ -1,3 +1,8 @@\n-const x = 1;\n+const x = 2;"
396
371
  }],
397
372
  "truncatedFiles": 1,
@@ -399,7 +374,7 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
399
374
  }
400
375
  ```
401
376
 
402
- `status` is one of `"modified"`, `"added"`, `"deleted"`, `"renamed"`. `oldPath` is present only for renamed files. `truncatedFiles` and `excludedFiles` are omitted when zero/empty (v2 field-omission contract).
377
+ `status` is one of `"modified"`, `"added"`, `"deleted"`, `"renamed"`. `oldPath` is present only for renamed files. Per-file `truncated` is present (`true`) only when the diff body was cut at `maxLinesPerFile` (v4). `truncatedFiles` and `excludedFiles` are omitted when zero/empty (field-omission contract).
403
378
 
404
379
  ### `git_diff_summary` — error codes
405
380
 
@@ -422,11 +397,10 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
422
397
  | `paths` | string[] | — | Multiple file paths to scope the diff; unioned with `path` (deduped). Each confined to the repo. |
423
398
  | `unified` | integer | — | Context lines around each change (passed as `-U<n>`, 0–100). Omit for git's default (3). |
424
399
  | `staged` | boolean | `false` | When `true`, runs `git diff --staged`. Ignored when `base` is provided. |
425
- | `workspaceRoot` | string | — | Explicit root; highest priority. |
426
- | `rootIndex` | int | — | Pick one of several MCP roots (0-based). |
400
+ | `workspaceRoot` | string | — | Repo path. Default: first MCP root / cwd. |
427
401
  | `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
428
402
 
429
- `git_diff` is a single-repo tool. It does not accept `absoluteGitRoots` or `allWorkspaceRoots`; use `workspaceRoot` or `rootIndex` to select the target repo.
403
+ `git_diff` is a single-repo tool; use `workspaceRoot` to select the target repo.
430
404
 
431
405
  ### `git_diff` — JSON shape (`format: "json"`)
432
406
 
@@ -456,7 +430,7 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
456
430
  | `path` | string | Optional single path. When provided, the response shows that path's content at `ref` instead of the full commit diff. |
457
431
  | `stat` | boolean | When `true`, runs `git show --stat` — commit message plus per-file diffstat, no full patch (`statOutput` in JSON). |
458
432
  | `paths` | string[] | Filter the shown patch/stat to these repo-relative paths; unioned with `path`. Each confined to the repo. |
459
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
433
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
460
434
 
461
435
  ### `git_show` — JSON shape (`format: "json"`)
462
436
 
@@ -486,7 +460,7 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
486
460
 
487
461
  | Parameter | Type | Notes |
488
462
  |-----------|------|-------|
489
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
463
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
490
464
 
491
465
  ### `git_stash_list` — JSON shape (`format: "json"`)
492
466
 
@@ -515,7 +489,7 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
515
489
  | `branch` | string | — | Optional branch/ref to fetch from that remote. |
516
490
  | `prune` | boolean | `false` | Pass `--prune` to remove deleted remote-tracking refs. |
517
491
  | `tags` | boolean | `false` | Pass `--tags` to also fetch all tags. |
518
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
492
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
519
493
 
520
494
  ### `git_fetch` — JSON shape (`format: "json"`)
521
495
 
@@ -552,7 +526,8 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
552
526
  | `ref` | string | working tree | Commit-ish to blame at. Validated as a safe ref token. |
553
527
  | `startLine` | int | — | Start of a line range (`-L`). Requires `endLine`. |
554
528
  | `endLine` | int | — | End of the line range, inclusive. Requires `startLine`. |
555
- | `workspaceRoot`, `rootIndex`, `format` | | Standard single-repo workspace pick + output format. |
529
+ | `maxLines` | int | `2000` | Max blamed lines to return (1–10000). Excess lines are dropped and signalled via `truncated`/`omittedLines`. |
530
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
556
531
 
557
532
  ### `git_blame` — JSON shape (`format: "json"`)
558
533
 
@@ -560,13 +535,26 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
560
535
  {
561
536
  "ref": "HEAD",
562
537
  "path": "src/server.ts",
563
- "lines": [
564
- { "line": 1, "sha": "a1b2c3d4…", "author": "Damon Blais", "date": "2026-04-12T18:32:01-07:00", "summary": "feat: add tool", "content": "import { FastMCP } from \"fastmcp\";" }
565
- ]
538
+ "groups": [
539
+ {
540
+ "sha": "a1b2c3d4…",
541
+ "author": "Damon Blais",
542
+ "date": "2026-04-12T18:32:01-07:00",
543
+ "summary": "feat: add tool",
544
+ "startLine": 1,
545
+ "endLine": 2,
546
+ "lines": [
547
+ { "line": 1, "content": "import { FastMCP } from \"fastmcp\";" },
548
+ { "line": 2, "content": "" }
549
+ ]
550
+ }
551
+ ],
552
+ "truncated": true,
553
+ "omittedLines": 120
566
554
  }
567
555
  ```
568
556
 
569
- `ref` is omitted when blaming the working tree.
557
+ One `groups` entry per **contiguous run of lines last touched by the same commit** (v4 run-length grouping — commit metadata is emitted once per run, not once per line). `ref` is omitted when blaming the working tree. `truncated`/`omittedLines` are omitted unless `maxLines` cut the output.
570
558
 
571
559
  ### `git_blame` — error codes
572
560
 
@@ -585,7 +573,7 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
585
573
  | Parameter | Type | Default | Notes |
586
574
  |-----------|------|---------|-------|
587
575
  | `includeRemotes` | boolean | `false` | Also list remote-tracking branches (`refs/remotes`); symbolic `origin/HEAD` is skipped. |
588
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
576
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
589
577
 
590
578
  ### `git_branch_list` — JSON shape (`format: "json"`)
591
579
 
@@ -616,7 +604,7 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
616
604
  |-----------|------|---------|-------|
617
605
  | `ref` | string | `HEAD` | Ref whose reflog to show. Validated as a safe ref token. |
618
606
  | `maxEntries` | int | `30` | Max entries to return (1–200). |
619
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
607
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
620
608
 
621
609
  ### `git_reflog` — JSON shape (`format: "json"`)
622
610
 
@@ -668,8 +656,7 @@ Do NOT do this: make two separate calls hoping to stage files incrementally. Tha
668
656
  | `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. |
669
657
  | `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. |
670
658
  | `dryRun` | boolean | Default `false`. When `true`, stages each entry, reports what would be committed (`staged`, `diffStat`), then unstages everything without writing commits. |
671
- | `workspaceRoot` | string | Explicit root; highest priority. |
672
- | `rootIndex` | int | Pick one of several MCP roots (0-based). |
659
+ | `workspaceRoot` | string | Repo path. Default: first MCP root / cwd. |
673
660
  | `format` | `"markdown"` \| `"json"` | Output format. Default: `"markdown"`. |
674
661
 
675
662
  ### `batch_commit` — JSON shape (`format: "json"`)
@@ -682,15 +669,11 @@ Do NOT do this: make two separate calls hoping to stage files incrementally. Tha
682
669
  "results": [{
683
670
  "index": 0,
684
671
  "ok": true,
685
- "sha": "a1b2c3d",
686
- "message": "feat: add foo",
687
- "files": ["src/foo.ts"]
672
+ "sha": "a1b2c3d"
688
673
  }, {
689
674
  "index": 1,
690
675
  "ok": true,
691
- "sha": "b2c3d4e",
692
- "message": "chore: update config",
693
- "files": ["config.json"]
676
+ "sha": "b2c3d4e"
694
677
  }],
695
678
  "push": {
696
679
  "ok": true,
@@ -700,7 +683,9 @@ Do NOT do this: make two separate calls hoping to stage files incrementally. Tha
700
683
  }
701
684
  ```
702
685
 
703
- 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`.
686
+ Successful `results[*]` entries carry only what's needed to confirm the commit landed (`index`, `ok`, `sha`, plus `output` when git printed something) they omit `message`/`files` since the caller already supplied both in the request. Failing entries echo `message` and `files` back (alongside `error`/`detail`) so the caller can identify the failed commit without cross-referencing the request.
687
+
688
+ On first failure `ok` is `false`, `committed` reflects only the entries that succeeded before the error, and the failing entry includes `message`, `files`, `error`, and `detail` fields. Remaining entries are skipped and not included in `results`.
704
689
 
705
690
  When `dryRun: true`, the top-level response includes `dryRun: true`; successful `results[*]` entries omit `sha` and instead include `staged` and `diffStat`.
706
691
 
@@ -735,7 +720,7 @@ The `push` object is present only when `push: "after"` was requested **and** eve
735
720
  | `message` | string | Merge commit message, used only when a merge commit is created. Defaults to `Merge branch '<source>' into <into>`. |
736
721
  | `deleteMergedBranches` | boolean | Default `false`. After **all** sources land cleanly, delete each source branch locally (`git branch -d`). **Protected names always skipped** (main, master, dev, develop, stable, trunk, prod, production, `release/*`, `release-*`, `hotfix/*`, `hotfix-*`). Never touches remote refs. |
737
722
  | `deleteMergedWorktrees` | boolean | Default `false`. After success, remove any local worktree currently checked out on a source branch (`git worktree remove`). Protected tails always skipped. |
738
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard workspace pick + output format. |
723
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
739
724
 
740
725
  ### `git_merge` — JSON shape (`format: "json"`)
741
726
 
@@ -798,7 +783,7 @@ On conflict: top-level `ok` is `false`, the conflicting entry has `ok: false` wi
798
783
  | `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. |
799
784
  | `deleteMergedWorktrees` | boolean | Default `false`. After success, remove any local worktree attached to a branch-kind source (`git worktree remove`). Protected tails always skipped. |
800
785
  | `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. |
801
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard workspace pick + output format. |
786
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
802
787
 
803
788
  ### `git_cherry_pick` — JSON shape (`format: "json"`)
804
789
 
@@ -864,7 +849,7 @@ For already-committed work, call **`git_push`** directly instead of creating an
864
849
  | `remote` | string | Remote to push to. Defaults to the remote inferred from the upstream tracking ref, or `origin` when `setUpstream` is true. |
865
850
  | `branch` | string | Branch to push. Defaults to the currently checked-out branch. Rejected on detached HEAD. |
866
851
  | `setUpstream` | boolean | Default `false`. Pass `-u` to set the upstream tracking ref; remote defaults to `origin`. |
867
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard workspace pick + output format. |
852
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
868
853
 
869
854
  ### `git_push` — JSON shape (`format: "json"`)
870
855
 
@@ -893,7 +878,7 @@ For already-committed work, call **`git_push`** directly instead of creating an
893
878
  | `message` | string | — | When provided, creates an annotated tag; otherwise creates a lightweight tag. |
894
879
  | `ref` | string | `HEAD` | Commit/ref to tag. Ignored when `delete: true`. |
895
880
  | `delete` | boolean | `false` | Delete the named tag instead of creating it. |
896
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
881
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
897
882
 
898
883
  ### `git_tag` — JSON shape (`format: "json"`)
899
884
 
@@ -923,7 +908,7 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
923
908
  | Parameter | Type | Notes |
924
909
  |-----------|------|-------|
925
910
  | `ref` | string | Target commit: `HEAD~N`, branch name, or full/short SHA. |
926
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard workspace pick + output format. |
911
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
927
912
 
928
913
  ### `git_reset_soft` — JSON shape (`format: "json"`)
929
914
 
@@ -949,7 +934,7 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
949
934
  |-----------|------|---------|-------|
950
935
  | `index` | int | `0` | Stash index to apply/pop (`stash@{index}`). |
951
936
  | `pop` | boolean | `false` | When `true`, runs `git stash pop` instead of `git stash apply`. |
952
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
937
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
953
938
 
954
939
  ### `git_stash_apply` — JSON shape (`format: "json"`)
955
940
 
@@ -987,7 +972,7 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
987
972
  | `path` | string | Filesystem path for the new worktree. Relative paths are resolved from the git toplevel. |
988
973
  | `branch` | string | Branch to check out. Created from `baseRef` if it does not already exist. |
989
974
  | `baseRef` | string | Commit-ish for branch creation. Default: `HEAD`. Ignored when `branch` already exists. |
990
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard workspace pick + output format. |
975
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
991
976
 
992
977
  ### `git_worktree_add` — JSON shape (`format: "json"`)
993
978
 
@@ -1010,7 +995,7 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
1010
995
  |-----------|------|-------|
1011
996
  | `path` | string | Path of the worktree to remove. |
1012
997
  | `force` | boolean | Default `false`. Pass `--force` to allow removal with uncommitted changes. |
1013
- | `workspaceRoot`, `rootIndex`, `format` | — | Standard workspace pick + output format. |
998
+ | `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
1014
999
 
1015
1000
  ### `git_worktree_remove` — error codes
1016
1001
 
@@ -1037,41 +1022,37 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
1037
1022
  |-----|---------|
1038
1023
  | `rethunk-git://presets` | JSON snapshot of `.rethunk/git-mcp-presets.json` at the resolved git toplevel (or structured errors). |
1039
1024
 
1040
- ## Workspace root resolution
1025
+ ## Root resolution
1041
1026
 
1042
- ### `absoluteGitRoots` (sibling clones)
1027
+ Every tool carries exactly **one** routing parameter:
1043
1028
 
1044
- When **`absoluteGitRoots`** is a **non-empty** string array, it **replaces** the normal workspace pick for that tool call:
1029
+ | Tools | Parameter | Accepts |
1030
+ |-------|-----------|---------|
1031
+ | Fan-out: `git_status`, `git_inventory`, `git_parity`, `list_presets`, `git_log` | `root` | string (one repo path) \| string[] (explicit repo list) \| `"*"` (every MCP root) |
1032
+ | All other 18 tools | `workspaceRoot` | string (one repo path) |
1045
1033
 
1046
- - Each entry is passed through `path.resolve`, then resolved to a **git toplevel** via the same logic as `workspaceRoot`. Duplicate toplevels are dropped (stable order, first wins).
1047
- - **Maximum** **256** paths (same cap as `git_inventory` `maxRoots` upper bound).
1048
- - **Mutually exclusive** with **`workspaceRoot`**, **`rootIndex`**, and **`allWorkspaceRoots: true`**. Combining them returns `{ "error": "absolute_git_roots_exclusive" }`.
1049
- - **Mutually exclusive** with a **`preset`** argument on root resolution (`absolute_git_roots_preset_conflict`).
1050
- - **`git_inventory` only:** also mutually exclusive with **`nestedRoots`** or **`preset`** on the same call (`absolute_git_roots_nested_or_preset_conflict`).
1051
- - **Mutating** tools (`batch_commit`, `git_push`, `git_merge`, …) **omit** this parameter from their schema; callers must use `workspaceRoot` / MCP roots for writes.
1052
- - **Read tools** that use **`requireSingleRepo`** (`git_diff_summary`, …) accept at most **one** distinct toplevel from `absoluteGitRoots`; more than one returns `absolute_git_roots_single_repo_only`.
1034
+ ### `root` forms (fan-out tools)
1035
+
1036
+ - **string** resolve that one path (same semantics as `workspaceRoot`).
1037
+ - **string[]** — explicit repo list (sibling clones). Each entry is passed through `path.resolve`, then resolved to a **git toplevel**; duplicate toplevels are dropped (stable order, first wins). Max **256** paths (`root_list_too_many`); an entry that is not inside a git repo returns `invalid_root_path`; zero resolved toplevels returns `root_list_empty`. Cannot be combined with a `preset` argument (`root_list_preset_conflict`; `git_inventory` also rejects arrays combined with `nestedRoots` — `root_list_nested_or_preset_conflict`).
1038
+ - **`"*"`** — every `file://` root reported by the MCP client; markdown output emits one `# {tool}` header with per-root subsections (`git_inventory` uses `### {gitTop}`; `git_status` uses `### MCP root: ...`), or combined JSON.
1053
1039
 
1054
1040
  Example — two sibling repos in one `git_status` call:
1055
1041
 
1056
1042
  ```json
1057
1043
  {
1058
1044
  "format": "json",
1059
- "absoluteGitRoots": [
1045
+ "root": [
1060
1046
  "/usr/local/src/com.github/Rethunk-AI/mcp-multi-root-git",
1061
1047
  "/usr/local/src/com.github/Rethunk-AI/rethunk-github-mcp"
1062
1048
  ]
1063
1049
  }
1064
1050
  ```
1065
1051
 
1066
- ### Default order (when `absoluteGitRoots` is absent or empty)
1067
-
1068
- Order applied when resolving which directory(ies) tools run against:
1052
+ ### Default order (when `root` / `workspaceRoot` is omitted)
1069
1053
 
1070
- 1. Explicit **`workspaceRoot`** on the tool call (highest priority).
1071
- 2. **`rootIndex`** (0-based) one `file://` MCP root when several exist.
1072
- 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.
1073
- 4. **`preset`** set and multiple roots — first root whose git toplevel defines that preset (respecting **`workspaceRootHint`** on the preset entry when present).
1074
- 5. Otherwise the first `file://` root reported by the MCP client through **`roots/list`**.
1075
- 6. **`process.cwd()`** if no file roots (e.g. CI with explicit `workspaceRoot`).
1054
+ 1. **`preset`** set and multiple MCP roots (fan-out tools) — first root whose git toplevel defines that preset (respecting **`workspaceRootHint`** on the preset entry when present).
1055
+ 2. Otherwise the first `file://` root reported by the MCP client through **`roots/list`**.
1056
+ 3. **`process.cwd()`** if no file roots (e.g. CI with explicit `workspaceRoot`).
1076
1057
 
1077
1058
  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,6 +1,6 @@
1
1
  {
2
2
  "name": "@rethunk/mcp-multi-root-git",
3
- "version": "2.9.0",
3
+ "version": "3.1.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,
@@ -30,7 +30,7 @@
30
30
  }
31
31
  },
32
32
  "scripts": {
33
- "build": "rimraf dist && tsc",
33
+ "build": "rimraf dist && tsc -p tsconfig.build.json",
34
34
  "coverage:check": "bun scripts/check-coverage.ts",
35
35
  "schema:tools": "bun scripts/generate-tool-parameters-schema.ts",
36
36
  "schema:tools:check": "bun scripts/generate-tool-parameters-schema.ts --check",
@@ -67,12 +67,13 @@
67
67
  "access": "public"
68
68
  },
69
69
  "dependencies": {
70
- "fastmcp": "^4.0.1",
70
+ "fastmcp": "^4.3.2",
71
71
  "zod": "^4.4.3"
72
72
  },
73
73
  "devDependencies": {
74
- "@biomejs/biome": "^2.4.16",
75
- "@types/node": "^25.9.1",
74
+ "@biomejs/biome": "^2.5.2",
75
+ "@types/bun": "^1.3.14",
76
+ "@types/node": "^26.1.0",
76
77
  "rimraf": "^6.1.3",
77
78
  "typescript": "^6.0.3"
78
79
  },