@arii/boomtick-mcp 0.5.0 → 0.6.1

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 (156) 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 +5 -0
  10. package/.mcp/schemas/github.get_pr_diff.json +5 -0
  11. package/.mcp/schemas/github.get_pr_reviews.json +5 -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 +5 -0
  24. package/.mcp/schemas/github_get_pr_diff.json +5 -0
  25. package/.mcp/schemas/github_get_pr_reviews.json +5 -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 +5 -0
  54. package/.mcp/schemas/repo.logs.json +5 -0
  55. package/.mcp/schemas/repo.read_agent_context.json +5 -0
  56. package/.mcp/schemas/repo.read_ci_logs.json +5 -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 +5 -0
  65. package/.mcp/schemas/repo_logs.json +5 -0
  66. package/.mcp/schemas/repo_read_agent_context.json +5 -0
  67. package/.mcp/schemas/repo_read_ci_logs.json +5 -0
  68. package/.mcp/schemas/repo_run_tests.json +5 -0
  69. package/CHANGELOG.md +14 -0
  70. package/actions/ai-review/action.yml +2 -2
  71. package/actions/audit/action.yml +1 -1
  72. package/actions/ci-validate/action.yml +2 -2
  73. package/actions/impact-analysis/action.yml +3 -3
  74. package/actions/run-project-gate/action.yml +5 -5
  75. package/actions/setup/action.yml +1 -1
  76. package/actions/test-build/action.yml +1 -1
  77. package/dist/config.js +1 -1
  78. package/dist/config.test.js +3 -3
  79. package/dist/lib/result.js +4 -0
  80. package/dist/lib/result.test.js +4 -2
  81. package/dist/lib/shell.js +6 -6
  82. package/dist/mcp/definitions.js +73 -2
  83. package/dist/mcp/http.js +91 -0
  84. package/dist/mcp/http.test.js +58 -0
  85. package/dist/mcp/server.js +65 -13
  86. package/dist/mcp/server.test.js +44 -0
  87. package/dist/tools/contract.js +109 -1
  88. package/dist/tools/github.checkout_branch.js +1 -1
  89. package/dist/tools/github.comment_triage_summary.js +1 -1
  90. package/dist/tools/github.create_issue.js +1 -1
  91. package/dist/tools/github.create_issue.test.js +6 -6
  92. package/dist/tools/github.create_pull_request.js +1 -1
  93. package/dist/tools/github.create_pull_request.test.js +4 -4
  94. package/dist/tools/github.get_pr.js +1 -1
  95. package/dist/tools/github.get_pr_commits.js +2 -2
  96. package/dist/tools/github.get_pr_diff.js +1 -1
  97. package/dist/tools/github.get_pr_reviews.js +2 -2
  98. package/dist/tools/github.get_pr_reviews.test.js +9 -9
  99. package/dist/tools/github.issue_comment.js +1 -1
  100. package/dist/tools/github.issue_update.js +1 -1
  101. package/dist/tools/github.issue_view.js +1 -1
  102. package/dist/tools/github.search_open_prs.js +1 -1
  103. package/dist/tools/jules/cancel-session.js +1 -1
  104. package/dist/tools/jules/create-session.test.js +11 -11
  105. package/dist/tools/jules/get-messages.js +1 -1
  106. package/dist/tools/jules/get-messages.test.js +3 -3
  107. package/dist/tools/jules/get-pr.test.js +3 -3
  108. package/dist/tools/jules/list-sessions.js +1 -1
  109. package/dist/tools/jules/send-message.js +1 -1
  110. package/dist/tools/jules/send-message.test.js +5 -5
  111. package/dist/tools/jules/shared.js +4 -4
  112. package/dist/tools/jules/trigger-feedback.js +1 -1
  113. package/dist/tools/jules/trigger-feedback.test.js +5 -5
  114. package/dist/tools/repo.get_command_schema.js +2 -2
  115. package/dist/tools/repo.get_workflow_runs.js +2 -2
  116. package/package.json +1 -1
  117. package/scripts/sync-mcp-schemas.ts +2 -0
  118. package/src/config.test.ts +3 -3
  119. package/src/config.ts +1 -1
  120. package/src/lib/result.test.ts +4 -2
  121. package/src/lib/result.ts +6 -0
  122. package/src/lib/shell.ts +6 -6
  123. package/src/mcp/definitions.ts +76 -3
  124. package/src/mcp/http.test.ts +72 -0
  125. package/src/mcp/http.ts +118 -0
  126. package/src/mcp/server.test.ts +52 -0
  127. package/src/mcp/server.ts +75 -12
  128. package/src/tools/contract.ts +109 -1
  129. package/src/tools/github.checkout_branch.ts +1 -1
  130. package/src/tools/github.comment_triage_summary.ts +1 -1
  131. package/src/tools/github.create_issue.test.ts +6 -6
  132. package/src/tools/github.create_issue.ts +1 -1
  133. package/src/tools/github.create_pull_request.test.ts +4 -4
  134. package/src/tools/github.create_pull_request.ts +1 -1
  135. package/src/tools/github.get_pr.ts +1 -1
  136. package/src/tools/github.get_pr_commits.ts +2 -2
  137. package/src/tools/github.get_pr_diff.ts +1 -1
  138. package/src/tools/github.get_pr_reviews.test.ts +9 -9
  139. package/src/tools/github.get_pr_reviews.ts +2 -2
  140. package/src/tools/github.issue_comment.ts +1 -1
  141. package/src/tools/github.issue_update.ts +1 -1
  142. package/src/tools/github.issue_view.ts +1 -1
  143. package/src/tools/github.search_open_prs.ts +1 -1
  144. package/src/tools/jules/cancel-session.ts +1 -1
  145. package/src/tools/jules/create-session.test.ts +11 -11
  146. package/src/tools/jules/get-messages.test.ts +3 -3
  147. package/src/tools/jules/get-messages.ts +1 -1
  148. package/src/tools/jules/get-pr.test.ts +3 -3
  149. package/src/tools/jules/list-sessions.ts +1 -1
  150. package/src/tools/jules/send-message.test.ts +5 -5
  151. package/src/tools/jules/send-message.ts +1 -1
  152. package/src/tools/jules/shared.ts +4 -4
  153. package/src/tools/jules/trigger-feedback.test.ts +5 -5
  154. package/src/tools/jules/trigger-feedback.ts +1 -1
  155. package/src/tools/repo.get_command_schema.ts +2 -2
  156. package/src/tools/repo.get_workflow_runs.ts +2 -2
@@ -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
+ }
@@ -5,6 +5,9 @@ import fs from "fs/promises";
5
5
  import * as healthModule from "./tools.js";
6
6
  import * as prDiffModule from "../tools/github.get_pr_diff.js";
7
7
  import * as ciLogsModule from "../tools/repo.read_ci_logs.js";
8
+ import * as prReviewsModule from "../tools/github.get_pr_reviews.js";
9
+ import * as prCommitsModule from "../tools/github.get_pr_commits.js";
10
+ import * as workflowRunsModule from "../tools/repo.get_workflow_runs.js";
8
11
 
9
12
  vi.mock("fs/promises");
10
13
  vi.mock("../config.js", () => ({
@@ -104,9 +107,32 @@ describe("BoomtickMCPServer", () => {
104
107
  expect(result.resources).toBeDefined();
105
108
  expect(Array.isArray(result.resources)).toBe(true);
106
109
  });
110
+
111
+ it("should return resource templates list", async () => {
112
+ const handler = requestHandlers.get("resources/templates/list");
113
+ expect(handler).toBeDefined();
114
+ const result = await handler!({ method: "resources/templates/list" });
115
+ expect(result.resourceTemplates).toBeDefined();
116
+ expect(Array.isArray(result.resourceTemplates)).toBe(true);
117
+ });
107
118
  });
108
119
 
109
120
  describe("ReadResource", () => {
121
+ it("should handle mcp://workspace/agent-contract", async () => {
122
+ const handler = requestHandlers.get("resources/read");
123
+ vi.mocked(fs.readFile).mockResolvedValue("# Agent Contract\nRule 1");
124
+
125
+ const result = await handler!({
126
+ method: "resources/read",
127
+ params: { uri: "mcp://workspace/agent-contract" },
128
+ });
129
+
130
+ expect(fs.readFile).toHaveBeenCalledWith("/mock/repo/.agents/AGENT_CONTRACT.md", "utf-8");
131
+ expect(result).toEqual({
132
+ contents: [{ uri: "mcp://workspace/agent-contract", mimeType: "text/markdown", text: "# Agent Contract\nRule 1" }],
133
+ });
134
+ });
135
+
110
136
  it("should handle repo://package-json", async () => {
111
137
  const handler = requestHandlers.get("resources/read");
112
138
  vi.mocked(fs.readFile).mockResolvedValue('{"name": "test"}');
@@ -196,7 +222,33 @@ describe("BoomtickMCPServer", () => {
196
222
 
197
223
  expect(result).toEqual({
198
224
  content: [{ type: "text", text: JSON.stringify({ status: "ok" }, null, 2) }],
225
+ structuredContent: { status: "ok" },
226
+ });
227
+ });
228
+
229
+ it("should execute github_get_pr_reviews and github_get_pr_commits via underscore or dot name", async () => {
230
+ const handler = requestHandlers.get("tools/call");
231
+ vi.spyOn(prReviewsModule, "getPrReviewsHandler").mockResolvedValue({ reviews: [] } as any);
232
+ vi.spyOn(prCommitsModule, "getPrCommitsHandler").mockResolvedValue({ commits: [] } as any);
233
+ vi.spyOn(workflowRunsModule, "getWorkflowRunsHandler").mockResolvedValue({ runs: [] } as any);
234
+
235
+ const resReviews = await handler!({
236
+ method: "tools/call",
237
+ params: { name: "github_get_pr_reviews", arguments: { prNumber: 123 } },
238
+ });
239
+ expect(resReviews.structuredContent).toEqual({ reviews: [] });
240
+
241
+ const resCommits = await handler!({
242
+ method: "tools/call",
243
+ params: { name: "github_get_pr_commits", arguments: { prNumber: 123 } },
244
+ });
245
+ expect(resCommits.structuredContent).toEqual({ commits: [] });
246
+
247
+ const resWorkflow = await handler!({
248
+ method: "tools/call",
249
+ params: { name: "repo_get_workflow_runs", arguments: {} },
199
250
  });
251
+ expect(resWorkflow.structuredContent).toEqual({ runs: [] });
200
252
  });
201
253
 
202
254
  it("should return error result for unknown tool", async () => {
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";
@@ -23,6 +24,7 @@ import { getCommandSchemaHandler, GetCommandSchemaInputSchema } from "../tools/r
23
24
  import { getPackageScriptsHandler, GetPackageScriptsInputSchema } from "../tools/repo.get_package_scripts.js";
24
25
  import { readCiLogsHandler, ReadCiLogsInputSchema } from "../tools/repo.read_ci_logs.js";
25
26
  import { repoLogsHandler, RepoLogsInputSchema } from "../tools/repo.logs.js";
27
+ import { getWorkflowRunsHandler, GetWorkflowRunsInputSchema } from "../tools/repo.get_workflow_runs.js";
26
28
  import { createRepairBranchHandler, CreateRepairBranchInputSchema } from "../tools/repo.create_repair_branch.js";
27
29
  import { createBranchHandler, CreateBranchInputSchema } from "../tools/repo.create_branch.js";
28
30
  import { runTestsHandler, RunTestsInputSchema } from "../tools/repo.run_tests.js";
@@ -31,6 +33,8 @@ import { openReplacementPrHandler, OpenReplacementPrInputSchema } from "../tools
31
33
  import { commentTriageSummaryHandler, CommentTriageSummaryInputSchema } from "../tools/github.comment_triage_summary.js";
32
34
  import { createPullRequestHandler, CreatePullRequestInputSchema } from "../tools/github.create_pull_request.js";
33
35
  import { getPrHandler, GetPrInputSchema } from "../tools/github.get_pr.js";
36
+ import { getPrReviewsHandler, GetPrReviewsInputSchema } from "../tools/github.get_pr_reviews.js";
37
+ import { getPrCommitsHandler, GetPrCommitsInputSchema } from "../tools/github.get_pr_commits.js";
34
38
  import { issueViewHandler, IssueViewInputSchema } from "../tools/github.issue_view.js";
35
39
  import { issueUpdateHandler, IssueUpdateInputSchema } from "../tools/github.issue_update.js";
36
40
  import { issueCommentHandler, IssueCommentInputSchema } from "../tools/github.issue_comment.js";
@@ -52,6 +56,7 @@ import path from "path";
52
56
 
53
57
  import { spawnSync } from "child_process";
54
58
  import { fileURLToPath } from "url";
59
+ import { createMcpHttpServer } from "./http.js";
55
60
 
56
61
  export class BoomtickMCPServer {
57
62
  private server: Server;
@@ -117,29 +122,55 @@ export class BoomtickMCPServer {
117
122
  };
118
123
  });
119
124
 
125
+ this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
126
+ return {
127
+ resourceTemplates: MCP_RESOURCE_TEMPLATES,
128
+ };
129
+ });
130
+
120
131
  this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
121
132
  const uri = request.params.uri;
122
- if (uri === "repo://package-json") {
133
+ if (
134
+ uri === "mcp://workspace/agent-contract" ||
135
+ uri === "mcp://workspace/agent_contract" ||
136
+ uri === "repo://agent-contract"
137
+ ) {
138
+ const contractPath = path.join(config.repoPath, ".agents", "AGENT_CONTRACT.md");
139
+ const content = await fs.readFile(contractPath, "utf-8");
140
+ return {
141
+ contents: [{ uri, mimeType: "text/markdown", text: content }],
142
+ };
143
+ }
144
+ if (
145
+ uri === "mcp://git/changed-files" ||
146
+ uri === "repo://changed-files"
147
+ ) {
148
+ const changedFiles = await getChangedFilesHandler(GetChangedFilesInputSchema.parse({}));
149
+ return {
150
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(changedFiles, null, 2) }],
151
+ };
152
+ }
153
+ if (uri === "repo://package-json" || uri === "mcp://workspace/package-json") {
123
154
  const content = await fs.readFile(path.join(config.repoPath, "package.json"), "utf-8");
124
155
  return {
125
156
  contents: [{ uri, mimeType: "application/json", text: content }],
126
157
  };
127
158
  }
128
- if (uri === "repo://design-tokens") {
159
+ if (uri === "repo://design-tokens" || uri === "mcp://workspace/design-tokens") {
129
160
  const tokensPath = path.join(config.repoPath, "src/styles/design-tokens.ts");
130
161
  const content = await fs.readFile(tokensPath, "utf-8");
131
162
  return {
132
163
  contents: [{ uri, mimeType: "text/typescript", text: content }],
133
164
  };
134
165
  }
135
- if (uri.startsWith("repo://diff/")) {
166
+ if (uri.startsWith("mcp://git/diff/") || uri.startsWith("repo://diff/")) {
136
167
  const prNumber = parseInt(uri.split("/").pop() || "");
137
168
  const diff = await getPrDiffHandler({ prNumber });
138
169
  return {
139
170
  contents: [{ uri, mimeType: "text/plain", text: diff.diffText }],
140
171
  };
141
172
  }
142
- if (uri.startsWith("repo://ci/")) {
173
+ if (uri.startsWith("mcp://ci/logs/") || uri.startsWith("repo://ci/")) {
143
174
  const prNumber = parseInt(uri.split("/").pop() || "");
144
175
  const logs = await readCiLogsHandler({ prNumber });
145
176
  return {
@@ -194,6 +225,9 @@ export class BoomtickMCPServer {
194
225
  return createSuccessResult(await readCiLogsHandler(ReadCiLogsInputSchema.parse(normArgs)));
195
226
  case "repo.logs":
196
227
  return createSuccessResult(await repoLogsHandler(RepoLogsInputSchema.parse(normArgs)));
228
+ case "repo.get.workflow.runs":
229
+ case "repo.get_workflow_runs":
230
+ return createSuccessResult(await getWorkflowRunsHandler(GetWorkflowRunsInputSchema.parse(normArgs)));
197
231
  case "repo.create.branch":
198
232
  case "repo.create_branch":
199
233
  return createSuccessResult(await createBranchHandler(CreateBranchInputSchema.parse(normArgs)));
@@ -218,6 +252,12 @@ export class BoomtickMCPServer {
218
252
  case "github.get.pr":
219
253
  case "github.get_pr":
220
254
  return createSuccessResult(await getPrHandler(GetPrInputSchema.parse(normArgs)));
255
+ case "github.get.pr.reviews":
256
+ case "github.get_pr_reviews":
257
+ return createSuccessResult(await getPrReviewsHandler(GetPrReviewsInputSchema.parse(normArgs)));
258
+ case "github.get.pr.commits":
259
+ case "github.get_pr_commits":
260
+ return createSuccessResult(await getPrCommitsHandler(GetPrCommitsInputSchema.parse(normArgs)));
221
261
  case "github.issue.view":
222
262
  case "github.issue_view":
223
263
  return createSuccessResult(await issueViewHandler(IssueViewInputSchema.parse(normArgs)));
@@ -263,7 +303,7 @@ export class BoomtickMCPServer {
263
303
  }
264
304
  });
265
305
  }
266
- async run() {
306
+ private prepareEnvironmentAndPreflight() {
267
307
  const projectRoot = config.repoPath || process.cwd();
268
308
  const homeDir = process.env.HOME || "";
269
309
  const extraPaths = [];
@@ -315,9 +355,9 @@ export class BoomtickMCPServer {
315
355
  // 3. SAFE REPOSITORY RESOLUTION
316
356
  const repoPath = config.repoPath || projectRoot;
317
357
 
318
- // 4. SECURE, UNIX-ONLY PRE-FLIGHT CHECK FOR TD-CLI
358
+ // 4. SECURE, UNIX-ONLY PRE-FLIGHT CHECK FOR TD
319
359
  try {
320
- const result = spawnSync("td-cli", ["doctor"], {
360
+ const result = spawnSync("td", ["doctor"], {
321
361
  encoding: "utf-8",
322
362
  cwd: repoPath,
323
363
  shell: false // Explicitly disabled to clear Semgrep rules
@@ -331,16 +371,39 @@ export class BoomtickMCPServer {
331
371
  console.error("status:", result.status);
332
372
  console.error("stdout:", result.stdout);
333
373
  console.error("stderr:", result.stderr);
334
- throw new Error(`td-cli doctor returned exit code: ${result.status}`);
374
+ throw new Error(`td doctor returned exit code: ${result.status}`);
335
375
  }
336
376
 
337
- console.error("✅ td-cli verified on PATH securely");
377
+ console.error("✅ td verified on PATH securely");
338
378
  } catch (error) {
339
- console.error("❌ Fatal: td-cli is not resolvable on PATH. MCP tools will fail.");
379
+ console.error("❌ Fatal: td is not resolvable on PATH. MCP tools will fail.");
340
380
  console.error("Please ensure the project is installed: pip install -e .");
341
- console.error("❌ Fatal: td-cli pre-flight check failed:", error instanceof Error ? error.message : String(error));
381
+ console.error("❌ Fatal: td pre-flight check failed:", error instanceof Error ? error.message : String(error));
342
382
  process.exit(1);
343
383
  }
384
+ }
385
+
386
+ async startHttp(port = parseInt(process.env.PORT || process.env.HTTP_PORT || "3000", 10), bearerToken?: string) {
387
+ this.prepareEnvironmentAndPreflight();
388
+ const mcpHttp = createMcpHttpServer({ port, bearerToken, mcpServer: this.server });
389
+ return await mcpHttp.listen(port);
390
+ }
391
+
392
+ async run() {
393
+ this.prepareEnvironmentAndPreflight();
394
+
395
+ const isHttp =
396
+ process.env.MCP_TRANSPORT === "http" ||
397
+ process.argv.includes("--http") ||
398
+ (process.env.PORT !== undefined && !process.argv.includes("--stdio")) ||
399
+ (process.env.HTTP_PORT !== undefined && !process.argv.includes("--stdio"));
400
+
401
+ if (isHttp) {
402
+ const port = parseInt(process.env.PORT || process.env.HTTP_PORT || "3000", 10);
403
+ const mcpHttp = createMcpHttpServer({ port, mcpServer: this.server });
404
+ await mcpHttp.listen(port);
405
+ return;
406
+ }
344
407
 
345
408
  const transport = new StdioServerTransport();
346
409
  await this.server.connect(transport);
@@ -847,6 +847,30 @@ export const CodeReviewResultJsonSchema = {
847
847
  "default": null,
848
848
  "title": "Line"
849
849
  },
850
+ "startLine": {
851
+ "anyOf": [
852
+ {
853
+ "type": "integer"
854
+ },
855
+ {
856
+ "type": "null"
857
+ }
858
+ ],
859
+ "default": null,
860
+ "title": "Startline"
861
+ },
862
+ "endLine": {
863
+ "anyOf": [
864
+ {
865
+ "type": "integer"
866
+ },
867
+ {
868
+ "type": "null"
869
+ }
870
+ ],
871
+ "default": null,
872
+ "title": "Endline"
873
+ },
850
874
  "snippet": {
851
875
  "anyOf": [
852
876
  {
@@ -882,6 +906,18 @@ export const CodeReviewResultJsonSchema = {
882
906
  ],
883
907
  "default": null,
884
908
  "title": "Counterexample"
909
+ },
910
+ "suggestion": {
911
+ "anyOf": [
912
+ {
913
+ "type": "string"
914
+ },
915
+ {
916
+ "type": "null"
917
+ }
918
+ ],
919
+ "default": null,
920
+ "title": "Suggestion"
885
921
  }
886
922
  },
887
923
  "required": [
@@ -1077,6 +1113,30 @@ export const CodeReviewStateJsonSchema = {
1077
1113
  "default": null,
1078
1114
  "title": "Line"
1079
1115
  },
1116
+ "startLine": {
1117
+ "anyOf": [
1118
+ {
1119
+ "type": "integer"
1120
+ },
1121
+ {
1122
+ "type": "null"
1123
+ }
1124
+ ],
1125
+ "default": null,
1126
+ "title": "Startline"
1127
+ },
1128
+ "endLine": {
1129
+ "anyOf": [
1130
+ {
1131
+ "type": "integer"
1132
+ },
1133
+ {
1134
+ "type": "null"
1135
+ }
1136
+ ],
1137
+ "default": null,
1138
+ "title": "Endline"
1139
+ },
1080
1140
  "snippet": {
1081
1141
  "anyOf": [
1082
1142
  {
@@ -1112,6 +1172,18 @@ export const CodeReviewStateJsonSchema = {
1112
1172
  ],
1113
1173
  "default": null,
1114
1174
  "title": "Counterexample"
1175
+ },
1176
+ "suggestion": {
1177
+ "anyOf": [
1178
+ {
1179
+ "type": "string"
1180
+ },
1181
+ {
1182
+ "type": "null"
1183
+ }
1184
+ ],
1185
+ "default": null,
1186
+ "title": "Suggestion"
1115
1187
  }
1116
1188
  },
1117
1189
  "required": [
@@ -2760,7 +2832,7 @@ export const ReviewCommentJsonSchema = {
2760
2832
  "type": "object"
2761
2833
  } as const;
2762
2834
 
2763
- export const ReviewFindingSchema = z.object({ "id": z.string(), "file": z.string(), "issue": z.string(), "status": z.enum(["open","resolved"]), "confidence": z.enum(["high","medium","low"]), "severity": z.union([z.enum(["HIGH","MEDIUM","LOW"]), z.null()]).default(null), "line": z.union([z.number().int(), z.null()]).default(null), "snippet": z.union([z.string(), z.null()]).default(null), "fixSummary": z.union([z.string(), z.null()]).default(null), "counterexample": z.union([z.string(), z.null()]).default(null) });
2835
+ export const ReviewFindingSchema = z.object({ "id": z.string(), "file": z.string(), "issue": z.string(), "status": z.enum(["open","resolved"]), "confidence": z.enum(["high","medium","low"]), "severity": z.union([z.enum(["HIGH","MEDIUM","LOW"]), z.null()]).default(null), "line": z.union([z.number().int(), z.null()]).default(null), "startLine": z.union([z.number().int(), z.null()]).default(null), "endLine": z.union([z.number().int(), z.null()]).default(null), "snippet": z.union([z.string(), z.null()]).default(null), "fixSummary": z.union([z.string(), z.null()]).default(null), "counterexample": z.union([z.string(), z.null()]).default(null), "suggestion": z.union([z.string(), z.null()]).default(null) });
2764
2836
 
2765
2837
  export type ReviewFinding = z.infer<typeof ReviewFindingSchema>;
2766
2838
 
@@ -2824,6 +2896,30 @@ export const ReviewFindingJsonSchema = {
2824
2896
  "default": null,
2825
2897
  "title": "Line"
2826
2898
  },
2899
+ "startLine": {
2900
+ "anyOf": [
2901
+ {
2902
+ "type": "integer"
2903
+ },
2904
+ {
2905
+ "type": "null"
2906
+ }
2907
+ ],
2908
+ "default": null,
2909
+ "title": "Startline"
2910
+ },
2911
+ "endLine": {
2912
+ "anyOf": [
2913
+ {
2914
+ "type": "integer"
2915
+ },
2916
+ {
2917
+ "type": "null"
2918
+ }
2919
+ ],
2920
+ "default": null,
2921
+ "title": "Endline"
2922
+ },
2827
2923
  "snippet": {
2828
2924
  "anyOf": [
2829
2925
  {
@@ -2859,6 +2955,18 @@ export const ReviewFindingJsonSchema = {
2859
2955
  ],
2860
2956
  "default": null,
2861
2957
  "title": "Counterexample"
2958
+ },
2959
+ "suggestion": {
2960
+ "anyOf": [
2961
+ {
2962
+ "type": "string"
2963
+ },
2964
+ {
2965
+ "type": "null"
2966
+ }
2967
+ ],
2968
+ "default": null,
2969
+ "title": "Suggestion"
2862
2970
  }
2863
2971
  },
2864
2972
  "required": [
@@ -7,7 +7,7 @@ export const CheckoutBranchInputSchema = z.object({
7
7
  });
8
8
 
9
9
  export async function checkoutBranchHandler(args: z.infer<typeof CheckoutBranchInputSchema>) {
10
- const result = await runCommand("td-cli", ["gh", "checkout", args.branch], { cwd: args.worktreePath });
10
+ const result = await runCommand("td", ["gh", "checkout", args.branch], { cwd: args.worktreePath });
11
11
 
12
12
  if (result.exitCode !== 0) {
13
13
  throw new Error(`Failed to checkout branch ${args.branch}: ${result.stderr}`);
@@ -7,7 +7,7 @@ export const CommentTriageSummaryInputSchema = z.object({
7
7
  });
8
8
 
9
9
  export async function commentTriageSummaryHandler(args: z.infer<typeof CommentTriageSummaryInputSchema>) {
10
- const result = await runCommand("td-cli", [
10
+ const result = await runCommand("td", [
11
11
  "gh",
12
12
  "post-comment",
13
13
  "--pr", args.prNumber.toString(),
@@ -23,11 +23,11 @@ describe("github.create_issue", () => {
23
23
  stderr: "",
24
24
  exitCode: 0,
25
25
  durationMs: 10,
26
- command: "td-cli gh create-issue --title 'Test Issue' --body 'Test Body'"
26
+ command: "td gh create-issue --title 'Test Issue' --body 'Test Body'"
27
27
  });
28
28
 
29
29
  const result = await createIssueHandler({ title: "Test Issue", body: "Test Body", file: null, repo: null });
30
- expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
30
+ expect(shell.runCommand).toHaveBeenCalledWith("td", [
31
31
  "gh",
32
32
  "create-issue",
33
33
  "--title",
@@ -58,7 +58,7 @@ describe("github.create_issue", () => {
58
58
  stderr: "",
59
59
  exitCode: 0,
60
60
  durationMs: 10,
61
- command: "td-cli gh create-issue --title 'Cross Repo Issue' --body 'Body' --repo 'org/other-repo'"
61
+ command: "td gh create-issue --title 'Cross Repo Issue' --body 'Body' --repo 'org/other-repo'"
62
62
  });
63
63
 
64
64
  const result = await createIssueHandler({
@@ -68,7 +68,7 @@ describe("github.create_issue", () => {
68
68
  repo: "org/other-repo"
69
69
  });
70
70
 
71
- expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
71
+ expect(shell.runCommand).toHaveBeenCalledWith("td", [
72
72
  "gh",
73
73
  "create-issue",
74
74
  "--title",
@@ -88,7 +88,7 @@ describe("github.create_issue", () => {
88
88
  stderr: "Auth failed",
89
89
  exitCode: 1,
90
90
  durationMs: 10,
91
- command: "td-cli gh create-issue"
91
+ command: "td gh create-issue"
92
92
  });
93
93
 
94
94
  await expect(createIssueHandler({ title: "Test Issue", body: "Test Body", file: null, repo: null })).rejects.toThrow("Failed to create issue: Auth failed");
@@ -105,7 +105,7 @@ describe("github.create_issue", () => {
105
105
  stderr: "",
106
106
  exitCode: 0,
107
107
  durationMs: 10,
108
- command: "td-cli gh create-issue"
108
+ command: "td gh create-issue"
109
109
  });
110
110
 
111
111
  await expect(createIssueHandler({ title: "Test Issue", body: "Test Body", file: null, repo: null })).rejects.toThrow("Failed to create issue: Repo not found");
@@ -15,7 +15,7 @@ export async function createIssueHandler(args: z.input<typeof CreateIssueInputSc
15
15
  if (params.file) cmdArgs.push("--file", params.file);
16
16
  cmdArgs.push("--repo", targetRepo);
17
17
 
18
- const result = await runCommand("td-cli", cmdArgs);
18
+ const result = await runCommand("td", cmdArgs);
19
19
 
20
20
  if (result.exitCode !== 0) {
21
21
  throw new Error(`Failed to create issue: ${sanitizeError(result.stderr)}`);
@@ -21,7 +21,7 @@ describe("github.create_pull_request", () => {
21
21
  stderr: "",
22
22
  exitCode: 0,
23
23
  durationMs: 10,
24
- command: "td-cli gh create-pr --title 'PR' --body 'Body' --head 'feature' --base 'main'"
24
+ command: "td gh create-pr --title 'PR' --body 'Body' --head 'feature' --base 'main'"
25
25
  });
26
26
 
27
27
  const result = await createPullRequestHandler({
@@ -50,7 +50,7 @@ describe("github.create_pull_request", () => {
50
50
  stderr: "",
51
51
  exitCode: 0,
52
52
  durationMs: 10,
53
- command: "td-cli gh create-pr --title 'PR' --body 'Body' --head 'feature' --base 'main' --repo 'org/other-repo'"
53
+ command: "td gh create-pr --title 'PR' --body 'Body' --head 'feature' --base 'main' --repo 'org/other-repo'"
54
54
  });
55
55
 
56
56
  const result = await createPullRequestHandler({
@@ -62,7 +62,7 @@ describe("github.create_pull_request", () => {
62
62
  repo: "org/other-repo"
63
63
  });
64
64
 
65
- expect(shell.runCommand).toHaveBeenCalledWith("td-cli", [
65
+ expect(shell.runCommand).toHaveBeenCalledWith("td", [
66
66
  "gh",
67
67
  "create-pr",
68
68
  "--title",
@@ -91,7 +91,7 @@ describe("github.create_pull_request", () => {
91
91
  stderr: "",
92
92
  exitCode: 0,
93
93
  durationMs: 10,
94
- command: "td-cli gh create-pr"
94
+ command: "td gh create-pr"
95
95
  });
96
96
 
97
97
  await expect(createPullRequestHandler({
@@ -23,7 +23,7 @@ export async function createPullRequestHandler(args: z.input<typeof CreatePullRe
23
23
  tdArgs.push("--draft");
24
24
  }
25
25
 
26
- const result = await runCommand("td-cli", tdArgs);
26
+ const result = await runCommand("td", tdArgs);
27
27
 
28
28
  if (result.exitCode !== 0) {
29
29
  // Attempt to parse stdout as JSON to see if there's a structured error message
@@ -12,7 +12,7 @@ export async function getPrHandler(args: z.infer<typeof GetPrInputSchema>) {
12
12
  const params = GetPrInputSchema.parse(args);
13
13
  const targetRepo = resolveRepo(params.repo);
14
14
 
15
- const result = await runCommand("td-cli", ["gh", "view", params.prNumber.toString(), "--repo", targetRepo]);
15
+ const result = await runCommand("td", ["gh", "view", params.prNumber.toString(), "--repo", targetRepo]);
16
16
 
17
17
  if (result.exitCode !== 0) {
18
18
  throw new Error(`Failed to get PR details: ${sanitizeError(result.stderr)}`);