@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,118 @@
1
+ import http from "node:http";
2
+ import crypto from "node:crypto";
3
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
+ import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
5
+
6
+ export interface HttpServerOptions {
7
+ port?: number;
8
+ bearerToken?: string;
9
+ mcpServer: Server;
10
+ }
11
+
12
+ export function createMcpHttpServer(options: HttpServerOptions) {
13
+ const {
14
+ port = 3000,
15
+ bearerToken = process.env.BOOMTICK_MCP_BEARER_TOKEN || process.env.MCP_BEARER_TOKEN || process.env.AUTH_TOKEN,
16
+ mcpServer,
17
+ } = options;
18
+
19
+ const transports = new Map<string, SSEServerTransport>();
20
+
21
+ const server = http.createServer(async (req, res) => {
22
+ // CORS Headers
23
+ res.setHeader("Access-Control-Allow-Origin", "*");
24
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
25
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization");
26
+
27
+ if (req.method === "OPTIONS") {
28
+ res.writeHead(204);
29
+ res.end();
30
+ return;
31
+ }
32
+
33
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
34
+
35
+ // Endpoint 1: OAuth Protected Resource metadata
36
+ if (url.pathname === "/.well-known/oauth-protected-resource" && req.method === "GET") {
37
+ res.writeHead(200, { "Content-Type": "application/json" });
38
+ res.end(
39
+ JSON.stringify({
40
+ resource: "mcp://boomtick-mcp",
41
+ "authorization_servers": [
42
+ process.env.MCP_OAUTH_AUTH_SERVER || "https://auth.boomtick.dev",
43
+ ],
44
+ scopes_supported: ["mcp:read", "mcp:write"],
45
+ })
46
+ );
47
+ return;
48
+ }
49
+
50
+ // Bearer Token Validation for SSE & Messages endpoints
51
+ if (url.pathname === "/sse" || url.pathname === "/messages") {
52
+ if (!bearerToken) {
53
+ res.writeHead(500, { "Content-Type": "application/json" });
54
+ res.end(JSON.stringify({ error: "Server misconfiguration: bearerToken is required for HTTP transport" }));
55
+ return;
56
+ }
57
+
58
+ const authHeader = req.headers.authorization;
59
+ const expectedAuth = `Bearer ${bearerToken}`;
60
+ const authBuffer = Buffer.from(authHeader || "");
61
+ const expectedBuffer = Buffer.from(expectedAuth);
62
+ const isMatch =
63
+ Boolean(authHeader) &&
64
+ authBuffer.length === expectedBuffer.length &&
65
+ crypto.timingSafeEqual(authBuffer, expectedBuffer);
66
+
67
+ if (!isMatch) {
68
+ res.writeHead(401, {
69
+ "Content-Type": "application/json",
70
+ "WWW-Authenticate": 'Bearer realm="boomtick-mcp", error="invalid_token"',
71
+ });
72
+ res.end(JSON.stringify({ error: "unauthorized", message: "Invalid or missing Bearer token" }));
73
+ return;
74
+ }
75
+ }
76
+
77
+ // Endpoint 2: SSE stream setup
78
+ if (url.pathname === "/sse" && req.method === "GET") {
79
+ const transport = new SSEServerTransport("/messages", res);
80
+ transports.set(transport.sessionId, transport);
81
+ transport.onclose = () => {
82
+ transports.delete(transport.sessionId);
83
+ };
84
+ await mcpServer.connect(transport);
85
+ return;
86
+ }
87
+
88
+ // Endpoint 3: POST messages
89
+ if (url.pathname === "/messages" && req.method === "POST") {
90
+ const sessionId = url.searchParams.get("sessionId");
91
+ const transport = sessionId ? transports.get(sessionId) : null;
92
+ if (transport) {
93
+ await transport.handlePostMessage(req, res);
94
+ } else {
95
+ res.writeHead(400, { "Content-Type": "application/json" });
96
+ res.end(JSON.stringify({ error: "Session not found or expired" }));
97
+ }
98
+ return;
99
+ }
100
+
101
+ res.writeHead(404, { "Content-Type": "application/json" });
102
+ res.end(JSON.stringify({ error: "Not found" }));
103
+ });
104
+
105
+ return {
106
+ server,
107
+ listen: (customPort?: number) =>
108
+ new Promise<number>((resolve) => {
109
+ const p = customPort ?? port;
110
+ server.listen(p, () => {
111
+ const addr = server.address();
112
+ const actualPort = typeof addr === "object" && addr ? addr.port : p;
113
+ console.error(`Boomtick MCP Server running on HTTP port ${actualPort}`);
114
+ resolve(actualPort);
115
+ });
116
+ }),
117
+ };
118
+ }
@@ -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", () => ({
@@ -107,34 +104,44 @@ describe("BoomtickMCPServer", () => {
107
104
  expect(result.resources).toBeDefined();
108
105
  expect(Array.isArray(result.resources)).toBe(true);
109
106
  });
107
+
108
+ it("should return resource templates list", async () => {
109
+ const handler = requestHandlers.get("resources/templates/list");
110
+ expect(handler).toBeDefined();
111
+ const result = await handler!({ method: "resources/templates/list" });
112
+ expect(result.resourceTemplates).toBeDefined();
113
+ expect(Array.isArray(result.resourceTemplates)).toBe(true);
114
+ });
110
115
  });
111
116
 
112
117
  describe("ReadResource", () => {
113
- it("should handle repo://package-json", async () => {
118
+ it("should handle mcp://workspace/agent-contract", async () => {
114
119
  const handler = requestHandlers.get("resources/read");
115
- vi.mocked(fs.readFile).mockResolvedValue('{"name": "test"}');
120
+ vi.mocked(fs.readFile).mockResolvedValue("# Agent Contract\nRule 1");
116
121
 
117
122
  const result = await handler!({
118
123
  method: "resources/read",
119
- params: { uri: "repo://package-json" },
124
+ params: { uri: "mcp://workspace/agent-contract" },
120
125
  });
121
126
 
127
+ expect(fs.readFile).toHaveBeenCalledWith("/mock/repo/.agents/AGENT_CONTRACT.md", "utf-8");
122
128
  expect(result).toEqual({
123
- contents: [{ uri: "repo://package-json", mimeType: "application/json", text: '{"name": "test"}' }],
129
+ contents: [{ uri: "mcp://workspace/agent-contract", mimeType: "text/markdown", text: "# Agent Contract\nRule 1" }],
124
130
  });
125
131
  });
126
132
 
127
- it("should handle repo://routes", async () => {
133
+ it("should handle repo://package-json", async () => {
128
134
  const handler = requestHandlers.get("resources/read");
129
- vi.spyOn(routeMapModule, "getRouteMapHandler").mockResolvedValue({ routes: ["/"] } as any);
135
+ vi.mocked(fs.readFile).mockResolvedValue('{"name": "test"}');
130
136
 
131
137
  const result = await handler!({
132
138
  method: "resources/read",
133
- params: { uri: "repo://routes" },
139
+ params: { uri: "repo://package-json" },
134
140
  });
135
141
 
136
- expect(result.contents[0].uri).toBe("repo://routes");
137
- expect(JSON.parse(result.contents[0].text)).toEqual({ routes: ["/"] });
142
+ expect(result).toEqual({
143
+ contents: [{ uri: "repo://package-json", mimeType: "application/json", text: '{"name": "test"}' }],
144
+ });
138
145
  });
139
146
 
140
147
  it("should handle repo://design-tokens", async () => {
@@ -178,32 +185,6 @@ describe("BoomtickMCPServer", () => {
178
185
  expect(JSON.parse(result.contents[0].text)).toEqual({ status: "success" });
179
186
  });
180
187
 
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
188
  it("should throw when resource is unknown", async () => {
208
189
  const handler = requestHandlers.get("resources/read");
209
190
 
@@ -238,6 +219,7 @@ describe("BoomtickMCPServer", () => {
238
219
 
239
220
  expect(result).toEqual({
240
221
  content: [{ type: "text", text: JSON.stringify({ status: "ok" }, null, 2) }],
222
+ structuredContent: { status: "ok" },
241
223
  });
242
224
  });
243
225
 
package/src/mcp/server.ts CHANGED
@@ -4,13 +4,14 @@ import {
4
4
  CallToolRequestSchema,
5
5
  ListToolsRequestSchema,
6
6
  ListResourcesRequestSchema,
7
+ ListResourceTemplatesRequestSchema,
7
8
  ReadResourceRequestSchema,
8
9
  ListPromptsRequestSchema,
9
10
  GetPromptRequestSchema,
10
11
  } from "@modelcontextprotocol/sdk/types.js";
11
12
  import { config } from "../config.js";
12
13
  import { createSuccessResult, createErrorResult } from "../lib/result.js";
13
- import { MCP_TOOLS, MCP_PROMPTS, MCP_RESOURCES } from "./definitions.js";
14
+ import { MCP_TOOLS, MCP_PROMPTS, MCP_RESOURCES, MCP_RESOURCE_TEMPLATES } from "./definitions.js";
14
15
  import { normalizeArguments } from "../lib/param_utils.js";
15
16
  import { healthHandler, HealthCheckInputSchema } from "./tools.js";
16
17
  import { searchOpenPrsHandler, SearchOpenPrsInputSchema } from "../tools/github.search_open_prs.js";
@@ -21,14 +22,11 @@ import { getChangedFilesHandler, GetChangedFilesInputSchema } from "../tools/rep
21
22
  import { readAgentContextHandler, ReadAgentContextInputSchema } from "../tools/repo.read_agent_context.js";
22
23
  import { getCommandSchemaHandler, GetCommandSchemaInputSchema } from "../tools/repo.get_command_schema.js";
23
24
  import { getPackageScriptsHandler, GetPackageScriptsInputSchema } from "../tools/repo.get_package_scripts.js";
24
- import { getRouteMapHandler, GetRouteMapInputSchema } from "../tools/repo.get_route_map.js";
25
25
  import { readCiLogsHandler, ReadCiLogsInputSchema } from "../tools/repo.read_ci_logs.js";
26
26
  import { repoLogsHandler, RepoLogsInputSchema } from "../tools/repo.logs.js";
27
27
  import { createRepairBranchHandler, CreateRepairBranchInputSchema } from "../tools/repo.create_repair_branch.js";
28
28
  import { createBranchHandler, CreateBranchInputSchema } from "../tools/repo.create_branch.js";
29
29
  import { runTestsHandler, RunTestsInputSchema } from "../tools/repo.run_tests.js";
30
- import { runLighthouseHandler, RunLighthouseInputSchema } from "../tools/repo.run_lighthouse.js";
31
- import { runPlaywrightHandler, RunPlaywrightInputSchema } from "../tools/repo.run_playwright.js";
32
30
  import { commitPatchHandler, CommitPatchInputSchema } from "../tools/repo.commit_patch.js";
33
31
  import { openReplacementPrHandler, OpenReplacementPrInputSchema } from "../tools/github.open_replacement_pr.js";
34
32
  import { commentTriageSummaryHandler, CommentTriageSummaryInputSchema } from "../tools/github.comment_triage_summary.js";
@@ -48,7 +46,6 @@ import { listJulesSessionsHandler, ListJulesSessionsInputSchema } from "../tools
48
46
  import { cancelJulesSessionHandler, CancelJulesSessionInputSchema } from "../tools/jules/cancel-session.js";
49
47
  import { getJulesPullRequestHandler, GetJulesPullRequestInputSchema } from "../tools/jules/get-pr.js";
50
48
  import { triggerJulesFeedbackHandler, TriggerJulesFeedbackInputSchema } from "../tools/jules/trigger-feedback.js";
51
- import { ddgsSearchHandler, DdgsSearchInputSchema } from "../tools/ddgs.search.js";
52
49
 
53
50
  import fs from "fs/promises";
54
51
  import { existsSync, readFileSync } from "fs";
@@ -56,6 +53,7 @@ import path from "path";
56
53
 
57
54
  import { spawnSync } from "child_process";
58
55
  import { fileURLToPath } from "url";
56
+ import { createMcpHttpServer } from "./http.js";
59
57
 
60
58
  export class BoomtickMCPServer {
61
59
  private server: Server;
@@ -121,55 +119,61 @@ export class BoomtickMCPServer {
121
119
  };
122
120
  });
123
121
 
122
+ this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
123
+ return {
124
+ resourceTemplates: MCP_RESOURCE_TEMPLATES,
125
+ };
126
+ });
127
+
124
128
  this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
125
129
  const uri = request.params.uri;
126
- if (uri === "repo://package-json") {
127
- const content = await fs.readFile(path.join(config.repoPath, "package.json"), "utf-8");
130
+ if (
131
+ uri === "mcp://workspace/agent-contract" ||
132
+ uri === "mcp://workspace/agent_contract" ||
133
+ uri === "repo://agent-contract"
134
+ ) {
135
+ const contractPath = path.join(config.repoPath, ".agents", "AGENT_CONTRACT.md");
136
+ const content = await fs.readFile(contractPath, "utf-8");
128
137
  return {
129
- contents: [{ uri, mimeType: "application/json", text: content }],
138
+ contents: [{ uri, mimeType: "text/markdown", text: content }],
130
139
  };
131
140
  }
132
- if (uri === "repo://routes") {
133
- const routeMap = await getRouteMapHandler({});
141
+ if (
142
+ uri === "mcp://git/changed-files" ||
143
+ uri === "repo://changed-files"
144
+ ) {
145
+ const changedFiles = await getChangedFilesHandler(GetChangedFilesInputSchema.parse({}));
134
146
  return {
135
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(routeMap, null, 2) }],
147
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(changedFiles, null, 2) }],
136
148
  };
137
149
  }
138
- if (uri === "repo://design-tokens") {
150
+ if (uri === "repo://package-json" || uri === "mcp://workspace/package-json") {
151
+ const content = await fs.readFile(path.join(config.repoPath, "package.json"), "utf-8");
152
+ return {
153
+ contents: [{ uri, mimeType: "application/json", text: content }],
154
+ };
155
+ }
156
+ if (uri === "repo://design-tokens" || uri === "mcp://workspace/design-tokens") {
139
157
  const tokensPath = path.join(config.repoPath, "src/styles/design-tokens.ts");
140
158
  const content = await fs.readFile(tokensPath, "utf-8");
141
159
  return {
142
160
  contents: [{ uri, mimeType: "text/typescript", text: content }],
143
161
  };
144
162
  }
145
- if (uri.startsWith("repo://diff/")) {
163
+ if (uri.startsWith("mcp://git/diff/") || uri.startsWith("repo://diff/")) {
146
164
  const prNumber = parseInt(uri.split("/").pop() || "");
147
165
  const diff = await getPrDiffHandler({ prNumber });
148
166
  return {
149
167
  contents: [{ uri, mimeType: "text/plain", text: diff.diffText }],
150
168
  };
151
169
  }
152
- if (uri.startsWith("repo://ci/")) {
170
+ if (uri.startsWith("mcp://ci/logs/") || uri.startsWith("repo://ci/")) {
153
171
  const prNumber = parseInt(uri.split("/").pop() || "");
154
172
  const logs = await readCiLogsHandler({ prNumber });
155
173
  return {
156
174
  contents: [{ uri, mimeType: "application/json", text: JSON.stringify(logs, null, 2) }],
157
175
  };
158
176
  }
159
- if (uri.startsWith("repo://lighthouse/")) {
160
- const branch = uri.split("/").pop() || "";
161
- const report = await runLighthouseHandler({ route: "/", worktreePath: `../boomtick-mcp-rescue-${branch}` });
162
- return {
163
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(report, null, 2) }],
164
- };
165
- }
166
- if (uri.startsWith("repo://playwright/")) {
167
- const branch = uri.split("/").pop() || "";
168
- const report = await runPlaywrightHandler({ worktreePath: `../boomtick-mcp-rescue-${branch}` });
169
- return {
170
- contents: [{ uri, mimeType: "application/json", text: JSON.stringify(report, null, 2) }],
171
- };
172
- }
173
177
  throw new Error(`Resource not found: ${uri}`);
174
178
  });
175
179
  }
@@ -213,9 +217,6 @@ export class BoomtickMCPServer {
213
217
  case "repo.get.package.scripts":
214
218
  case "repo.get_package_scripts":
215
219
  return createSuccessResult(await getPackageScriptsHandler(GetPackageScriptsInputSchema.parse(normArgs)));
216
- case "repo.get.route.map":
217
- case "repo.get_route_map":
218
- return createSuccessResult(await getRouteMapHandler(GetRouteMapInputSchema.parse(normArgs)));
219
220
  case "repo.read.ci.logs":
220
221
  case "repo.read_ci_logs":
221
222
  return createSuccessResult(await readCiLogsHandler(ReadCiLogsInputSchema.parse(normArgs)));
@@ -230,12 +231,6 @@ export class BoomtickMCPServer {
230
231
  case "repo.run.tests":
231
232
  case "repo.run_tests":
232
233
  return createSuccessResult(await runTestsHandler(RunTestsInputSchema.parse(normArgs)));
233
- case "repo.run.lighthouse":
234
- case "repo.run_lighthouse":
235
- return createSuccessResult(await runLighthouseHandler(RunLighthouseInputSchema.parse(normArgs)));
236
- case "repo.run.playwright":
237
- case "repo.run_playwright":
238
- return createSuccessResult(await runPlaywrightHandler(RunPlaywrightInputSchema.parse(normArgs)));
239
234
  case "repo.commit.patch":
240
235
  case "repo.commit_patch":
241
236
  return createSuccessResult(await commitPatchHandler(CommitPatchInputSchema.parse(normArgs)));
@@ -288,9 +283,6 @@ export class BoomtickMCPServer {
288
283
  case "jules.trigger.feedback":
289
284
  case "jules.trigger_feedback":
290
285
  return createSuccessResult(await triggerJulesFeedbackHandler(TriggerJulesFeedbackInputSchema.parse(normArgs)));
291
- case "agent.search.ddgs":
292
- case "agent.search_ddgs":
293
- return createSuccessResult(await ddgsSearchHandler(DdgsSearchInputSchema.parse(normArgs)));
294
286
  default:
295
287
  return createErrorResult(`Tool not found: ${request.params.name}`);
296
288
  }
@@ -299,7 +291,7 @@ export class BoomtickMCPServer {
299
291
  }
300
292
  });
301
293
  }
302
- async run() {
294
+ private prepareEnvironmentAndPreflight() {
303
295
  const projectRoot = config.repoPath || process.cwd();
304
296
  const homeDir = process.env.HOME || "";
305
297
  const extraPaths = [];
@@ -351,9 +343,9 @@ export class BoomtickMCPServer {
351
343
  // 3. SAFE REPOSITORY RESOLUTION
352
344
  const repoPath = config.repoPath || projectRoot;
353
345
 
354
- // 4. SECURE, UNIX-ONLY PRE-FLIGHT CHECK FOR TD-CLI
346
+ // 4. SECURE, UNIX-ONLY PRE-FLIGHT CHECK FOR TD
355
347
  try {
356
- const result = spawnSync("td-cli", ["doctor"], {
348
+ const result = spawnSync("td", ["doctor"], {
357
349
  encoding: "utf-8",
358
350
  cwd: repoPath,
359
351
  shell: false // Explicitly disabled to clear Semgrep rules
@@ -367,24 +359,38 @@ export class BoomtickMCPServer {
367
359
  console.error("status:", result.status);
368
360
  console.error("stdout:", result.stdout);
369
361
  console.error("stderr:", result.stderr);
370
- throw new Error(`td-cli doctor returned exit code: ${result.status}`);
362
+ throw new Error(`td doctor returned exit code: ${result.status}`);
371
363
  }
372
364
 
373
- console.error("✅ td-cli verified on PATH securely");
365
+ console.error("✅ td verified on PATH securely");
374
366
  } catch (error) {
375
- console.error("❌ Fatal: td-cli is not resolvable on PATH. MCP tools will fail.");
367
+ console.error("❌ Fatal: td is not resolvable on PATH. MCP tools will fail.");
376
368
  console.error("Please ensure the project is installed: pip install -e .");
377
- console.error("❌ Fatal: td-cli pre-flight check failed:", error instanceof Error ? error.message : String(error));
369
+ console.error("❌ Fatal: td pre-flight check failed:", error instanceof Error ? error.message : String(error));
378
370
  process.exit(1);
379
371
  }
372
+ }
380
373
 
381
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
382
- const ddgsScriptPath = path.join(__dirname, "..", "tools", "ddgs_search.py");
383
- try {
384
- await fs.access(ddgsScriptPath);
385
- } catch (err) {
386
- console.error(`❌ Fatal: ddgs_search.py not found at ${ddgsScriptPath}`);
387
- process.exit(1);
374
+ async startHttp(port = parseInt(process.env.PORT || process.env.HTTP_PORT || "3000", 10), bearerToken?: string) {
375
+ this.prepareEnvironmentAndPreflight();
376
+ const mcpHttp = createMcpHttpServer({ port, bearerToken, mcpServer: this.server });
377
+ return await mcpHttp.listen(port);
378
+ }
379
+
380
+ async run() {
381
+ this.prepareEnvironmentAndPreflight();
382
+
383
+ const isHttp =
384
+ process.env.MCP_TRANSPORT === "http" ||
385
+ process.argv.includes("--http") ||
386
+ (process.env.PORT !== undefined && !process.argv.includes("--stdio")) ||
387
+ (process.env.HTTP_PORT !== undefined && !process.argv.includes("--stdio"));
388
+
389
+ if (isHttp) {
390
+ const port = parseInt(process.env.PORT || process.env.HTTP_PORT || "3000", 10);
391
+ const mcpHttp = createMcpHttpServer({ port, mcpServer: this.server });
392
+ await mcpHttp.listen(port);
393
+ return;
388
394
  }
389
395
 
390
396
  const transport = new StdioServerTransport();