@arii/boomtick-mcp 0.4.1 → 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.
Files changed (163) hide show
  1. package/.mcp/schemas/boomtick.health.json +14 -10
  2. package/.mcp/schemas/boomtick_health.json +16 -0
  3. package/.mcp/schemas/github.checkout_branch.json +28 -24
  4. package/.mcp/schemas/github.comment_triage_summary.json +21 -17
  5. package/.mcp/schemas/github.create_issue.json +55 -50
  6. package/.mcp/schemas/github.create_pull_request.json +53 -48
  7. package/.mcp/schemas/github.get_merge_conflict_files.json +21 -17
  8. package/.mcp/schemas/github.get_pr.json +30 -12
  9. package/.mcp/schemas/github.get_pr_commits.json +32 -0
  10. package/.mcp/schemas/github.get_pr_diff.json +30 -12
  11. package/.mcp/schemas/github.get_pr_reviews.json +32 -0
  12. package/.mcp/schemas/github.issue_comment.json +35 -17
  13. package/.mcp/schemas/github.issue_update.json +114 -96
  14. package/.mcp/schemas/github.issue_view.json +30 -12
  15. package/.mcp/schemas/github.open_replacement_pr.json +64 -60
  16. package/.mcp/schemas/github.search_open_prs.json +61 -43
  17. package/.mcp/schemas/github_checkout_branch.json +31 -0
  18. package/.mcp/schemas/github_comment_triage_summary.json +24 -0
  19. package/.mcp/schemas/github_create_issue.json +58 -0
  20. package/.mcp/schemas/github_create_pull_request.json +56 -0
  21. package/.mcp/schemas/github_get_merge_conflict_files.json +24 -0
  22. package/.mcp/schemas/github_get_pr.json +32 -0
  23. package/.mcp/schemas/github_get_pr_commits.json +32 -0
  24. package/.mcp/schemas/github_get_pr_diff.json +32 -0
  25. package/.mcp/schemas/github_get_pr_reviews.json +32 -0
  26. package/.mcp/schemas/github_issue_comment.json +38 -0
  27. package/.mcp/schemas/github_issue_update.json +123 -0
  28. package/.mcp/schemas/github_issue_view.json +32 -0
  29. package/.mcp/schemas/github_open_replacement_pr.json +67 -0
  30. package/.mcp/schemas/github_search_open_prs.json +65 -0
  31. package/.mcp/schemas/jules.cancel_session.json +16 -12
  32. package/.mcp/schemas/jules.create_session.json +41 -37
  33. package/.mcp/schemas/jules.get_messages.json +16 -12
  34. package/.mcp/schemas/jules.get_pr.json +16 -12
  35. package/.mcp/schemas/jules.get_session.json +16 -12
  36. package/.mcp/schemas/jules.list_sessions.json +33 -29
  37. package/.mcp/schemas/jules.send_message.json +30 -26
  38. package/.mcp/schemas/jules.trigger_feedback.json +16 -12
  39. package/.mcp/schemas/jules_cancel_session.json +18 -0
  40. package/.mcp/schemas/jules_create_session.json +44 -0
  41. package/.mcp/schemas/jules_get_messages.json +18 -0
  42. package/.mcp/schemas/jules_get_pr.json +18 -0
  43. package/.mcp/schemas/jules_get_session.json +18 -0
  44. package/.mcp/schemas/jules_list_sessions.json +36 -0
  45. package/.mcp/schemas/jules_send_message.json +35 -0
  46. package/.mcp/schemas/jules_trigger_feedback.json +18 -0
  47. package/.mcp/schemas/repo.commit_patch.json +34 -30
  48. package/.mcp/schemas/repo.create_branch.json +21 -17
  49. package/.mcp/schemas/repo.create_repair_branch.json +34 -30
  50. package/.mcp/schemas/repo.get_changed_files.json +19 -15
  51. package/.mcp/schemas/repo.get_command_schema.json +16 -12
  52. package/.mcp/schemas/repo.get_package_scripts.json +21 -17
  53. package/.mcp/schemas/repo.get_workflow_runs.json +50 -0
  54. package/.mcp/schemas/repo.logs.json +42 -24
  55. package/.mcp/schemas/repo.read_agent_context.json +7 -3
  56. package/.mcp/schemas/repo.read_ci_logs.json +35 -17
  57. package/.mcp/schemas/repo.run_tests.json +40 -36
  58. package/.mcp/schemas/repo_commit_patch.json +39 -0
  59. package/.mcp/schemas/repo_create_branch.json +24 -0
  60. package/.mcp/schemas/repo_create_repair_branch.json +37 -0
  61. package/.mcp/schemas/repo_get_changed_files.json +22 -0
  62. package/.mcp/schemas/repo_get_command_schema.json +18 -0
  63. package/.mcp/schemas/repo_get_package_scripts.json +23 -0
  64. package/.mcp/schemas/repo_get_workflow_runs.json +50 -0
  65. package/.mcp/schemas/repo_logs.json +45 -0
  66. package/.mcp/schemas/repo_read_agent_context.json +9 -0
  67. package/.mcp/schemas/repo_read_ci_logs.json +38 -0
  68. package/.mcp/schemas/repo_run_tests.json +45 -0
  69. package/CHANGELOG.md +16 -0
  70. package/README.md +0 -1
  71. package/actions/impact-analysis/action.yml +4 -16
  72. package/actions/run-project-gate/action.yml +2 -9
  73. package/actions/test-build/action.yml +1 -19
  74. package/dist/lib/git.js +74 -0
  75. package/dist/lib/git.test.js +82 -0
  76. package/dist/lib/param_utils.js +35 -0
  77. package/dist/lib/param_utils.test.js +44 -0
  78. package/dist/mcp/definitions.js +27 -40
  79. package/dist/mcp/schemas.test.js +28 -0
  80. package/dist/mcp/server.js +64 -74
  81. package/dist/mcp/server.test.js +0 -33
  82. package/dist/tools/contract.js +229 -10
  83. package/dist/tools/github.create_issue.js +3 -2
  84. package/dist/tools/github.create_issue.test.js +10 -0
  85. package/dist/tools/github.create_pull_request.js +4 -4
  86. package/dist/tools/github.get_merge_conflict_files.js +10 -18
  87. package/dist/tools/github.get_pr.js +4 -1
  88. package/dist/tools/github.get_pr_commits.js +21 -0
  89. package/dist/tools/github.get_pr_diff.js +6 -2
  90. package/dist/tools/github.get_pr_reviews.js +21 -0
  91. package/dist/tools/github.get_pr_reviews.test.js +99 -0
  92. package/dist/tools/github.issue_comment.js +8 -1
  93. package/dist/tools/github.issue_update.js +3 -0
  94. package/dist/tools/github.issue_view.js +4 -1
  95. package/dist/tools/github.search_open_prs.js +4 -0
  96. package/dist/tools/jules/create-session.js +1 -2
  97. package/dist/tools/jules/get-pr.js +1 -2
  98. package/dist/tools/jules/get-session.js +1 -2
  99. package/dist/tools/jules/shared.js +24 -0
  100. package/dist/tools/repo.commit_patch.js +30 -10
  101. package/dist/tools/repo.get_changed_files.js +2 -13
  102. package/dist/tools/repo.get_workflow_runs.js +29 -0
  103. package/dist/tools/repo.logs.js +5 -1
  104. package/dist/tools/repo.read_agent_context.js +27 -1
  105. package/dist/tools/repo.read_agent_context.test.js +1 -1
  106. package/dist/tools/repo.read_ci_logs.js +5 -1
  107. package/dist/tools/repo.run_tests.js +26 -4
  108. package/package.json +4 -7
  109. package/scripts/sync-mcp-schemas.ts +6 -1
  110. package/src/agents/verifier-agent.prompt.md +0 -4
  111. package/src/lib/git.test.ts +110 -0
  112. package/src/lib/git.ts +109 -0
  113. package/src/lib/param_utils.test.ts +49 -0
  114. package/src/lib/param_utils.ts +42 -0
  115. package/src/mcp/definitions.ts +30 -43
  116. package/src/mcp/schemas.test.ts +34 -0
  117. package/src/mcp/server.test.ts +0 -42
  118. package/src/mcp/server.ts +65 -75
  119. package/src/tools/contract.ts +241 -10
  120. package/src/tools/github.create_issue.test.ts +10 -0
  121. package/src/tools/github.create_issue.ts +3 -1
  122. package/src/tools/github.create_pull_request.ts +4 -5
  123. package/src/tools/github.get_merge_conflict_files.ts +10 -20
  124. package/src/tools/github.get_pr.ts +4 -1
  125. package/src/tools/github.get_pr_commits.ts +27 -0
  126. package/src/tools/github.get_pr_diff.ts +7 -2
  127. package/src/tools/github.get_pr_reviews.test.ts +110 -0
  128. package/src/tools/github.get_pr_reviews.ts +27 -0
  129. package/src/tools/github.issue_comment.ts +8 -1
  130. package/src/tools/github.issue_update.ts +4 -1
  131. package/src/tools/github.issue_view.ts +4 -1
  132. package/src/tools/github.search_open_prs.ts +4 -0
  133. package/src/tools/jules/create-session.ts +1 -2
  134. package/src/tools/jules/get-pr.ts +1 -2
  135. package/src/tools/jules/get-session.ts +1 -2
  136. package/src/tools/jules/shared.ts +35 -0
  137. package/src/tools/repo.commit_patch.ts +35 -11
  138. package/src/tools/repo.get_changed_files.ts +2 -16
  139. package/src/tools/repo.get_workflow_runs.ts +36 -0
  140. package/src/tools/repo.logs.ts +5 -1
  141. package/src/tools/repo.read_agent_context.test.ts +1 -1
  142. package/src/tools/repo.read_agent_context.ts +35 -1
  143. package/src/tools/repo.read_ci_logs.ts +5 -1
  144. package/src/tools/repo.run_tests.ts +28 -5
  145. package/.mcp/schemas/agent.search_ddgs.json +0 -27
  146. package/.mcp/schemas/repo.get_route_map.json +0 -19
  147. package/.mcp/schemas/repo.run_lighthouse.json +0 -25
  148. package/.mcp/schemas/repo.run_playwright.json +0 -32
  149. package/dist/lib/td-cli.js +0 -54
  150. package/dist/tools/ddgs.search.js +0 -50
  151. package/dist/tools/ddgs.search.test.js +0 -67
  152. package/dist/tools/ddgs_search.py +0 -23
  153. package/dist/tools/repo.get_route_map.js +0 -37
  154. package/dist/tools/repo.run_lighthouse.js +0 -37
  155. package/dist/tools/repo.run_playwright.js +0 -29
  156. package/scripts/export-mcp-schema.ts +0 -7
  157. package/src/lib/td-cli.ts +0 -73
  158. package/src/tools/ddgs.search.test.ts +0 -92
  159. package/src/tools/ddgs.search.ts +0 -58
  160. package/src/tools/ddgs_search.py +0 -23
  161. package/src/tools/repo.get_route_map.ts +0 -41
  162. package/src/tools/repo.run_lighthouse.ts +0 -40
  163. package/src/tools/repo.run_playwright.ts +0 -35
@@ -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
@@ -2,6 +2,40 @@ import { runCommand } from "./shell.js";
2
2
  import { config } from "../config.js";
3
3
  import path from "path";
4
4
  import fs from "fs/promises";
5
+ import { execSync } from "child_process";
6
+
7
+ const repoCache = new Map<string, string>();
8
+
9
+ export function resolveRepo(explicitRepo?: string | null, cwd?: string): string {
10
+ if (explicitRepo && explicitRepo.trim().length > 0) {
11
+ return explicitRepo.trim();
12
+ }
13
+
14
+ const targetCwd = cwd || process.cwd();
15
+ if (repoCache.has(targetCwd)) {
16
+ return repoCache.get(targetCwd)!;
17
+ }
18
+
19
+ try {
20
+ const url = execSync("git remote get-url origin", {
21
+ cwd: targetCwd,
22
+ encoding: "utf-8",
23
+ stdio: ["ignore", "pipe", "ignore"],
24
+ }).trim();
25
+
26
+ const match = url.match(/[:/]([^/]+\/[^/.]+)(\.git)?$/);
27
+ if (match) {
28
+ repoCache.set(targetCwd, match[1]);
29
+ return match[1];
30
+ }
31
+ } catch (e) {
32
+ // Ignore error and fall back to static config
33
+ }
34
+
35
+ const fallback = `${config.githubOwner}/${config.githubRepo}`;
36
+ repoCache.set(targetCwd, fallback);
37
+ return fallback;
38
+ }
5
39
 
6
40
  function validatePrNumber(value: unknown): number {
7
41
  const prNumber = Number(value);
@@ -39,3 +73,78 @@ export async function removeWorktree(worktreePath: string): Promise<void> {
39
73
  await runCommand("git", ["worktree", "remove", "--force", worktreePath]);
40
74
  await runCommand("git", ["worktree", "prune"]);
41
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
+ }
@@ -0,0 +1,49 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { normalizeArguments } from "./param_utils.js";
3
+
4
+ describe("param_utils.normalizeArguments", () => {
5
+ it("should convert snake_case keys to camelCase", () => {
6
+ const input = {
7
+ pr_number: 123,
8
+ issue_number: 456,
9
+ worktree_path: "/tmp/worktree",
10
+ base_branch: "main",
11
+ };
12
+ const output = normalizeArguments(input);
13
+ expect(output).toEqual({
14
+ prNumber: 123,
15
+ issueNumber: 456,
16
+ worktreePath: "/tmp/worktree",
17
+ baseBranch: "main",
18
+ });
19
+ });
20
+
21
+ it("should coerce numeric strings for prNumber and issueNumber", () => {
22
+ const input = {
23
+ pr_number: "789",
24
+ issue_number: "101",
25
+ };
26
+ const output = normalizeArguments(input);
27
+ expect(output).toEqual({
28
+ prNumber: 789,
29
+ issueNumber: 101,
30
+ });
31
+ });
32
+
33
+ it("should synchronize branch and branchName", () => {
34
+ expect(normalizeArguments({ branch: "feat-x" })).toEqual({
35
+ branch: "feat-x",
36
+ branchName: "feat-x",
37
+ });
38
+
39
+ expect(normalizeArguments({ branch_name: "feat-y" })).toEqual({
40
+ branchName: "feat-y",
41
+ branch: "feat-y",
42
+ });
43
+ });
44
+
45
+ it("should handle null or empty arguments safely", () => {
46
+ expect(normalizeArguments(null)).toEqual({});
47
+ expect(normalizeArguments(undefined)).toEqual({});
48
+ });
49
+ });
@@ -0,0 +1,42 @@
1
+ export function normalizeArguments(args: Record<string, unknown> | null | undefined): Record<string, unknown> {
2
+ if (!args || typeof args !== "object") {
3
+ return {};
4
+ }
5
+
6
+ const result: Record<string, unknown> = {};
7
+
8
+ for (const [key, value] of Object.entries(args)) {
9
+ // Convert snake_case to camelCase
10
+ const camelKey = key.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
11
+
12
+ // Normalize value recursively if it's an object/array
13
+ let normalizedValue = value;
14
+ if (Array.isArray(value)) {
15
+ normalizedValue = value.map((item) =>
16
+ item && typeof item === "object" ? normalizeArguments(item as Record<string, unknown>) : item
17
+ );
18
+ } else if (value && typeof value === "object" && !(value instanceof RegExp)) {
19
+ normalizedValue = normalizeArguments(value as Record<string, unknown>);
20
+ }
21
+
22
+ // Coerce numeric string to integer for prNumber / issueNumber if applicable
23
+ if ((camelKey === "prNumber" || camelKey === "issueNumber") && typeof normalizedValue === "string") {
24
+ const parsedNum = parseInt(normalizedValue, 10);
25
+ if (!isNaN(parsedNum)) {
26
+ normalizedValue = parsedNum;
27
+ }
28
+ }
29
+
30
+ result[camelKey] = normalizedValue;
31
+ }
32
+
33
+ // Parameter aliases harmonization
34
+ // branch vs branchName harmonization
35
+ if (result.branch !== undefined && result.branchName === undefined) {
36
+ result.branchName = result.branch;
37
+ } else if (result.branchName !== undefined && result.branch === undefined) {
38
+ result.branch = result.branchName;
39
+ }
40
+
41
+ return result;
42
+ }
@@ -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
- SearchDdgsInputJsonSchema,
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,21 +78,9 @@ 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
- export const MCP_TOOLS: Tool[] = [
83
+ const BASE_MCP_TOOLS: Tool[] = [
103
84
  {
104
85
  name: "boomtick.health",
105
86
  description: "Check the health and configuration of the MCP server.",
@@ -145,11 +126,6 @@ export const 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 @@ export const 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 @@ export const 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,9 +251,15 @@ export const 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
  ];
255
+
256
+ export const MCP_TOOLS: Tool[] = BASE_MCP_TOOLS.flatMap((tool) => {
257
+ const underscoreName = tool.name.replace(/\./g, "_");
258
+ if (underscoreName !== tool.name) {
259
+ return [
260
+ { ...tool, name: underscoreName },
261
+ tool,
262
+ ];
263
+ }
264
+ return [tool];
265
+ });
@@ -0,0 +1,34 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import * as fs from "fs";
3
+ import * as path from "path";
4
+ import { execSync } from "child_process";
5
+
6
+ describe("MCP tool schemas export", () => {
7
+ it("should generate valid schema files with non-null parameters object", () => {
8
+ const schemasDir = path.resolve(process.cwd(), ".mcp/schemas");
9
+ if (!fs.existsSync(schemasDir)) {
10
+ execSync("npx tsx scripts/sync-mcp-schemas.ts", { cwd: process.cwd() });
11
+ }
12
+ expect(fs.existsSync(schemasDir)).toBe(true);
13
+
14
+ const files = fs.readdirSync(schemasDir).filter((f) => f.endsWith(".json"));
15
+ expect(files.length).toBeGreaterThan(0);
16
+
17
+ for (const file of files) {
18
+ const filePath = path.join(schemasDir, file);
19
+ const content = JSON.parse(fs.readFileSync(filePath, "utf-8"));
20
+
21
+ expect(content).toHaveProperty("name");
22
+ expect(typeof content.name).toBe("string");
23
+
24
+ expect(content).toHaveProperty("description");
25
+ expect(typeof content.description).toBe("string");
26
+
27
+ expect(content).toHaveProperty("parameters");
28
+ expect(content.parameters).not.toBeNull();
29
+ expect(typeof content.parameters).toBe("object");
30
+ expect(content.parameters.type).toBe("object");
31
+ expect(content.parameters.properties).toBeDefined();
32
+ }
33
+ });
34
+ });
@@ -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