@arii/boomtick-mcp 0.4.2 → 0.6.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 (205) hide show
  1. package/.mcp/schemas/boomtick.health.json +5 -0
  2. package/.mcp/schemas/boomtick_health.json +5 -0
  3. package/.mcp/schemas/github.checkout_branch.json +5 -0
  4. package/.mcp/schemas/github.comment_triage_summary.json +5 -0
  5. package/.mcp/schemas/github.create_issue.json +5 -0
  6. package/.mcp/schemas/github.create_pull_request.json +5 -0
  7. package/.mcp/schemas/github.get_merge_conflict_files.json +5 -0
  8. package/.mcp/schemas/github.get_pr.json +5 -0
  9. package/.mcp/schemas/github.get_pr_commits.json +37 -0
  10. package/.mcp/schemas/github.get_pr_diff.json +5 -0
  11. package/.mcp/schemas/github.get_pr_reviews.json +37 -0
  12. package/.mcp/schemas/github.issue_comment.json +5 -0
  13. package/.mcp/schemas/github.issue_update.json +5 -0
  14. package/.mcp/schemas/github.issue_view.json +5 -0
  15. package/.mcp/schemas/github.open_replacement_pr.json +5 -0
  16. package/.mcp/schemas/github.search_open_prs.json +5 -0
  17. package/.mcp/schemas/github_checkout_branch.json +5 -0
  18. package/.mcp/schemas/github_comment_triage_summary.json +5 -0
  19. package/.mcp/schemas/github_create_issue.json +5 -0
  20. package/.mcp/schemas/github_create_pull_request.json +5 -0
  21. package/.mcp/schemas/github_get_merge_conflict_files.json +5 -0
  22. package/.mcp/schemas/github_get_pr.json +5 -0
  23. package/.mcp/schemas/github_get_pr_commits.json +37 -0
  24. package/.mcp/schemas/github_get_pr_diff.json +5 -0
  25. package/.mcp/schemas/github_get_pr_reviews.json +37 -0
  26. package/.mcp/schemas/github_issue_comment.json +5 -0
  27. package/.mcp/schemas/github_issue_update.json +5 -0
  28. package/.mcp/schemas/github_issue_view.json +5 -0
  29. package/.mcp/schemas/github_open_replacement_pr.json +5 -0
  30. package/.mcp/schemas/github_search_open_prs.json +5 -0
  31. package/.mcp/schemas/jules.cancel_session.json +5 -0
  32. package/.mcp/schemas/jules.create_session.json +5 -0
  33. package/.mcp/schemas/jules.get_messages.json +5 -0
  34. package/.mcp/schemas/jules.get_pr.json +5 -0
  35. package/.mcp/schemas/jules.get_session.json +5 -0
  36. package/.mcp/schemas/jules.list_sessions.json +5 -0
  37. package/.mcp/schemas/jules.send_message.json +5 -0
  38. package/.mcp/schemas/jules.trigger_feedback.json +5 -0
  39. package/.mcp/schemas/jules_cancel_session.json +5 -0
  40. package/.mcp/schemas/jules_create_session.json +5 -0
  41. package/.mcp/schemas/jules_get_messages.json +5 -0
  42. package/.mcp/schemas/jules_get_pr.json +5 -0
  43. package/.mcp/schemas/jules_get_session.json +5 -0
  44. package/.mcp/schemas/jules_list_sessions.json +5 -0
  45. package/.mcp/schemas/jules_send_message.json +5 -0
  46. package/.mcp/schemas/jules_trigger_feedback.json +5 -0
  47. package/.mcp/schemas/repo.commit_patch.json +5 -0
  48. package/.mcp/schemas/repo.create_branch.json +5 -0
  49. package/.mcp/schemas/repo.create_repair_branch.json +5 -0
  50. package/.mcp/schemas/repo.get_changed_files.json +5 -0
  51. package/.mcp/schemas/repo.get_command_schema.json +6 -1
  52. package/.mcp/schemas/repo.get_package_scripts.json +5 -0
  53. package/.mcp/schemas/repo.get_workflow_runs.json +55 -0
  54. package/.mcp/schemas/repo.logs.json +19 -0
  55. package/.mcp/schemas/repo.read_agent_context.json +5 -0
  56. package/.mcp/schemas/repo.read_ci_logs.json +19 -0
  57. package/.mcp/schemas/repo.run_tests.json +5 -0
  58. package/.mcp/schemas/repo_commit_patch.json +5 -0
  59. package/.mcp/schemas/repo_create_branch.json +5 -0
  60. package/.mcp/schemas/repo_create_repair_branch.json +5 -0
  61. package/.mcp/schemas/repo_get_changed_files.json +5 -0
  62. package/.mcp/schemas/repo_get_command_schema.json +6 -1
  63. package/.mcp/schemas/repo_get_package_scripts.json +5 -0
  64. package/.mcp/schemas/repo_get_workflow_runs.json +55 -0
  65. package/.mcp/schemas/repo_logs.json +19 -0
  66. package/.mcp/schemas/repo_read_agent_context.json +5 -0
  67. package/.mcp/schemas/repo_read_ci_logs.json +19 -0
  68. package/.mcp/schemas/repo_run_tests.json +5 -0
  69. package/CHANGELOG.md +14 -0
  70. package/README.md +0 -1
  71. package/actions/ai-review/action.yml +2 -2
  72. package/actions/audit/action.yml +1 -1
  73. package/actions/ci-validate/action.yml +2 -2
  74. package/actions/impact-analysis/action.yml +7 -19
  75. package/actions/run-project-gate/action.yml +7 -14
  76. package/actions/setup/action.yml +1 -1
  77. package/actions/test-build/action.yml +2 -20
  78. package/dist/config.js +1 -1
  79. package/dist/config.test.js +3 -3
  80. package/dist/lib/git.js +44 -0
  81. package/dist/lib/git.test.js +82 -0
  82. package/dist/lib/result.js +4 -0
  83. package/dist/lib/result.test.js +4 -2
  84. package/dist/lib/shell.js +6 -6
  85. package/dist/mcp/definitions.js +86 -38
  86. package/dist/mcp/http.js +91 -0
  87. package/dist/mcp/http.test.js +58 -0
  88. package/dist/mcp/server.js +51 -57
  89. package/dist/mcp/server.test.js +19 -32
  90. package/dist/tools/contract.js +244 -3
  91. package/dist/tools/github.checkout_branch.js +1 -1
  92. package/dist/tools/github.comment_triage_summary.js +1 -1
  93. package/dist/tools/github.create_issue.js +1 -1
  94. package/dist/tools/github.create_issue.test.js +6 -6
  95. package/dist/tools/github.create_pull_request.js +1 -1
  96. package/dist/tools/github.create_pull_request.test.js +4 -4
  97. package/dist/tools/github.get_merge_conflict_files.js +10 -18
  98. package/dist/tools/github.get_pr.js +1 -1
  99. package/dist/tools/github.get_pr_commits.js +21 -0
  100. package/dist/tools/github.get_pr_diff.js +1 -1
  101. package/dist/tools/github.get_pr_reviews.js +21 -0
  102. package/dist/tools/github.get_pr_reviews.test.js +99 -0
  103. package/dist/tools/github.issue_comment.js +1 -1
  104. package/dist/tools/github.issue_update.js +1 -1
  105. package/dist/tools/github.issue_view.js +1 -1
  106. package/dist/tools/github.search_open_prs.js +1 -1
  107. package/dist/tools/jules/cancel-session.js +1 -1
  108. package/dist/tools/jules/create-session.js +1 -2
  109. package/dist/tools/jules/create-session.test.js +11 -11
  110. package/dist/tools/jules/get-messages.js +1 -1
  111. package/dist/tools/jules/get-messages.test.js +3 -3
  112. package/dist/tools/jules/get-pr.js +1 -2
  113. package/dist/tools/jules/get-pr.test.js +3 -3
  114. package/dist/tools/jules/get-session.js +1 -2
  115. package/dist/tools/jules/list-sessions.js +1 -1
  116. package/dist/tools/jules/send-message.js +1 -1
  117. package/dist/tools/jules/send-message.test.js +5 -5
  118. package/dist/tools/jules/shared.js +24 -0
  119. package/dist/tools/jules/trigger-feedback.js +1 -1
  120. package/dist/tools/jules/trigger-feedback.test.js +5 -5
  121. package/dist/tools/repo.get_changed_files.js +2 -13
  122. package/dist/tools/repo.get_command_schema.js +2 -2
  123. package/dist/tools/repo.get_workflow_runs.js +29 -0
  124. package/dist/tools/repo.logs.js +5 -1
  125. package/dist/tools/repo.read_agent_context.js +27 -1
  126. package/dist/tools/repo.read_agent_context.test.js +1 -1
  127. package/dist/tools/repo.read_ci_logs.js +5 -1
  128. package/dist/tools/repo.run_tests.js +1 -1
  129. package/package.json +3 -6
  130. package/scripts/sync-mcp-schemas.ts +2 -0
  131. package/src/agents/verifier-agent.prompt.md +0 -4
  132. package/src/config.test.ts +3 -3
  133. package/src/config.ts +1 -1
  134. package/src/lib/git.test.ts +110 -0
  135. package/src/lib/git.ts +75 -0
  136. package/src/lib/result.test.ts +4 -2
  137. package/src/lib/result.ts +6 -0
  138. package/src/lib/shell.ts +6 -6
  139. package/src/mcp/definitions.ts +91 -42
  140. package/src/mcp/http.test.ts +72 -0
  141. package/src/mcp/http.ts +118 -0
  142. package/src/mcp/server.test.ts +20 -38
  143. package/src/mcp/server.ts +60 -54
  144. package/src/tools/contract.ts +256 -3
  145. package/src/tools/github.checkout_branch.ts +1 -1
  146. package/src/tools/github.comment_triage_summary.ts +1 -1
  147. package/src/tools/github.create_issue.test.ts +6 -6
  148. package/src/tools/github.create_issue.ts +1 -1
  149. package/src/tools/github.create_pull_request.test.ts +4 -4
  150. package/src/tools/github.create_pull_request.ts +1 -1
  151. package/src/tools/github.get_merge_conflict_files.ts +10 -20
  152. package/src/tools/github.get_pr.ts +1 -1
  153. package/src/tools/github.get_pr_commits.ts +27 -0
  154. package/src/tools/github.get_pr_diff.ts +1 -1
  155. package/src/tools/github.get_pr_reviews.test.ts +110 -0
  156. package/src/tools/github.get_pr_reviews.ts +27 -0
  157. package/src/tools/github.issue_comment.ts +1 -1
  158. package/src/tools/github.issue_update.ts +1 -1
  159. package/src/tools/github.issue_view.ts +1 -1
  160. package/src/tools/github.search_open_prs.ts +1 -1
  161. package/src/tools/jules/cancel-session.ts +1 -1
  162. package/src/tools/jules/create-session.test.ts +11 -11
  163. package/src/tools/jules/create-session.ts +1 -2
  164. package/src/tools/jules/get-messages.test.ts +3 -3
  165. package/src/tools/jules/get-messages.ts +1 -1
  166. package/src/tools/jules/get-pr.test.ts +3 -3
  167. package/src/tools/jules/get-pr.ts +1 -2
  168. package/src/tools/jules/get-session.ts +1 -2
  169. package/src/tools/jules/list-sessions.ts +1 -1
  170. package/src/tools/jules/send-message.test.ts +5 -5
  171. package/src/tools/jules/send-message.ts +1 -1
  172. package/src/tools/jules/shared.ts +35 -0
  173. package/src/tools/jules/trigger-feedback.test.ts +5 -5
  174. package/src/tools/jules/trigger-feedback.ts +1 -1
  175. package/src/tools/repo.get_changed_files.ts +2 -16
  176. package/src/tools/repo.get_command_schema.ts +2 -2
  177. package/src/tools/repo.get_workflow_runs.ts +36 -0
  178. package/src/tools/repo.logs.ts +5 -1
  179. package/src/tools/repo.read_agent_context.test.ts +1 -1
  180. package/src/tools/repo.read_agent_context.ts +35 -1
  181. package/src/tools/repo.read_ci_logs.ts +5 -1
  182. package/src/tools/repo.run_tests.ts +1 -1
  183. package/.mcp/schemas/agent.search_ddgs.json +0 -31
  184. package/.mcp/schemas/agent_search_ddgs.json +0 -31
  185. package/.mcp/schemas/repo.get_route_map.json +0 -23
  186. package/.mcp/schemas/repo.run_lighthouse.json +0 -29
  187. package/.mcp/schemas/repo.run_playwright.json +0 -36
  188. package/.mcp/schemas/repo_get_route_map.json +0 -23
  189. package/.mcp/schemas/repo_run_lighthouse.json +0 -29
  190. package/.mcp/schemas/repo_run_playwright.json +0 -36
  191. package/dist/lib/td-cli.js +0 -54
  192. package/dist/tools/ddgs.search.js +0 -50
  193. package/dist/tools/ddgs.search.test.js +0 -67
  194. package/dist/tools/ddgs_search.py +0 -23
  195. package/dist/tools/repo.get_route_map.js +0 -37
  196. package/dist/tools/repo.run_lighthouse.js +0 -37
  197. package/dist/tools/repo.run_playwright.js +0 -34
  198. package/scripts/export-mcp-schema.ts +0 -7
  199. package/src/lib/td-cli.ts +0 -73
  200. package/src/tools/ddgs.search.test.ts +0 -92
  201. package/src/tools/ddgs.search.ts +0 -58
  202. package/src/tools/ddgs_search.py +0 -23
  203. package/src/tools/repo.get_route_map.ts +0 -41
  204. package/src/tools/repo.run_lighthouse.ts +0 -40
  205. package/src/tools/repo.run_playwright.ts +0 -42
@@ -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
+ }
@@ -3,7 +3,7 @@ import { createSuccessResult, createErrorResult } from "./result.js";
3
3
 
4
4
  describe("result utils", () => {
5
5
  describe("createSuccessResult", () => {
6
- it("should format string data directly without JSON stringifying", () => {
6
+ it("should format string data directly without JSON stringifying and provide structuredContent", () => {
7
7
  const result = createSuccessResult("simple string message");
8
8
  expect(result).toEqual({
9
9
  content: [
@@ -12,11 +12,12 @@ describe("result utils", () => {
12
12
  text: "simple string message",
13
13
  },
14
14
  ],
15
+ structuredContent: { result: "simple string message" },
15
16
  });
16
17
  expect(result.isError).toBeUndefined();
17
18
  });
18
19
 
19
- it("should format object data as pretty-printed JSON string", () => {
20
+ it("should format object data as pretty-printed JSON string and provide structuredContent", () => {
20
21
  const objData = { foo: "bar", count: 42 };
21
22
  const result = createSuccessResult(objData);
22
23
  expect(result).toEqual({
@@ -26,6 +27,7 @@ describe("result utils", () => {
26
27
  text: JSON.stringify(objData, null, 2),
27
28
  },
28
29
  ],
30
+ structuredContent: objData,
29
31
  });
30
32
  expect(result.isError).toBeUndefined();
31
33
  });
package/src/lib/result.ts CHANGED
@@ -5,6 +5,11 @@ export interface ToolResult extends CallToolResult {
5
5
  }
6
6
 
7
7
  export function createSuccessResult(data: any): ToolResult {
8
+ const structuredContent =
9
+ typeof data === "object" && data !== null
10
+ ? data
11
+ : { result: data };
12
+
8
13
  return {
9
14
  content: [
10
15
  {
@@ -12,6 +17,7 @@ export function createSuccessResult(data: any): ToolResult {
12
17
  text: typeof data === "string" ? data : JSON.stringify(data, null, 2),
13
18
  },
14
19
  ],
20
+ structuredContent,
15
21
  };
16
22
  }
17
23
 
package/src/lib/shell.ts CHANGED
@@ -6,7 +6,7 @@ import fs from "fs";
6
6
  export const ALLOWED_COMMANDS = {
7
7
  git: "git",
8
8
  gh: "gh",
9
- "td-cli": "td-cli",
9
+ td: "td",
10
10
  pnpm: "pnpm",
11
11
  ls: "ls",
12
12
  rm: "rm",
@@ -39,15 +39,15 @@ export async function runCommand(
39
39
  const start = Date.now();
40
40
  const timeout = options.timeout || 60000;
41
41
 
42
- // Resolve path for 'gh' or 'td-cli' if specified
42
+ // Resolve path for 'gh' or 'td' if specified
43
43
  let finalCmd = ALLOWED_COMMANDS[safeCmd] as string;
44
44
  if (safeCmd === "gh") {
45
45
  finalCmd = config.ghPath;
46
- } else if (safeCmd === "td-cli") {
46
+ } else if (safeCmd === "td") {
47
47
  // If we have a repo-relative .venv, prioritize it
48
- const venvBin = path.join(config.repoPath, ".venv", "bin", "td-cli");
49
- const localVenvBin = path.join(process.cwd(), ".venv", "bin", "td-cli");
50
- const parentVenvBin = path.join(config.repoPath, "..", ".venv", "bin", "td-cli");
48
+ const venvBin = path.join(config.repoPath, ".venv", "bin", "td");
49
+ const localVenvBin = path.join(process.cwd(), ".venv", "bin", "td");
50
+ const parentVenvBin = path.join(config.repoPath, "..", ".venv", "bin", "td");
51
51
 
52
52
  // In standalone mode, config.repoPath is the boomtick-pkg root.
53
53
  // We check common venv locations to ensure we call the isolated binary.
@@ -1,4 +1,4 @@
1
- import { Tool, Prompt, Resource } from "@modelcontextprotocol/sdk/types.js";
1
+ import { Tool, Prompt, Resource, ResourceTemplate } from "@modelcontextprotocol/sdk/types.js";
2
2
  import {
3
3
  HealthCheckInputJsonSchema,
4
4
  SearchPRsInputJsonSchema,
@@ -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[] = [
@@ -62,16 +61,28 @@ export const MCP_PROMPTS: Prompt[] = [
62
61
 
63
62
  export const MCP_RESOURCES: Resource[] = [
64
63
  {
65
- uri: "repo://package-json",
66
- name: "package.json",
64
+ uri: "mcp://workspace/agent-contract",
65
+ name: "Agent Contract",
66
+ mimeType: "text/markdown",
67
+ description: "The agent contract specification file (.agents/AGENT_CONTRACT.md).",
68
+ },
69
+ {
70
+ uri: "repo://agent-contract",
71
+ name: "Agent Contract (Legacy URI)",
72
+ mimeType: "text/markdown",
73
+ description: "The agent contract specification file (.agents/AGENT_CONTRACT.md).",
74
+ },
75
+ {
76
+ uri: "mcp://git/changed-files",
77
+ name: "Git Changed Files",
67
78
  mimeType: "application/json",
68
- description: "The root package.json file of the repository.",
79
+ description: "List of changed files in the repository.",
69
80
  },
70
81
  {
71
- uri: "repo://routes",
72
- name: "Route Map",
82
+ uri: "repo://package-json",
83
+ name: "package.json",
73
84
  mimeType: "application/json",
74
- description: "The mapping of application routes to content files.",
85
+ description: "The root package.json file of the repository.",
75
86
  },
76
87
  {
77
88
  uri: "repo://design-tokens",
@@ -79,201 +90,239 @@ export const MCP_RESOURCES: Resource[] = [
79
90
  mimeType: "application/json",
80
91
  description: "The design tokens used in the repository.",
81
92
  },
93
+ ];
94
+
95
+ export const MCP_RESOURCE_TEMPLATES: ResourceTemplate[] = [
82
96
  {
83
- uri: "repo://repair-report/{branch}",
84
- name: "Repair Report",
85
- mimeType: "application/json",
86
- description: "The validation report for a specific repair branch.",
97
+ uriTemplate: "mcp://git/diff/{prNumber}",
98
+ name: "Git PR Diff",
99
+ mimeType: "text/plain",
100
+ description: "Diff and changed files for a pull request.",
87
101
  },
88
102
  {
89
- uri: "repo://lighthouse/{branch}",
90
- name: "Lighthouse Report",
103
+ uriTemplate: "mcp://ci/logs/{prNumber}",
104
+ name: "CI Logs",
91
105
  mimeType: "application/json",
92
- description: "Lighthouse CI report for a specific branch.",
106
+ description: "CI logs for a pull request.",
93
107
  },
94
108
  {
95
- uri: "repo://playwright/{branch}",
96
- name: "Playwright Report",
109
+ uriTemplate: "repo://repair-report/{branch}",
110
+ name: "Repair Report",
97
111
  mimeType: "application/json",
98
- description: "Playwright test report for a specific branch.",
112
+ description: "The validation report for a specific repair branch.",
99
113
  },
100
114
  ];
101
115
 
116
+ const DEFAULT_OUTPUT_SCHEMA = {
117
+ type: "object",
118
+ properties: {},
119
+ additionalProperties: true,
120
+ } as const;
121
+
102
122
  const BASE_MCP_TOOLS: Tool[] = [
103
123
  {
104
124
  name: "boomtick.health",
105
125
  description: "Check the health and configuration of the MCP server.",
106
126
  inputSchema: HealthCheckInputJsonSchema as any,
127
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
107
128
  },
108
129
  {
109
130
  name: "github.search_open_prs",
110
131
  description: "Search for open pull requests in the repository.",
111
132
  inputSchema: SearchPRsInputJsonSchema as any,
133
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
112
134
  },
113
135
  {
114
136
  name: "repo.read_agent_context",
115
137
  description: "Read the full repository index including file tree, CLI schema, and MCP tools.",
116
138
  inputSchema: ReadAgentContextSchema as any,
139
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
117
140
  },
118
141
  {
119
142
  name: "github.get_pr_diff",
120
143
  description: "Get the diff and changed files for a pull request.",
121
144
  inputSchema: GetPrDiffInputJsonSchema as any,
145
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
122
146
  },
123
147
  {
124
148
  name: "github.checkout_branch",
125
149
  description: "Checkout a specific branch in the repository or worktree.",
126
150
  inputSchema: CheckoutBranchInputJsonSchema as any,
151
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
127
152
  },
128
153
  {
129
154
  name: "github.get_merge_conflict_files",
130
155
  description: "Detect files that conflict when a PR is merged with the base branch.",
131
156
  inputSchema: GetMergeConflictFilesInputJsonSchema as any,
157
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
132
158
  },
133
159
  {
134
160
  name: "repo.get_changed_files",
135
161
  description: "Get the list of changed files between two refs.",
136
162
  inputSchema: GetChangedFilesInputJsonSchema as any,
163
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
137
164
  },
138
165
  {
139
166
  name: "repo.get_command_schema",
140
- description: "Retrieve a lightweight, targeted schema for a specific td-cli subcommand. Use this instead of reading the entire cli-schema.json to save tokens.",
167
+ description: "Retrieve a lightweight, targeted schema for a specific td subcommand. Use this instead of reading the entire cli-schema.json to save tokens.",
141
168
  inputSchema: GetCommandSchemaInputJsonSchema as any,
169
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
142
170
  },
143
171
  {
144
172
  name: "repo.get_package_scripts",
145
173
  description: "Get the scripts defined in package.json.",
146
174
  inputSchema: GetPackageScriptsInputJsonSchema as any,
147
- },
148
- {
149
- name: "repo.get_route_map",
150
- description: "Get the mapping of routes to content files.",
151
- inputSchema: GetRouteMapInputJsonSchema as any,
175
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
152
176
  },
153
177
  {
154
178
  name: "repo.read_ci_logs",
155
179
  description: "Read CI logs for a given pull request.",
156
180
  inputSchema: ReadCiLogsInputJsonSchema as any,
181
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
157
182
  },
158
183
  {
159
184
  name: "repo.logs",
160
185
  description: "Stream or grep combined CI logs for all jobs in a pull request.",
161
186
  inputSchema: RepoLogsInputJsonSchema as any,
187
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
162
188
  },
163
189
  {
164
190
  name: "repo.create_branch",
165
191
  description: "Creates a new clean git branch from a target base branch.",
166
192
  inputSchema: CreateBranchInputJsonSchema as any,
193
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
167
194
  },
168
195
  {
169
196
  name: "repo.create_repair_branch",
170
197
  description: "Creates a fresh worktree and a new repair branch for an existing PR. This isolates the repair work from the main codebase.",
171
198
  inputSchema: CreateRepairBranchInputJsonSchema as any,
199
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
172
200
  },
173
201
  {
174
202
  name: "repo.run_tests",
175
203
  description: "Run repository tests and checks.",
176
204
  inputSchema: RunTestsInputJsonSchema as any,
177
- },
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,
205
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
187
206
  },
188
207
  {
189
208
  name: "repo.commit_patch",
190
209
  description: "Commit verified repair changes in a worktree.",
191
210
  inputSchema: CommitPatchInputJsonSchema as any,
211
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
192
212
  },
193
213
  {
194
214
  name: "github.open_replacement_pr",
195
215
  description: "Open a new PR that replaces or repairs the original PR.",
196
216
  inputSchema: OpenReplacementPrInputJsonSchema as any,
217
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
197
218
  },
198
219
  {
199
220
  name: "github.create_pull_request",
200
221
  description: "Creates a pull request on GitHub using the MCP server's integrated credentials. Bypasses terminal CLI constraints.",
201
222
  inputSchema: CreatePullRequestInputJsonSchema as any,
223
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
202
224
  },
203
225
  {
204
226
  name: "github.comment_triage_summary",
205
227
  description: "Comment on the original PR with a diagnosis and replacement link.",
206
228
  inputSchema: CommentTriageSummaryInputJsonSchema as any,
229
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
207
230
  },
208
231
  {
209
232
  name: "github.get_pr",
210
233
  description: "Get details of a GitHub PR including title, state, and URLs.",
211
234
  inputSchema: GetPrInputJsonSchema as any,
235
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
236
+ },
237
+ {
238
+ name: "github.get_pr_reviews",
239
+ description: "Get top-level reviews, verdicts, states, and bodies for a GitHub PR.",
240
+ inputSchema: GetPrReviewsInputJsonSchema as any,
241
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
242
+ },
243
+ {
244
+ name: "github.get_pr_commits",
245
+ description: "List commits and commit metadata (SHAs, authors, messages) for a GitHub PR.",
246
+ inputSchema: GetPrCommitsInputJsonSchema as any,
247
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
248
+ },
249
+ {
250
+ name: "repo.get_workflow_runs",
251
+ description: "List recent GitHub Actions workflow runs for the repository.",
252
+ inputSchema: GetWorkflowRunsInputJsonSchema as any,
253
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
212
254
  },
213
255
  {
214
256
  name: "github.issue_view",
215
257
  description: "View details of a GitHub issue including title, body, and state.",
216
258
  inputSchema: IssueViewInputJsonSchema as any,
259
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
217
260
  },
218
261
  {
219
262
  name: "github.issue_update",
220
263
  description: "Update a GitHub issue's body, labels, and/or state.",
221
264
  inputSchema: IssueUpdateInputJsonSchema as any,
265
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
222
266
  },
223
267
  {
224
268
  name: "github.issue_comment",
225
269
  description: "Add a new comment to a GitHub issue.",
226
270
  inputSchema: IssueCommentInputJsonSchema as any,
271
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
227
272
  },
228
273
  {
229
274
  name: "github.create_issue",
230
275
  description: "Create a new GitHub issue.",
231
276
  inputSchema: CreateIssueInputJsonSchema as any,
277
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
232
278
  },
233
279
  {
234
280
  name: "jules.create_session",
235
281
  description: "Create a Jules session that performs work externally and may generate a GitHub pull request.",
236
282
  inputSchema: CreateJulesSessionInputJsonSchema as any,
283
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
237
284
  },
238
285
  {
239
286
  name: "jules.get_session",
240
287
  description: "Get the status and details of a Jules session.",
241
288
  inputSchema: JulesSessionIdInputJsonSchema as any,
289
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
242
290
  },
243
291
  {
244
292
  name: "jules.send_message",
245
293
  description: "Send a message to an active Jules session.",
246
294
  inputSchema: JulesSendMessageInputJsonSchema as any,
295
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
247
296
  },
248
297
  {
249
298
  name: "jules.get_messages",
250
299
  description: "Get the message history of a Jules session.",
251
300
  inputSchema: JulesSessionIdInputJsonSchema as any,
301
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
252
302
  },
253
303
  {
254
304
  name: "jules.list_sessions",
255
305
  description: "List all Jules sessions.",
256
306
  inputSchema: JulesListSessionsInputJsonSchema as any,
307
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
257
308
  },
258
309
  {
259
310
  name: "jules.cancel_session",
260
311
  description: "Cancel an ongoing Jules session.",
261
312
  inputSchema: JulesSessionIdInputJsonSchema as any,
313
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
262
314
  },
263
315
  {
264
316
  name: "jules.get_pr",
265
317
  description: "Get the generated pull request url associated with an active Jules agent session.",
266
318
  inputSchema: JulesSessionIdInputJsonSchema as any,
319
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
267
320
  },
268
321
  {
269
322
  name: "jules.trigger_feedback",
270
323
  description: "Automatically collect CI status/logs for the PR associated with a Jules session and send them back as feedback.",
271
324
  inputSchema: JulesSessionIdInputJsonSchema as any,
272
- },
273
- {
274
- name: "agent.search_ddgs",
275
- description: "Search the web using DuckDuckGo (via ddgs python library).",
276
- inputSchema: SearchDdgsInputJsonSchema as any,
325
+ outputSchema: DEFAULT_OUTPUT_SCHEMA as any,
277
326
  },
278
327
  ];
279
328
 
@@ -0,0 +1,72 @@
1
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
2
+ import http from "node:http";
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { createMcpHttpServer } from "./http.js";
5
+
6
+ describe("createMcpHttpServer", () => {
7
+ let mcpServer: Server;
8
+ let httpServerObj: ReturnType<typeof createMcpHttpServer>;
9
+ let port: number;
10
+
11
+ beforeEach(async () => {
12
+ mcpServer = new Server({ name: "test-server", version: "1.0.0" }, { capabilities: {} });
13
+ });
14
+
15
+ afterEach(() => {
16
+ if (httpServerObj?.server) {
17
+ httpServerObj.server.close();
18
+ }
19
+ });
20
+
21
+ it("should respond with OAuth protected resource metadata on /.well-known/oauth-protected-resource", async () => {
22
+ httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
23
+ port = await httpServerObj.listen(0);
24
+
25
+ const res = await fetch(`http://localhost:${port}/.well-known/oauth-protected-resource`);
26
+ expect(res.status).toBe(200);
27
+ const body = await res.json();
28
+ expect(body.resource).toBe("mcp://boomtick-mcp");
29
+ expect(body.authorization_servers).toBeDefined();
30
+ expect(body.scopes_supported).toEqual(["mcp:read", "mcp:write"]);
31
+ });
32
+
33
+ it("should reject requests when Bearer token is missing or invalid", async () => {
34
+ httpServerObj = createMcpHttpServer({ port: 0, bearerToken: "secret123", mcpServer });
35
+ port = await httpServerObj.listen(0);
36
+
37
+ // Request without token
38
+ const res1 = await fetch(`http://localhost:${port}/sse`);
39
+ expect(res1.status).toBe(401);
40
+ expect(res1.headers.get("www-authenticate")).toContain('Bearer realm="boomtick-mcp"');
41
+
42
+ // Request with wrong token
43
+ const res2 = await fetch(`http://localhost:${port}/sse`, {
44
+ headers: { Authorization: "Bearer wrong" },
45
+ });
46
+ expect(res2.status).toBe(401);
47
+
48
+ // Request with valid token
49
+ const res3 = await fetch(`http://localhost:${port}/.well-known/oauth-protected-resource`, {
50
+ headers: { Authorization: "Bearer secret123" },
51
+ });
52
+ expect(res3.status).toBe(200);
53
+ });
54
+
55
+ it("should handle 404 for unknown endpoints", async () => {
56
+ httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
57
+ port = await httpServerObj.listen(0);
58
+
59
+ const res = await fetch(`http://localhost:${port}/unknown-path`);
60
+ expect(res.status).toBe(404);
61
+ });
62
+
63
+ it("should return 500 if bearer token is not configured on server for protected endpoints", async () => {
64
+ httpServerObj = createMcpHttpServer({ port: 0, mcpServer });
65
+ port = await httpServerObj.listen(0);
66
+
67
+ const res = await fetch(`http://localhost:${port}/sse`);
68
+ expect(res.status).toBe(500);
69
+ const body = await res.json();
70
+ expect(body.error).toContain("Server misconfiguration");
71
+ });
72
+ });