@rethunk/mcp-multi-root-git 2.9.1 → 4.0.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 +23 -19
- package/CHANGELOG.md +145 -39
- package/HUMANS.md +23 -42
- package/README.md +30 -13
- package/dist/repo-paths.js +57 -13
- package/dist/server/batch-commit-tool.js +203 -53
- package/dist/server/error-codes.js +31 -16
- package/dist/server/git-blame-tool.js +66 -19
- package/dist/server/git-branch-tool.js +151 -0
- package/dist/server/git-cherry-pick-tool.js +221 -12
- package/dist/server/git-conflicts-tool.js +230 -0
- package/dist/server/git-diff-summary-tool.js +39 -28
- package/dist/server/git-diff-tool.js +56 -31
- package/dist/server/git-grep-tool.js +188 -0
- package/dist/server/git-inventory-tool.js +30 -10
- package/dist/server/git-log-tool.js +37 -6
- package/dist/server/git-merge-tool.js +71 -13
- package/dist/server/git-parity-tool.js +15 -6
- package/dist/server/git-push-tool.js +4 -2
- package/dist/server/git-refs.js +48 -17
- package/dist/server/git-reset-soft-tool.js +1 -1
- package/dist/server/git-revert-tool.js +160 -0
- package/dist/server/git-show-tool.js +5 -10
- package/dist/server/git-stash-tool.js +112 -78
- package/dist/server/git-status-tool.js +2 -2
- package/dist/server/git-tag-tool.js +8 -13
- package/dist/server/git-worktree-tool.js +67 -59
- package/dist/server/git.js +116 -24
- package/dist/server/inventory.js +90 -32
- package/dist/server/list-presets-tool.js +2 -8
- package/dist/server/presets-resource.js +37 -20
- package/dist/server/presets.js +87 -15
- package/dist/server/roots.js +52 -79
- package/dist/server/schemas.js +18 -19
- package/dist/server/test-harness.js +11 -4
- package/dist/server/tool-parameter-schemas.js +47 -58
- package/dist/server/tools.js +36 -17
- package/dist/server.js +1 -1
- package/docs/install.md +52 -5
- package/docs/mcp-tools.md +472 -284
- package/package.json +6 -6
- package/schemas/batch_commit.json +5 -17
- package/schemas/git_blame.json +13 -11
- package/schemas/git_branch.json +54 -0
- package/schemas/git_cherry_pick.json +13 -15
- package/schemas/git_cherry_pick_continue.json +34 -0
- package/schemas/git_conflicts.json +38 -0
- package/schemas/git_diff.json +12 -10
- package/schemas/git_diff_summary.json +1 -15
- package/schemas/git_grep.json +85 -0
- package/schemas/git_inventory.json +32 -23
- package/schemas/git_log.json +20 -24
- package/schemas/git_merge.json +3 -15
- package/schemas/git_parity.json +13 -23
- package/schemas/git_push.json +1 -13
- package/schemas/git_reset_soft.json +1 -13
- package/schemas/git_revert.json +47 -0
- package/schemas/git_show.json +2 -8
- package/schemas/git_stash_apply.json +2 -8
- package/schemas/git_stash_push.json +47 -0
- package/schemas/git_status.json +13 -23
- package/schemas/git_tag.json +1 -7
- package/schemas/git_worktree_add.json +2 -14
- package/schemas/git_worktree_remove.json +2 -14
- package/schemas/index.json +28 -23
- package/schemas/list_presets.json +13 -23
- package/tool-parameters.schema.json +407 -423
- package/dist/server/git-branch-list-tool.js +0 -138
- package/dist/server/git-fetch-tool.js +0 -266
- package/dist/server/git-reflog-tool.js +0 -126
- package/schemas/git_branch_list.json +0 -36
- package/schemas/git_fetch.json +0 -52
- package/schemas/git_reflog.json +0 -44
- package/schemas/git_stash_list.json +0 -30
- package/schemas/git_worktree_list.json +0 -30
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 **
|
|
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,30 @@ 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
|
|
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
|
|
16
|
-
| `git_parity` | `rethunk-git_git_parity` | Compare `git rev-parse HEAD` for path pairs. `pairs`, `preset`, `presetMerge`, `format`, plus
|
|
17
|
-
| `list_presets` | `rethunk-git_list_presets` | List preset names/counts from `.rethunk/git-mcp-presets.json`; invalid JSON/schema surface as errors.
|
|
18
|
-
| `git_log` | `rethunk-git_git_log` | Path-filtered, time-windowed `git log` across one or more
|
|
19
|
-
| `
|
|
20
|
-
| `
|
|
21
|
-
| `
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `git_blame` | `rethunk-git_git_blame` |
|
|
25
|
-
| `
|
|
26
|
-
| `
|
|
27
|
-
| `
|
|
28
|
-
| `
|
|
29
|
-
| `
|
|
30
|
-
| `
|
|
31
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
34
|
-
| `
|
|
35
|
-
| `
|
|
36
|
-
| `git_stash_apply` | `rethunk-git_git_stash_apply` | Apply or pop a stash entry for one repo. Args: `index?`, `pop?`, plus
|
|
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; optional `compareRefs: { left, right }` for ahead/behind between arbitrary local refs (independent of upstream). `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. Optional `follow: true` follows renames (`git log --follow`; requires exactly one `paths` entry). Args: `since`, `paths`, `follow?`, `grep`, `author`, `maxCommits`, `branch`, plus `root` + `format` (`markdown`/`json`/`oneline`). **Read-only.** |
|
|
19
|
+
| `git_grep` | `rethunk-git_git_grep` | Read-only pickaxe history search across one or more roots (`pickaxe: { mode: "S"\|"G", term }` → `git log -S`/`-G`, `commits[]` per root). `pickaxe` is required — content-mode working-tree search was removed in v6 (use the client's native grep/rg tooling). Args: `pickaxe`, `ref?`, `paths?`, `ignoreCase?`, `maxMatches?`, plus `root` + `format`. **Read-only.** |
|
|
20
|
+
| `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.** |
|
|
21
|
+
| `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. Optional `maxBytes` caps returned UTF-8 bytes (`truncated: true` when cut). Args: `workspaceRoot`, `format`, `base?`, `head?`, `path?`, `paths?`, `unified?`, `staged?`, `maxBytes?`. **Read-only.** |
|
|
22
|
+
| `git_show` | `rethunk-git_git_show` | Inspect one commit or ref. Returns commit message plus patch (or `--stat` diffstat). Optional `path`/`paths` filter the patch to those paths (`git show <ref> -- <path>`), not a raw blob checkout. Args: `ref`, `path?`, `paths?`, `stat?`, plus `workspaceRoot` + `format`. **Read-only.** |
|
|
23
|
+
| `git_conflicts` | `rethunk-git_git_conflicts` | Inspect an in-progress conflicted merge/cherry-pick/revert/rebase in the working tree (markers under the git dir). Per conflicted file, parses `<<<<<<</\|\|\|\|\|\|\|/=======/>>>>>>>` into ours/theirs (and base, for diff3-style) hunks. **Note:** `git_merge` and `git_cherry_pick` always attempt `--abort` before returning a conflict report, so a successful abort leaves a clean tree — call this tool for conflicts still in progress (manual ops, failed abort, or other tools), not as a follow-up to those tools' conflict payloads. Args: `withHunks?` (default `true`), `maxLinesPerFile?` (default `200`, max `2000`), plus `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
|
+
| `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.** |
|
|
26
|
+
| `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.** |
|
|
27
|
+
| `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. **`auto`/`rebase` rewrite the source branch tip in place** when rebasing (new SHAs on the source ref), then fast-forward the destination — not destination-only. Refuses on dirty tree; stops on first conflict and attempts `--abort` (abort failure surfaces `rebase_abort_failed` / `merge_abort_failed`). Optional `deleteMergedBranches` / `deleteMergedWorktrees` cascade cleanup, always skipping protected names (main/master/dev/develop/stable/trunk/prod/production/head, plus `release/*`/`release-*`/`hotfix/*`/`hotfix-*` with separator + suffix). Args: `sources`, `into?`, `strategy?`, `message?`, cleanup flags + `workspaceRoot` + `format`. **Mutating.** |
|
|
28
|
+
| `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). Hard-caps expanded/deduped picks at **100** commits per call (`cherry_pick_too_many_commits`). Uses `--empty=drop` so patch-equivalent re-applies add nothing. Refuses on dirty tree; refuses when a cherry-pick is already in progress (`cherry_pick_in_progress`). Stops on first conflict: `onConflict: "abort"` (default) attempts `--abort` (abort failure → `cherry_pick_abort_failed`); `onConflict: "pause"` leaves the conflict and native sequencer state in place (`conflict.paused: true`) for `git_cherry_pick_continue`. Same cleanup flags as `git_merge` (branch-kind sources only, protected names skipped, and only run on full success); 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?`, `onConflict?` + `workspaceRoot` + `format`. **Mutating.** |
|
|
29
|
+
| `git_cherry_pick_continue` | `rethunk-git_git_cherry_pick_continue` | Resume or abort a cherry-pick left in progress (by `git_cherry_pick`'s `onConflict: "pause"` or any other means — reads `CHERRY_PICK_HEAD` live off `.git`, stateless). `action: "continue"` (default) requires no remaining unmerged paths (`cherry_pick_unresolved_paths` otherwise), then runs `git -c core.editor=true cherry-pick --continue`; if a later pick then conflicts, reports it the same shape as a paused `git_cherry_pick` call (`conflict.paused: true`) so this tool can be called again. `action: "abort"` rolls back via `git cherry-pick --abort` (same abort helper/reporting as `git_cherry_pick`). Errors `no_cherry_pick_in_progress` when nothing is in progress. Args: `action?` + `workspaceRoot` + `format`. **Mutating.** |
|
|
30
|
+
| `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.** |
|
|
31
|
+
| `git_revert` | `rethunk-git_git_revert` | Create new commit(s) that undo the changes introduced by one or more source commits (`git revert`), applied in listed order. Never rewrites history — safe on shared/pushed branches, unlike `git_reset_soft`. Refuses on dirty tree; on conflict aborts and leaves the tree clean. Args: `sources`, `noCommit?`, `mainline?`, plus `workspaceRoot` + `format`. **Mutating — not idempotent.** |
|
|
32
|
+
| `git_tag` | `rethunk-git_git_tag` | Create/delete annotated or lightweight tags for one repo. Args: `tag`, `message?`, `ref?`, `delete?`, plus `workspaceRoot` + `format`. **Mutating.** |
|
|
33
|
+
| `git_branch` | `rethunk-git_git_branch` | Create, delete, or rename a local branch. `action: "create"` bases a new branch on `from` (default `HEAD`); `action: "delete"` removes `name` (`force: true` for `-D` on an unmerged branch); `action: "rename"` renames `name` to `newName`. Refuses protected branch names (main/master/dev/develop/stable/trunk/prod/production/head, plus `release/*`/`release-*`/`hotfix/*`/`hotfix-*` with separator + suffix) in any role — as source, target, or rename endpoint. Args: `action`, `name`, `from?`, `newName?`, `force?`, plus `workspaceRoot` + `format`. **Mutating.** |
|
|
34
|
+
| `git_worktree_add` | `rethunk-git_git_worktree_add` | Create a new linked worktree, creating the branch from `baseRef` if it does not yet exist. Sibling paths outside the git toplevel are allowed; leading `-` / option-like basenames and NUL bytes are rejected (`invalid_paths`). Path is passed to git after `--`. Refuses on protected branch names. Args: `path`, `branch`, `baseRef?`, plus `workspaceRoot` + `format`. **Mutating.** |
|
|
35
|
+
| `git_worktree_remove` | `rethunk-git_git_worktree_remove` | Remove a registered worktree; refuses to remove the main worktree. Same path argv rules as add (leading `-` / NUL → `invalid_paths`; path after `--`). Optional `force: true` for dirty trees. Args: `path`, `force?`, plus `workspaceRoot` + `format`. **Mutating.** |
|
|
36
|
+
| `git_stash_apply` | `rethunk-git_git_stash_apply` | Apply or pop a stash entry for one repo (`destructiveHint: true` — pop can delete a stash entry). On failure emits `error: stash_apply_failed` and optional `conflictPaths`. Args: `index?`, `pop?`, plus `workspaceRoot` + `format`. **Mutating.** |
|
|
37
|
+
| `git_stash_push` | `rethunk-git_git_stash_push` | Stash working-tree changes (`git stash push`). Optional `message`, `includeUntracked` (-u), `keepIndex` (--keep-index), `paths` to scope. Args: `message?`, `includeUntracked?`, `paths?`, `keepIndex?`, plus `workspaceRoot` + `format`. **Mutating.** |
|
|
37
38
|
|
|
38
39
|
Pass **`format: "json"`** on any tool for structured JSON instead of markdown (default).
|
|
39
40
|
|
|
@@ -44,10 +45,7 @@ Pass **`format: "json"`** on any tool for structured JSON instead of markdown (d
|
|
|
44
45
|
| Parameter | Type | Default | Notes |
|
|
45
46
|
|-----------|------|---------|-------|
|
|
46
47
|
| `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
|
-
| `
|
|
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. |
|
|
48
|
+
| `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
49
|
| `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
|
|
52
50
|
|
|
53
51
|
### `git_status` — JSON shape (`format: "json"`)
|
|
@@ -76,11 +74,9 @@ Error payloads appear as top-level JSON or inline in individual repo rows (`ok:
|
|
|
76
74
|
| `not_a_git_repository` | repo row `statusText` | Root is not inside a git repository. |
|
|
77
75
|
| `(submodule path escapes repository — rejected)` | repo row `statusText` | `.gitmodules` path resolves outside the git toplevel (security guard). |
|
|
78
76
|
| `(no .git — submodule not checked out?)` | repo row `statusText` | Submodule directory exists but has no `.git` — not initialized. |
|
|
79
|
-
| `
|
|
80
|
-
| `
|
|
81
|
-
| `
|
|
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. |
|
|
77
|
+
| `invalid_root_path` | top-level | A `root` array entry is empty or not a git-recognized directory. |
|
|
78
|
+
| `root_list_too_many` | top-level | More than 256 entries in the `root` array. |
|
|
79
|
+
| `root_list_empty` | top-level | The `root` array resolved to zero git toplevels. |
|
|
84
80
|
|
|
85
81
|
---
|
|
86
82
|
|
|
@@ -88,16 +84,14 @@ Error payloads appear as top-level JSON or inline in individual repo rows (`ok:
|
|
|
88
84
|
|
|
89
85
|
| Parameter | Type | Default | Notes |
|
|
90
86
|
|-----------|------|---------|-------|
|
|
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 `
|
|
92
|
-
| `preset` | string | — | Preset name from `.rethunk/git-mcp-presets.json`. Loads `nestedRoots` from the preset's entry. Cannot combine with `
|
|
87
|
+
| `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`. May be combined with `preset` when `presetMerge: true` (merged) or when `presetMerge` is false (inline list replaces the preset's `nestedRoots`). |
|
|
88
|
+
| `preset` | string | — | Preset name from `.rethunk/git-mcp-presets.json`. Loads `nestedRoots` from the preset's entry. Cannot combine with an array `root`. |
|
|
93
89
|
| `presetMerge` | boolean | `false` | When `true`, merge inline `nestedRoots` with preset roots instead of replacing. |
|
|
94
90
|
| `remote` | string | — | Fixed remote for ahead/behind tracking. Must be paired with `branch`. |
|
|
95
91
|
| `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. |
|
|
92
|
+
| `compareRefs` | `{ left: string, right: string }` | — | Optional ahead/behind between arbitrary local refs (independent of upstream). Ahead = commits reachable as `left..right`; behind = `right..left`. Each side validated with `isSafeGitAncestorRef` → `unsafe_ref_token` on rejection. |
|
|
96
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. |
|
|
97
|
-
| `
|
|
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`. |
|
|
94
|
+
| `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
95
|
| `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
|
|
102
96
|
|
|
103
97
|
### `git_inventory` — JSON shape (`format: "json"`)
|
|
@@ -105,7 +99,7 @@ Error payloads appear as top-level JSON or inline in individual repo rows (`ok:
|
|
|
105
99
|
```json
|
|
106
100
|
{
|
|
107
101
|
"inventories": [{
|
|
108
|
-
"
|
|
102
|
+
"workspaceRoot": "/abs/path",
|
|
109
103
|
"entries": [{
|
|
110
104
|
"label": ".",
|
|
111
105
|
"path": "/abs/path",
|
|
@@ -114,7 +108,13 @@ Error payloads appear as top-level JSON or inline in individual repo rows (`ok:
|
|
|
114
108
|
"headAbbrev": "a1b2c3d",
|
|
115
109
|
"upstreamRef": "origin/main",
|
|
116
110
|
"ahead": "2",
|
|
117
|
-
"behind": "0"
|
|
111
|
+
"behind": "0",
|
|
112
|
+
"compareRefs": {
|
|
113
|
+
"left": "main",
|
|
114
|
+
"right": "feature",
|
|
115
|
+
"ahead": "1",
|
|
116
|
+
"behind": "0"
|
|
117
|
+
}
|
|
118
118
|
}],
|
|
119
119
|
"nestedRootsTruncated": true,
|
|
120
120
|
"nestedRootsOmittedCount": 3,
|
|
@@ -123,7 +123,7 @@ Error payloads appear as top-level JSON or inline in individual repo rows (`ok:
|
|
|
123
123
|
}
|
|
124
124
|
```
|
|
125
125
|
|
|
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
|
|
126
|
+
`nestedRootsTruncated` / `nestedRootsOmittedCount` present only when `maxRoots` cut the list. `upstream` object present only when `remote`+`branch` were supplied (fixed mode). `compareRefs` on an entry is present only when the call requested `compareRefs` and counts could be computed; optional `note` appears when a ref is unreadable or counts fail. `presetSchemaVersion` present only when a preset was loaded. Non-git paths produce skip entries with plain `skipReason` text such as `(not a git repository)` (not nested JSON). See [Field omission](#field-omission-consumer-contract-v2) for `entries[*]` optional fields.
|
|
127
127
|
|
|
128
128
|
### `git_inventory` — error codes
|
|
129
129
|
|
|
@@ -132,16 +132,13 @@ Error payloads appear as top-level JSON or inline in individual repo rows (`ok:
|
|
|
132
132
|
| `git_not_found` | `git` binary not on `PATH`. |
|
|
133
133
|
| `remote_branch_mismatch` | Only one of `remote` / `branch` was provided; supply both or neither. |
|
|
134
134
|
| `invalid_remote_or_branch` | `remote` or `branch` contains characters outside the safe token set. |
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `absolute_git_roots_empty` | `absoluteGitRoots` resolved to zero toplevels. |
|
|
141
|
-
| `root_index_out_of_range` | `rootIndex` exceeds the number of MCP file roots. |
|
|
135
|
+
| `unsafe_ref_token` | A `compareRefs.left` / `compareRefs.right` token failed `isSafeGitAncestorRef` validation. |
|
|
136
|
+
| `root_list_nested_or_preset_conflict` | `root` array combined with `nestedRoots` or `preset`. |
|
|
137
|
+
| `invalid_root_path` | A `root` array entry is empty or not a git-recognized directory. |
|
|
138
|
+
| `root_list_too_many` | More than 256 entries in the `root` array. |
|
|
139
|
+
| `root_list_empty` | The `root` array resolved to zero git toplevels. |
|
|
142
140
|
| `preset_not_found` | Named preset does not exist in the preset file. |
|
|
143
|
-
| `
|
|
144
|
-
| `invalid_schema` | Preset file fails schema validation. |
|
|
141
|
+
| `preset_file_invalid` | Preset file failed to load. Discriminator `kind`: `"invalid_json"` (parse failure; may include `message`) or `"schema"` (Zod validation failure; may include `issues`). Always includes `presetFile`. |
|
|
145
142
|
|
|
146
143
|
Skip entries (individual repos that could not be inventoried) appear inline in `entries[*]` with `skipReason` rather than as top-level errors.
|
|
147
144
|
|
|
@@ -154,10 +151,7 @@ Skip entries (individual repos that could not be inventoried) appear inline in `
|
|
|
154
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`). |
|
|
155
152
|
| `preset` | string | Preset name from `.rethunk/git-mcp-presets.json`. Loads `parityPairs` from the preset's entry. |
|
|
156
153
|
| `presetMerge` | boolean | Default `false`. When `true`, merge inline `pairs` with preset pairs instead of replacing. |
|
|
157
|
-
| `
|
|
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. |
|
|
154
|
+
| `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
155
|
| `format` | `"markdown"` \| `"json"` | Output format. Default: `"markdown"`. |
|
|
162
156
|
|
|
163
157
|
### `git_parity` — JSON shape (`format: "json"`)
|
|
@@ -165,7 +159,7 @@ Skip entries (individual repos that could not be inventoried) appear inline in `
|
|
|
165
159
|
```json
|
|
166
160
|
{
|
|
167
161
|
"parity": [{
|
|
168
|
-
"
|
|
162
|
+
"workspaceRoot": "/abs/path",
|
|
169
163
|
"status": "MISMATCH",
|
|
170
164
|
"pairs": [
|
|
171
165
|
{
|
|
@@ -188,7 +182,7 @@ Skip entries (individual repos that could not be inventoried) appear inline in `
|
|
|
188
182
|
}
|
|
189
183
|
```
|
|
190
184
|
|
|
191
|
-
`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.
|
|
185
|
+
`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, non-git path, or `git rev-parse HEAD` failure), `error` carries a plain description string (e.g. `not a git repository: <path>`) and both SHA fields are absent — never a nested minified JSON blob. `presetSchemaVersion` is present when a preset was loaded.
|
|
192
186
|
|
|
193
187
|
### `git_parity` — error codes
|
|
194
188
|
|
|
@@ -196,15 +190,12 @@ Skip entries (individual repos that could not be inventoried) appear inline in `
|
|
|
196
190
|
|------|---------|
|
|
197
191
|
| `git_not_found` | `git` binary not on `PATH`. |
|
|
198
192
|
| `no_pairs` | Neither inline `pairs` nor a preset with `parityPairs` was supplied. |
|
|
199
|
-
| `
|
|
200
|
-
| `
|
|
201
|
-
| `
|
|
202
|
-
| `
|
|
203
|
-
| `absolute_git_roots_empty` | `absoluteGitRoots` resolved to zero toplevels. |
|
|
204
|
-
| `root_index_out_of_range` | `rootIndex` exceeds the number of MCP file roots. |
|
|
193
|
+
| `root_list_preset_conflict` | `root` array combined with a `preset` argument. |
|
|
194
|
+
| `invalid_root_path` | A `root` array entry is empty or not a git-recognized directory. |
|
|
195
|
+
| `root_list_too_many` | More than 256 entries in the `root` array. |
|
|
196
|
+
| `root_list_empty` | The `root` array resolved to zero git toplevels. |
|
|
205
197
|
| `preset_not_found` | Named preset does not exist in the preset file. |
|
|
206
|
-
| `
|
|
207
|
-
| `invalid_schema` | Preset file fails schema validation. |
|
|
198
|
+
| `preset_file_invalid` | Preset file failed to load. Discriminator `kind`: `"invalid_json"` or `"schema"` (see `git_inventory`). |
|
|
208
199
|
|
|
209
200
|
Path-escape and `rev-parse` failures are reported inline in `pairs[*].error`, not as top-level error codes.
|
|
210
201
|
|
|
@@ -214,10 +205,7 @@ Path-escape and `rev-parse` failures are reported inline in `pairs[*].error`, no
|
|
|
214
205
|
|
|
215
206
|
| Parameter | Type | Notes |
|
|
216
207
|
|-----------|------|-------|
|
|
217
|
-
| `
|
|
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. |
|
|
208
|
+
| `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
209
|
| `format` | `"markdown"` \| `"json"` | Output format. Default: `"markdown"`. |
|
|
222
210
|
|
|
223
211
|
### `list_presets` — JSON shape (`format: "json"`)
|
|
@@ -250,40 +238,43 @@ Path-escape and `rev-parse` failures are reported inline in `pairs[*].error`, no
|
|
|
250
238
|
|------|---------|
|
|
251
239
|
| `git_not_found` | `git` binary not on `PATH`. |
|
|
252
240
|
| `not_a_git_repository` | Root is not inside a git repository (reported inline in the `roots[*].error` field, not top-level). |
|
|
253
|
-
| `
|
|
254
|
-
| `
|
|
255
|
-
| `
|
|
256
|
-
| `
|
|
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. |
|
|
241
|
+
| `preset_file_invalid` | Preset file failed to load (inline in `roots[*].error`). Discriminator `kind`: `"invalid_json"` or `"schema"`. |
|
|
242
|
+
| `invalid_root_path` | A `root` array entry is empty or not a git-recognized directory. |
|
|
243
|
+
| `root_list_too_many` | More than 256 entries in the `root` array. |
|
|
244
|
+
| `root_list_empty` | The `root` array resolved to zero git toplevels. |
|
|
260
245
|
|
|
261
246
|
---
|
|
262
247
|
|
|
263
248
|
## JSON responses
|
|
264
249
|
|
|
265
|
-
Tool JSON bodies are minified and contain only the payload — no `rethunkGitMcp` envelope. Current `MCP_JSON_FORMAT_VERSION` is **`"
|
|
250
|
+
Tool JSON bodies are minified and contain only the payload — no `rethunkGitMcp` envelope. Current `MCP_JSON_FORMAT_VERSION` is **`"6"`** (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`**.
|
|
251
|
+
|
|
252
|
+
v6 changes from v5: the tool surface shrank to **24 tools** — `git_fetch`, `git_remote`, `git_describe`, `git_stash_list`, `git_reflog`, `git_branch_list`, and `git_worktree_list` were removed (thin wrappers over tiny-output git commands; see [CONTRIBUTING.md — Tool inclusion criteria](../CONTRIBUTING.md#tool-inclusion-criteria)); `git_grep` dropped content mode — `pickaxe` is now required, `pattern`/`filesOnly` are gone, and `results[*]` always carries `commits[]` (never `matches`/`files`); the `pattern_or_pickaxe_required` error code was removed.
|
|
253
|
+
|
|
254
|
+
v5 changes from v4: `batch_commit` successful `results[*]` entries drop the echoed `message`/`files` (the caller already supplied both in the request); failing entries still carry `message`/`files` alongside `error`/`detail` for diagnosis.
|
|
255
|
+
|
|
256
|
+
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
257
|
|
|
267
258
|
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
259
|
|
|
269
|
-
###
|
|
260
|
+
### Field omission (consumer contract, v2+)
|
|
270
261
|
|
|
271
|
-
To keep responses compact, **optional fields are omitted when they would be empty, `null`, or `false
|
|
262
|
+
To keep responses compact, **optional fields are usually omitted when they would be empty, `null`, or `false`**. Consumers must test for *presence*, not compare to `null`, except where a tool documents an intentional `null` (documented exception: `list_presets` emits `gitTop: null` when the workspace is not inside a git repo).
|
|
272
263
|
|
|
273
264
|
**`git_inventory` → `inventories[*]`**
|
|
274
265
|
|
|
275
|
-
- Always present: `
|
|
266
|
+
- Always present: `workspaceRoot`, `entries`.
|
|
276
267
|
- Omitted when not applicable: `presetSchemaVersion`, `nestedRootsTruncated`, `nestedRootsOmittedCount`, and the whole `upstream` object (emitted only when a fixed `remote`/`branch` pair was supplied; in `auto` mode it is absent).
|
|
277
268
|
|
|
278
269
|
**`git_inventory` → `entries[*]` (`InventoryEntryJson`)**
|
|
279
270
|
|
|
280
271
|
- Always present: `label`, `path`, `upstreamMode` (`"auto"` or `"fixed"`).
|
|
281
|
-
- Optional (omitted when empty/absent): `branchStatus`, `headAbbrev`, `upstreamRef`, `ahead`, `behind`, `upstreamNote`, `detached` (only emitted as `true`), `skipReason` (only on skipped entries).
|
|
272
|
+
- Optional (omitted when empty/absent): `branchStatus`, `headAbbrev`, `upstreamRef`, `ahead`, `behind`, `upstreamNote`, `compareRefs` (when requested), `detached` (only emitted as `true`), `skipReason` (only on skipped entries).
|
|
282
273
|
- **Removed in v2:** `shortStatus`. The porcelain entries now live inside `branchStatus` (the full `git status --short -b` body — branch header line followed by porcelain lines).
|
|
283
274
|
|
|
284
275
|
**Errors** (any tool)
|
|
285
276
|
|
|
286
|
-
- 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).
|
|
277
|
+
- Error payloads carry an `error` code string and any structured context (e.g. `preset`, `presetFile`, `kind`). 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). For preset load failures the wire code is always `preset_file_invalid` with discriminator `kind` (`"invalid_json"` or `"schema"`); `message` is retained on the `invalid_json` kind (parse output) and `issues` on the `schema` kind.
|
|
287
278
|
|
|
288
279
|
**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).
|
|
289
280
|
|
|
@@ -292,15 +283,14 @@ To keep responses compact, **optional fields are omitted when they would be empt
|
|
|
292
283
|
| Parameter | Type | Default | Notes |
|
|
293
284
|
|-----------|------|---------|-------|
|
|
294
285
|
| `since` | string | `"7.days"` | Passed to `git log --since=`. Accepts ISO timestamps (`2026-04-01T00:00:00Z`) or git relative forms (`48.hours`, `2.weeks.ago`). |
|
|
295
|
-
| `paths` | string[] | (all) | Restrict to commits touching these paths (appended after `--`). |
|
|
286
|
+
| `paths` | string[] | (all) | Restrict to commits touching these paths (appended after `--`). Each entry is confined with `resolvePathForRepo` / `assertRelativePathUnderTop` → `path_escapes_repo` on escape. |
|
|
287
|
+
| `follow` | boolean | `false` | Pass `--follow` for rename-aware history. Requires **exactly one** `paths` entry; otherwise top-level `invalid_paths` with `detail: "follow requires exactly one path"`. |
|
|
296
288
|
| `grep` | string | — | Filter by commit message regex (git `--grep`, always case-insensitive). |
|
|
297
289
|
| `author` | string | — | Filter by author name or email (`--author=`). |
|
|
298
290
|
| `maxCommits` | int | `50` | Max commits per root. Hard cap: `500`. |
|
|
299
291
|
| `branch` | string | `HEAD` | Ref/branch to log from. |
|
|
300
|
-
| `
|
|
301
|
-
| `
|
|
302
|
-
| `allWorkspaceRoots` | boolean | `false` | Fan out across all MCP roots. |
|
|
303
|
-
| `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. |
|
|
292
|
+
| `root` | string \| string[] \| `"*"` | — | Repo path (string), explicit list of repo paths (array, max 256), or `"*"` for every MCP root. Default: first MCP root / cwd. |
|
|
293
|
+
| `format` | `"markdown"` \| `"json"` \| `"oneline"` | `"markdown"` | Output format. `oneline` returns the first 7 characters of each commit SHA plus the subject (`<abbrev> <subject>`) per line with no headers (single root) or `### repo (branch)` separators per group (multi-root). Display shorthand only — the JSON field remains full `sha` (v3 removed the old `sha7` field). Lowest-token option for post-commit verification. |
|
|
304
294
|
|
|
305
295
|
### `git_log` — JSON shape (`format: "json"`)
|
|
306
296
|
|
|
@@ -337,25 +327,68 @@ v2 field-omission rules still apply: `filesChanged`, `insertions`, `deletions` o
|
|
|
337
327
|
| `git_not_found` | `git` binary not on `PATH`. |
|
|
338
328
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
339
329
|
| `invalid_since` | The `since` string contains shell metacharacters and was rejected. |
|
|
340
|
-
| `invalid_paths` |
|
|
330
|
+
| `invalid_paths` | A `paths` entry failed validation, or `follow: true` was set without exactly one `paths` entry (`detail: "follow requires exactly one path"`). |
|
|
331
|
+
| `path_escapes_repo` | A `paths` entry resolves outside that root's git toplevel. |
|
|
341
332
|
| `unsafe_ref_token` | `branch` contains characters outside the argv-safe subset. |
|
|
342
333
|
| `git_log_failed` | `git log` exited non-zero (e.g. unknown branch ref). |
|
|
343
|
-
| `
|
|
344
|
-
| `
|
|
345
|
-
| `
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
|
334
|
+
| `invalid_root_path` | A `root` array entry is empty, not inside a git worktree, or not a directory git recognizes. |
|
|
335
|
+
| `root_list_too_many` | More than 256 entries in the `root` array. |
|
|
336
|
+
| `root_list_empty` | The `root` array produced zero git toplevels after resolution. |
|
|
337
|
+
|
|
338
|
+
### `git_grep` — parameters
|
|
339
|
+
|
|
340
|
+
| Parameter | Type | Default | Notes |
|
|
341
|
+
|-----------|------|---------|-------|
|
|
342
|
+
| `pickaxe` | `{ mode: "S" \| "G", term: string }` | — | **Required.** Pickaxe history search (`git log -S` / `-G`): `S` finds commits that changed the occurrence count of `term`; `G` finds commits whose diff lines match the `term` regex. JSON results are `commits[]` of `{ sha, subject }` per root. |
|
|
343
|
+
| `ref` | string | — | Commit/branch/tag to use as the history tip. Validated as a safe ref token (`isSafeGitAncestorRef`); rejects `--`-prefixed or otherwise unsafe tokens. |
|
|
344
|
+
| `paths` | string[] | — | Limit history to these paths. Each must resolve within the repo root (`resolvePathForRepo` / `assertRelativePathUnderTop`); escaping paths are rejected per-root. |
|
|
345
|
+
| `ignoreCase` | boolean | `false` | Case-insensitive match (`-i`; affects `G` mode regexes). |
|
|
346
|
+
| `maxMatches` | integer | `200` | Cap on commits per root. Hard cap `1000`. |
|
|
347
|
+
| `root` | string \| string[] \| `"*"` | — | Repo path, array of paths (max 256), or `"*"` for every MCP root. Default: first MCP root / cwd. |
|
|
348
|
+
| `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
|
|
349
|
+
|
|
350
|
+
Content-mode working-tree search (`pattern`, `filesOnly`, ref-tree search) was **removed in v6** — it duplicated the native grep/rg tooling every MCP client already ships. Pickaxe history is the part with no native equivalent.
|
|
351
|
+
|
|
352
|
+
### `git_grep` — JSON shape (`format: "json"`)
|
|
353
|
+
|
|
354
|
+
```json
|
|
355
|
+
{
|
|
356
|
+
"results": [
|
|
357
|
+
{
|
|
358
|
+
"root": "/abs/workspace",
|
|
359
|
+
"repo": "workspace",
|
|
360
|
+
"commits": [
|
|
361
|
+
{ "sha": "a1b2c3d4e5f6…", "subject": "feat: introduce needle" }
|
|
362
|
+
],
|
|
363
|
+
"truncated": true
|
|
364
|
+
}
|
|
365
|
+
]
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
One `results` entry per resolved root, each carrying `commits[]` of `{ sha, subject }` hits. `truncated: true` is present only when the per-root commit count exceeded `maxMatches`. A root that fails to resolve or whose git invocation errors carries `error` (and optional `detail`) instead of `commits`. Zero pickaxe hits is **not** an error — `commits` is simply empty.
|
|
370
|
+
|
|
371
|
+
### `git_grep` — error codes
|
|
372
|
+
|
|
373
|
+
| Code | Meaning |
|
|
374
|
+
|------|---------|
|
|
375
|
+
| `git_not_found` | `git` binary not on `PATH`. |
|
|
376
|
+
| `not_a_git_repository` | A resolved root is not inside a git repository. |
|
|
377
|
+
| `unsafe_ref_token` | `ref` failed `isSafeGitAncestorRef` validation. |
|
|
378
|
+
| `path_escapes_repo` | A `paths` entry resolves outside that root's git toplevel. |
|
|
379
|
+
| `git_grep_failed` | Pickaxe `git log` exited non-zero (bad regex, unknown ref, etc.). |
|
|
380
|
+
| `invalid_root_path` / `root_list_too_many` / `root_list_empty` | `root` array validation (shared fan-out behavior). |
|
|
381
|
+
|
|
382
|
+
---
|
|
350
383
|
|
|
351
|
-
### `git_parity` — `
|
|
384
|
+
### `git_parity` — `root` array example
|
|
352
385
|
|
|
353
|
-
|
|
386
|
+
Pass a `root` array when the same parity pair should be checked across sibling clones that are not all MCP workspace roots:
|
|
354
387
|
|
|
355
388
|
```json
|
|
356
389
|
{
|
|
357
390
|
"format": "json",
|
|
358
|
-
"
|
|
391
|
+
"root": [
|
|
359
392
|
"/usr/local/src/com.github/Rethunk-AI/mcp-multi-root-git",
|
|
360
393
|
"/usr/local/src/com.github/Rethunk-AI/rethunk-github-mcp"
|
|
361
394
|
],
|
|
@@ -363,19 +396,20 @@ Use **`absoluteGitRoots`** when the same parity pair should be checked across si
|
|
|
363
396
|
}
|
|
364
397
|
```
|
|
365
398
|
|
|
366
|
-
The response contains one **`parity[]`** entry per resolved git toplevel. `
|
|
399
|
+
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.
|
|
400
|
+
|
|
401
|
+
**Diff-family filtering divergence (intentional):** `git_diff_summary` filters client-side via a `fileFilter` glob applied to the summarized file list; `git_diff` and `git_show` instead take server-confined `paths[]` (each entry validated with `resolvePathForRepo`/`assertRelativePathUnderTop` and rejected with `path_escapes_repo` on escape). Don't expect glob syntax on `git_diff`/`git_show` `paths[]`, and don't expect path confinement guarantees on `git_diff_summary`'s `fileFilter`.
|
|
367
402
|
|
|
368
403
|
### `git_diff_summary` — parameters
|
|
369
404
|
|
|
370
405
|
| Parameter | Type | Default | Notes |
|
|
371
406
|
|-----------|------|---------|-------|
|
|
372
|
-
| `range` | string | unstaged | Diff range. `"staged"` / `"cached"` for index; `"HEAD"` for last commit; `"A..B"` or `"A...B"` for revision ranges; single ref. Default: unstaged working-tree changes. |
|
|
407
|
+
| `range` | string | unstaged | Diff range. `"staged"` / `"cached"` for index; `"HEAD"` for last commit; `"A..B"` or `"A...B"` for revision ranges; single ref. Ancestor notation (`HEAD~3`, `main^2`) is accepted on any endpoint. Default: unstaged working-tree changes. |
|
|
373
408
|
| `fileFilter` | string | — | Glob pattern to restrict output to matching files (e.g. `"*.ts"`, `"src/**"`). |
|
|
374
409
|
| `maxLinesPerFile` | int | `50` | Max diff lines to include per file (1–2000). |
|
|
375
410
|
| `maxFiles` | int | `30` | Max files to include in output (1–500). |
|
|
376
411
|
| `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 | — |
|
|
378
|
-
| `rootIndex` | int | — | Pick one of several MCP roots (0-based). |
|
|
412
|
+
| `workspaceRoot` | string | — | Repo path. Default: first MCP root / cwd. |
|
|
379
413
|
| `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
|
|
380
414
|
|
|
381
415
|
### `git_diff_summary` — JSON shape (`format: "json"`)
|
|
@@ -391,7 +425,6 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
|
|
|
391
425
|
"status": "modified",
|
|
392
426
|
"additions": 8,
|
|
393
427
|
"deletions": 3,
|
|
394
|
-
"truncated": false,
|
|
395
428
|
"diff": "@@ -1,3 +1,8 @@\n-const x = 1;\n+const x = 2;"
|
|
396
429
|
}],
|
|
397
430
|
"truncatedFiles": 1,
|
|
@@ -399,7 +432,7 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
|
|
|
399
432
|
}
|
|
400
433
|
```
|
|
401
434
|
|
|
402
|
-
`status` is one of `"modified"`, `"added"`, `"deleted"`, `"renamed"`. `oldPath` is present only for renamed files. `truncatedFiles` and `excludedFiles` are omitted when zero/empty (
|
|
435
|
+
`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). `totalFiles` / `totalAdditions` / `totalDeletions` count the post-`excludePatterns`+`fileFilter` set (before `maxFiles` display truncation). `excludedFiles` includes both exclude-pattern hits and `fileFilter` drops. `truncatedFiles` is the count omitted by `maxFiles`. `truncatedFiles` and `excludedFiles` are omitted when zero/empty (field-omission contract).
|
|
403
436
|
|
|
404
437
|
### `git_diff_summary` — error codes
|
|
405
438
|
|
|
@@ -416,27 +449,29 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
|
|
|
416
449
|
|
|
417
450
|
| Parameter | Type | Default | Notes |
|
|
418
451
|
|-----------|------|---------|-------|
|
|
419
|
-
| `base` | string | — | Base ref for a revision diff. When omitted with no `staged`, the tool shows unstaged changes. |
|
|
420
|
-
| `head` | string | `HEAD` | Head ref for a revision diff. Used only when `base` is provided. |
|
|
452
|
+
| `base` | string | — | Base ref for a revision diff. When omitted with no `staged`, the tool shows unstaged changes. Ancestor notation (`HEAD~3`, `main^2`) is accepted. |
|
|
453
|
+
| `head` | string | `HEAD` | Head ref for a revision diff. Used only when `base` is provided. Ancestor notation (`HEAD~3`, `main^2`) is accepted. |
|
|
421
454
|
| `path` | string | — | Optional single file path to scope the diff. Confined to the repo (`path_escapes_repo` on escape). |
|
|
422
455
|
| `paths` | string[] | — | Multiple file paths to scope the diff; unioned with `path` (deduped). Each confined to the repo. |
|
|
423
456
|
| `unified` | integer | — | Context lines around each change (passed as `-U<n>`, 0–100). Omit for git's default (3). |
|
|
424
457
|
| `staged` | boolean | `false` | When `true`, runs `git diff --staged`. Ignored when `base` is provided. |
|
|
425
|
-
| `
|
|
426
|
-
| `
|
|
458
|
+
| `maxBytes` | integer | `512000` | Cap on UTF-8 bytes of returned diff text (1024–10000000). Oversized output is truncated; JSON emits `truncated: true` (omitted when false). |
|
|
459
|
+
| `workspaceRoot` | string | — | Repo path. Default: first MCP root / cwd. |
|
|
427
460
|
| `format` | `"markdown"` \| `"json"` | `"markdown"` | Output format. |
|
|
428
461
|
|
|
429
|
-
`git_diff` is a single-repo tool.
|
|
462
|
+
`git_diff` is a single-repo tool; use `workspaceRoot` to select the target repo. When `base` is set, `staged` is ignored and `head` is used; `head` alone (without `base`) still yields the unstaged working-tree diff.
|
|
430
463
|
|
|
431
464
|
### `git_diff` — JSON shape (`format: "json"`)
|
|
432
465
|
|
|
433
466
|
```json
|
|
434
467
|
{
|
|
435
468
|
"range": "HEAD~1..HEAD (src/server.ts)",
|
|
436
|
-
"diff": "diff --git a/src/server.ts b/src/server.ts\n..."
|
|
469
|
+
"diff": "diff --git a/src/server.ts b/src/server.ts\n...",
|
|
470
|
+
"truncated": true
|
|
437
471
|
}
|
|
438
472
|
```
|
|
439
473
|
|
|
474
|
+
`truncated: true` is present only when the `maxBytes` cap fired.
|
|
440
475
|
### `git_diff` — error codes
|
|
441
476
|
|
|
442
477
|
| Code | Meaning |
|
|
@@ -452,11 +487,11 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
|
|
|
452
487
|
|
|
453
488
|
| Parameter | Type | Notes |
|
|
454
489
|
|-----------|------|-------|
|
|
455
|
-
| `ref` | string | Commit, branch, tag, or other git rev-spec to inspect. |
|
|
456
|
-
| `path` | string | Optional single path. When provided, the
|
|
490
|
+
| `ref` | string | Commit, branch, tag, or other git rev-spec to inspect. Ancestor notation (`HEAD~3`, `main^2`) is accepted. |
|
|
491
|
+
| `path` | string | Optional single path. When provided, filters the shown patch/stat to that path (`git show <ref> -- <path>`), not a raw blob (`ref:path`) checkout. |
|
|
457
492
|
| `stat` | boolean | When `true`, runs `git show --stat` — commit message plus per-file diffstat, no full patch (`statOutput` in JSON). |
|
|
458
493
|
| `paths` | string[] | Filter the shown patch/stat to these repo-relative paths; unioned with `path`. Each confined to the repo. |
|
|
459
|
-
| `workspaceRoot`, `
|
|
494
|
+
| `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
|
|
460
495
|
|
|
461
496
|
### `git_show` — JSON shape (`format: "json"`)
|
|
462
497
|
|
|
@@ -477,69 +512,50 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
|
|
|
477
512
|
|------|---------|
|
|
478
513
|
| `unsafe_ref_token` | `ref` contains characters outside the argv-safe subset. |
|
|
479
514
|
| `path_escapes_repo` | `path` resolves outside the git toplevel. |
|
|
480
|
-
| `git_show_failed` | `git show` exited non-zero (e.g. unknown ref). |
|
|
515
|
+
| `git_show_failed` | `git show` exited non-zero (e.g. unknown ref). Includes `detail` (stderr/stdout trim), matching peer tools. |
|
|
481
516
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
482
517
|
|
|
483
518
|
---
|
|
484
519
|
|
|
485
|
-
### `
|
|
486
|
-
|
|
487
|
-
| Parameter | Type | Notes |
|
|
488
|
-
|-----------|------|-------|
|
|
489
|
-
| `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
|
|
490
|
-
|
|
491
|
-
### `git_stash_list` — JSON shape (`format: "json"`)
|
|
492
|
-
|
|
493
|
-
```json
|
|
494
|
-
{
|
|
495
|
-
"stashes": [
|
|
496
|
-
{ "index": 0, "message": "WIP on main: abc1234 feat: add tool", "sha": "abc1234" }
|
|
497
|
-
]
|
|
498
|
-
}
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
### `git_stash_list` — error codes
|
|
502
|
-
|
|
503
|
-
| Code | Meaning |
|
|
504
|
-
|------|---------|
|
|
505
|
-
| `stash_list_failed` | `git stash list` failed unexpectedly. |
|
|
506
|
-
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
507
|
-
|
|
508
|
-
---
|
|
509
|
-
|
|
510
|
-
### `git_fetch` — parameters
|
|
520
|
+
### `git_conflicts` — parameters
|
|
511
521
|
|
|
512
522
|
| Parameter | Type | Default | Notes |
|
|
513
523
|
|-----------|------|---------|-------|
|
|
514
|
-
| `
|
|
515
|
-
| `
|
|
516
|
-
| `
|
|
517
|
-
| `tags` | boolean | `false` | Pass `--tags` to also fetch all tags. |
|
|
518
|
-
| `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
|
|
524
|
+
| `withHunks` | boolean | `true` | Parse conflict-marker hunks per file. Set `false` to return just the conflicted path list (skips reading file contents). |
|
|
525
|
+
| `maxLinesPerFile` | int | `200` | Cap on lines scanned per file (1–2000) before marking `truncated: true` on that file and dropping any hunk still open at the cutoff. |
|
|
526
|
+
| `workspaceRoot`, `format` | — | — | Standard single-repo pick + output format. |
|
|
519
527
|
|
|
520
|
-
### `
|
|
528
|
+
### `git_conflicts` — JSON shape (`format: "json"`)
|
|
521
529
|
|
|
522
530
|
```json
|
|
523
531
|
{
|
|
524
|
-
"
|
|
525
|
-
"
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
+
"state": "merge",
|
|
533
|
+
"files": [
|
|
534
|
+
{
|
|
535
|
+
"path": "shared.txt",
|
|
536
|
+
"hunks": [
|
|
537
|
+
{
|
|
538
|
+
"startLine": 2,
|
|
539
|
+
"ours": "BETA",
|
|
540
|
+
"theirs": "ALPHA",
|
|
541
|
+
"oursLabel": "HEAD",
|
|
542
|
+
"theirsLabel": "feature"
|
|
543
|
+
}
|
|
544
|
+
]
|
|
545
|
+
}
|
|
546
|
+
]
|
|
532
547
|
}
|
|
533
548
|
```
|
|
534
549
|
|
|
535
|
-
`
|
|
550
|
+
`state` is omitted when no merge/cherry-pick/revert/rebase is in progress (e.g. conflicts were left by some other means). A clean repo returns `{ "files": [] }`. Each file entry's `hunks` is omitted when empty (unreadable, binary, or no markers found — file still listed by `path` alone); `base` appears only for diff3-style markers (`|||||||`); `oursLabel`/`theirsLabel` are omitted when git did not attach a label to that marker. Per-file optional `error: "path_escapes_repo"` when a conflict path fails confinement (rare; paths normally come from git). Incomplete conflict markers within the scan window set `truncated: true` (same as line-cap truncation).
|
|
551
|
+
|
|
552
|
+
### `git_conflicts` — error codes
|
|
536
553
|
|
|
537
|
-
|
|
554
|
+
Only the shared single-repo prelude errors apply — the tool itself never fails on conflict state:
|
|
538
555
|
|
|
539
556
|
| Code | Meaning |
|
|
540
557
|
|------|---------|
|
|
541
|
-
| `
|
|
542
|
-
| `unsafe_ref_token` | `branch` contains characters outside the argv-safe subset. |
|
|
558
|
+
| `git_not_found` | `git` binary not on `PATH`. |
|
|
543
559
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
544
560
|
|
|
545
561
|
---
|
|
@@ -549,10 +565,11 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
|
|
|
549
565
|
| Parameter | Type | Default | Notes |
|
|
550
566
|
|-----------|------|---------|-------|
|
|
551
567
|
| `path` | string | — | **Required.** Repo-relative file to annotate. Confined to the repo (`path_escapes_repo` on escape). |
|
|
552
|
-
| `ref` | string | working tree | Commit-ish to blame at. Validated as a safe ref token. |
|
|
553
|
-
| `startLine` | int | — | Start of a line range (`-L`). Requires `endLine`. |
|
|
554
|
-
| `endLine` | int | — | End of the line range, inclusive. Requires `startLine`. |
|
|
555
|
-
| `
|
|
568
|
+
| `ref` | string | working tree | Commit-ish to blame at. Validated as a safe ref token. Ancestor notation (`HEAD~3`, `main^2`) is accepted. |
|
|
569
|
+
| `startLine` | int | — | Start of a line range (`-L`). Requires `endLine`. Max `1000000`. |
|
|
570
|
+
| `endLine` | int | — | End of the line range, inclusive. Requires `startLine`. Max `1000000`. |
|
|
571
|
+
| `maxLines` | int | `2000` | Max blamed lines to return (1–10000). Excess lines are dropped and signalled via `truncated`/`omittedLines`. |
|
|
572
|
+
| `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
|
|
556
573
|
|
|
557
574
|
### `git_blame` — JSON shape (`format: "json"`)
|
|
558
575
|
|
|
@@ -560,13 +577,26 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
|
|
|
560
577
|
{
|
|
561
578
|
"ref": "HEAD",
|
|
562
579
|
"path": "src/server.ts",
|
|
563
|
-
"
|
|
564
|
-
{
|
|
565
|
-
|
|
580
|
+
"groups": [
|
|
581
|
+
{
|
|
582
|
+
"sha": "a1b2c3d4…",
|
|
583
|
+
"author": "Damon Blais",
|
|
584
|
+
"date": "2026-04-12T18:32:01-07:00",
|
|
585
|
+
"summary": "feat: add tool",
|
|
586
|
+
"startLine": 1,
|
|
587
|
+
"endLine": 2,
|
|
588
|
+
"lines": [
|
|
589
|
+
{ "line": 1, "content": "import { FastMCP } from \"fastmcp\";" },
|
|
590
|
+
{ "line": 2, "content": "" }
|
|
591
|
+
]
|
|
592
|
+
}
|
|
593
|
+
],
|
|
594
|
+
"truncated": true,
|
|
595
|
+
"omittedLines": 120
|
|
566
596
|
}
|
|
567
597
|
```
|
|
568
598
|
|
|
569
|
-
`ref` is omitted when blaming the working tree.
|
|
599
|
+
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
600
|
|
|
571
601
|
### `git_blame` — error codes
|
|
572
602
|
|
|
@@ -580,74 +610,17 @@ The response contains one **`parity[]`** entry per resolved git toplevel. `absol
|
|
|
580
610
|
|
|
581
611
|
---
|
|
582
612
|
|
|
583
|
-
### `git_branch_list` — parameters
|
|
584
|
-
|
|
585
|
-
| Parameter | Type | Default | Notes |
|
|
586
|
-
|-----------|------|---------|-------|
|
|
587
|
-
| `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. |
|
|
589
|
-
|
|
590
|
-
### `git_branch_list` — JSON shape (`format: "json"`)
|
|
591
|
-
|
|
592
|
-
```json
|
|
593
|
-
{
|
|
594
|
-
"branches": [
|
|
595
|
-
{ "name": "main", "sha": "a1b2c3d4…", "current": true, "upstream": "origin/main" },
|
|
596
|
-
{ "name": "feature/x", "sha": "b2c3d4e5…", "current": false }
|
|
597
|
-
],
|
|
598
|
-
"remotes": [{ "name": "origin/main", "sha": "a1b2c3d4…" }]
|
|
599
|
-
}
|
|
600
|
-
```
|
|
601
|
-
|
|
602
|
-
`upstream` is omitted when a branch has no upstream. `remotes` is present only when `includeRemotes: true`.
|
|
603
|
-
|
|
604
|
-
### `git_branch_list` — error codes
|
|
605
|
-
|
|
606
|
-
| Code | Meaning |
|
|
607
|
-
|------|---------|
|
|
608
|
-
| `branch_list_failed` | `git for-each-ref` exited non-zero. `detail` carries stderr. |
|
|
609
|
-
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
610
|
-
|
|
611
|
-
---
|
|
612
|
-
|
|
613
|
-
### `git_reflog` — parameters
|
|
614
|
-
|
|
615
|
-
| Parameter | Type | Default | Notes |
|
|
616
|
-
|-----------|------|---------|-------|
|
|
617
|
-
| `ref` | string | `HEAD` | Ref whose reflog to show. Validated as a safe ref token. |
|
|
618
|
-
| `maxEntries` | int | `30` | Max entries to return (1–200). |
|
|
619
|
-
| `workspaceRoot`, `rootIndex`, `format` | — | Standard single-repo workspace pick + output format. |
|
|
620
|
-
|
|
621
|
-
### `git_reflog` — JSON shape (`format: "json"`)
|
|
622
|
-
|
|
623
|
-
```json
|
|
624
|
-
{
|
|
625
|
-
"ref": "HEAD",
|
|
626
|
-
"entries": [
|
|
627
|
-
{ "sha": "a1b2c3d4…", "selector": "HEAD@{0}", "message": "commit: feat: add tool" }
|
|
628
|
-
]
|
|
629
|
-
}
|
|
630
|
-
```
|
|
631
|
-
|
|
632
|
-
### `git_reflog` — error codes
|
|
633
|
-
|
|
634
|
-
| Code | Meaning |
|
|
635
|
-
|------|---------|
|
|
636
|
-
| `unsafe_ref_token` | `ref` contains characters outside the argv-safe subset. |
|
|
637
|
-
| `reflog_failed` | `git reflog show` exited non-zero. `detail` carries stderr. |
|
|
638
|
-
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
639
|
-
|
|
640
|
-
---
|
|
641
|
-
|
|
642
613
|
### `batch_commit` — atomic staging semantics
|
|
643
614
|
|
|
644
615
|
**Critical for AI agents:** Each call to `batch_commit` is **self-contained and atomic per-commit entry**.
|
|
645
616
|
|
|
646
|
-
- **All files in a single entry are staged together.** When you list `files: ["src/foo.ts", "src/bar.ts"]` in one commit entry, both are staged
|
|
617
|
+
- **All files in a single entry are staged together.** When you list `files: ["src/foo.ts", "src/bar.ts"]` in one commit entry, both are staged as a unit before the commit is created.
|
|
618
|
+
- **Index isolation per commit.** Unrelated paths that were already staged before the call are temporarily unstaged around an **index-based** `git commit` so they are not included. Pathspec/`--only` mode is intentionally avoided — it commits from the worktree and would squash hunk-level (`{ path, lines }`) staging.
|
|
647
619
|
- **Each commit entry is processed sequentially within the call.** The tool stages files, commits, then moves to the next entry. All entries within a single `batch_commit` call happen in one atomic MCP transaction.
|
|
648
620
|
- **A single `batch_commit` call cannot be split across multiple MCP calls.** Do NOT attempt incremental staging like "call 1 with file A, then call 2 with file B hoping they stage together." Each call is independent — call 1's commit lands immediately; call 2's changes are a separate transaction.
|
|
649
|
-
- **Failed entry stops the batch.** If staging or commit fails on entry N, the tool aborts and skips remaining entries.
|
|
621
|
+
- **Failed entry stops the batch.** If staging or commit fails on entry N, the tool aborts and skips remaining entries. On mid-entry `stage_failed`, paths already staged for that entry are unstaged (`git restore --staged`). **Entries that succeeded before the failure remain committed** — they are not rolled back.
|
|
650
622
|
- **Include all files for a logical change in a single `batch_commit` call.** Group related files in each commit entry, list them all in the `files` array, and include all necessary entries in the `commits` array.
|
|
623
|
+
- **dryRun** uses path-scoped `diff --stat`, unstages between entries, and restores the full pre-call index via `write-tree`/`read-tree` (including overlapping pre-staged paths).
|
|
651
624
|
|
|
652
625
|
Example: to commit two related changes atomically, pass both entries in one call:
|
|
653
626
|
```json
|
|
@@ -665,11 +638,10 @@ Do NOT do this: make two separate calls hoping to stage files incrementally. Tha
|
|
|
665
638
|
|
|
666
639
|
| Parameter | Type | Notes |
|
|
667
640
|
|-----------|------|-------|
|
|
668
|
-
| `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. |
|
|
641
|
+
| `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 (`from`/`to` each max `1000000`; `from > to` → `invalid_line_range`); 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. Rejects `.`, repo-root, and directory pathspecs (`invalid_paths`). |
|
|
669
642
|
| `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
643
|
| `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 |
|
|
672
|
-
| `rootIndex` | int | Pick one of several MCP roots (0-based). |
|
|
644
|
+
| `workspaceRoot` | string | Repo path. Default: first MCP root / cwd. |
|
|
673
645
|
| `format` | `"markdown"` \| `"json"` | Output format. Default: `"markdown"`. |
|
|
674
646
|
|
|
675
647
|
### `batch_commit` — JSON shape (`format: "json"`)
|
|
@@ -682,15 +654,11 @@ Do NOT do this: make two separate calls hoping to stage files incrementally. Tha
|
|
|
682
654
|
"results": [{
|
|
683
655
|
"index": 0,
|
|
684
656
|
"ok": true,
|
|
685
|
-
"sha": "a1b2c3d"
|
|
686
|
-
"message": "feat: add foo",
|
|
687
|
-
"files": ["src/foo.ts"]
|
|
657
|
+
"sha": "a1b2c3d"
|
|
688
658
|
}, {
|
|
689
659
|
"index": 1,
|
|
690
660
|
"ok": true,
|
|
691
|
-
"sha": "b2c3d4e"
|
|
692
|
-
"message": "chore: update config",
|
|
693
|
-
"files": ["config.json"]
|
|
661
|
+
"sha": "b2c3d4e"
|
|
694
662
|
}],
|
|
695
663
|
"push": {
|
|
696
664
|
"ok": true,
|
|
@@ -700,7 +668,9 @@ Do NOT do this: make two separate calls hoping to stage files incrementally. Tha
|
|
|
700
668
|
}
|
|
701
669
|
```
|
|
702
670
|
|
|
703
|
-
|
|
671
|
+
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.
|
|
672
|
+
|
|
673
|
+
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
674
|
|
|
705
675
|
When `dryRun: true`, the top-level response includes `dryRun: true`; successful `results[*]` entries omit `sha` and instead include `staged` and `diffStat`.
|
|
706
676
|
|
|
@@ -711,6 +681,8 @@ The `push` object is present only when `push: "after"` was requested **and** eve
|
|
|
711
681
|
| Code | Meaning |
|
|
712
682
|
|------|---------|
|
|
713
683
|
| `path_escapes_repository` | One of the listed file paths resolves outside the git toplevel. |
|
|
684
|
+
| `invalid_paths` | A pathspec is `.`, the repo root, or a directory (file paths only). |
|
|
685
|
+
| `invalid_line_range` | A `{path, lines}` entry has `from > to`. |
|
|
714
686
|
| `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. |
|
|
715
687
|
| `commit_failed` | `git commit` failed (e.g. nothing staged, hooks rejected). |
|
|
716
688
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
@@ -731,11 +703,11 @@ The `push` object is present only when `push: "after"` was requested **and** eve
|
|
|
731
703
|
|-----------|------|-------|
|
|
732
704
|
| `sources` | `string[]` | Source branches to merge, in order. 1–20 entries. Each must be a valid git ref token. |
|
|
733
705
|
| `into` | string | Destination branch. Defaults to the currently checked-out branch. Rejected when HEAD is detached. |
|
|
734
|
-
| `strategy` | `"auto"` \| `"ff-only"` \| `"rebase"` \| `"merge"` | Default `"auto"`: cascade **fast-forward → rebase → merge-commit** per source. `"ff-only"` fails on divergence. `"rebase"` rebases source onto destination and fast-forwards; no merge-commit fallback. `"merge"` always creates a merge commit (`--no-ff`). |
|
|
706
|
+
| `strategy` | `"auto"` \| `"ff-only"` \| `"rebase"` \| `"merge"` | Default `"auto"`: cascade **fast-forward → rebase → merge-commit** per source. `"ff-only"` fails on divergence. `"rebase"` rebases source onto destination and fast-forwards; no merge-commit fallback. `"merge"` always creates a merge commit (`--no-ff`). **`auto`/`rebase` rewrite the source branch tip when rebasing** (history rewrite of the source ref), then fast-forward the destination — agents must not treat `auto` as destination-only. |
|
|
735
707
|
| `message` | string | Merge commit message, used only when a merge commit is created. Defaults to `Merge branch '<source>' into <into>`. |
|
|
736
|
-
| `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
|
|
708
|
+
| `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, head, plus `release/*`/`release-*`/`hotfix/*`/`hotfix-*` — pattern requires a separator and non-empty suffix; bare `release`/`hotfix` are not protected). Never touches remote refs. |
|
|
737
709
|
| `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`, `
|
|
710
|
+
| `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
|
|
739
711
|
|
|
740
712
|
### `git_merge` — JSON shape (`format: "json"`)
|
|
741
713
|
|
|
@@ -768,7 +740,7 @@ The `push` object is present only when `push: "after"` was requested **and** eve
|
|
|
768
740
|
|
|
769
741
|
**`outcome`** (per source): `fast_forward`, `rebase_then_ff`, `merge_commit`, `up_to_date`, or `conflicts`. Cleanup fields (`branchDeleted`, `worktreeRemoved`) are only emitted when the corresponding flag was set and the operation actually ran — both are omitted for up-to-date sources and are never populated on partial-failure runs.
|
|
770
742
|
|
|
771
|
-
On conflict: top-level `ok` is `false`, the conflicting entry has `ok: false` with `conflictStage` (`"rebase"` or `"merge"`), `conflictPaths` (array of paths with unresolved markers), and an `error` code. Remaining sources are not attempted.
|
|
743
|
+
On conflict: top-level `ok` is `false`, the conflicting entry has `ok: false` with `conflictStage` (`"rebase"` or `"merge"`), `conflictPaths` (array of paths with unresolved markers), and an `error` code. The tool attempts `git rebase --abort` / `git merge --abort`. When abort succeeds, the tree is clean. When abort itself fails, the per-source `error` is `rebase_abort_failed` / `merge_abort_failed` (tree may still be mid-rebase/merge; `detail` carries abort stderr). Remaining sources are not attempted.
|
|
772
744
|
|
|
773
745
|
### `git_merge` — error codes
|
|
774
746
|
|
|
@@ -782,8 +754,10 @@ On conflict: top-level `ok` is `false`, the conflicting entry has `ok: false` wi
|
|
|
782
754
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
783
755
|
| `source_not_found` (per source) | A source branch name does not resolve. |
|
|
784
756
|
| `cannot_fast_forward` (per source) | `strategy: "ff-only"` refused because branches have diverged. |
|
|
785
|
-
| `rebase_conflicts` (per source) | Rebase encountered conflicts.
|
|
786
|
-
| `merge_conflicts` (per source) | Merge commit encountered conflicts.
|
|
757
|
+
| `rebase_conflicts` (per source) | Rebase encountered conflicts. The tool attempts `git rebase --abort`; on abort success the tree is clean. On abort failure see `rebase_abort_failed`. |
|
|
758
|
+
| `merge_conflicts` (per source) | Merge commit encountered conflicts. The tool attempts `git merge --abort`; on abort success the tree is clean. On abort failure see `merge_abort_failed`. |
|
|
759
|
+
| `rebase_abort_failed` (per source) | `git rebase --abort` failed after a conflict; tree may still be mid-rebase. `detail` carries abort stderr. |
|
|
760
|
+
| `merge_abort_failed` (per source) | `git merge --abort` failed after a conflict; tree may still be mid-merge. `detail` carries abort stderr. |
|
|
787
761
|
| `merge_failed` (per source) | `git merge --ff-only` failed unexpectedly. `detail` carries stderr. |
|
|
788
762
|
| `merge_base_failed` (per source) | `git merge-base` failed (usually unrelated histories). |
|
|
789
763
|
|
|
@@ -793,12 +767,13 @@ On conflict: top-level `ok` is `false`, the conflicting entry has `ok: false` wi
|
|
|
793
767
|
|
|
794
768
|
| Parameter | Type | Notes |
|
|
795
769
|
|-----------|------|-------|
|
|
796
|
-
| `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). |
|
|
770
|
+
| `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). After SHA-reachability filtering, at most **100** commits are fed to `git cherry-pick` per call; oversize returns `cherry_pick_too_many_commits` with `picked` + `max`. |
|
|
797
771
|
| `onto` | string | Destination branch. Defaults to the currently checked-out branch. Rejected when HEAD is detached. |
|
|
798
772
|
| `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
773
|
| `deleteMergedWorktrees` | boolean | Default `false`. After success, remove any local worktree attached to a branch-kind source (`git worktree remove`). Protected tails always skipped. |
|
|
800
774
|
| `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
|
-
| `
|
|
775
|
+
| `onConflict` | `"abort"` \| `"pause"` | Default `"abort"`: on conflict, run `cherry-pick --abort` and roll back the whole range (unchanged behavior). `"pause"`: on conflict, leave the conflict and native cherry-pick sequencer state in place — commits already applied stay applied — so it can be resolved and resumed via `git_cherry_pick_continue` (below). |
|
|
776
|
+
| `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
|
|
802
777
|
|
|
803
778
|
### `git_cherry_pick` — JSON shape (`format: "json"`)
|
|
804
779
|
|
|
@@ -838,7 +813,26 @@ On conflict, the response has `ok: false` and a top-level `conflict` object:
|
|
|
838
813
|
}
|
|
839
814
|
```
|
|
840
815
|
|
|
841
|
-
|
|
816
|
+
With the default `onConflict: "abort"`, the tool attempts `git cherry-pick --abort`. On abort success the tree is clean. On abort failure: top-level `error: cherry_pick_abort_failed`, and `conflict.abortFailed: true` (+ optional `abortDetail`); `CHERRY_PICK_HEAD` may remain.
|
|
817
|
+
|
|
818
|
+
With `onConflict: "pause"`, the tool does **not** abort — `conflict.paused: true` is added and `CHERRY_PICK_HEAD`/the sequencer are left in place. `applied` reflects commits from the same range that landed before the conflicting one (cheaply derived via `rev-list --count`, not rolled back). Resolve the conflict and call `git_cherry_pick_continue` to resume, or abort it explicitly with the same tool (`action: "abort"`):
|
|
819
|
+
|
|
820
|
+
```json
|
|
821
|
+
{
|
|
822
|
+
"ok": false,
|
|
823
|
+
"onto": "main",
|
|
824
|
+
"picked": 3,
|
|
825
|
+
"applied": 1,
|
|
826
|
+
"results": [ ... ],
|
|
827
|
+
"conflict": {
|
|
828
|
+
"stage": "cherry-pick",
|
|
829
|
+
"paused": true,
|
|
830
|
+
"commit": "abcdef1",
|
|
831
|
+
"paths": ["src/foo.ts"],
|
|
832
|
+
"detail": "…git stderr…"
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
```
|
|
842
836
|
|
|
843
837
|
### `git_cherry_pick` — error codes
|
|
844
838
|
|
|
@@ -847,10 +841,67 @@ Repo state is cleaned (`git cherry-pick --abort`) before returning — no partia
|
|
|
847
841
|
| `unsafe_ref_token` | A source or `onto` contains characters outside the argv-safe subset. |
|
|
848
842
|
| `onto_detached_head` | HEAD is detached and no `onto` was given. |
|
|
849
843
|
| `working_tree_dirty` | Uncommitted changes present. Commit, stash, or discard before cherry-picking. |
|
|
844
|
+
| `cherry_pick_in_progress` | A cherry-pick is already in progress (`CHERRY_PICK_HEAD` set, e.g. left paused by a prior call). Response includes `commit`. Resolve via `git_cherry_pick_continue` first. |
|
|
850
845
|
| `checkout_failed` | Could not switch to `onto`. |
|
|
851
846
|
| `destination_not_found` | `onto` does not resolve to a commit. |
|
|
852
847
|
| `source_not_found` | A source spec resolves to neither a branch, a range, nor a commit. |
|
|
853
848
|
| `range_resolution_failed` | `git rev-list` failed to expand a range spec. |
|
|
849
|
+
| `cherry_pick_too_many_commits` | Expanded/deduped pick list exceeds the hard cap of 100. Response includes `picked` + `max`. |
|
|
850
|
+
| `cherry_pick_abort_failed` | `git cherry-pick --abort` failed after a conflict (`onConflict: "abort"`, the default); tree may still be mid-cherry-pick. |
|
|
851
|
+
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
852
|
+
|
|
853
|
+
---
|
|
854
|
+
|
|
855
|
+
### `git_cherry_pick_continue` — parameters
|
|
856
|
+
|
|
857
|
+
| Parameter | Type | Notes |
|
|
858
|
+
|-----------|------|-------|
|
|
859
|
+
| `action` | `"continue"` \| `"abort"` | Default `"continue"`. `"continue"`: precheck no unmerged paths remain, then resume the native sequencer (`git -c core.editor=true cherry-pick --continue`). `"abort"`: roll back to the pre-cherry-pick HEAD (`git cherry-pick --abort`, same helper `git_cherry_pick` uses). |
|
|
860
|
+
| `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
|
|
861
|
+
|
|
862
|
+
This tool is **stateless** — it probes `CHERRY_PICK_HEAD` live off `.git` on every call rather than depending on the pausing `git_cherry_pick` call, so it works for a cherry-pick left in progress by any means.
|
|
863
|
+
|
|
864
|
+
### `git_cherry_pick_continue` — JSON shape (`format: "json"`)
|
|
865
|
+
|
|
866
|
+
Success:
|
|
867
|
+
|
|
868
|
+
```json
|
|
869
|
+
{ "ok": true, "action": "continue", "applied": 2, "headSha": "a1b2c3d…" }
|
|
870
|
+
```
|
|
871
|
+
|
|
872
|
+
`applied` counts commits added to HEAD since the call started (the resolved pick plus any further picks the sequencer completed on its own).
|
|
873
|
+
|
|
874
|
+
If resuming lands on another conflict later in the same range, the response mirrors a paused `git_cherry_pick` call so the loop is resumable — call this tool again after resolving:
|
|
875
|
+
|
|
876
|
+
```json
|
|
877
|
+
{
|
|
878
|
+
"ok": false,
|
|
879
|
+
"action": "continue",
|
|
880
|
+
"applied": 1,
|
|
881
|
+
"conflict": {
|
|
882
|
+
"stage": "cherry-pick",
|
|
883
|
+
"paused": true,
|
|
884
|
+
"commit": "def4567",
|
|
885
|
+
"paths": ["src/bar.ts"],
|
|
886
|
+
"detail": "…git stderr…"
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
`action: "abort"` success:
|
|
892
|
+
|
|
893
|
+
```json
|
|
894
|
+
{ "ok": true, "action": "abort", "headSha": "a1b2c3d…" }
|
|
895
|
+
```
|
|
896
|
+
|
|
897
|
+
### `git_cherry_pick_continue` — error codes
|
|
898
|
+
|
|
899
|
+
| Code | Meaning |
|
|
900
|
+
|------|---------|
|
|
901
|
+
| `no_cherry_pick_in_progress` | `CHERRY_PICK_HEAD` is not set — nothing to continue or abort. |
|
|
902
|
+
| `cherry_pick_unresolved_paths` | `action: "continue"` called while conflicted paths are still unmerged. Response includes `paths`. Stage resolutions first. |
|
|
903
|
+
| `cherry_pick_continue_failed` | `git cherry-pick --continue` failed for a reason other than a new conflict (e.g. the resolved pick would produce an empty commit). Response includes `detail`. |
|
|
904
|
+
| `cherry_pick_abort_failed` | `git cherry-pick --abort` failed (`action: "abort"`); tree may still be mid-cherry-pick. |
|
|
854
905
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
855
906
|
|
|
856
907
|
---
|
|
@@ -864,7 +915,7 @@ For already-committed work, call **`git_push`** directly instead of creating an
|
|
|
864
915
|
| `remote` | string | Remote to push to. Defaults to the remote inferred from the upstream tracking ref, or `origin` when `setUpstream` is true. |
|
|
865
916
|
| `branch` | string | Branch to push. Defaults to the currently checked-out branch. Rejected on detached HEAD. |
|
|
866
917
|
| `setUpstream` | boolean | Default `false`. Pass `-u` to set the upstream tracking ref; remote defaults to `origin`. |
|
|
867
|
-
| `workspaceRoot`, `
|
|
918
|
+
| `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
|
|
868
919
|
|
|
869
920
|
### `git_push` — JSON shape (`format: "json"`)
|
|
870
921
|
|
|
@@ -893,7 +944,7 @@ For already-committed work, call **`git_push`** directly instead of creating an
|
|
|
893
944
|
| `message` | string | — | When provided, creates an annotated tag; otherwise creates a lightweight tag. |
|
|
894
945
|
| `ref` | string | `HEAD` | Commit/ref to tag. Ignored when `delete: true`. |
|
|
895
946
|
| `delete` | boolean | `false` | Delete the named tag instead of creating it. |
|
|
896
|
-
| `workspaceRoot`, `
|
|
947
|
+
| `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
|
|
897
948
|
|
|
898
949
|
### `git_tag` — JSON shape (`format: "json"`)
|
|
899
950
|
|
|
@@ -918,12 +969,46 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
|
|
|
918
969
|
|
|
919
970
|
---
|
|
920
971
|
|
|
972
|
+
### `git_branch` — parameters
|
|
973
|
+
|
|
974
|
+
| Parameter | Type | Default | Notes |
|
|
975
|
+
|-----------|------|---------|-------|
|
|
976
|
+
| `action` | `"create"` \| `"delete"` \| `"rename"` | — | Required. Which branch operation to perform. |
|
|
977
|
+
| `name` | string | — | Required. Branch to create/delete, or the existing branch name for rename. |
|
|
978
|
+
| `from` | string | `HEAD` | Commit-ish to base a new branch on. `create` only; ignored otherwise. |
|
|
979
|
+
| `newName` | string | — | New branch name. Required when `action: "rename"`. |
|
|
980
|
+
| `force` | boolean | `false` | Force-delete an unmerged branch (`git branch -D`). `delete` only; never overrides protected-branch rejection. |
|
|
981
|
+
| `workspaceRoot`, `format` | — | — | Standard single-repo pick + output format. |
|
|
982
|
+
|
|
983
|
+
### `git_branch` — JSON shape (`format: "json"`)
|
|
984
|
+
|
|
985
|
+
```json
|
|
986
|
+
{ "action": "create", "branch": "feature/x", "sha": "a1b2c3d4e5f6..." }
|
|
987
|
+
```
|
|
988
|
+
|
|
989
|
+
`action` echoes the requested operation; `branch` is the resulting branch name (the created/deleted name, or the new name after a rename); `sha` is the branch tip — for `delete`, the tip SHA the branch pointed to immediately before removal.
|
|
990
|
+
|
|
991
|
+
### `git_branch` — error codes
|
|
992
|
+
|
|
993
|
+
| Code | Meaning |
|
|
994
|
+
|------|---------|
|
|
995
|
+
| `unsafe_ref_token` | `name`, `from`, or `newName` contains disallowed characters. |
|
|
996
|
+
| `protected_branch` | `name` (source) or `newName` (rename target) is on the protected names list. Checked for every action, regardless of `force`. |
|
|
997
|
+
| `missing_new_name` | `action: "rename"` without a non-empty `newName`. |
|
|
998
|
+
| `ref_not_found` | `from` (create) or `name` (delete) did not resolve to a commit. |
|
|
999
|
+
| `branch_create_failed` | `git branch <name> <from>` exited non-zero (e.g. name already exists). `detail` carries stderr. |
|
|
1000
|
+
| `branch_delete_failed` | `git branch -d`/`-D` exited non-zero (e.g. unmerged without `force`, or deleting the checked-out branch). `detail` carries stderr. |
|
|
1001
|
+
| `branch_rename_failed` | `git branch -m` exited non-zero (e.g. target name already exists), or the SHA could not be resolved after a successful rename. `detail` carries stderr/diagnostic text. |
|
|
1002
|
+
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
1003
|
+
|
|
1004
|
+
---
|
|
1005
|
+
|
|
921
1006
|
### `git_reset_soft` — parameters
|
|
922
1007
|
|
|
923
1008
|
| Parameter | Type | Notes |
|
|
924
1009
|
|-----------|------|-------|
|
|
925
1010
|
| `ref` | string | Target commit: `HEAD~N`, branch name, or full/short SHA. |
|
|
926
|
-
| `workspaceRoot`, `
|
|
1011
|
+
| `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
|
|
927
1012
|
|
|
928
1013
|
### `git_reset_soft` — JSON shape (`format: "json"`)
|
|
929
1014
|
|
|
@@ -937,22 +1022,66 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
|
|
|
937
1022
|
|------|---------|
|
|
938
1023
|
| `unsafe_ref_token` | `ref` contains characters outside the ancestor-safe token set. |
|
|
939
1024
|
| `working_tree_dirty` | Working tree has uncommitted/unstaged changes; clean up before resetting. |
|
|
940
|
-
| `status_failed` | `git status` failed unexpectedly. |
|
|
941
1025
|
| `reset_failed` | `git reset --soft` failed (e.g. ref does not exist). |
|
|
942
1026
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
943
1027
|
|
|
944
1028
|
---
|
|
945
1029
|
|
|
1030
|
+
### `git_revert` — parameters
|
|
1031
|
+
|
|
1032
|
+
| Parameter | Type | Default | Notes |
|
|
1033
|
+
|-----------|------|---------|-------|
|
|
1034
|
+
| `sources` | string[] | — | Required, 1–20 entries. Commits to revert, applied in order: SHA, branch/tag name, or ancestor notation (`HEAD~1`). Each validated as an ancestor-safe ref token. |
|
|
1035
|
+
| `noCommit` | boolean | `false` | Pass `--no-commit`: stage the revert(s) in the index/working tree without committing. Working tree is intentionally left staged in this case (documented exception to the tree-clean guarantee). |
|
|
1036
|
+
| `mainline` | int ≥ 1 | — | Parent number (`-m N`) to diff against — required when reverting a merge commit. |
|
|
1037
|
+
| `workspaceRoot`, `format` | — | — | Standard single-repo pick + output format. |
|
|
1038
|
+
|
|
1039
|
+
### `git_revert` — JSON shape (`format: "json"`)
|
|
1040
|
+
|
|
1041
|
+
Success (committed):
|
|
1042
|
+
|
|
1043
|
+
```json
|
|
1044
|
+
{ "ok": true, "reverted": [{ "source": "a1b2c3d", "sha": "f9e8d7c…" }] }
|
|
1045
|
+
```
|
|
1046
|
+
|
|
1047
|
+
Success (`noCommit: true` — no commits made):
|
|
1048
|
+
|
|
1049
|
+
```json
|
|
1050
|
+
{ "ok": true, "staged": true, "sources": ["a1b2c3d"], "stagedCount": 1 }
|
|
1051
|
+
```
|
|
1052
|
+
|
|
1053
|
+
Conflict (aborted, tree left clean):
|
|
1054
|
+
|
|
1055
|
+
```json
|
|
1056
|
+
{ "ok": false, "aborted": true, "commit": "a1b2c3d…", "conflicts": ["shared.txt"], "detail": "..." }
|
|
1057
|
+
```
|
|
1058
|
+
|
|
1059
|
+
`commit`/`detail` are omitted when unavailable. `reverted[].sha` is the new commit created by reverting `source`, in the same order as `sources` (one new commit per source when `noCommit` is `false`).
|
|
1060
|
+
|
|
1061
|
+
### `git_revert` — error codes
|
|
1062
|
+
|
|
1063
|
+
| Code | Meaning |
|
|
1064
|
+
|------|---------|
|
|
1065
|
+
| `unsafe_ref_token` | A `sources` entry contains characters outside the ancestor-safe token set. |
|
|
1066
|
+
| `working_tree_dirty` | Working tree has uncommitted/unstaged changes; clean up before reverting. |
|
|
1067
|
+
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
1068
|
+
|
|
1069
|
+
Conflict / other `git revert` failures do **not** use the `error` field — see the `aborted: true` shape above (mirrors `git_cherry_pick`'s conflict reporting).
|
|
1070
|
+
|
|
1071
|
+
---
|
|
1072
|
+
|
|
946
1073
|
### `git_stash_apply` — parameters
|
|
947
1074
|
|
|
948
1075
|
| Parameter | Type | Default | Notes |
|
|
949
1076
|
|-----------|------|---------|-------|
|
|
950
|
-
| `index` | int | `0` | Stash index to apply/pop (`stash@{index}`). |
|
|
1077
|
+
| `index` | int | `0` | Stash index to apply/pop (`stash@{index}`). Max `10000`. |
|
|
951
1078
|
| `pop` | boolean | `false` | When `true`, runs `git stash pop` instead of `git stash apply`. |
|
|
952
|
-
| `workspaceRoot`, `
|
|
1079
|
+
| `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
|
|
953
1080
|
|
|
954
1081
|
### `git_stash_apply` — JSON shape (`format: "json"`)
|
|
955
1082
|
|
|
1083
|
+
Success (`applied: true`, no `error`):
|
|
1084
|
+
|
|
956
1085
|
```json
|
|
957
1086
|
{
|
|
958
1087
|
"applied": true,
|
|
@@ -962,32 +1091,77 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
|
|
|
962
1091
|
}
|
|
963
1092
|
```
|
|
964
1093
|
|
|
965
|
-
|
|
1094
|
+
On failure (`applied: false`):
|
|
1095
|
+
|
|
1096
|
+
```json
|
|
1097
|
+
{
|
|
1098
|
+
"error": "stash_apply_failed",
|
|
1099
|
+
"applied": false,
|
|
1100
|
+
"stashIndex": 0,
|
|
1101
|
+
"popped": false,
|
|
1102
|
+
"output": "…",
|
|
1103
|
+
"conflictPaths": ["conflict.txt"]
|
|
1104
|
+
}
|
|
1105
|
+
```
|
|
1106
|
+
|
|
1107
|
+
`error: "stash_apply_failed"` on every failed apply/pop. `conflictPaths` is omitted when empty (omit-empty rule); populated via `git diff --name-only --diff-filter=U`. The tree is left as git left it (no auto-abort). With `pop: true`, a conflicted pop retains the stash entry (git does not drop it until the apply succeeds). Markdown failure output lists conflict paths under a `Conflicts:` block when present. `output` is omitted when git produced no stdout/stderr text. `popped` mirrors the requested `pop` flag (whether pop was attempted), not whether the stash entry was removed.
|
|
966
1108
|
|
|
967
1109
|
### `git_stash_apply` — error codes
|
|
968
1110
|
|
|
969
1111
|
| Code | Meaning |
|
|
970
1112
|
|------|---------|
|
|
971
1113
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
1114
|
+
| `stash_apply_failed` | Apply/pop failed (also on the failure payload above with `applied: false`). |
|
|
972
1115
|
|
|
973
1116
|
---
|
|
974
1117
|
|
|
975
|
-
### `
|
|
1118
|
+
### `git_stash_push` — parameters
|
|
1119
|
+
|
|
1120
|
+
| Parameter | Type | Default | Notes |
|
|
1121
|
+
|-----------|------|---------|-------|
|
|
1122
|
+
| `message` | string | — | Stash subject (`git stash push -m <message>`). |
|
|
1123
|
+
| `includeUntracked` | boolean | `false` | Also stash untracked files (`git stash push -u`). |
|
|
1124
|
+
| `keepIndex` | boolean | `false` | Keep staged changes in the index after stashing (`git stash push --keep-index`). |
|
|
1125
|
+
| `paths` | string[] | — | Scope the stash to specific paths, relative to git root. Each path must resolve within the repo root or the call fails with `path_escapes_repo`. |
|
|
1126
|
+
| `workspaceRoot`, `format` | — | — | Standard single-repo pick + output format. |
|
|
1127
|
+
|
|
1128
|
+
### `git_stash_push` — JSON shape (`format: "json"`)
|
|
1129
|
+
|
|
1130
|
+
Success:
|
|
1131
|
+
|
|
1132
|
+
```json
|
|
1133
|
+
{
|
|
1134
|
+
"stashed": true,
|
|
1135
|
+
"ref": "stash@{0}",
|
|
1136
|
+
"sha": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
|
|
1137
|
+
"message": "On main: wip: pushed"
|
|
1138
|
+
}
|
|
1139
|
+
```
|
|
1140
|
+
|
|
1141
|
+
Nothing to stash (git exits 0 printing "No local changes to save"):
|
|
976
1142
|
|
|
977
1143
|
```json
|
|
978
|
-
{ "
|
|
1144
|
+
{ "stashed": false, "reason": "no_local_changes" }
|
|
979
1145
|
```
|
|
980
1146
|
|
|
981
|
-
|
|
1147
|
+
### `git_stash_push` — error codes
|
|
1148
|
+
|
|
1149
|
+
| Code | Meaning |
|
|
1150
|
+
|------|---------|
|
|
1151
|
+
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
1152
|
+
| `path_escapes_repo` | A `paths` entry resolves outside the repository root. |
|
|
1153
|
+
| `stash_push_failed` | `git stash push` exited non-zero for a reason other than "nothing to stash" (see `detail`). |
|
|
1154
|
+
|
|
1155
|
+
---
|
|
982
1156
|
|
|
983
1157
|
### `git_worktree_add` — parameters
|
|
984
1158
|
|
|
985
1159
|
| Parameter | Type | Notes |
|
|
986
1160
|
|-----------|------|-------|
|
|
987
|
-
| `path` | string | Filesystem path for the new worktree. Relative paths are resolved from the git toplevel. |
|
|
988
|
-
| `branch` | string | Branch to check out. Created from `baseRef` if it does not already exist. |
|
|
989
|
-
| `baseRef` | string | Commit-ish for branch creation. Default: `HEAD`. Ignored when `branch` already exists. |
|
|
990
|
-
| `workspaceRoot`, `
|
|
1161
|
+
| `path` | string | Filesystem path for the new worktree. Relative paths are resolved from the git toplevel. Sibling worktrees **outside** the git toplevel remain allowed (absolute or relative). Leading `-` / option-like basenames and NUL bytes are rejected with `invalid_paths`. The path is passed to git after `--` so it cannot be parsed as an option. |
|
|
1162
|
+
| `branch` | string | Branch to check out. Created from `baseRef` if it does not already exist. Trimmed before validation and spawn. |
|
|
1163
|
+
| `baseRef` | string | Commit-ish for branch creation. Default: `HEAD`. Ignored when `branch` already exists. Trimmed before validation and spawn. |
|
|
1164
|
+
| `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
|
|
991
1165
|
|
|
992
1166
|
### `git_worktree_add` — JSON shape (`format: "json"`)
|
|
993
1167
|
|
|
@@ -1001,6 +1175,7 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
|
|
|
1001
1175
|
|------|---------|
|
|
1002
1176
|
| `unsafe_ref_token` | `branch` or `baseRef` contains disallowed characters. |
|
|
1003
1177
|
| `protected_branch` | `branch` is on the protected names list. |
|
|
1178
|
+
| `invalid_paths` | `path` has a leading `-` / option-like basename, contains a NUL byte, or otherwise fails argv-safe path checks. |
|
|
1004
1179
|
| `worktree_add_failed` | `git worktree add` exited non-zero. `detail` carries stderr. |
|
|
1005
1180
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
1006
1181
|
|
|
@@ -1008,9 +1183,15 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
|
|
|
1008
1183
|
|
|
1009
1184
|
| Parameter | Type | Notes |
|
|
1010
1185
|
|-----------|------|-------|
|
|
1011
|
-
| `path` | string | Path of the worktree to remove. |
|
|
1186
|
+
| `path` | string | Path of the worktree to remove. Same argv rules as add (leading `-` / NUL → `invalid_paths`; path passed after `--`). Sibling paths outside the toplevel are allowed when registered. |
|
|
1012
1187
|
| `force` | boolean | Default `false`. Pass `--force` to allow removal with uncommitted changes. |
|
|
1013
|
-
| `workspaceRoot`, `
|
|
1188
|
+
| `workspaceRoot`, `format` | — | Standard single-repo pick + output format. |
|
|
1189
|
+
|
|
1190
|
+
### `git_worktree_remove` — JSON shape (`format: "json"`)
|
|
1191
|
+
|
|
1192
|
+
```json
|
|
1193
|
+
{ "ok": true, "path": "/abs/worktree" }
|
|
1194
|
+
```
|
|
1014
1195
|
|
|
1015
1196
|
### `git_worktree_remove` — error codes
|
|
1016
1197
|
|
|
@@ -1018,6 +1199,7 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
|
|
|
1018
1199
|
|------|---------|
|
|
1019
1200
|
| `cannot_remove_main_worktree` | `path` resolves to the main (non-linked) worktree. |
|
|
1020
1201
|
| `worktree_not_found` | `path` is not registered as a worktree in this repo. |
|
|
1202
|
+
| `invalid_paths` | `path` has a leading `-` / option-like basename, contains a NUL byte, or otherwise fails argv-safe path checks. |
|
|
1021
1203
|
| `worktree_remove_failed` | `git worktree remove` failed. Pass `force: true` if there are uncommitted changes. |
|
|
1022
1204
|
| `not_a_git_repository` | The resolved workspace root is not inside a git repository. |
|
|
1023
1205
|
|
|
@@ -1027,9 +1209,9 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
|
|
|
1027
1209
|
|
|
1028
1210
|
| Variable | Default | Notes |
|
|
1029
1211
|
|----------|---------|-------|
|
|
1030
|
-
| `GIT_SUBPROCESS_PARALLELISM` | CPU-based | Max concurrent git subprocesses for multi-root fan-out (`git_inventory`, `git_parity`, multi-root `git_log`). |
|
|
1212
|
+
| `GIT_SUBPROCESS_PARALLELISM` | CPU-based | Max concurrent git subprocesses for multi-root fan-out (`git_inventory`, `git_parity`, multi-root `git_log`, multi-root `git_grep`). |
|
|
1031
1213
|
| `GIT_SUBPROCESS_TIMEOUT_MS` | `120000` | Per-subprocess timeout in ms; on expiry the child is killed (SIGTERM) and the call resolves as failed. Set `0` (or negative) to disable (unbounded). |
|
|
1032
|
-
| `RETHUNK_GIT_TOOLS` | _(unset — all)_ | Comma-separated allowlist of exact tool names. Unset or empty → all
|
|
1214
|
+
| `RETHUNK_GIT_TOOLS` | _(unset — all)_ | Comma-separated allowlist of exact tool names. Unset or empty → all 24 tools registered. Non-empty → only the listed tools; unknown names warned to stderr. If every listed name is unknown, zero tools are registered (restriction honored literally). The presets resource is always available. Example: `RETHUNK_GIT_TOOLS=git_status,git_diff_summary,git_diff,git_log,batch_commit,git_push`. |
|
|
1033
1215
|
|
|
1034
1216
|
## Resource
|
|
1035
1217
|
|
|
@@ -1037,41 +1219,47 @@ For deletions, `type` is `"deleted"` and `sha` is an empty string.
|
|
|
1037
1219
|
|-----|---------|
|
|
1038
1220
|
| `rethunk-git://presets` | JSON snapshot of `.rethunk/git-mcp-presets.json` at the resolved git toplevel (or structured errors). |
|
|
1039
1221
|
|
|
1040
|
-
|
|
1222
|
+
Error payloads from this resource may include:
|
|
1041
1223
|
|
|
1042
|
-
|
|
1224
|
+
| Code | Meaning |
|
|
1225
|
+
|------|---------|
|
|
1226
|
+
| `no_workspace_root` | No MCP workspace root could be resolved for the session. |
|
|
1227
|
+
| `not_a_git_repository` | The resolved path is not inside a git repository. |
|
|
1228
|
+
| `preset_file_invalid` | Preset file failed to load (`kind`: `"invalid_json"` or `"schema"`). |
|
|
1229
|
+
|
|
1230
|
+
When the file is missing, the resource returns `fileExists: false` with an empty `presets` object (not an error).
|
|
1231
|
+
|
|
1232
|
+
## Root resolution
|
|
1043
1233
|
|
|
1044
|
-
|
|
1234
|
+
Every tool carries exactly **one** routing parameter:
|
|
1045
1235
|
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1236
|
+
| Tools | Parameter | Accepts |
|
|
1237
|
+
|-------|-----------|---------|
|
|
1238
|
+
| Fan-out: `git_status`, `git_inventory`, `git_parity`, `list_presets`, `git_log`, `git_grep` | `root` | string (one repo path) \| string[] (explicit repo list) \| `"*"` (every MCP root) |
|
|
1239
|
+
| All other 18 tools | `workspaceRoot` | string (one repo path) |
|
|
1240
|
+
|
|
1241
|
+
### `root` forms (fan-out tools)
|
|
1242
|
+
|
|
1243
|
+
- **string** — resolve that one path (same semantics as `workspaceRoot`).
|
|
1244
|
+
- **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`).
|
|
1245
|
+
- **`"*"`** — every `file://` root reported by the MCP client (capped at **256** resolved toplevels — same `root_list_too_many` as an explicit array); markdown output emits one `# {tool}` header with per-root subsections (`git_inventory` uses `### {gitTop}`; `git_status` uses `### MCP root: ...`), or combined JSON.
|
|
1053
1246
|
|
|
1054
1247
|
Example — two sibling repos in one `git_status` call:
|
|
1055
1248
|
|
|
1056
1249
|
```json
|
|
1057
1250
|
{
|
|
1058
1251
|
"format": "json",
|
|
1059
|
-
"
|
|
1252
|
+
"root": [
|
|
1060
1253
|
"/usr/local/src/com.github/Rethunk-AI/mcp-multi-root-git",
|
|
1061
1254
|
"/usr/local/src/com.github/Rethunk-AI/rethunk-github-mcp"
|
|
1062
1255
|
]
|
|
1063
1256
|
}
|
|
1064
1257
|
```
|
|
1065
1258
|
|
|
1066
|
-
### Default order (when `
|
|
1067
|
-
|
|
1068
|
-
Order applied when resolving which directory(ies) tools run against:
|
|
1259
|
+
### Default order (when `root` / `workspaceRoot` is omitted)
|
|
1069
1260
|
|
|
1070
|
-
1.
|
|
1071
|
-
2.
|
|
1072
|
-
3. **`
|
|
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`).
|
|
1261
|
+
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).
|
|
1262
|
+
2. Otherwise the first `file://` root reported by the MCP client through **`roots/list`**.
|
|
1263
|
+
3. **`process.cwd()`** if no file roots (e.g. CI with explicit `workspaceRoot`).
|
|
1076
1264
|
|
|
1077
1265
|
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.
|