@rethunk/mcp-multi-root-git 2.3.4 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +29 -3
- package/CHANGELOG.md +43 -0
- package/HUMANS.md +54 -5
- package/README.md +15 -1
- package/dist/server/batch-commit-tool.js +244 -19
- package/dist/server/git-diff-tool.js +132 -0
- package/dist/server/git-fetch-tool.js +131 -0
- package/dist/server/git-push-tool.js +8 -1
- package/dist/server/git-show-tool.js +148 -0
- package/dist/server/git-stash-tool.js +131 -0
- package/dist/server/git-tag-tool.js +162 -0
- package/dist/server/git.js +18 -2
- package/dist/server/roots.js +8 -4
- package/dist/server/test-harness.js +68 -5
- package/dist/server/tool-parameter-schemas.js +21 -1
- package/dist/server/tools.js +11 -0
- package/docs/install.md +19 -2
- package/docs/mcp-tools.md +214 -5
- package/package.json +7 -5
- package/schemas/batch_commit.json +125 -0
- package/schemas/git_cherry_pick.json +63 -0
- package/schemas/git_diff.json +62 -0
- package/schemas/git_diff_summary.json +75 -0
- package/schemas/git_fetch.json +52 -0
- package/schemas/git_inventory.json +74 -0
- package/schemas/git_log.json +75 -0
- package/schemas/git_merge.json +79 -0
- package/schemas/git_parity.json +74 -0
- package/schemas/git_push.json +50 -0
- package/schemas/git_reset_soft.json +42 -0
- package/schemas/git_show.json +39 -0
- package/schemas/git_stash_apply.json +44 -0
- package/schemas/git_stash_list.json +30 -0
- package/schemas/git_status.json +49 -0
- package/schemas/git_tag.json +50 -0
- package/schemas/git_worktree_add.json +52 -0
- package/schemas/git_worktree_list.json +30 -0
- package/schemas/git_worktree_remove.json +48 -0
- package/schemas/index.json +108 -0
- package/schemas/list_presets.json +44 -0
- package/tool-parameters.schema.json +317 -4
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isSafeGitUpstreamToken, spawnGitAsync } from "./git.js";
|
|
3
|
+
import { jsonRespond } from "./json.js";
|
|
4
|
+
import { requireSingleRepo } from "./roots.js";
|
|
5
|
+
import { WorkspacePickSchema } from "./schemas.js";
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
// Helpers
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
/**
|
|
10
|
+
* Get the SHA of a given ref (tag, commit, branch, etc).
|
|
11
|
+
*/
|
|
12
|
+
async function getRefSha(gitTop, ref) {
|
|
13
|
+
const result = await spawnGitAsync(gitTop, ["rev-parse", ref]);
|
|
14
|
+
if (!result.ok)
|
|
15
|
+
return null;
|
|
16
|
+
return result.stdout.trim();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check if a tag is annotated or lightweight.
|
|
20
|
+
*/
|
|
21
|
+
async function getTagType(gitTop, tag) {
|
|
22
|
+
// For annotated tags, `git cat-file -t <tag>` returns "tag"
|
|
23
|
+
// For lightweight tags, it returns "commit"
|
|
24
|
+
const result = await spawnGitAsync(gitTop, ["cat-file", "-t", tag]);
|
|
25
|
+
if (!result.ok)
|
|
26
|
+
return null;
|
|
27
|
+
const type = result.stdout.trim();
|
|
28
|
+
if (type === "tag")
|
|
29
|
+
return "annotated";
|
|
30
|
+
if (type === "commit")
|
|
31
|
+
return "lightweight";
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
// Tool registration
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
export function registerGitTagTool(server) {
|
|
38
|
+
server.addTool({
|
|
39
|
+
name: "git_tag",
|
|
40
|
+
description: "Create, delete, or inspect git tags. Create annotated tags (with message) or lightweight tags (ref only). " +
|
|
41
|
+
"Returns tag name, type, and SHA.",
|
|
42
|
+
annotations: {
|
|
43
|
+
readOnlyHint: false,
|
|
44
|
+
destructiveHint: true,
|
|
45
|
+
},
|
|
46
|
+
parameters: WorkspacePickSchema.omit({ absoluteGitRoots: true, allWorkspaceRoots: true })
|
|
47
|
+
.pick({
|
|
48
|
+
workspaceRoot: true,
|
|
49
|
+
rootIndex: true,
|
|
50
|
+
format: true,
|
|
51
|
+
})
|
|
52
|
+
.extend({
|
|
53
|
+
tag: z.string().min(1).describe("Tag name (e.g. 'v1.2.3')."),
|
|
54
|
+
message: z
|
|
55
|
+
.string()
|
|
56
|
+
.optional()
|
|
57
|
+
.describe("If provided, create an annotated tag with this message. If absent, create a lightweight tag."),
|
|
58
|
+
ref: z
|
|
59
|
+
.string()
|
|
60
|
+
.optional()
|
|
61
|
+
.describe("Commit/ref to tag (default: HEAD). Ignored if `delete` is true."),
|
|
62
|
+
delete: z
|
|
63
|
+
.boolean()
|
|
64
|
+
.optional()
|
|
65
|
+
.default(false)
|
|
66
|
+
.describe("If true, delete the named tag instead of creating it."),
|
|
67
|
+
}),
|
|
68
|
+
execute: async (args) => {
|
|
69
|
+
const pre = requireSingleRepo(server, args);
|
|
70
|
+
if (!pre.ok)
|
|
71
|
+
return jsonRespond(pre.error);
|
|
72
|
+
const gitTop = pre.gitTop;
|
|
73
|
+
const tag = args.tag.trim();
|
|
74
|
+
if (!tag) {
|
|
75
|
+
return jsonRespond({ error: "tag_empty" });
|
|
76
|
+
}
|
|
77
|
+
// Validate tag name: no shell metacharacters
|
|
78
|
+
if (!isSafeGitUpstreamToken(tag)) {
|
|
79
|
+
return jsonRespond({ error: "tag_unsafe", tag });
|
|
80
|
+
}
|
|
81
|
+
// Handle deletion
|
|
82
|
+
if (args.delete === true) {
|
|
83
|
+
const delResult = await spawnGitAsync(gitTop, ["tag", "-d", tag]);
|
|
84
|
+
if (!delResult.ok) {
|
|
85
|
+
return jsonRespond({
|
|
86
|
+
error: "tag_delete_failed",
|
|
87
|
+
detail: (delResult.stderr || delResult.stdout).trim(),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
if (args.format === "json") {
|
|
91
|
+
return jsonRespond({
|
|
92
|
+
tag,
|
|
93
|
+
type: "deleted",
|
|
94
|
+
sha: "", // Deleted tags have no SHA
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
return `Deleted tag: ${tag}`;
|
|
98
|
+
}
|
|
99
|
+
// Determine the ref to tag (default HEAD)
|
|
100
|
+
const ref = (args.ref ?? "HEAD").trim();
|
|
101
|
+
if (!isSafeGitUpstreamToken(ref)) {
|
|
102
|
+
return jsonRespond({ error: "ref_unsafe", ref });
|
|
103
|
+
}
|
|
104
|
+
// Get the SHA of the ref to tag
|
|
105
|
+
const sha = await getRefSha(gitTop, ref);
|
|
106
|
+
if (!sha) {
|
|
107
|
+
return jsonRespond({
|
|
108
|
+
error: "ref_not_found",
|
|
109
|
+
ref,
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
// Create tag (annotated or lightweight)
|
|
113
|
+
const tagArgs = ["tag"];
|
|
114
|
+
if (args.message) {
|
|
115
|
+
// Annotated tag
|
|
116
|
+
tagArgs.push("-a", "-m", args.message);
|
|
117
|
+
}
|
|
118
|
+
else {
|
|
119
|
+
// Lightweight tag (just the tag name and ref)
|
|
120
|
+
}
|
|
121
|
+
tagArgs.push(tag, ref);
|
|
122
|
+
const createResult = await spawnGitAsync(gitTop, tagArgs);
|
|
123
|
+
if (!createResult.ok) {
|
|
124
|
+
return jsonRespond({
|
|
125
|
+
error: "tag_create_failed",
|
|
126
|
+
detail: (createResult.stderr || createResult.stdout).trim(),
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
// Verify the tag was created and get its type
|
|
130
|
+
const tagType = await getTagType(gitTop, tag);
|
|
131
|
+
if (!tagType) {
|
|
132
|
+
return jsonRespond({
|
|
133
|
+
error: "tag_verification_failed",
|
|
134
|
+
tag,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
const result = {
|
|
138
|
+
tag,
|
|
139
|
+
type: tagType,
|
|
140
|
+
sha,
|
|
141
|
+
};
|
|
142
|
+
if (args.format === "json") {
|
|
143
|
+
return jsonRespond(result);
|
|
144
|
+
}
|
|
145
|
+
// Markdown output
|
|
146
|
+
const lines = [];
|
|
147
|
+
lines.push(`# Tag: ${tag}`);
|
|
148
|
+
lines.push("");
|
|
149
|
+
lines.push(`**Type:** ${tagType}`);
|
|
150
|
+
lines.push(`**SHA:** \`${sha}\``);
|
|
151
|
+
if (args.message) {
|
|
152
|
+
lines.push("");
|
|
153
|
+
lines.push("**Message:**");
|
|
154
|
+
lines.push("");
|
|
155
|
+
lines.push("```");
|
|
156
|
+
lines.push(args.message);
|
|
157
|
+
lines.push("```");
|
|
158
|
+
}
|
|
159
|
+
return lines.join("\n");
|
|
160
|
+
},
|
|
161
|
+
});
|
|
162
|
+
}
|
package/dist/server/git.js
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { cpus } from "node:os";
|
|
3
4
|
import { join } from "node:path";
|
|
4
|
-
/**
|
|
5
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Parallel git subprocesses for inventory rows and git_status submodule rows.
|
|
7
|
+
* Reads from GIT_SUBPROCESS_PARALLELISM env var (default 4), clamped to [1, 2×CPU_COUNT].
|
|
8
|
+
*/
|
|
9
|
+
function resolveGitSubprocessParallelism() {
|
|
10
|
+
const env = process.env.GIT_SUBPROCESS_PARALLELISM;
|
|
11
|
+
if (env) {
|
|
12
|
+
const n = Number.parseInt(env, 10);
|
|
13
|
+
if (!Number.isNaN(n) && n >= 1) {
|
|
14
|
+
const cpuCount = cpus().length;
|
|
15
|
+
const maxParallel = cpuCount * 2;
|
|
16
|
+
return Math.min(n, maxParallel);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
return 4;
|
|
20
|
+
}
|
|
21
|
+
export const GIT_SUBPROCESS_PARALLELISM = resolveGitSubprocessParallelism();
|
|
6
22
|
let gitPathState = "unknown";
|
|
7
23
|
const GIT_NOT_FOUND_BODY = {
|
|
8
24
|
error: "git_not_found",
|
package/dist/server/roots.js
CHANGED
|
@@ -15,12 +15,16 @@ function uriToPath(uri) {
|
|
|
15
15
|
}
|
|
16
16
|
function listFileRoots(server) {
|
|
17
17
|
const sessions = server.sessions;
|
|
18
|
-
const roots = sessions[0]?.roots ?? [];
|
|
19
18
|
const paths = [];
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
const seen = new Set();
|
|
20
|
+
for (const session of sessions) {
|
|
21
|
+
for (const root of session.roots ?? []) {
|
|
22
|
+
const p = uriToPath(root.uri);
|
|
23
|
+
if (!p || seen.has(p))
|
|
24
|
+
continue;
|
|
25
|
+
seen.add(p);
|
|
23
26
|
paths.push(p);
|
|
27
|
+
}
|
|
24
28
|
}
|
|
25
29
|
return paths;
|
|
26
30
|
}
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
* const result = await tool({ workspaceRoot: dir, commits: [...] });
|
|
17
17
|
* // result is string (markdown) or JSON-parseable string
|
|
18
18
|
*/
|
|
19
|
-
import {
|
|
19
|
+
import { execFileSync } from "node:child_process";
|
|
20
|
+
import { appendFileSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
20
21
|
import { tmpdir } from "node:os";
|
|
21
22
|
import { join } from "node:path";
|
|
22
23
|
// Stub context — no tool currently uses context
|
|
@@ -54,13 +55,20 @@ export function cleanupTmpPaths() {
|
|
|
54
55
|
rmSync(p, { recursive: true, force: true });
|
|
55
56
|
}
|
|
56
57
|
}
|
|
58
|
+
export function writeTestGitConfig(repo) {
|
|
59
|
+
appendFileSync(join(repo, ".git", "config"), "\n[user]\n\temail = test@example.com\n\tname = Test User\n[commit]\n\tgpgsign = false\n");
|
|
60
|
+
}
|
|
57
61
|
// ---------------------------------------------------------------------------
|
|
58
62
|
// Fake server
|
|
59
63
|
// ---------------------------------------------------------------------------
|
|
60
|
-
function makeFakeServer() {
|
|
64
|
+
function makeFakeServer(roots = []) {
|
|
61
65
|
const tools = [];
|
|
62
66
|
const server = {
|
|
63
|
-
sessions: [
|
|
67
|
+
sessions: [
|
|
68
|
+
{
|
|
69
|
+
roots: roots.map((uri) => ({ uri })),
|
|
70
|
+
},
|
|
71
|
+
],
|
|
64
72
|
addTool(tool) {
|
|
65
73
|
tools.push({ name: tool.name, parameters: tool.parameters, execute: tool.execute });
|
|
66
74
|
},
|
|
@@ -78,8 +86,8 @@ function makeFakeServer() {
|
|
|
78
86
|
* The returned function accepts tool args (always include `workspaceRoot`)
|
|
79
87
|
* and returns the raw result as a string.
|
|
80
88
|
*/
|
|
81
|
-
export function captureTool(register, toolName) {
|
|
82
|
-
const { server, tools } = makeFakeServer();
|
|
89
|
+
export function captureTool(register, toolName, roots = []) {
|
|
90
|
+
const { server, tools } = makeFakeServer(roots);
|
|
83
91
|
register(server);
|
|
84
92
|
const pick = toolName ? tools.find((t) => t.name === toolName) : tools[0];
|
|
85
93
|
if (!pick) {
|
|
@@ -97,3 +105,58 @@ export function captureToolDefinitions(register) {
|
|
|
97
105
|
register(server);
|
|
98
106
|
return tools;
|
|
99
107
|
}
|
|
108
|
+
// ---------------------------------------------------------------------------
|
|
109
|
+
// Shared git test helpers (extracted from per-file duplication)
|
|
110
|
+
// ---------------------------------------------------------------------------
|
|
111
|
+
/** Execute git command with standard test environment and encoding. */
|
|
112
|
+
export function gitCmd(cwd, ...args) {
|
|
113
|
+
const opts = {
|
|
114
|
+
cwd,
|
|
115
|
+
encoding: "utf8",
|
|
116
|
+
env: {
|
|
117
|
+
...process.env,
|
|
118
|
+
GIT_AUTHOR_NAME: "Test User",
|
|
119
|
+
GIT_AUTHOR_EMAIL: "test@example.com",
|
|
120
|
+
GIT_COMMITTER_NAME: "Test User",
|
|
121
|
+
GIT_COMMITTER_EMAIL: "test@example.com",
|
|
122
|
+
GIT_AUTHOR_DATE: "2025-01-01T00:00:00Z",
|
|
123
|
+
GIT_COMMITTER_DATE: "2025-01-01T00:00:00Z",
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
return execFileSync("git", args, opts);
|
|
127
|
+
}
|
|
128
|
+
/** Initialize a basic git repo with test config. */
|
|
129
|
+
export function makeRepo(prefix = "mcp-test-repo-") {
|
|
130
|
+
const dir = mkTmpDir(prefix);
|
|
131
|
+
gitCmd(dir, "init", "-b", "main");
|
|
132
|
+
writeTestGitConfig(dir);
|
|
133
|
+
return dir;
|
|
134
|
+
}
|
|
135
|
+
/** Initialize a repo with a seed commit (useful for branch/cherry-pick tests). */
|
|
136
|
+
export function makeRepoWithSeed(prefix = "mcp-test-repo-") {
|
|
137
|
+
const dir = makeRepo(prefix);
|
|
138
|
+
writeFileSync(join(dir, "seed.txt"), "seed\n");
|
|
139
|
+
gitCmd(dir, "add", "seed.txt");
|
|
140
|
+
gitCmd(dir, "commit", "-m", "chore: seed");
|
|
141
|
+
return dir;
|
|
142
|
+
}
|
|
143
|
+
/** Initialize work repo + bare remote with tracking set up. */
|
|
144
|
+
export function makeRepoWithUpstream(workPrefix = "mcp-work-", remotePrefix = "mcp-remote-") {
|
|
145
|
+
const remote = mkTmpDir(remotePrefix);
|
|
146
|
+
gitCmd(remote, "init", "--bare", "-b", "main");
|
|
147
|
+
const work = makeRepoWithSeed(workPrefix);
|
|
148
|
+
gitCmd(work, "remote", "add", "origin", remote);
|
|
149
|
+
gitCmd(work, "push", "-u", "origin", "main");
|
|
150
|
+
return { work, remote };
|
|
151
|
+
}
|
|
152
|
+
/** Initialize a git repo with main branch and test config. */
|
|
153
|
+
export function gitInitMain(dir) {
|
|
154
|
+
gitCmd(dir, "init", "-b", "main");
|
|
155
|
+
writeTestGitConfig(dir);
|
|
156
|
+
}
|
|
157
|
+
/** Add a commit to a repo with specified file content. */
|
|
158
|
+
export function addCommit(dir, file, content, message) {
|
|
159
|
+
writeFileSync(join(dir, file), content);
|
|
160
|
+
gitCmd(dir, "add", file);
|
|
161
|
+
gitCmd(dir, "commit", "-m", message);
|
|
162
|
+
}
|
|
@@ -2,13 +2,18 @@ import { z } from "zod";
|
|
|
2
2
|
import { registerBatchCommitTool } from "./batch-commit-tool.js";
|
|
3
3
|
import { registerGitCherryPickTool } from "./git-cherry-pick-tool.js";
|
|
4
4
|
import { registerGitDiffSummaryTool } from "./git-diff-summary-tool.js";
|
|
5
|
+
import { registerGitDiffTool } from "./git-diff-tool.js";
|
|
6
|
+
import { registerGitFetchTool } from "./git-fetch-tool.js";
|
|
5
7
|
import { registerGitInventoryTool } from "./git-inventory-tool.js";
|
|
6
8
|
import { registerGitLogTool } from "./git-log-tool.js";
|
|
7
9
|
import { registerGitMergeTool } from "./git-merge-tool.js";
|
|
8
10
|
import { registerGitParityTool } from "./git-parity-tool.js";
|
|
9
11
|
import { registerGitPushTool } from "./git-push-tool.js";
|
|
10
12
|
import { registerGitResetSoftTool } from "./git-reset-soft-tool.js";
|
|
13
|
+
import { registerGitShowTool } from "./git-show-tool.js";
|
|
14
|
+
import { registerGitStashApplyTool, registerGitStashListTool } from "./git-stash-tool.js";
|
|
11
15
|
import { registerGitStatusTool } from "./git-status-tool.js";
|
|
16
|
+
import { registerGitTagTool } from "./git-tag-tool.js";
|
|
12
17
|
import { registerGitWorktreeAddTool, registerGitWorktreeListTool, registerGitWorktreeRemoveTool, } from "./git-worktree-tool.js";
|
|
13
18
|
import { registerListPresetsTool } from "./list-presets-tool.js";
|
|
14
19
|
export const READ_ONLY_ABSOLUTE_ROOT_TOOLS = [
|
|
@@ -19,18 +24,27 @@ export const READ_ONLY_ABSOLUTE_ROOT_TOOLS = [
|
|
|
19
24
|
"git_log",
|
|
20
25
|
"git_diff_summary",
|
|
21
26
|
];
|
|
27
|
+
export const READ_ONLY_SINGLE_REPO_TOOLS = [
|
|
28
|
+
"git_diff",
|
|
29
|
+
"git_show",
|
|
30
|
+
"git_worktree_list",
|
|
31
|
+
"git_stash_list",
|
|
32
|
+
];
|
|
22
33
|
export const MUTATING_TOOLS = [
|
|
34
|
+
"git_fetch",
|
|
23
35
|
"batch_commit",
|
|
24
36
|
"git_push",
|
|
25
37
|
"git_merge",
|
|
26
38
|
"git_cherry_pick",
|
|
27
39
|
"git_reset_soft",
|
|
40
|
+
"git_tag",
|
|
28
41
|
"git_worktree_add",
|
|
29
42
|
"git_worktree_remove",
|
|
43
|
+
"git_stash_apply",
|
|
30
44
|
];
|
|
31
45
|
export const ALL_PARAMETER_SCHEMA_TOOLS = [
|
|
32
46
|
...READ_ONLY_ABSOLUTE_ROOT_TOOLS,
|
|
33
|
-
|
|
47
|
+
...READ_ONLY_SINGLE_REPO_TOOLS,
|
|
34
48
|
...MUTATING_TOOLS,
|
|
35
49
|
];
|
|
36
50
|
function captureParameterTools(register) {
|
|
@@ -52,14 +66,20 @@ export function captureToolParameterSchemas() {
|
|
|
52
66
|
registerListPresetsTool(server);
|
|
53
67
|
registerGitLogTool(server);
|
|
54
68
|
registerGitDiffSummaryTool(server);
|
|
69
|
+
registerGitDiffTool(server);
|
|
70
|
+
registerGitShowTool(server);
|
|
55
71
|
registerGitWorktreeListTool(server);
|
|
72
|
+
registerGitStashListTool(server);
|
|
73
|
+
registerGitFetchTool(server);
|
|
56
74
|
registerBatchCommitTool(server);
|
|
57
75
|
registerGitPushTool(server);
|
|
58
76
|
registerGitMergeTool(server);
|
|
59
77
|
registerGitCherryPickTool(server);
|
|
60
78
|
registerGitResetSoftTool(server);
|
|
79
|
+
registerGitTagTool(server);
|
|
61
80
|
registerGitWorktreeAddTool(server);
|
|
62
81
|
registerGitWorktreeRemoveTool(server);
|
|
82
|
+
registerGitStashApplyTool(server);
|
|
63
83
|
});
|
|
64
84
|
return Object.fromEntries(tools.map((tool) => [tool.name, z.toJSONSchema(tool.parameters)]));
|
|
65
85
|
}
|
package/dist/server/tools.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { registerBatchCommitTool } from "./batch-commit-tool.js";
|
|
2
2
|
import { registerGitCherryPickTool } from "./git-cherry-pick-tool.js";
|
|
3
3
|
import { registerGitDiffSummaryTool } from "./git-diff-summary-tool.js";
|
|
4
|
+
import { registerGitDiffTool } from "./git-diff-tool.js";
|
|
5
|
+
import { registerGitFetchTool } from "./git-fetch-tool.js";
|
|
4
6
|
import { registerGitInventoryTool } from "./git-inventory-tool.js";
|
|
5
7
|
import { registerGitLogTool } from "./git-log-tool.js";
|
|
6
8
|
import { registerGitMergeTool } from "./git-merge-tool.js";
|
|
7
9
|
import { registerGitParityTool } from "./git-parity-tool.js";
|
|
8
10
|
import { registerGitPushTool } from "./git-push-tool.js";
|
|
9
11
|
import { registerGitResetSoftTool } from "./git-reset-soft-tool.js";
|
|
12
|
+
import { registerGitShowTool } from "./git-show-tool.js";
|
|
13
|
+
import { registerGitStashApplyTool, registerGitStashListTool } from "./git-stash-tool.js";
|
|
10
14
|
import { registerGitStatusTool } from "./git-status-tool.js";
|
|
15
|
+
import { registerGitTagTool } from "./git-tag-tool.js";
|
|
11
16
|
import { registerGitWorktreeAddTool, registerGitWorktreeListTool, registerGitWorktreeRemoveTool, } from "./git-worktree-tool.js";
|
|
12
17
|
import { registerListPresetsTool } from "./list-presets-tool.js";
|
|
13
18
|
import { registerPresetsResource } from "./presets-resource.js";
|
|
@@ -19,15 +24,21 @@ export function registerRethunkGitTools(server) {
|
|
|
19
24
|
registerListPresetsTool(server);
|
|
20
25
|
registerGitLogTool(server);
|
|
21
26
|
registerGitDiffSummaryTool(server);
|
|
27
|
+
registerGitDiffTool(server);
|
|
28
|
+
registerGitShowTool(server);
|
|
22
29
|
registerGitWorktreeListTool(server);
|
|
30
|
+
registerGitStashListTool(server);
|
|
31
|
+
registerGitFetchTool(server);
|
|
23
32
|
// Mutating tools
|
|
24
33
|
registerBatchCommitTool(server);
|
|
25
34
|
registerGitPushTool(server);
|
|
26
35
|
registerGitMergeTool(server);
|
|
27
36
|
registerGitCherryPickTool(server);
|
|
28
37
|
registerGitResetSoftTool(server);
|
|
38
|
+
registerGitTagTool(server);
|
|
29
39
|
registerGitWorktreeAddTool(server);
|
|
30
40
|
registerGitWorktreeRemoveTool(server);
|
|
41
|
+
registerGitStashApplyTool(server);
|
|
31
42
|
// Resources
|
|
32
43
|
registerPresetsResource(server);
|
|
33
44
|
}
|
package/docs/install.md
CHANGED
|
@@ -10,6 +10,7 @@ This package is an MCP **stdio** server. The client starts the process and passe
|
|
|
10
10
|
- [GitHub Packages](#github-packages)
|
|
11
11
|
- [Ways to run the binary](#ways-to-run-the-binary)
|
|
12
12
|
- [Configuration shape (stdio)](#configuration-shape-stdio)
|
|
13
|
+
- [Environment variables](#environment-variables)
|
|
13
14
|
- [Cursor](#cursor)
|
|
14
15
|
- [Visual Studio Code (GitHub Copilot)](#visual-studio-code-github-copilot)
|
|
15
16
|
- [Claude Desktop](#claude-desktop)
|
|
@@ -86,11 +87,27 @@ Register the server under a stable name (this documentation uses **`rethunk-git`
|
|
|
86
87
|
|
|
87
88
|
Omit any `cwd` / `workingDirectory` field unless your client requires it for unrelated reasons. This server resolves repos from MCP **roots**, not from the process cwd.
|
|
88
89
|
|
|
90
|
+
## Environment variables
|
|
91
|
+
|
|
92
|
+
| Variable | Default | Purpose |
|
|
93
|
+
|----------|---------|---------|
|
|
94
|
+
| `GIT_SUBPROCESS_PARALLELISM` | `4` | Number of parallel git subprocesses for inventory operations and `git_status` submodule rows. Valid range: 1 to 2×CPU count (auto-clamped to prevent runaway spawning). Increase on high-core machines to accelerate large fleet scans; decrease if system resources are constrained. |
|
|
95
|
+
|
|
96
|
+
Set these in the environment where the MCP client launches the server (e.g. in your shell, in the MCP client config as `env`, or in a startup script).
|
|
97
|
+
|
|
98
|
+
Example: Running the server with 8 parallel git processes on a 4-core machine:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
GIT_SUBPROCESS_PARALLELISM=8 npx -y @rethunk/mcp-multi-root-git
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
On a 4-core machine (CPU count = 4), the max parallelism is clamped to 8 (2×4). The value requested is used if valid.
|
|
105
|
+
|
|
89
106
|
## Cursor
|
|
90
107
|
|
|
91
108
|
**User scope (all workspaces):** `~/.cursor/mcp.json` on macOS/Linux, or `%USERPROFILE%\.cursor\mcp.json` on Windows.
|
|
92
109
|
|
|
93
|
-
**Project scope:** `.cursor/mcp.json` in the workspace (
|
|
110
|
+
**Project scope:** `.cursor/mcp.json` in the workspace (use when a team wants repo-local MCP wiring).
|
|
94
111
|
|
|
95
112
|
Cursor uses a top-level **`mcpServers`** object:
|
|
96
113
|
|
|
@@ -185,7 +202,7 @@ For contributors working inside a clone of [mcp-multi-root-git](https://github.c
|
|
|
185
202
|
1. **Dependencies, build, and CI parity:** **[HUMANS.md](../HUMANS.md)** — *Development* (`bun install`, `bun run build`, `bun run check`). Do not duplicate that workflow here.
|
|
186
203
|
2. **Run the dev server** (no `dist/` required): from the repo root, **`bun src/server.ts`** (stdio MCP).
|
|
187
204
|
|
|
188
|
-
**MCP registration for a local checkout:** point your client at that command (or at **`dist/server.js`** via `node` after `bun run build` — see HUMANS). **Cursor:**
|
|
205
|
+
**MCP registration for a local checkout:** point your client at that command (or at **`dist/server.js`** via `node` after `bun run build` — see HUMANS). **Cursor:** add the command to either your user-scope or project-scope `mcp.json`, and open the workspace at the repository root so relative args resolve.
|
|
189
206
|
|
|
190
207
|
**Reload** the MCP connection after changing server code.
|
|
191
208
|
|