@arii/boomtick-mcp 0.4.2 → 0.5.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/.mcp/schemas/github.get_pr_commits.json +32 -0
- package/.mcp/schemas/github.get_pr_reviews.json +32 -0
- package/.mcp/schemas/github_get_pr_commits.json +32 -0
- package/.mcp/schemas/github_get_pr_reviews.json +32 -0
- package/.mcp/schemas/repo.get_workflow_runs.json +50 -0
- package/.mcp/schemas/repo.logs.json +14 -0
- package/.mcp/schemas/repo.read_ci_logs.json +14 -0
- package/.mcp/schemas/repo_get_workflow_runs.json +50 -0
- package/.mcp/schemas/repo_logs.json +14 -0
- package/.mcp/schemas/repo_read_ci_logs.json +14 -0
- package/CHANGELOG.md +7 -0
- package/README.md +0 -1
- package/actions/impact-analysis/action.yml +4 -16
- package/actions/run-project-gate/action.yml +2 -9
- package/actions/test-build/action.yml +1 -19
- package/dist/lib/git.js +44 -0
- package/dist/lib/git.test.js +82 -0
- package/dist/mcp/definitions.js +16 -39
- package/dist/mcp/server.js +0 -46
- package/dist/mcp/server.test.js +0 -33
- package/dist/tools/contract.js +135 -2
- package/dist/tools/github.get_merge_conflict_files.js +10 -18
- package/dist/tools/github.get_pr_commits.js +21 -0
- package/dist/tools/github.get_pr_reviews.js +21 -0
- package/dist/tools/github.get_pr_reviews.test.js +99 -0
- package/dist/tools/jules/create-session.js +1 -2
- package/dist/tools/jules/get-pr.js +1 -2
- package/dist/tools/jules/get-session.js +1 -2
- package/dist/tools/jules/shared.js +24 -0
- package/dist/tools/repo.get_changed_files.js +2 -13
- package/dist/tools/repo.get_workflow_runs.js +29 -0
- package/dist/tools/repo.logs.js +5 -1
- package/dist/tools/repo.read_agent_context.js +27 -1
- package/dist/tools/repo.read_agent_context.test.js +1 -1
- package/dist/tools/repo.read_ci_logs.js +5 -1
- package/dist/tools/repo.run_tests.js +1 -1
- package/package.json +3 -6
- package/src/agents/verifier-agent.prompt.md +0 -4
- package/src/lib/git.test.ts +110 -0
- package/src/lib/git.ts +75 -0
- package/src/mcp/definitions.ts +18 -42
- package/src/mcp/server.test.ts +0 -42
- package/src/mcp/server.ts +0 -45
- package/src/tools/contract.ts +147 -2
- package/src/tools/github.get_merge_conflict_files.ts +10 -20
- package/src/tools/github.get_pr_commits.ts +27 -0
- package/src/tools/github.get_pr_reviews.test.ts +110 -0
- package/src/tools/github.get_pr_reviews.ts +27 -0
- package/src/tools/jules/create-session.ts +1 -2
- package/src/tools/jules/get-pr.ts +1 -2
- package/src/tools/jules/get-session.ts +1 -2
- package/src/tools/jules/shared.ts +35 -0
- package/src/tools/repo.get_changed_files.ts +2 -16
- package/src/tools/repo.get_workflow_runs.ts +36 -0
- package/src/tools/repo.logs.ts +5 -1
- package/src/tools/repo.read_agent_context.test.ts +1 -1
- package/src/tools/repo.read_agent_context.ts +35 -1
- package/src/tools/repo.read_ci_logs.ts +5 -1
- package/src/tools/repo.run_tests.ts +1 -1
- package/.mcp/schemas/agent.search_ddgs.json +0 -31
- package/.mcp/schemas/agent_search_ddgs.json +0 -31
- package/.mcp/schemas/repo.get_route_map.json +0 -23
- package/.mcp/schemas/repo.run_lighthouse.json +0 -29
- package/.mcp/schemas/repo.run_playwright.json +0 -36
- package/.mcp/schemas/repo_get_route_map.json +0 -23
- package/.mcp/schemas/repo_run_lighthouse.json +0 -29
- package/.mcp/schemas/repo_run_playwright.json +0 -36
- package/dist/lib/td-cli.js +0 -54
- package/dist/tools/ddgs.search.js +0 -50
- package/dist/tools/ddgs.search.test.js +0 -67
- package/dist/tools/ddgs_search.py +0 -23
- package/dist/tools/repo.get_route_map.js +0 -37
- package/dist/tools/repo.run_lighthouse.js +0 -37
- package/dist/tools/repo.run_playwright.js +0 -34
- package/scripts/export-mcp-schema.ts +0 -7
- package/src/lib/td-cli.ts +0 -73
- package/src/tools/ddgs.search.test.ts +0 -92
- package/src/tools/ddgs.search.ts +0 -58
- package/src/tools/ddgs_search.py +0 -23
- package/src/tools/repo.get_route_map.ts +0 -41
- package/src/tools/repo.run_lighthouse.ts +0 -40
- package/src/tools/repo.run_playwright.ts +0 -42
package/dist/tools/repo.logs.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { runTDCli } from "
|
|
2
|
+
import { runTDCli } from "./jules/shared.js";
|
|
3
3
|
export const RepoLogsInputSchema = z.object({
|
|
4
4
|
prNumber: z.number(),
|
|
5
5
|
grep: z.string().optional(),
|
|
6
|
+
repo: z.string().optional(),
|
|
6
7
|
});
|
|
7
8
|
export async function repoLogsHandler(args) {
|
|
8
9
|
const params = ["repo", "logs", args.prNumber.toString()];
|
|
9
10
|
if (args.grep) {
|
|
10
11
|
params.push("--grep", args.grep);
|
|
11
12
|
}
|
|
13
|
+
if (args.repo) {
|
|
14
|
+
params.push("--repo", args.repo);
|
|
15
|
+
}
|
|
12
16
|
const output = await runTDCli(params);
|
|
13
17
|
return {
|
|
14
18
|
logs: output.logs || (output.data ? output.data.logs : undefined)
|
|
@@ -1,6 +1,32 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import { config } from "../config.js";
|
|
3
|
-
import {
|
|
3
|
+
import { execSync } from "child_process";
|
|
4
|
+
import * as fs from "fs";
|
|
5
|
+
import * as path from "path";
|
|
6
|
+
export const loadStandaloneAgentContext = (repoRoot = process.cwd()) => {
|
|
7
|
+
const configPath = path.join(repoRoot, "project_config.json");
|
|
8
|
+
let projectConfig = {};
|
|
9
|
+
if (fs.existsSync(configPath)) {
|
|
10
|
+
try {
|
|
11
|
+
projectConfig = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
console.warn(`[MCP Warning] Failed to parse ${configPath}:`, e);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
let gitCommit = "unknown";
|
|
18
|
+
try {
|
|
19
|
+
gitCommit = execSync("git rev-parse HEAD", { cwd: repoRoot, encoding: "utf-8" }).trim();
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
// Fallback if git is unavailable
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
packageName: projectConfig.packageName ?? "boomtick",
|
|
26
|
+
gitCommit,
|
|
27
|
+
config: projectConfig,
|
|
28
|
+
};
|
|
29
|
+
};
|
|
4
30
|
export const ReadAgentContextInputSchema = z.object({});
|
|
5
31
|
export async function readAgentContextHandler(_args) {
|
|
6
32
|
const context = loadStandaloneAgentContext(config.repoPath);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
-
import { loadStandaloneAgentContext } from "
|
|
2
|
+
import { loadStandaloneAgentContext } from "./repo.read_agent_context.js";
|
|
3
3
|
describe("Standalone Agent Context Loader", () => {
|
|
4
4
|
it("should load agent context without submodule references", () => {
|
|
5
5
|
const context = loadStandaloneAgentContext(process.cwd());
|
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { runTDCli } from "
|
|
2
|
+
import { runTDCli } from "./jules/shared.js";
|
|
3
3
|
export const ReadCiLogsInputSchema = z.object({
|
|
4
4
|
prNumber: z.number(),
|
|
5
5
|
all: z.boolean().optional(),
|
|
6
|
+
repo: z.string().optional(),
|
|
6
7
|
});
|
|
7
8
|
export async function readCiLogsHandler(args) {
|
|
8
9
|
const params = ["repo", "ci-logs", args.prNumber.toString()];
|
|
9
10
|
if (args.all) {
|
|
10
11
|
params.push("--all");
|
|
11
12
|
}
|
|
13
|
+
if (args.repo) {
|
|
14
|
+
params.push("--repo", args.repo);
|
|
15
|
+
}
|
|
12
16
|
const output = await runTDCli(params);
|
|
13
17
|
return {
|
|
14
18
|
checks: output.checks,
|
|
@@ -5,7 +5,7 @@ export const RunTestsInputSchema = z.object({
|
|
|
5
5
|
timeoutSeconds: z.number().optional().default(300),
|
|
6
6
|
worktreePath: z.string().optional(),
|
|
7
7
|
});
|
|
8
|
-
|
|
8
|
+
function parseCommandArguments(fullCmd) {
|
|
9
9
|
const tokens = [];
|
|
10
10
|
const regex = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g;
|
|
11
11
|
let match;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arii/boomtick-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Boomtick MCP Guide: PR Rescue + Merge Conflict Agent",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,7 +17,6 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@modelcontextprotocol/sdk": "^1.6.0",
|
|
20
|
-
"dotenv": "^17.4.2",
|
|
21
20
|
"zod": "^4.4.3"
|
|
22
21
|
},
|
|
23
22
|
"engines": {
|
|
@@ -27,14 +26,12 @@
|
|
|
27
26
|
"devDependencies": {
|
|
28
27
|
"@types/node": "^20.17.15",
|
|
29
28
|
"json-schema-to-zod": "^2.8.1",
|
|
30
|
-
"shx": "^0.4.0",
|
|
31
29
|
"tsx": "^4.22.4",
|
|
32
30
|
"typescript": "^5.7.3",
|
|
33
|
-
"vitest": "^4.1.9"
|
|
34
|
-
"yaml": "^2.9.0"
|
|
31
|
+
"vitest": "^4.1.9"
|
|
35
32
|
},
|
|
36
33
|
"scripts": {
|
|
37
|
-
"build": "tsx scripts/sync-contracts.ts && tsc &&
|
|
34
|
+
"build": "tsx scripts/sync-contracts.ts && tsc && tsx scripts/sync-mcp-schemas.ts",
|
|
38
35
|
"watch": "tsc -w",
|
|
39
36
|
"start": "node dist/index.js",
|
|
40
37
|
"test": "tsx scripts/sync-contracts.ts && tsx scripts/sync-mcp-schemas.ts && vitest run",
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { describe, expect, it, vi, beforeEach } from "vitest";
|
|
2
|
+
import { getChangedFiles, getMergeConflictFiles } from "./git.js";
|
|
3
|
+
import * as shell from "./shell.js";
|
|
4
|
+
|
|
5
|
+
vi.mock("./shell.js", () => ({
|
|
6
|
+
runCommand: vi.fn(),
|
|
7
|
+
}));
|
|
8
|
+
|
|
9
|
+
describe("Git Helpers", () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.resetAllMocks();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe("getChangedFiles", () => {
|
|
15
|
+
it("should parse null-delimited git diff output correctly", async () => {
|
|
16
|
+
// Mock git diff output with null character delimiters:
|
|
17
|
+
// Status M -> file1.txt
|
|
18
|
+
// Status A -> file2.txt
|
|
19
|
+
// Status R100 -> old.txt -> new.txt
|
|
20
|
+
const mockStdout = "M\0file1.txt\0A\0file2.txt\0R100\0old.txt\0new.txt\0";
|
|
21
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
22
|
+
exitCode: 0,
|
|
23
|
+
stdout: mockStdout,
|
|
24
|
+
stderr: "",
|
|
25
|
+
durationMs: 10,
|
|
26
|
+
command: "git diff --name-status -z main...HEAD",
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const result = await getChangedFiles("main", "HEAD");
|
|
30
|
+
|
|
31
|
+
expect(shell.runCommand).toHaveBeenCalledWith(
|
|
32
|
+
"git",
|
|
33
|
+
["diff", "--name-status", "-z", "main...HEAD"],
|
|
34
|
+
{ cwd: undefined }
|
|
35
|
+
);
|
|
36
|
+
expect(result).toEqual([
|
|
37
|
+
{ status: "M", path: "file1.txt" },
|
|
38
|
+
{ status: "A", path: "file2.txt" },
|
|
39
|
+
{ status: "R100", path: "new.txt", oldPath: "old.txt" },
|
|
40
|
+
]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("should throw error if git diff fails", async () => {
|
|
44
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
45
|
+
exitCode: 1,
|
|
46
|
+
stdout: "",
|
|
47
|
+
stderr: "fatal: bad revision",
|
|
48
|
+
durationMs: 10,
|
|
49
|
+
command: "git diff --name-status -z invalid...HEAD",
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
await expect(getChangedFiles("invalid", "HEAD")).rejects.toThrow(
|
|
53
|
+
"Failed to get changed files: fatal: bad revision"
|
|
54
|
+
);
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("getMergeConflictFiles", () => {
|
|
59
|
+
it("should parse git merge-tree output when conflicts exist", async () => {
|
|
60
|
+
// Output with 40-char tree hash followed by conflicting filenames
|
|
61
|
+
const mockStdout =
|
|
62
|
+
"5d71843ed2e051b2be91361db7bea0fc4ee1e5a0\nfile1.txt\nsrc/file2.ts\n";
|
|
63
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
64
|
+
exitCode: 1, // merge-tree exits 1 on conflicts
|
|
65
|
+
stdout: mockStdout,
|
|
66
|
+
stderr: "",
|
|
67
|
+
durationMs: 10,
|
|
68
|
+
command: "git merge-tree --write-tree --name-only main feature",
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const conflicts = await getMergeConflictFiles("main", "feature");
|
|
72
|
+
|
|
73
|
+
expect(shell.runCommand).toHaveBeenCalledWith(
|
|
74
|
+
"git",
|
|
75
|
+
["merge-tree", "--write-tree", "--name-only", "main", "feature"],
|
|
76
|
+
{ cwd: undefined }
|
|
77
|
+
);
|
|
78
|
+
expect(conflicts).toEqual(["file1.txt", "src/file2.ts"]);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("should return empty array when no conflicts exist", async () => {
|
|
82
|
+
const mockStdout = "5d71843ed2e051b2be91361db7bea0fc4ee1e5a0\n";
|
|
83
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
84
|
+
exitCode: 0,
|
|
85
|
+
stdout: mockStdout,
|
|
86
|
+
stderr: "",
|
|
87
|
+
durationMs: 10,
|
|
88
|
+
command: "git merge-tree --write-tree --name-only main feature",
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const conflicts = await getMergeConflictFiles("main", "feature");
|
|
92
|
+
|
|
93
|
+
expect(conflicts).toEqual([]);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("should throw error if git merge-tree exits with unexpected exit code", async () => {
|
|
97
|
+
vi.mocked(shell.runCommand).mockResolvedValueOnce({
|
|
98
|
+
exitCode: 128,
|
|
99
|
+
stdout: "",
|
|
100
|
+
stderr: "fatal: not a valid object name",
|
|
101
|
+
durationMs: 10,
|
|
102
|
+
command: "git merge-tree --write-tree --name-only invalid feature",
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
await expect(getMergeConflictFiles("invalid", "feature")).rejects.toThrow(
|
|
106
|
+
"Git merge-tree failed: fatal: not a valid object name"
|
|
107
|
+
);
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
});
|
package/src/lib/git.ts
CHANGED
|
@@ -73,3 +73,78 @@ export async function removeWorktree(worktreePath: string): Promise<void> {
|
|
|
73
73
|
await runCommand("git", ["worktree", "remove", "--force", worktreePath]);
|
|
74
74
|
await runCommand("git", ["worktree", "prune"]);
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
export interface ChangedFile {
|
|
78
|
+
status: string;
|
|
79
|
+
path: string;
|
|
80
|
+
oldPath?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export async function getChangedFiles(
|
|
84
|
+
base: string = "main",
|
|
85
|
+
head: string = "HEAD",
|
|
86
|
+
cwd?: string
|
|
87
|
+
): Promise<ChangedFile[]> {
|
|
88
|
+
const result = await runCommand(
|
|
89
|
+
"git",
|
|
90
|
+
["diff", "--name-status", "-z", `${base}...${head}`],
|
|
91
|
+
{ cwd }
|
|
92
|
+
);
|
|
93
|
+
|
|
94
|
+
if (result.exitCode !== 0) {
|
|
95
|
+
throw new Error(`Failed to get changed files: ${result.stderr}`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const tokens = result.stdout.split("\0").filter((t) => t.length > 0);
|
|
99
|
+
const files: ChangedFile[] = [];
|
|
100
|
+
|
|
101
|
+
let i = 0;
|
|
102
|
+
while (i < tokens.length) {
|
|
103
|
+
const status = tokens[i];
|
|
104
|
+
if (status.startsWith("R") || status.startsWith("C")) {
|
|
105
|
+
const oldPath = tokens[i + 1];
|
|
106
|
+
const newPath = tokens[i + 2];
|
|
107
|
+
if (oldPath && newPath) {
|
|
108
|
+
files.push({ status, path: newPath, oldPath });
|
|
109
|
+
}
|
|
110
|
+
i += 3;
|
|
111
|
+
} else {
|
|
112
|
+
const filePath = tokens[i + 1];
|
|
113
|
+
if (filePath) {
|
|
114
|
+
files.push({ status, path: filePath });
|
|
115
|
+
}
|
|
116
|
+
i += 2;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return files;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export async function getMergeConflictFiles(
|
|
124
|
+
baseBranch: string = "main",
|
|
125
|
+
headRef: string = "HEAD",
|
|
126
|
+
cwd?: string
|
|
127
|
+
): Promise<string[]> {
|
|
128
|
+
const result = await runCommand(
|
|
129
|
+
"git",
|
|
130
|
+
["merge-tree", "--write-tree", "--name-only", baseBranch, headRef],
|
|
131
|
+
{ cwd }
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
// git merge-tree returns exit code 1 if conflicts are present, 0 if clean merge
|
|
135
|
+
if (result.exitCode !== 0 && result.exitCode !== 1) {
|
|
136
|
+
throw new Error(`Git merge-tree failed: ${result.stderr}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const lines = result.stdout
|
|
140
|
+
.split("\n")
|
|
141
|
+
.map((l) => l.trim())
|
|
142
|
+
.filter((l) => l.length > 0);
|
|
143
|
+
|
|
144
|
+
// First line is tree hash if successful or conflict output
|
|
145
|
+
// When conflicts exist, git merge-tree --write-tree --name-only outputs conflicted filenames
|
|
146
|
+
// Skip tree hash line if present (40 hex chars)
|
|
147
|
+
const files = lines.filter((line) => !/^[0-9a-f]{40}$/i.test(line));
|
|
148
|
+
|
|
149
|
+
return files;
|
|
150
|
+
}
|
package/src/mcp/definitions.ts
CHANGED
|
@@ -7,14 +7,11 @@ import {
|
|
|
7
7
|
GetMergeConflictFilesInputJsonSchema,
|
|
8
8
|
GetChangedFilesInputJsonSchema,
|
|
9
9
|
GetPackageScriptsInputJsonSchema,
|
|
10
|
-
GetRouteMapInputJsonSchema,
|
|
11
10
|
ReadCiLogsInputJsonSchema,
|
|
12
11
|
RepoLogsInputJsonSchema,
|
|
13
12
|
CreateBranchInputJsonSchema,
|
|
14
13
|
CreateRepairBranchInputJsonSchema,
|
|
15
14
|
RunTestsInputJsonSchema,
|
|
16
|
-
RunLighthouseInputJsonSchema,
|
|
17
|
-
RunPlaywrightInputJsonSchema,
|
|
18
15
|
CommitPatchInputJsonSchema,
|
|
19
16
|
OpenReplacementPrInputJsonSchema,
|
|
20
17
|
CreatePullRequestInputJsonSchema,
|
|
@@ -30,7 +27,9 @@ import {
|
|
|
30
27
|
JulesSessionIdInputJsonSchema,
|
|
31
28
|
JulesSendMessageInputJsonSchema,
|
|
32
29
|
JulesListSessionsInputJsonSchema,
|
|
33
|
-
|
|
30
|
+
GetPrReviewsInputJsonSchema,
|
|
31
|
+
GetPrCommitsInputJsonSchema,
|
|
32
|
+
GetWorkflowRunsInputJsonSchema,
|
|
34
33
|
} from "../tools/contract.js";
|
|
35
34
|
|
|
36
35
|
export const MCP_PROMPTS: Prompt[] = [
|
|
@@ -67,12 +66,6 @@ export const MCP_RESOURCES: Resource[] = [
|
|
|
67
66
|
mimeType: "application/json",
|
|
68
67
|
description: "The root package.json file of the repository.",
|
|
69
68
|
},
|
|
70
|
-
{
|
|
71
|
-
uri: "repo://routes",
|
|
72
|
-
name: "Route Map",
|
|
73
|
-
mimeType: "application/json",
|
|
74
|
-
description: "The mapping of application routes to content files.",
|
|
75
|
-
},
|
|
76
69
|
{
|
|
77
70
|
uri: "repo://design-tokens",
|
|
78
71
|
name: "Design Tokens",
|
|
@@ -85,18 +78,6 @@ export const MCP_RESOURCES: Resource[] = [
|
|
|
85
78
|
mimeType: "application/json",
|
|
86
79
|
description: "The validation report for a specific repair branch.",
|
|
87
80
|
},
|
|
88
|
-
{
|
|
89
|
-
uri: "repo://lighthouse/{branch}",
|
|
90
|
-
name: "Lighthouse Report",
|
|
91
|
-
mimeType: "application/json",
|
|
92
|
-
description: "Lighthouse CI report for a specific branch.",
|
|
93
|
-
},
|
|
94
|
-
{
|
|
95
|
-
uri: "repo://playwright/{branch}",
|
|
96
|
-
name: "Playwright Report",
|
|
97
|
-
mimeType: "application/json",
|
|
98
|
-
description: "Playwright test report for a specific branch.",
|
|
99
|
-
},
|
|
100
81
|
];
|
|
101
82
|
|
|
102
83
|
const BASE_MCP_TOOLS: Tool[] = [
|
|
@@ -145,11 +126,6 @@ const BASE_MCP_TOOLS: Tool[] = [
|
|
|
145
126
|
description: "Get the scripts defined in package.json.",
|
|
146
127
|
inputSchema: GetPackageScriptsInputJsonSchema as any,
|
|
147
128
|
},
|
|
148
|
-
{
|
|
149
|
-
name: "repo.get_route_map",
|
|
150
|
-
description: "Get the mapping of routes to content files.",
|
|
151
|
-
inputSchema: GetRouteMapInputJsonSchema as any,
|
|
152
|
-
},
|
|
153
129
|
{
|
|
154
130
|
name: "repo.read_ci_logs",
|
|
155
131
|
description: "Read CI logs for a given pull request.",
|
|
@@ -175,16 +151,6 @@ const BASE_MCP_TOOLS: Tool[] = [
|
|
|
175
151
|
description: "Run repository tests and checks.",
|
|
176
152
|
inputSchema: RunTestsInputJsonSchema as any,
|
|
177
153
|
},
|
|
178
|
-
{
|
|
179
|
-
name: "repo.run_lighthouse",
|
|
180
|
-
description: "Run Lighthouse CI audits.",
|
|
181
|
-
inputSchema: RunLighthouseInputJsonSchema as any,
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
name: "repo.run_playwright",
|
|
185
|
-
description: "Run Playwright E2E tests.",
|
|
186
|
-
inputSchema: RunPlaywrightInputJsonSchema as any,
|
|
187
|
-
},
|
|
188
154
|
{
|
|
189
155
|
name: "repo.commit_patch",
|
|
190
156
|
description: "Commit verified repair changes in a worktree.",
|
|
@@ -210,6 +176,21 @@ const BASE_MCP_TOOLS: Tool[] = [
|
|
|
210
176
|
description: "Get details of a GitHub PR including title, state, and URLs.",
|
|
211
177
|
inputSchema: GetPrInputJsonSchema as any,
|
|
212
178
|
},
|
|
179
|
+
{
|
|
180
|
+
name: "github.get_pr_reviews",
|
|
181
|
+
description: "Get top-level reviews, verdicts, states, and bodies for a GitHub PR.",
|
|
182
|
+
inputSchema: GetPrReviewsInputJsonSchema as any,
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: "github.get_pr_commits",
|
|
186
|
+
description: "List commits and commit metadata (SHAs, authors, messages) for a GitHub PR.",
|
|
187
|
+
inputSchema: GetPrCommitsInputJsonSchema as any,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "repo.get_workflow_runs",
|
|
191
|
+
description: "List recent GitHub Actions workflow runs for the repository.",
|
|
192
|
+
inputSchema: GetWorkflowRunsInputJsonSchema as any,
|
|
193
|
+
},
|
|
213
194
|
{
|
|
214
195
|
name: "github.issue_view",
|
|
215
196
|
description: "View details of a GitHub issue including title, body, and state.",
|
|
@@ -270,11 +251,6 @@ const BASE_MCP_TOOLS: Tool[] = [
|
|
|
270
251
|
description: "Automatically collect CI status/logs for the PR associated with a Jules session and send them back as feedback.",
|
|
271
252
|
inputSchema: JulesSessionIdInputJsonSchema as any,
|
|
272
253
|
},
|
|
273
|
-
{
|
|
274
|
-
name: "agent.search_ddgs",
|
|
275
|
-
description: "Search the web using DuckDuckGo (via ddgs python library).",
|
|
276
|
-
inputSchema: SearchDdgsInputJsonSchema as any,
|
|
277
|
-
},
|
|
278
254
|
];
|
|
279
255
|
|
|
280
256
|
export const MCP_TOOLS: Tool[] = BASE_MCP_TOOLS.flatMap((tool) => {
|
package/src/mcp/server.test.ts
CHANGED
|
@@ -3,11 +3,8 @@ import { BoomtickMCPServer } from "./server.js";
|
|
|
3
3
|
import { config } from "../config.js";
|
|
4
4
|
import fs from "fs/promises";
|
|
5
5
|
import * as healthModule from "./tools.js";
|
|
6
|
-
import * as routeMapModule from "../tools/repo.get_route_map.js";
|
|
7
6
|
import * as prDiffModule from "../tools/github.get_pr_diff.js";
|
|
8
7
|
import * as ciLogsModule from "../tools/repo.read_ci_logs.js";
|
|
9
|
-
import * as lighthouseModule from "../tools/repo.run_lighthouse.js";
|
|
10
|
-
import * as playwrightModule from "../tools/repo.run_playwright.js";
|
|
11
8
|
|
|
12
9
|
vi.mock("fs/promises");
|
|
13
10
|
vi.mock("../config.js", () => ({
|
|
@@ -124,19 +121,6 @@ describe("BoomtickMCPServer", () => {
|
|
|
124
121
|
});
|
|
125
122
|
});
|
|
126
123
|
|
|
127
|
-
it("should handle repo://routes", async () => {
|
|
128
|
-
const handler = requestHandlers.get("resources/read");
|
|
129
|
-
vi.spyOn(routeMapModule, "getRouteMapHandler").mockResolvedValue({ routes: ["/"] } as any);
|
|
130
|
-
|
|
131
|
-
const result = await handler!({
|
|
132
|
-
method: "resources/read",
|
|
133
|
-
params: { uri: "repo://routes" },
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
expect(result.contents[0].uri).toBe("repo://routes");
|
|
137
|
-
expect(JSON.parse(result.contents[0].text)).toEqual({ routes: ["/"] });
|
|
138
|
-
});
|
|
139
|
-
|
|
140
124
|
it("should handle repo://design-tokens", async () => {
|
|
141
125
|
const handler = requestHandlers.get("resources/read");
|
|
142
126
|
vi.mocked(fs.readFile).mockResolvedValue("export const tokens = {};");
|
|
@@ -178,32 +162,6 @@ describe("BoomtickMCPServer", () => {
|
|
|
178
162
|
expect(JSON.parse(result.contents[0].text)).toEqual({ status: "success" });
|
|
179
163
|
});
|
|
180
164
|
|
|
181
|
-
it("should handle repo://lighthouse/:branch", async () => {
|
|
182
|
-
const handler = requestHandlers.get("resources/read");
|
|
183
|
-
vi.spyOn(lighthouseModule, "runLighthouseHandler").mockResolvedValue({ score: 100 } as any);
|
|
184
|
-
|
|
185
|
-
const result = await handler!({
|
|
186
|
-
method: "resources/read",
|
|
187
|
-
params: { uri: "repo://lighthouse/feature-x" },
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
expect(result.contents[0].uri).toBe("repo://lighthouse/feature-x");
|
|
191
|
-
expect(JSON.parse(result.contents[0].text)).toEqual({ score: 100 });
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("should handle repo://playwright/:branch", async () => {
|
|
195
|
-
const handler = requestHandlers.get("resources/read");
|
|
196
|
-
vi.spyOn(playwrightModule, "runPlaywrightHandler").mockResolvedValue({ passed: true } as any);
|
|
197
|
-
|
|
198
|
-
const result = await handler!({
|
|
199
|
-
method: "resources/read",
|
|
200
|
-
params: { uri: "repo://playwright/feature-x" },
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
expect(result.contents[0].uri).toBe("repo://playwright/feature-x");
|
|
204
|
-
expect(JSON.parse(result.contents[0].text)).toEqual({ passed: true });
|
|
205
|
-
});
|
|
206
|
-
|
|
207
165
|
it("should throw when resource is unknown", async () => {
|
|
208
166
|
const handler = requestHandlers.get("resources/read");
|
|
209
167
|
|
package/src/mcp/server.ts
CHANGED
|
@@ -21,14 +21,11 @@ import { getChangedFilesHandler, GetChangedFilesInputSchema } from "../tools/rep
|
|
|
21
21
|
import { readAgentContextHandler, ReadAgentContextInputSchema } from "../tools/repo.read_agent_context.js";
|
|
22
22
|
import { getCommandSchemaHandler, GetCommandSchemaInputSchema } from "../tools/repo.get_command_schema.js";
|
|
23
23
|
import { getPackageScriptsHandler, GetPackageScriptsInputSchema } from "../tools/repo.get_package_scripts.js";
|
|
24
|
-
import { getRouteMapHandler, GetRouteMapInputSchema } from "../tools/repo.get_route_map.js";
|
|
25
24
|
import { readCiLogsHandler, ReadCiLogsInputSchema } from "../tools/repo.read_ci_logs.js";
|
|
26
25
|
import { repoLogsHandler, RepoLogsInputSchema } from "../tools/repo.logs.js";
|
|
27
26
|
import { createRepairBranchHandler, CreateRepairBranchInputSchema } from "../tools/repo.create_repair_branch.js";
|
|
28
27
|
import { createBranchHandler, CreateBranchInputSchema } from "../tools/repo.create_branch.js";
|
|
29
28
|
import { runTestsHandler, RunTestsInputSchema } from "../tools/repo.run_tests.js";
|
|
30
|
-
import { runLighthouseHandler, RunLighthouseInputSchema } from "../tools/repo.run_lighthouse.js";
|
|
31
|
-
import { runPlaywrightHandler, RunPlaywrightInputSchema } from "../tools/repo.run_playwright.js";
|
|
32
29
|
import { commitPatchHandler, CommitPatchInputSchema } from "../tools/repo.commit_patch.js";
|
|
33
30
|
import { openReplacementPrHandler, OpenReplacementPrInputSchema } from "../tools/github.open_replacement_pr.js";
|
|
34
31
|
import { commentTriageSummaryHandler, CommentTriageSummaryInputSchema } from "../tools/github.comment_triage_summary.js";
|
|
@@ -48,7 +45,6 @@ import { listJulesSessionsHandler, ListJulesSessionsInputSchema } from "../tools
|
|
|
48
45
|
import { cancelJulesSessionHandler, CancelJulesSessionInputSchema } from "../tools/jules/cancel-session.js";
|
|
49
46
|
import { getJulesPullRequestHandler, GetJulesPullRequestInputSchema } from "../tools/jules/get-pr.js";
|
|
50
47
|
import { triggerJulesFeedbackHandler, TriggerJulesFeedbackInputSchema } from "../tools/jules/trigger-feedback.js";
|
|
51
|
-
import { ddgsSearchHandler, DdgsSearchInputSchema } from "../tools/ddgs.search.js";
|
|
52
48
|
|
|
53
49
|
import fs from "fs/promises";
|
|
54
50
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -129,12 +125,6 @@ export class BoomtickMCPServer {
|
|
|
129
125
|
contents: [{ uri, mimeType: "application/json", text: content }],
|
|
130
126
|
};
|
|
131
127
|
}
|
|
132
|
-
if (uri === "repo://routes") {
|
|
133
|
-
const routeMap = await getRouteMapHandler({});
|
|
134
|
-
return {
|
|
135
|
-
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(routeMap, null, 2) }],
|
|
136
|
-
};
|
|
137
|
-
}
|
|
138
128
|
if (uri === "repo://design-tokens") {
|
|
139
129
|
const tokensPath = path.join(config.repoPath, "src/styles/design-tokens.ts");
|
|
140
130
|
const content = await fs.readFile(tokensPath, "utf-8");
|
|
@@ -156,20 +146,6 @@ export class BoomtickMCPServer {
|
|
|
156
146
|
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(logs, null, 2) }],
|
|
157
147
|
};
|
|
158
148
|
}
|
|
159
|
-
if (uri.startsWith("repo://lighthouse/")) {
|
|
160
|
-
const branch = uri.split("/").pop() || "";
|
|
161
|
-
const report = await runLighthouseHandler({ route: "/", worktreePath: `../boomtick-mcp-rescue-${branch}` });
|
|
162
|
-
return {
|
|
163
|
-
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(report, null, 2) }],
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
if (uri.startsWith("repo://playwright/")) {
|
|
167
|
-
const branch = uri.split("/").pop() || "";
|
|
168
|
-
const report = await runPlaywrightHandler({ worktreePath: `../boomtick-mcp-rescue-${branch}` });
|
|
169
|
-
return {
|
|
170
|
-
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(report, null, 2) }],
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
149
|
throw new Error(`Resource not found: ${uri}`);
|
|
174
150
|
});
|
|
175
151
|
}
|
|
@@ -213,9 +189,6 @@ export class BoomtickMCPServer {
|
|
|
213
189
|
case "repo.get.package.scripts":
|
|
214
190
|
case "repo.get_package_scripts":
|
|
215
191
|
return createSuccessResult(await getPackageScriptsHandler(GetPackageScriptsInputSchema.parse(normArgs)));
|
|
216
|
-
case "repo.get.route.map":
|
|
217
|
-
case "repo.get_route_map":
|
|
218
|
-
return createSuccessResult(await getRouteMapHandler(GetRouteMapInputSchema.parse(normArgs)));
|
|
219
192
|
case "repo.read.ci.logs":
|
|
220
193
|
case "repo.read_ci_logs":
|
|
221
194
|
return createSuccessResult(await readCiLogsHandler(ReadCiLogsInputSchema.parse(normArgs)));
|
|
@@ -230,12 +203,6 @@ export class BoomtickMCPServer {
|
|
|
230
203
|
case "repo.run.tests":
|
|
231
204
|
case "repo.run_tests":
|
|
232
205
|
return createSuccessResult(await runTestsHandler(RunTestsInputSchema.parse(normArgs)));
|
|
233
|
-
case "repo.run.lighthouse":
|
|
234
|
-
case "repo.run_lighthouse":
|
|
235
|
-
return createSuccessResult(await runLighthouseHandler(RunLighthouseInputSchema.parse(normArgs)));
|
|
236
|
-
case "repo.run.playwright":
|
|
237
|
-
case "repo.run_playwright":
|
|
238
|
-
return createSuccessResult(await runPlaywrightHandler(RunPlaywrightInputSchema.parse(normArgs)));
|
|
239
206
|
case "repo.commit.patch":
|
|
240
207
|
case "repo.commit_patch":
|
|
241
208
|
return createSuccessResult(await commitPatchHandler(CommitPatchInputSchema.parse(normArgs)));
|
|
@@ -288,9 +255,6 @@ export class BoomtickMCPServer {
|
|
|
288
255
|
case "jules.trigger.feedback":
|
|
289
256
|
case "jules.trigger_feedback":
|
|
290
257
|
return createSuccessResult(await triggerJulesFeedbackHandler(TriggerJulesFeedbackInputSchema.parse(normArgs)));
|
|
291
|
-
case "agent.search.ddgs":
|
|
292
|
-
case "agent.search_ddgs":
|
|
293
|
-
return createSuccessResult(await ddgsSearchHandler(DdgsSearchInputSchema.parse(normArgs)));
|
|
294
258
|
default:
|
|
295
259
|
return createErrorResult(`Tool not found: ${request.params.name}`);
|
|
296
260
|
}
|
|
@@ -378,15 +342,6 @@ export class BoomtickMCPServer {
|
|
|
378
342
|
process.exit(1);
|
|
379
343
|
}
|
|
380
344
|
|
|
381
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
382
|
-
const ddgsScriptPath = path.join(__dirname, "..", "tools", "ddgs_search.py");
|
|
383
|
-
try {
|
|
384
|
-
await fs.access(ddgsScriptPath);
|
|
385
|
-
} catch (err) {
|
|
386
|
-
console.error(`❌ Fatal: ddgs_search.py not found at ${ddgsScriptPath}`);
|
|
387
|
-
process.exit(1);
|
|
388
|
-
}
|
|
389
|
-
|
|
390
345
|
const transport = new StdioServerTransport();
|
|
391
346
|
await this.server.connect(transport);
|
|
392
347
|
console.error("Boomtick MCP Server running on stdio");
|