@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
@@ -15,12 +15,12 @@ describe("config", () => {
15
15
  process.env = originalEnv;
16
16
  });
17
17
  describe("initializeConfig", () => {
18
- it("should load dynamic config from td-cli config view using active cwd", async () => {
18
+ it("should load dynamic config from td config view using active cwd", async () => {
19
19
  const mockConfig = { github_repo: "owner/repo", base_branch: "origin/main", vite_base_path: "/app/" };
20
20
  vi.mocked(execSync).mockReturnValue(JSON.stringify(mockConfig));
21
21
  const { initializeConfig } = await import("./config.js");
22
22
  const result = initializeConfig();
23
- expect(execSync).toHaveBeenCalledWith("td-cli config view", expect.objectContaining({
23
+ expect(execSync).toHaveBeenCalledWith("td config view", expect.objectContaining({
24
24
  cwd: process.cwd()
25
25
  }));
26
26
  expect(result).toEqual(mockConfig);
@@ -93,7 +93,7 @@ describe("config", () => {
93
93
  delete process.env.GITHUB_REPOSITORY;
94
94
  delete process.env.GH_REPO;
95
95
  vi.mocked(execSync).mockImplementation(() => {
96
- throw new Error("td-cli failed");
96
+ throw new Error("td failed");
97
97
  });
98
98
  process.env.NODE_ENV = "production";
99
99
  process.env.CI = "false";
package/dist/lib/git.js CHANGED
@@ -59,3 +59,47 @@ export async function removeWorktree(worktreePath) {
59
59
  await runCommand("git", ["worktree", "remove", "--force", worktreePath]);
60
60
  await runCommand("git", ["worktree", "prune"]);
61
61
  }
62
+ export async function getChangedFiles(base = "main", head = "HEAD", cwd) {
63
+ const result = await runCommand("git", ["diff", "--name-status", "-z", `${base}...${head}`], { cwd });
64
+ if (result.exitCode !== 0) {
65
+ throw new Error(`Failed to get changed files: ${result.stderr}`);
66
+ }
67
+ const tokens = result.stdout.split("\0").filter((t) => t.length > 0);
68
+ const files = [];
69
+ let i = 0;
70
+ while (i < tokens.length) {
71
+ const status = tokens[i];
72
+ if (status.startsWith("R") || status.startsWith("C")) {
73
+ const oldPath = tokens[i + 1];
74
+ const newPath = tokens[i + 2];
75
+ if (oldPath && newPath) {
76
+ files.push({ status, path: newPath, oldPath });
77
+ }
78
+ i += 3;
79
+ }
80
+ else {
81
+ const filePath = tokens[i + 1];
82
+ if (filePath) {
83
+ files.push({ status, path: filePath });
84
+ }
85
+ i += 2;
86
+ }
87
+ }
88
+ return files;
89
+ }
90
+ export async function getMergeConflictFiles(baseBranch = "main", headRef = "HEAD", cwd) {
91
+ const result = await runCommand("git", ["merge-tree", "--write-tree", "--name-only", baseBranch, headRef], { cwd });
92
+ // git merge-tree returns exit code 1 if conflicts are present, 0 if clean merge
93
+ if (result.exitCode !== 0 && result.exitCode !== 1) {
94
+ throw new Error(`Git merge-tree failed: ${result.stderr}`);
95
+ }
96
+ const lines = result.stdout
97
+ .split("\n")
98
+ .map((l) => l.trim())
99
+ .filter((l) => l.length > 0);
100
+ // First line is tree hash if successful or conflict output
101
+ // When conflicts exist, git merge-tree --write-tree --name-only outputs conflicted filenames
102
+ // Skip tree hash line if present (40 hex chars)
103
+ const files = lines.filter((line) => !/^[0-9a-f]{40}$/i.test(line));
104
+ return files;
105
+ }
@@ -0,0 +1,82 @@
1
+ import { describe, expect, it, vi, beforeEach } from "vitest";
2
+ import { getChangedFiles, getMergeConflictFiles } from "./git.js";
3
+ import * as shell from "./shell.js";
4
+ vi.mock("./shell.js", () => ({
5
+ runCommand: vi.fn(),
6
+ }));
7
+ describe("Git Helpers", () => {
8
+ beforeEach(() => {
9
+ vi.resetAllMocks();
10
+ });
11
+ describe("getChangedFiles", () => {
12
+ it("should parse null-delimited git diff output correctly", async () => {
13
+ // Mock git diff output with null character delimiters:
14
+ // Status M -> file1.txt
15
+ // Status A -> file2.txt
16
+ // Status R100 -> old.txt -> new.txt
17
+ const mockStdout = "M\0file1.txt\0A\0file2.txt\0R100\0old.txt\0new.txt\0";
18
+ vi.mocked(shell.runCommand).mockResolvedValueOnce({
19
+ exitCode: 0,
20
+ stdout: mockStdout,
21
+ stderr: "",
22
+ durationMs: 10,
23
+ command: "git diff --name-status -z main...HEAD",
24
+ });
25
+ const result = await getChangedFiles("main", "HEAD");
26
+ expect(shell.runCommand).toHaveBeenCalledWith("git", ["diff", "--name-status", "-z", "main...HEAD"], { cwd: undefined });
27
+ expect(result).toEqual([
28
+ { status: "M", path: "file1.txt" },
29
+ { status: "A", path: "file2.txt" },
30
+ { status: "R100", path: "new.txt", oldPath: "old.txt" },
31
+ ]);
32
+ });
33
+ it("should throw error if git diff fails", async () => {
34
+ vi.mocked(shell.runCommand).mockResolvedValueOnce({
35
+ exitCode: 1,
36
+ stdout: "",
37
+ stderr: "fatal: bad revision",
38
+ durationMs: 10,
39
+ command: "git diff --name-status -z invalid...HEAD",
40
+ });
41
+ await expect(getChangedFiles("invalid", "HEAD")).rejects.toThrow("Failed to get changed files: fatal: bad revision");
42
+ });
43
+ });
44
+ describe("getMergeConflictFiles", () => {
45
+ it("should parse git merge-tree output when conflicts exist", async () => {
46
+ // Output with 40-char tree hash followed by conflicting filenames
47
+ const mockStdout = "5d71843ed2e051b2be91361db7bea0fc4ee1e5a0\nfile1.txt\nsrc/file2.ts\n";
48
+ vi.mocked(shell.runCommand).mockResolvedValueOnce({
49
+ exitCode: 1, // merge-tree exits 1 on conflicts
50
+ stdout: mockStdout,
51
+ stderr: "",
52
+ durationMs: 10,
53
+ command: "git merge-tree --write-tree --name-only main feature",
54
+ });
55
+ const conflicts = await getMergeConflictFiles("main", "feature");
56
+ expect(shell.runCommand).toHaveBeenCalledWith("git", ["merge-tree", "--write-tree", "--name-only", "main", "feature"], { cwd: undefined });
57
+ expect(conflicts).toEqual(["file1.txt", "src/file2.ts"]);
58
+ });
59
+ it("should return empty array when no conflicts exist", async () => {
60
+ const mockStdout = "5d71843ed2e051b2be91361db7bea0fc4ee1e5a0\n";
61
+ vi.mocked(shell.runCommand).mockResolvedValueOnce({
62
+ exitCode: 0,
63
+ stdout: mockStdout,
64
+ stderr: "",
65
+ durationMs: 10,
66
+ command: "git merge-tree --write-tree --name-only main feature",
67
+ });
68
+ const conflicts = await getMergeConflictFiles("main", "feature");
69
+ expect(conflicts).toEqual([]);
70
+ });
71
+ it("should throw error if git merge-tree exits with unexpected exit code", async () => {
72
+ vi.mocked(shell.runCommand).mockResolvedValueOnce({
73
+ exitCode: 128,
74
+ stdout: "",
75
+ stderr: "fatal: not a valid object name",
76
+ durationMs: 10,
77
+ command: "git merge-tree --write-tree --name-only invalid feature",
78
+ });
79
+ await expect(getMergeConflictFiles("invalid", "feature")).rejects.toThrow("Git merge-tree failed: fatal: not a valid object name");
80
+ });
81
+ });
82
+ });
@@ -1,4 +1,7 @@
1
1
  export function createSuccessResult(data) {
2
+ const structuredContent = typeof data === "object" && data !== null
3
+ ? data
4
+ : { result: data };
2
5
  return {
3
6
  content: [
4
7
  {
@@ -6,6 +9,7 @@ export function createSuccessResult(data) {
6
9
  text: typeof data === "string" ? data : JSON.stringify(data, null, 2),
7
10
  },
8
11
  ],
12
+ structuredContent,
9
13
  };
10
14
  }
11
15
  export function createErrorResult(message) {
@@ -2,7 +2,7 @@ import { describe, it, expect } from "vitest";
2
2
  import { createSuccessResult, createErrorResult } from "./result.js";
3
3
  describe("result utils", () => {
4
4
  describe("createSuccessResult", () => {
5
- it("should format string data directly without JSON stringifying", () => {
5
+ it("should format string data directly without JSON stringifying and provide structuredContent", () => {
6
6
  const result = createSuccessResult("simple string message");
7
7
  expect(result).toEqual({
8
8
  content: [
@@ -11,10 +11,11 @@ describe("result utils", () => {
11
11
  text: "simple string message",
12
12
  },
13
13
  ],
14
+ structuredContent: { result: "simple string message" },
14
15
  });
15
16
  expect(result.isError).toBeUndefined();
16
17
  });
17
- it("should format object data as pretty-printed JSON string", () => {
18
+ it("should format object data as pretty-printed JSON string and provide structuredContent", () => {
18
19
  const objData = { foo: "bar", count: 42 };
19
20
  const result = createSuccessResult(objData);
20
21
  expect(result).toEqual({
@@ -24,6 +25,7 @@ describe("result utils", () => {
24
25
  text: JSON.stringify(objData, null, 2),
25
26
  },
26
27
  ],
28
+ structuredContent: objData,
27
29
  });
28
30
  expect(result.isError).toBeUndefined();
29
31
  });
package/dist/lib/shell.js CHANGED
@@ -5,7 +5,7 @@ import fs from "fs";
5
5
  export const ALLOWED_COMMANDS = {
6
6
  git: "git",
7
7
  gh: "gh",
8
- "td-cli": "td-cli",
8
+ td: "td",
9
9
  pnpm: "pnpm",
10
10
  ls: "ls",
11
11
  rm: "rm",
@@ -20,16 +20,16 @@ export async function runCommand(cmd, args, options = {}) {
20
20
  const safeCmd = cmd;
21
21
  const start = Date.now();
22
22
  const timeout = options.timeout || 60000;
23
- // Resolve path for 'gh' or 'td-cli' if specified
23
+ // Resolve path for 'gh' or 'td' if specified
24
24
  let finalCmd = ALLOWED_COMMANDS[safeCmd];
25
25
  if (safeCmd === "gh") {
26
26
  finalCmd = config.ghPath;
27
27
  }
28
- else if (safeCmd === "td-cli") {
28
+ else if (safeCmd === "td") {
29
29
  // If we have a repo-relative .venv, prioritize it
30
- const venvBin = path.join(config.repoPath, ".venv", "bin", "td-cli");
31
- const localVenvBin = path.join(process.cwd(), ".venv", "bin", "td-cli");
32
- const parentVenvBin = path.join(config.repoPath, "..", ".venv", "bin", "td-cli");
30
+ const venvBin = path.join(config.repoPath, ".venv", "bin", "td");
31
+ const localVenvBin = path.join(process.cwd(), ".venv", "bin", "td");
32
+ const parentVenvBin = path.join(config.repoPath, "..", ".venv", "bin", "td");
33
33
  // In standalone mode, config.repoPath is the boomtick-pkg root.
34
34
  // We check common venv locations to ensure we call the isolated binary.
35
35
  if (fs.existsSync(venvBin)) {
@@ -1,4 +1,4 @@
1
- import { HealthCheckInputJsonSchema, SearchPRsInputJsonSchema, GetPrDiffInputJsonSchema, CheckoutBranchInputJsonSchema, GetMergeConflictFilesInputJsonSchema, GetChangedFilesInputJsonSchema, GetPackageScriptsInputJsonSchema, GetRouteMapInputJsonSchema, ReadCiLogsInputJsonSchema, RepoLogsInputJsonSchema, CreateBranchInputJsonSchema, CreateRepairBranchInputJsonSchema, RunTestsInputJsonSchema, RunLighthouseInputJsonSchema, RunPlaywrightInputJsonSchema, CommitPatchInputJsonSchema, OpenReplacementPrInputJsonSchema, CreatePullRequestInputJsonSchema, CommentTriageSummaryInputJsonSchema, GetPrInputJsonSchema, IssueViewInputJsonSchema, IssueUpdateInputJsonSchema, IssueCommentInputJsonSchema, CreateIssueInputJsonSchema, GetCommandSchemaInputJsonSchema, ReadAgentContextInputJsonSchema as ReadAgentContextSchema, CreateJulesSessionInputJsonSchema, JulesSessionIdInputJsonSchema, JulesSendMessageInputJsonSchema, JulesListSessionsInputJsonSchema, SearchDdgsInputJsonSchema, } from "../tools/contract.js";
1
+ import { HealthCheckInputJsonSchema, SearchPRsInputJsonSchema, GetPrDiffInputJsonSchema, CheckoutBranchInputJsonSchema, GetMergeConflictFilesInputJsonSchema, GetChangedFilesInputJsonSchema, GetPackageScriptsInputJsonSchema, ReadCiLogsInputJsonSchema, RepoLogsInputJsonSchema, CreateBranchInputJsonSchema, CreateRepairBranchInputJsonSchema, RunTestsInputJsonSchema, CommitPatchInputJsonSchema, OpenReplacementPrInputJsonSchema, CreatePullRequestInputJsonSchema, CommentTriageSummaryInputJsonSchema, GetPrInputJsonSchema, IssueViewInputJsonSchema, IssueUpdateInputJsonSchema, IssueCommentInputJsonSchema, CreateIssueInputJsonSchema, GetCommandSchemaInputJsonSchema, ReadAgentContextInputJsonSchema as ReadAgentContextSchema, CreateJulesSessionInputJsonSchema, JulesSessionIdInputJsonSchema, JulesSendMessageInputJsonSchema, JulesListSessionsInputJsonSchema, GetPrReviewsInputJsonSchema, GetPrCommitsInputJsonSchema, GetWorkflowRunsInputJsonSchema, } from "../tools/contract.js";
2
2
  export const MCP_PROMPTS = [
3
3
  {
4
4
  name: "conflict-scout",
@@ -27,16 +27,28 @@ export const MCP_PROMPTS = [
27
27
  ];
28
28
  export const MCP_RESOURCES = [
29
29
  {
30
- uri: "repo://package-json",
31
- name: "package.json",
30
+ uri: "mcp://workspace/agent-contract",
31
+ name: "Agent Contract",
32
+ mimeType: "text/markdown",
33
+ description: "The agent contract specification file (.agents/AGENT_CONTRACT.md).",
34
+ },
35
+ {
36
+ uri: "repo://agent-contract",
37
+ name: "Agent Contract (Legacy URI)",
38
+ mimeType: "text/markdown",
39
+ description: "The agent contract specification file (.agents/AGENT_CONTRACT.md).",
40
+ },
41
+ {
42
+ uri: "mcp://git/changed-files",
43
+ name: "Git Changed Files",
32
44
  mimeType: "application/json",
33
- description: "The root package.json file of the repository.",
45
+ description: "List of changed files in the repository.",
34
46
  },
35
47
  {
36
- uri: "repo://routes",
37
- name: "Route Map",
48
+ uri: "repo://package-json",
49
+ name: "package.json",
38
50
  mimeType: "application/json",
39
- description: "The mapping of application routes to content files.",
51
+ description: "The root package.json file of the repository.",
40
52
  },
41
53
  {
42
54
  uri: "repo://design-tokens",
@@ -44,200 +56,236 @@ export const MCP_RESOURCES = [
44
56
  mimeType: "application/json",
45
57
  description: "The design tokens used in the repository.",
46
58
  },
59
+ ];
60
+ export const MCP_RESOURCE_TEMPLATES = [
47
61
  {
48
- uri: "repo://repair-report/{branch}",
49
- name: "Repair Report",
50
- mimeType: "application/json",
51
- description: "The validation report for a specific repair branch.",
62
+ uriTemplate: "mcp://git/diff/{prNumber}",
63
+ name: "Git PR Diff",
64
+ mimeType: "text/plain",
65
+ description: "Diff and changed files for a pull request.",
52
66
  },
53
67
  {
54
- uri: "repo://lighthouse/{branch}",
55
- name: "Lighthouse Report",
68
+ uriTemplate: "mcp://ci/logs/{prNumber}",
69
+ name: "CI Logs",
56
70
  mimeType: "application/json",
57
- description: "Lighthouse CI report for a specific branch.",
71
+ description: "CI logs for a pull request.",
58
72
  },
59
73
  {
60
- uri: "repo://playwright/{branch}",
61
- name: "Playwright Report",
74
+ uriTemplate: "repo://repair-report/{branch}",
75
+ name: "Repair Report",
62
76
  mimeType: "application/json",
63
- description: "Playwright test report for a specific branch.",
77
+ description: "The validation report for a specific repair branch.",
64
78
  },
65
79
  ];
80
+ const DEFAULT_OUTPUT_SCHEMA = {
81
+ type: "object",
82
+ properties: {},
83
+ additionalProperties: true,
84
+ };
66
85
  const BASE_MCP_TOOLS = [
67
86
  {
68
87
  name: "boomtick.health",
69
88
  description: "Check the health and configuration of the MCP server.",
70
89
  inputSchema: HealthCheckInputJsonSchema,
90
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
71
91
  },
72
92
  {
73
93
  name: "github.search_open_prs",
74
94
  description: "Search for open pull requests in the repository.",
75
95
  inputSchema: SearchPRsInputJsonSchema,
96
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
76
97
  },
77
98
  {
78
99
  name: "repo.read_agent_context",
79
100
  description: "Read the full repository index including file tree, CLI schema, and MCP tools.",
80
101
  inputSchema: ReadAgentContextSchema,
102
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
81
103
  },
82
104
  {
83
105
  name: "github.get_pr_diff",
84
106
  description: "Get the diff and changed files for a pull request.",
85
107
  inputSchema: GetPrDiffInputJsonSchema,
108
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
86
109
  },
87
110
  {
88
111
  name: "github.checkout_branch",
89
112
  description: "Checkout a specific branch in the repository or worktree.",
90
113
  inputSchema: CheckoutBranchInputJsonSchema,
114
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
91
115
  },
92
116
  {
93
117
  name: "github.get_merge_conflict_files",
94
118
  description: "Detect files that conflict when a PR is merged with the base branch.",
95
119
  inputSchema: GetMergeConflictFilesInputJsonSchema,
120
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
96
121
  },
97
122
  {
98
123
  name: "repo.get_changed_files",
99
124
  description: "Get the list of changed files between two refs.",
100
125
  inputSchema: GetChangedFilesInputJsonSchema,
126
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
101
127
  },
102
128
  {
103
129
  name: "repo.get_command_schema",
104
- 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.",
130
+ description: "Retrieve a lightweight, targeted schema for a specific td subcommand. Use this instead of reading the entire cli-schema.json to save tokens.",
105
131
  inputSchema: GetCommandSchemaInputJsonSchema,
132
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
106
133
  },
107
134
  {
108
135
  name: "repo.get_package_scripts",
109
136
  description: "Get the scripts defined in package.json.",
110
137
  inputSchema: GetPackageScriptsInputJsonSchema,
111
- },
112
- {
113
- name: "repo.get_route_map",
114
- description: "Get the mapping of routes to content files.",
115
- inputSchema: GetRouteMapInputJsonSchema,
138
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
116
139
  },
117
140
  {
118
141
  name: "repo.read_ci_logs",
119
142
  description: "Read CI logs for a given pull request.",
120
143
  inputSchema: ReadCiLogsInputJsonSchema,
144
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
121
145
  },
122
146
  {
123
147
  name: "repo.logs",
124
148
  description: "Stream or grep combined CI logs for all jobs in a pull request.",
125
149
  inputSchema: RepoLogsInputJsonSchema,
150
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
126
151
  },
127
152
  {
128
153
  name: "repo.create_branch",
129
154
  description: "Creates a new clean git branch from a target base branch.",
130
155
  inputSchema: CreateBranchInputJsonSchema,
156
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
131
157
  },
132
158
  {
133
159
  name: "repo.create_repair_branch",
134
160
  description: "Creates a fresh worktree and a new repair branch for an existing PR. This isolates the repair work from the main codebase.",
135
161
  inputSchema: CreateRepairBranchInputJsonSchema,
162
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
136
163
  },
137
164
  {
138
165
  name: "repo.run_tests",
139
166
  description: "Run repository tests and checks.",
140
167
  inputSchema: RunTestsInputJsonSchema,
141
- },
142
- {
143
- name: "repo.run_lighthouse",
144
- description: "Run Lighthouse CI audits.",
145
- inputSchema: RunLighthouseInputJsonSchema,
146
- },
147
- {
148
- name: "repo.run_playwright",
149
- description: "Run Playwright E2E tests.",
150
- inputSchema: RunPlaywrightInputJsonSchema,
168
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
151
169
  },
152
170
  {
153
171
  name: "repo.commit_patch",
154
172
  description: "Commit verified repair changes in a worktree.",
155
173
  inputSchema: CommitPatchInputJsonSchema,
174
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
156
175
  },
157
176
  {
158
177
  name: "github.open_replacement_pr",
159
178
  description: "Open a new PR that replaces or repairs the original PR.",
160
179
  inputSchema: OpenReplacementPrInputJsonSchema,
180
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
161
181
  },
162
182
  {
163
183
  name: "github.create_pull_request",
164
184
  description: "Creates a pull request on GitHub using the MCP server's integrated credentials. Bypasses terminal CLI constraints.",
165
185
  inputSchema: CreatePullRequestInputJsonSchema,
186
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
166
187
  },
167
188
  {
168
189
  name: "github.comment_triage_summary",
169
190
  description: "Comment on the original PR with a diagnosis and replacement link.",
170
191
  inputSchema: CommentTriageSummaryInputJsonSchema,
192
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
171
193
  },
172
194
  {
173
195
  name: "github.get_pr",
174
196
  description: "Get details of a GitHub PR including title, state, and URLs.",
175
197
  inputSchema: GetPrInputJsonSchema,
198
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
199
+ },
200
+ {
201
+ name: "github.get_pr_reviews",
202
+ description: "Get top-level reviews, verdicts, states, and bodies for a GitHub PR.",
203
+ inputSchema: GetPrReviewsInputJsonSchema,
204
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
205
+ },
206
+ {
207
+ name: "github.get_pr_commits",
208
+ description: "List commits and commit metadata (SHAs, authors, messages) for a GitHub PR.",
209
+ inputSchema: GetPrCommitsInputJsonSchema,
210
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
211
+ },
212
+ {
213
+ name: "repo.get_workflow_runs",
214
+ description: "List recent GitHub Actions workflow runs for the repository.",
215
+ inputSchema: GetWorkflowRunsInputJsonSchema,
216
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
176
217
  },
177
218
  {
178
219
  name: "github.issue_view",
179
220
  description: "View details of a GitHub issue including title, body, and state.",
180
221
  inputSchema: IssueViewInputJsonSchema,
222
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
181
223
  },
182
224
  {
183
225
  name: "github.issue_update",
184
226
  description: "Update a GitHub issue's body, labels, and/or state.",
185
227
  inputSchema: IssueUpdateInputJsonSchema,
228
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
186
229
  },
187
230
  {
188
231
  name: "github.issue_comment",
189
232
  description: "Add a new comment to a GitHub issue.",
190
233
  inputSchema: IssueCommentInputJsonSchema,
234
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
191
235
  },
192
236
  {
193
237
  name: "github.create_issue",
194
238
  description: "Create a new GitHub issue.",
195
239
  inputSchema: CreateIssueInputJsonSchema,
240
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
196
241
  },
197
242
  {
198
243
  name: "jules.create_session",
199
244
  description: "Create a Jules session that performs work externally and may generate a GitHub pull request.",
200
245
  inputSchema: CreateJulesSessionInputJsonSchema,
246
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
201
247
  },
202
248
  {
203
249
  name: "jules.get_session",
204
250
  description: "Get the status and details of a Jules session.",
205
251
  inputSchema: JulesSessionIdInputJsonSchema,
252
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
206
253
  },
207
254
  {
208
255
  name: "jules.send_message",
209
256
  description: "Send a message to an active Jules session.",
210
257
  inputSchema: JulesSendMessageInputJsonSchema,
258
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
211
259
  },
212
260
  {
213
261
  name: "jules.get_messages",
214
262
  description: "Get the message history of a Jules session.",
215
263
  inputSchema: JulesSessionIdInputJsonSchema,
264
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
216
265
  },
217
266
  {
218
267
  name: "jules.list_sessions",
219
268
  description: "List all Jules sessions.",
220
269
  inputSchema: JulesListSessionsInputJsonSchema,
270
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
221
271
  },
222
272
  {
223
273
  name: "jules.cancel_session",
224
274
  description: "Cancel an ongoing Jules session.",
225
275
  inputSchema: JulesSessionIdInputJsonSchema,
276
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
226
277
  },
227
278
  {
228
279
  name: "jules.get_pr",
229
280
  description: "Get the generated pull request url associated with an active Jules agent session.",
230
281
  inputSchema: JulesSessionIdInputJsonSchema,
282
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
231
283
  },
232
284
  {
233
285
  name: "jules.trigger_feedback",
234
286
  description: "Automatically collect CI status/logs for the PR associated with a Jules session and send them back as feedback.",
235
287
  inputSchema: JulesSessionIdInputJsonSchema,
236
- },
237
- {
238
- name: "agent.search_ddgs",
239
- description: "Search the web using DuckDuckGo (via ddgs python library).",
240
- inputSchema: SearchDdgsInputJsonSchema,
288
+ outputSchema: DEFAULT_OUTPUT_SCHEMA,
241
289
  },
242
290
  ];
243
291
  export const MCP_TOOLS = BASE_MCP_TOOLS.flatMap((tool) => {
@@ -0,0 +1,91 @@
1
+ import http from "node:http";
2
+ import crypto from "node:crypto";
3
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
4
+ export function createMcpHttpServer(options) {
5
+ const { port = 3000, bearerToken = process.env.BOOMTICK_MCP_BEARER_TOKEN || process.env.MCP_BEARER_TOKEN || process.env.AUTH_TOKEN, mcpServer, } = options;
6
+ const transports = new Map();
7
+ const server = http.createServer(async (req, res) => {
8
+ // CORS Headers
9
+ res.setHeader("Access-Control-Allow-Origin", "*");
10
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
11
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
12
+ if (req.method === "OPTIONS") {
13
+ res.writeHead(204);
14
+ res.end();
15
+ return;
16
+ }
17
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
18
+ // Endpoint 1: OAuth Protected Resource metadata
19
+ if (url.pathname === "/.well-known/oauth-protected-resource" && req.method === "GET") {
20
+ res.writeHead(200, { "Content-Type": "application/json" });
21
+ res.end(JSON.stringify({
22
+ resource: "mcp://boomtick-mcp",
23
+ "authorization_servers": [
24
+ process.env.MCP_OAUTH_AUTH_SERVER || "https://auth.boomtick.dev",
25
+ ],
26
+ scopes_supported: ["mcp:read", "mcp:write"],
27
+ }));
28
+ return;
29
+ }
30
+ // Bearer Token Validation for SSE & Messages endpoints
31
+ if (url.pathname === "/sse" || url.pathname === "/messages") {
32
+ if (!bearerToken) {
33
+ res.writeHead(500, { "Content-Type": "application/json" });
34
+ res.end(JSON.stringify({ error: "Server misconfiguration: bearerToken is required for HTTP transport" }));
35
+ return;
36
+ }
37
+ const authHeader = req.headers.authorization;
38
+ const expectedAuth = `Bearer ${bearerToken}`;
39
+ const authBuffer = Buffer.from(authHeader || "");
40
+ const expectedBuffer = Buffer.from(expectedAuth);
41
+ const isMatch = Boolean(authHeader) &&
42
+ authBuffer.length === expectedBuffer.length &&
43
+ crypto.timingSafeEqual(authBuffer, expectedBuffer);
44
+ if (!isMatch) {
45
+ res.writeHead(401, {
46
+ "Content-Type": "application/json",
47
+ "WWW-Authenticate": 'Bearer realm="boomtick-mcp", error="invalid_token"',
48
+ });
49
+ res.end(JSON.stringify({ error: "unauthorized", message: "Invalid or missing Bearer token" }));
50
+ return;
51
+ }
52
+ }
53
+ // Endpoint 2: SSE stream setup
54
+ if (url.pathname === "/sse" && req.method === "GET") {
55
+ const transport = new SSEServerTransport("/messages", res);
56
+ transports.set(transport.sessionId, transport);
57
+ transport.onclose = () => {
58
+ transports.delete(transport.sessionId);
59
+ };
60
+ await mcpServer.connect(transport);
61
+ return;
62
+ }
63
+ // Endpoint 3: POST messages
64
+ if (url.pathname === "/messages" && req.method === "POST") {
65
+ const sessionId = url.searchParams.get("sessionId");
66
+ const transport = sessionId ? transports.get(sessionId) : null;
67
+ if (transport) {
68
+ await transport.handlePostMessage(req, res);
69
+ }
70
+ else {
71
+ res.writeHead(400, { "Content-Type": "application/json" });
72
+ res.end(JSON.stringify({ error: "Session not found or expired" }));
73
+ }
74
+ return;
75
+ }
76
+ res.writeHead(404, { "Content-Type": "application/json" });
77
+ res.end(JSON.stringify({ error: "Not found" }));
78
+ });
79
+ return {
80
+ server,
81
+ listen: (customPort) => new Promise((resolve) => {
82
+ const p = customPort ?? port;
83
+ server.listen(p, () => {
84
+ const addr = server.address();
85
+ const actualPort = typeof addr === "object" && addr ? addr.port : p;
86
+ console.error(`Boomtick MCP Server running on HTTP port ${actualPort}`);
87
+ resolve(actualPort);
88
+ });
89
+ }),
90
+ };
91
+ }