@rethunk/mcp-multi-root-git 2.3.1 → 2.3.3
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 +5 -4
- package/CHANGELOG.md +23 -0
- package/HUMANS.md +1 -13
- package/dist/server/batch-commit-tool.js +1 -1
- package/dist/server/git-cherry-pick-tool.js +1 -1
- package/dist/server/git-inventory-tool.js +5 -0
- package/dist/server/git-merge-tool.js +1 -1
- package/dist/server/git-push-tool.js +1 -1
- package/dist/server/git-reset-soft-tool.js +1 -1
- package/dist/server/git-worktree-tool.js +2 -2
- package/dist/server/list-presets-tool.js +1 -0
- package/dist/server/roots.js +65 -0
- package/dist/server/schemas.js +8 -0
- package/docs/mcp-tools.md +37 -5
- package/package.json +2 -1
package/AGENTS.md
CHANGED
|
@@ -7,7 +7,8 @@ IDEs injecting this as context: do not re-link from rules.
|
|
|
7
7
|
**Canonical docs — do not duplicate:**
|
|
8
8
|
- Install + per-client wiring → [docs/install.md](docs/install.md)
|
|
9
9
|
- Tools, JSON shape, resources, root resolution → [docs/mcp-tools.md](docs/mcp-tools.md)
|
|
10
|
-
-
|
|
10
|
+
- Dev setup, CI, commit conventions → [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
11
|
+
- Presets, auth, publish → [HUMANS.md](HUMANS.md)
|
|
11
12
|
|
|
12
13
|
## Implementation map
|
|
13
14
|
|
|
@@ -16,9 +17,9 @@ IDEs injecting this as context: do not re-link from rules.
|
|
|
16
17
|
| [`src/server.ts`](src/server.ts) | `FastMCP` + `roots: { enabled: true }`; `readMcpServerVersion()`; `registerRethunkGitTools` |
|
|
17
18
|
| [`src/server/json.ts`](src/server/json.ts) | `jsonRespond()` (minified, no envelope), `spreadWhen`, `spreadDefined` |
|
|
18
19
|
| [`src/server/git.ts`](src/server/git.ts) | `gateGit`, `spawnGitAsync`, `asyncPool`, `GIT_SUBPROCESS_PARALLELISM`, `gitTopLevel`, `gitRevParseGitDir`, `gitRevParseHead`, `parseGitSubmodulePaths`, `hasGitMetadata`, `gitStatusSnapshotAsync`, `gitStatusShortBranchAsync`, `fetchAheadBehind`, `isSafeGitUpstreamToken` |
|
|
19
|
-
| [`src/server/roots.ts`](src/server/roots.ts) | `requireGitAndRoots`, `requireSingleRepo` — shared tool preludes; session root resolution |
|
|
20
|
+
| [`src/server/roots.ts`](src/server/roots.ts) | `requireGitAndRoots`, `requireSingleRepo`, `resolveAbsoluteGitRootsList`, `GitRootPickArgs` — shared tool preludes; session root resolution; optional `absoluteGitRoots` bulk pick |
|
|
20
21
|
| [`src/server/presets.ts`](src/server/presets.ts) | `PRESET_FILE_PATH`, `loadPresetsFromGitTop`, `presetLoadErrorPayload`, `applyPresetNestedRoots`, `applyPresetParityPairs`; Zod schemas must match [`git-mcp-presets.schema.json`](git-mcp-presets.schema.json) |
|
|
21
|
-
| [`src/server/schemas.ts`](src/server/schemas.ts) | `WorkspacePickSchema`, `MAX_INVENTORY_ROOTS_DEFAULT
|
|
22
|
+
| [`src/server/schemas.ts`](src/server/schemas.ts) | `WorkspacePickSchema`, `MAX_INVENTORY_ROOTS_DEFAULT`, **`MAX_ABSOLUTE_GIT_ROOTS`** (256), optional **`absoluteGitRoots`** on workspace pick |
|
|
22
23
|
| [`src/server/inventory.ts`](src/server/inventory.ts) | `InventoryEntryJson`, `validateRepoPath`, `makeSkipEntry`, `buildInventorySectionMarkdown`, `collectInventoryEntry` |
|
|
23
24
|
| [`src/server/git-refs.ts`](src/server/git-refs.ts) | `isProtectedBranch`, `isSafeGitRefToken`, `isSafeGitRangeToken`, `isSafeGitAncestorRef`; `getCurrentBranch`, `resolveRef`, `isWorkingTreeClean`, `isFullyMergedInto`, `commitListBetween`; `listWorktrees`, `worktreeForBranch`; `conflictPaths` |
|
|
24
25
|
| [`src/server/tools.ts`](src/server/tools.ts) | `registerRethunkGitTools` — dispatches to `register*` below |
|
|
@@ -48,7 +49,7 @@ IDEs injecting this as context: do not re-link from rules.
|
|
|
48
49
|
|
|
49
50
|
Local: `bun run build` | `bun run check` | `bun run test`. CI ([`ci.yml`](.github/workflows/ci.yml)) runs same on PRs + `main` after `bun install --frozen-lockfile`, uploads prerelease `npm pack` artifact. Tag `v*.*.*` matching `package.json` version → [`release.yml`](.github/workflows/release.yml) publishes to GitHub Packages as `@rethunk-ai/mcp-multi-root-git` + cuts GitHub Release. npmjs publish is manual (see [HUMANS.md](HUMANS.md)).
|
|
50
51
|
|
|
51
|
-
Optional [`.githooks/`](.githooks): `bun run setup-hooks` once per clone. pre-commit=`check`; pre-push=frozen install + build + check.
|
|
52
|
+
Optional [`.githooks/`](.githooks): `bun run setup-hooks` once per clone. pre-commit=`check`; pre-push=frozen install + build + check + test.
|
|
52
53
|
|
|
53
54
|
Path confinement: [`src/repo-paths.ts`](src/repo-paths.ts) — extend tests when changing.
|
|
54
55
|
|
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@rethunk/mcp-multi-root-git` are documented here. Format loosely follows [Keep a Changelog](https://keepachangelog.com); the project uses [Semantic Versioning](https://semver.org).
|
|
4
4
|
|
|
5
|
+
## [2.3.3] — 2026-04-21
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- **`absoluteGitRoots`** on the workspace pick schema: pass absolute paths to many independent git clones in one MCP call for **`git_status`**, **`git_inventory`**, **`git_log`**, **`git_parity`**, **`git_diff_summary`** (single distinct toplevel only), and **`list_presets`**. Mutating tools omit this parameter from their Zod surface. See **`docs/mcp-tools.md`** (*Workspace root resolution*).
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **`requireGitAndRoots`** / **`requireSingleRepo`**: new prelude **`resolveAbsoluteGitRootsList`** with dedupe, fail-fast on invalid paths, and mutual exclusion with `workspaceRoot` / `rootIndex` / `allWorkspaceRoots` / `preset` (and `nestedRoots`+`preset` guarded in **`git_inventory`**).
|
|
14
|
+
|
|
15
|
+
## [2.3.2] — 2026-04-21
|
|
16
|
+
|
|
17
|
+
### CI
|
|
18
|
+
|
|
19
|
+
- Coverage gate added: `bun run test:coverage` enforces an 80% line-coverage minimum in CI (`check` job).
|
|
20
|
+
|
|
21
|
+
### Documentation
|
|
22
|
+
|
|
23
|
+
- **`CONTRIBUTING.md`** — new file; consolidates dev setup, hook table, commit conventions, CI description, PR checklist, path-confinement guidance, and how-to-add-a-tool guidance for mutating tools.
|
|
24
|
+
- **`HUMANS.md`** — Development section replaced with a pointer to `CONTRIBUTING.md`; preset file, `git_not_found`, install reference, and publishing steps remain.
|
|
25
|
+
- **`AGENTS.md`** — corrected pre-push hook description (missing `test` step) and updated canonical-docs link for dev/CI content.
|
|
26
|
+
- **`.cursor/rules/rethunk-git-mcp.mdc`** — removed stale "shell is fine for `git diff` / `git cherry-pick`" examples; both now have MCP tool equivalents (`git_diff_summary`, `git_cherry_pick`).
|
|
27
|
+
|
|
5
28
|
## [2.3.1] — 2026-04-21
|
|
6
29
|
|
|
7
30
|
Documentation-only patch following the 2.3.0 release.
|
package/HUMANS.md
CHANGED
|
@@ -84,19 +84,7 @@ If **`git`** is missing or not runnable, tools and the presets resource respond
|
|
|
84
84
|
|
|
85
85
|
## Development
|
|
86
86
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
bun install
|
|
91
|
-
bun run build # rimraf dist + tsc → dist/server.js, dist/server/*.js, dist/repo-paths.js
|
|
92
|
-
bun run check # Biome
|
|
93
|
-
bun run check:fix # Biome --write
|
|
94
|
-
bun run setup-hooks # once per clone: use .githooks (pre-commit: check; pre-push: CI parity)
|
|
95
|
-
```
|
|
96
|
-
|
|
97
|
-
**Git hooks:** after `setup-hooks`, **pre-commit** runs `bun run check`; **pre-push** runs `bun install --frozen-lockfile`, `bun run build`, `bun run check`, and `bun run test` (same order as CI). Set **`SKIP_GIT_HOOKS=1`** or use **`--no-verify`** to bypass.
|
|
98
|
-
|
|
99
|
-
**CI:** [`.github/workflows/ci.yml`](.github/workflows/ci.yml) runs on pull requests and pushes to `main`: **`actions/setup-node` with Node 24** (minimum 22 asserted), then `bun install --frozen-lockfile`, `bun run build`, `bun run check`, and `bun run test`. A follow-up job **`prerelease-pack`** builds the same tree and runs **`npm pack`**, then uploads a **prerelease `.tgz` artifact** (named with the commit SHA) you can download from the workflow run’s **Artifacts** section (retention 90 days). Match the check job locally before opening a PR.
|
|
87
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for dev setup, build commands, git hooks, commit conventions, CI, and how to add a tool.
|
|
100
88
|
|
|
101
89
|
## Publishing
|
|
102
90
|
|
|
@@ -53,7 +53,7 @@ export function registerBatchCommitTool(server) {
|
|
|
53
53
|
destructiveHint: false,
|
|
54
54
|
idempotentHint: false,
|
|
55
55
|
},
|
|
56
|
-
parameters: WorkspacePickSchema.extend({
|
|
56
|
+
parameters: WorkspacePickSchema.omit({ absoluteGitRoots: true }).extend({
|
|
57
57
|
commits: z
|
|
58
58
|
.array(CommitEntrySchema)
|
|
59
59
|
.min(1)
|
|
@@ -103,7 +103,7 @@ export function registerGitCherryPickTool(server) {
|
|
|
103
103
|
destructiveHint: false,
|
|
104
104
|
idempotentHint: false,
|
|
105
105
|
},
|
|
106
|
-
parameters: WorkspacePickSchema.extend({
|
|
106
|
+
parameters: WorkspacePickSchema.omit({ absoluteGitRoots: true }).extend({
|
|
107
107
|
sources: z
|
|
108
108
|
.array(z.string().min(1))
|
|
109
109
|
.min(1)
|
|
@@ -25,6 +25,11 @@ export function registerGitInventoryTool(server) {
|
|
|
25
25
|
maxRoots: z.number().int().min(1).max(256).optional().default(MAX_INVENTORY_ROOTS_DEFAULT),
|
|
26
26
|
}),
|
|
27
27
|
execute: async (args) => {
|
|
28
|
+
if (args.absoluteGitRoots != null && args.absoluteGitRoots.length > 0) {
|
|
29
|
+
if (args.preset || (args.nestedRoots?.length ?? 0) > 0) {
|
|
30
|
+
return jsonRespond({ error: "absolute_git_roots_nested_or_preset_conflict" });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
28
33
|
const pre = requireGitAndRoots(server, args, args.preset);
|
|
29
34
|
if (!pre.ok) {
|
|
30
35
|
return jsonRespond(pre.error);
|
|
@@ -217,7 +217,7 @@ export function registerGitMergeTool(server) {
|
|
|
217
217
|
destructiveHint: false,
|
|
218
218
|
idempotentHint: false,
|
|
219
219
|
},
|
|
220
|
-
parameters: WorkspacePickSchema.extend({
|
|
220
|
+
parameters: WorkspacePickSchema.omit({ absoluteGitRoots: true }).extend({
|
|
221
221
|
sources: z
|
|
222
222
|
.array(z.string().min(1))
|
|
223
223
|
.min(1)
|
|
@@ -15,7 +15,7 @@ export function registerGitPushTool(server) {
|
|
|
15
15
|
destructiveHint: false,
|
|
16
16
|
idempotentHint: false,
|
|
17
17
|
},
|
|
18
|
-
parameters: WorkspacePickSchema.extend({
|
|
18
|
+
parameters: WorkspacePickSchema.omit({ absoluteGitRoots: true }).extend({
|
|
19
19
|
remote: z
|
|
20
20
|
.string()
|
|
21
21
|
.optional()
|
|
@@ -16,7 +16,7 @@ export function registerGitResetSoftTool(server) {
|
|
|
16
16
|
destructiveHint: false,
|
|
17
17
|
idempotentHint: false,
|
|
18
18
|
},
|
|
19
|
-
parameters: WorkspacePickSchema.extend({
|
|
19
|
+
parameters: WorkspacePickSchema.omit({ absoluteGitRoots: true }).extend({
|
|
20
20
|
ref: z
|
|
21
21
|
.string()
|
|
22
22
|
.min(1)
|
|
@@ -55,7 +55,7 @@ export function registerGitWorktreeAddTool(server) {
|
|
|
55
55
|
destructiveHint: false,
|
|
56
56
|
idempotentHint: false,
|
|
57
57
|
},
|
|
58
|
-
parameters: WorkspacePickSchema.extend({
|
|
58
|
+
parameters: WorkspacePickSchema.omit({ absoluteGitRoots: true }).extend({
|
|
59
59
|
path: z
|
|
60
60
|
.string()
|
|
61
61
|
.min(1)
|
|
@@ -139,7 +139,7 @@ export function registerGitWorktreeRemoveTool(server) {
|
|
|
139
139
|
destructiveHint: true,
|
|
140
140
|
idempotentHint: false,
|
|
141
141
|
},
|
|
142
|
-
parameters: WorkspacePickSchema.extend({
|
|
142
|
+
parameters: WorkspacePickSchema.omit({ absoluteGitRoots: true }).extend({
|
|
143
143
|
path: z
|
|
144
144
|
.string()
|
|
145
145
|
.min(1)
|
package/dist/server/roots.js
CHANGED
|
@@ -2,6 +2,7 @@ import { basename, resolve } from "node:path";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { gateGit, gitTopLevel } from "./git.js";
|
|
4
4
|
import { loadPresetsFromGitTop, presetLoadErrorPayload } from "./presets.js";
|
|
5
|
+
import { MAX_ABSOLUTE_GIT_ROOTS } from "./schemas.js";
|
|
5
6
|
function uriToPath(uri) {
|
|
6
7
|
if (!uri.startsWith("file://"))
|
|
7
8
|
return null;
|
|
@@ -39,6 +40,51 @@ function pathMatchesWorkspaceRootHint(rootPath, hint) {
|
|
|
39
40
|
return true;
|
|
40
41
|
return basename(rootPath) === h;
|
|
41
42
|
}
|
|
43
|
+
function hasExclusiveWorkspacePick(args) {
|
|
44
|
+
if (args.workspaceRoot?.trim())
|
|
45
|
+
return true;
|
|
46
|
+
if (args.rootIndex != null)
|
|
47
|
+
return true;
|
|
48
|
+
if (args.allWorkspaceRoots === true)
|
|
49
|
+
return true;
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Resolve `absoluteGitRoots` to unique git toplevels (stable order, first occurrence wins).
|
|
54
|
+
*/
|
|
55
|
+
export function resolveAbsoluteGitRootsList(raw) {
|
|
56
|
+
if (raw.length > MAX_ABSOLUTE_GIT_ROOTS) {
|
|
57
|
+
return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error: {
|
|
60
|
+
error: "absolute_git_roots_too_many",
|
|
61
|
+
max: MAX_ABSOLUTE_GIT_ROOTS,
|
|
62
|
+
count: raw.length,
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const seen = new Set();
|
|
67
|
+
const tops = [];
|
|
68
|
+
for (const item of raw) {
|
|
69
|
+
const trimmed = item.trim();
|
|
70
|
+
if (trimmed.length === 0) {
|
|
71
|
+
return { ok: false, error: { error: "invalid_absolute_git_root", path: item } };
|
|
72
|
+
}
|
|
73
|
+
const abs = resolve(trimmed);
|
|
74
|
+
const top = gitTopLevel(abs);
|
|
75
|
+
if (!top) {
|
|
76
|
+
return { ok: false, error: { error: "invalid_absolute_git_root", path: abs } };
|
|
77
|
+
}
|
|
78
|
+
if (seen.has(top))
|
|
79
|
+
continue;
|
|
80
|
+
seen.add(top);
|
|
81
|
+
tops.push(top);
|
|
82
|
+
}
|
|
83
|
+
if (tops.length === 0) {
|
|
84
|
+
return { ok: false, error: { error: "absolute_git_roots_empty" } };
|
|
85
|
+
}
|
|
86
|
+
return { ok: true, roots: tops };
|
|
87
|
+
}
|
|
42
88
|
function resolveWorkspaceRoots(server, args) {
|
|
43
89
|
if (args.workspaceRoot?.trim()) {
|
|
44
90
|
return { ok: true, roots: [resolve(args.workspaceRoot.trim())] };
|
|
@@ -110,6 +156,16 @@ export function requireGitAndRoots(server, args, presetName) {
|
|
|
110
156
|
if (!gg.ok) {
|
|
111
157
|
return { ok: false, error: gg.body };
|
|
112
158
|
}
|
|
159
|
+
const abs = args.absoluteGitRoots;
|
|
160
|
+
if (abs != null && abs.length > 0) {
|
|
161
|
+
if (presetName) {
|
|
162
|
+
return { ok: false, error: { error: "absolute_git_roots_preset_conflict" } };
|
|
163
|
+
}
|
|
164
|
+
if (hasExclusiveWorkspacePick(args)) {
|
|
165
|
+
return { ok: false, error: { error: "absolute_git_roots_exclusive" } };
|
|
166
|
+
}
|
|
167
|
+
return resolveAbsoluteGitRootsList(abs);
|
|
168
|
+
}
|
|
113
169
|
const rootsRes = presetName
|
|
114
170
|
? resolveRootsForPreset(server, args, presetName)
|
|
115
171
|
: resolveWorkspaceRoots(server, args);
|
|
@@ -127,6 +183,15 @@ export function requireSingleRepo(server, args, presetName = undefined) {
|
|
|
127
183
|
const pre = requireGitAndRoots(server, args, presetName);
|
|
128
184
|
if (!pre.ok)
|
|
129
185
|
return pre;
|
|
186
|
+
if (args.absoluteGitRoots != null && args.absoluteGitRoots.length > 0 && pre.roots.length !== 1) {
|
|
187
|
+
return {
|
|
188
|
+
ok: false,
|
|
189
|
+
error: {
|
|
190
|
+
error: "absolute_git_roots_single_repo_only",
|
|
191
|
+
rootCount: pre.roots.length,
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
}
|
|
130
195
|
const rootInput = pre.roots[0];
|
|
131
196
|
if (!rootInput)
|
|
132
197
|
return { ok: false, error: { error: "no_workspace_root" } };
|
package/dist/server/schemas.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { MAX_INVENTORY_ROOTS_DEFAULT } from "./inventory.js";
|
|
3
3
|
const FormatSchema = z.enum(["markdown", "json"]).optional().default("markdown");
|
|
4
|
+
/** Max paths in `absoluteGitRoots` (matches `git_inventory` `maxRoots` hard cap). */
|
|
5
|
+
export const MAX_ABSOLUTE_GIT_ROOTS = 256;
|
|
4
6
|
export const WorkspacePickSchema = z.object({
|
|
5
7
|
workspaceRoot: z.string().optional().describe("Highest-priority override."),
|
|
6
8
|
rootIndex: z
|
|
@@ -14,6 +16,12 @@ export const WorkspacePickSchema = z.object({
|
|
|
14
16
|
.optional()
|
|
15
17
|
.default(false)
|
|
16
18
|
.describe("Fan out across all MCP file roots."),
|
|
19
|
+
/** Independent git worktrees (sibling clones). Mutually exclusive with workspaceRoot, rootIndex, allWorkspaceRoots, and (git_inventory) preset/nestedRoots. */
|
|
20
|
+
absoluteGitRoots: z
|
|
21
|
+
.array(z.string())
|
|
22
|
+
.max(MAX_ABSOLUTE_GIT_ROOTS)
|
|
23
|
+
.optional()
|
|
24
|
+
.describe("Absolute paths to git repo roots. Use for many sibling clones under a non-git parent directory."),
|
|
17
25
|
format: FormatSchema,
|
|
18
26
|
});
|
|
19
27
|
export { MAX_INVENTORY_ROOTS_DEFAULT };
|
package/docs/mcp-tools.md
CHANGED
|
@@ -11,12 +11,12 @@ MCP clients expose tools as `{serverName}_{toolName}`. With the server registere
|
|
|
11
11
|
|
|
12
12
|
| Short id | Client id (server `rethunk-git`) | Purpose |
|
|
13
13
|
|----------|-----------------------------------|---------|
|
|
14
|
-
| `git_status` | `rethunk-git_git_status` | `git status --short -b` per MCP root and optional submodules (`includeSubmodules`); parallel submodule status. Args include `allWorkspaceRoots`, `rootIndex`, `workspaceRoot`, `format`. **Read-only.** |
|
|
15
|
-
| `git_inventory` | `rethunk-git_git_inventory` | Status + ahead/behind per path; default upstream each repo’s `@{u}`; pass **both** `remote` and `branch` for fixed tracking. `nestedRoots`, `preset`, `presetMerge`, `maxRoots`, `format`, plus workspace pick args. **Read-only.** |
|
|
14
|
+
| `git_status` | `rethunk-git_git_status` | `git status --short -b` per MCP root and optional submodules (`includeSubmodules`); parallel submodule status. Args include `absoluteGitRoots`, `allWorkspaceRoots`, `rootIndex`, `workspaceRoot`, `format`. **Read-only.** |
|
|
15
|
+
| `git_inventory` | `rethunk-git_git_inventory` | Status + ahead/behind per path; default upstream each repo’s `@{u}`; pass **both** `remote` and `branch` for fixed tracking. `nestedRoots`, `preset`, `presetMerge`, `maxRoots`, `format`, plus workspace pick args (`absoluteGitRoots` cannot combine with `preset`/`nestedRoots`). **Read-only.** |
|
|
16
16
|
| `git_parity` | `rethunk-git_git_parity` | Compare `git rev-parse HEAD` for path pairs. `pairs`, `preset`, `presetMerge`, `format`, plus workspace pick args. **Read-only.** |
|
|
17
|
-
| `list_presets` | `rethunk-git_list_presets` | List preset names/counts from `.rethunk/git-mcp-presets.json`; invalid JSON/schema surface as errors. Workspace pick + `format` only. **Read-only.** |
|
|
18
|
-
| `git_log` | `rethunk-git_git_log` | Path-filtered, time-windowed `git log` across one or more workspace roots. Returns commit history with author, date, subject, and shortstat. Args: `since`, `paths`, `grep`, `author`, `maxCommits`, `branch`, plus workspace pick args + `format`. **Read-only.** |
|
|
19
|
-
| `git_diff_summary` | `rethunk-git_git_diff_summary` | Structured, token-efficient diff viewer. Returns per-file diffs with additions/deletions counts, truncated to configurable line limits, with lock files/dist/vendor excluded by default. Args: `range`, `fileFilter`, `maxLinesPerFile`, `maxFiles`, `excludePatterns`, plus workspace pick args + `format`. **Read-only.** |
|
|
17
|
+
| `list_presets` | `rethunk-git_list_presets` | List preset names/counts from `.rethunk/git-mcp-presets.json`; invalid JSON/schema surface as errors. Workspace pick + `format` only (includes `absoluteGitRoots`). **Read-only.** |
|
|
18
|
+
| `git_log` | `rethunk-git_git_log` | Path-filtered, time-windowed `git log` across one or more workspace roots. Returns commit history with author, date, subject, and shortstat. Args: `since`, `paths`, `grep`, `author`, `maxCommits`, `branch`, plus workspace pick args (`absoluteGitRoots` for sibling clones) + `format`. **Read-only.** |
|
|
19
|
+
| `git_diff_summary` | `rethunk-git_git_diff_summary` | Structured, token-efficient diff viewer. Returns per-file diffs with additions/deletions counts, truncated to configurable line limits, with lock files/dist/vendor excluded by default. Args: `range`, `fileFilter`, `maxLinesPerFile`, `maxFiles`, `excludePatterns`, plus workspace pick args (optional single-entry `absoluteGitRoots`) + `format`. **Read-only.** |
|
|
20
20
|
| `git_worktree_list` | `rethunk-git_git_worktree_list` | List all worktrees (`git worktree list --porcelain`). Workspace pick + `format`. **Read-only.** |
|
|
21
21
|
| `git_push` | `rethunk-git_git_push` | Push the current branch to its upstream. Optional `remote`, `branch`, `setUpstream` (passes `-u`). Refuses on detached HEAD; never force-pushes. Workspace pick + `format`. **Mutating.** |
|
|
22
22
|
| `git_worktree_add` | `rethunk-git_git_worktree_add` | Create a new linked worktree, creating the branch from `baseRef` if it does not yet exist. Refuses on protected branch names. Args: `path`, `branch`, `baseRef?`, plus workspace pick + `format`. **Mutating.** |
|
|
@@ -106,6 +106,12 @@ v2 field-omission rules still apply: `filesChanged`, `insertions`, `deletions` o
|
|
|
106
106
|
| `invalid_paths` | One of the `paths` entries contains shell metacharacters and was rejected. |
|
|
107
107
|
| `git_log_failed` | `git log` exited non-zero (e.g. unknown branch ref). |
|
|
108
108
|
| `root_index_out_of_range` | `rootIndex` exceeds the number of MCP file roots. |
|
|
109
|
+
| `absolute_git_roots_exclusive` | `absoluteGitRoots` was combined with `workspaceRoot`, `rootIndex`, or `allWorkspaceRoots: true`. |
|
|
110
|
+
| `absolute_git_roots_preset_conflict` | `absoluteGitRoots` was combined with a `preset` argument (root resolution). |
|
|
111
|
+
| `invalid_absolute_git_root` | An `absoluteGitRoots` entry is empty, not inside a git worktree, or not a directory git recognizes. |
|
|
112
|
+
| `absolute_git_roots_too_many` | More than 256 entries in `absoluteGitRoots`. |
|
|
113
|
+
| `absolute_git_roots_empty` | `absoluteGitRoots` produced zero git toplevels after resolution. |
|
|
114
|
+
| `absolute_git_roots_single_repo_only` | A single-repo tool received `absoluteGitRoots` resolving to more than one distinct git toplevel. |
|
|
109
115
|
|
|
110
116
|
### `git_diff_summary` — parameters
|
|
111
117
|
|
|
@@ -456,6 +462,32 @@ Repo state is cleaned (`git cherry-pick --abort`) before returning — no partia
|
|
|
456
462
|
|
|
457
463
|
## Workspace root resolution
|
|
458
464
|
|
|
465
|
+
### `absoluteGitRoots` (sibling clones)
|
|
466
|
+
|
|
467
|
+
When **`absoluteGitRoots`** is a **non-empty** string array, it **replaces** the normal workspace pick for that tool call:
|
|
468
|
+
|
|
469
|
+
- Each entry is passed through `path.resolve`, then resolved to a **git toplevel** via the same logic as `workspaceRoot`. Duplicate toplevels are dropped (stable order, first wins).
|
|
470
|
+
- **Maximum** **256** paths (same cap as `git_inventory` `maxRoots` upper bound).
|
|
471
|
+
- **Mutually exclusive** with **`workspaceRoot`**, **`rootIndex`**, and **`allWorkspaceRoots: true`**. Combining them returns `{ "error": "absolute_git_roots_exclusive" }`.
|
|
472
|
+
- **Mutually exclusive** with a **`preset`** argument on root resolution (`absolute_git_roots_preset_conflict`).
|
|
473
|
+
- **`git_inventory` only:** also mutually exclusive with **`nestedRoots`** or **`preset`** on the same call (`absolute_git_roots_nested_or_preset_conflict`).
|
|
474
|
+
- **Mutating** tools (`batch_commit`, `git_push`, `git_merge`, …) **omit** this parameter from their schema; callers must use `workspaceRoot` / MCP roots for writes.
|
|
475
|
+
- **Read tools** that use **`requireSingleRepo`** (`git_diff_summary`, …) accept at most **one** distinct toplevel from `absoluteGitRoots`; more than one returns `absolute_git_roots_single_repo_only`.
|
|
476
|
+
|
|
477
|
+
Example — two sibling repos in one `git_status` call:
|
|
478
|
+
|
|
479
|
+
```json
|
|
480
|
+
{
|
|
481
|
+
"format": "json",
|
|
482
|
+
"absoluteGitRoots": [
|
|
483
|
+
"/usr/local/src/com.github/Rethunk-AI/mcp-multi-root-git",
|
|
484
|
+
"/usr/local/src/com.github/Rethunk-AI/rethunk-github-mcp"
|
|
485
|
+
]
|
|
486
|
+
}
|
|
487
|
+
```
|
|
488
|
+
|
|
489
|
+
### Default order (when `absoluteGitRoots` is absent or empty)
|
|
490
|
+
|
|
459
491
|
Order applied when resolving which directory(ies) tools run against:
|
|
460
492
|
|
|
461
493
|
1. Explicit **`workspaceRoot`** on the tool call (highest priority).
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rethunk/mcp-multi-root-git",
|
|
3
|
-
"version": "2.3.
|
|
3
|
+
"version": "2.3.3",
|
|
4
4
|
"description": "MCP stdio server: multi-root git status, inventory, and HEAD parity checks. Generic tools usable by any workspace.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"check": "biome check .",
|
|
33
33
|
"check:fix": "biome check --write .",
|
|
34
34
|
"test": "bun test src/",
|
|
35
|
+
"test:coverage": "bun test src/ --coverage",
|
|
35
36
|
"prepublishOnly": "bun run build && bun run check && bun run test",
|
|
36
37
|
"setup-hooks": "git config core.hooksPath .githooks"
|
|
37
38
|
},
|