@arii/boomtick-mcp 0.5.0 → 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 (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 +7 -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 +53 -13
  86. package/dist/mcp/server.test.js +20 -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 +1 -1
  96. package/dist/tools/github.get_pr_diff.js +1 -1
  97. package/dist/tools/github.get_pr_reviews.js +1 -1
  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 +1 -1
  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 +24 -0
  127. package/src/mcp/server.ts +63 -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 +1 -1
  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 +1 -1
  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 +1 -1
@@ -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
+ }
@@ -104,9 +104,32 @@ describe("BoomtickMCPServer", () => {
104
104
  expect(result.resources).toBeDefined();
105
105
  expect(Array.isArray(result.resources)).toBe(true);
106
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
+ });
107
115
  });
108
116
 
109
117
  describe("ReadResource", () => {
118
+ it("should handle mcp://workspace/agent-contract", async () => {
119
+ const handler = requestHandlers.get("resources/read");
120
+ vi.mocked(fs.readFile).mockResolvedValue("# Agent Contract\nRule 1");
121
+
122
+ const result = await handler!({
123
+ method: "resources/read",
124
+ params: { uri: "mcp://workspace/agent-contract" },
125
+ });
126
+
127
+ expect(fs.readFile).toHaveBeenCalledWith("/mock/repo/.agents/AGENT_CONTRACT.md", "utf-8");
128
+ expect(result).toEqual({
129
+ contents: [{ uri: "mcp://workspace/agent-contract", mimeType: "text/markdown", text: "# Agent Contract\nRule 1" }],
130
+ });
131
+ });
132
+
110
133
  it("should handle repo://package-json", async () => {
111
134
  const handler = requestHandlers.get("resources/read");
112
135
  vi.mocked(fs.readFile).mockResolvedValue('{"name": "test"}');
@@ -196,6 +219,7 @@ describe("BoomtickMCPServer", () => {
196
219
 
197
220
  expect(result).toEqual({
198
221
  content: [{ type: "text", text: JSON.stringify({ status: "ok" }, null, 2) }],
222
+ structuredContent: { status: "ok" },
199
223
  });
200
224
  });
201
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";
@@ -52,6 +53,7 @@ import path from "path";
52
53
 
53
54
  import { spawnSync } from "child_process";
54
55
  import { fileURLToPath } from "url";
56
+ import { createMcpHttpServer } from "./http.js";
55
57
 
56
58
  export class BoomtickMCPServer {
57
59
  private server: Server;
@@ -117,29 +119,55 @@ export class BoomtickMCPServer {
117
119
  };
118
120
  });
119
121
 
122
+ this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
123
+ return {
124
+ resourceTemplates: MCP_RESOURCE_TEMPLATES,
125
+ };
126
+ });
127
+
120
128
  this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
121
129
  const uri = request.params.uri;
122
- if (uri === "repo://package-json") {
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");
137
+ return {
138
+ contents: [{ uri, mimeType: "text/markdown", text: content }],
139
+ };
140
+ }
141
+ if (
142
+ uri === "mcp://git/changed-files" ||
143
+ uri === "repo://changed-files"
144
+ ) {
145
+ const changedFiles = await getChangedFilesHandler(GetChangedFilesInputSchema.parse({}));
146
+ return {
147
+ contents: [{ uri, mimeType: "application/json", text: JSON.stringify(changedFiles, null, 2) }],
148
+ };
149
+ }
150
+ if (uri === "repo://package-json" || uri === "mcp://workspace/package-json") {
123
151
  const content = await fs.readFile(path.join(config.repoPath, "package.json"), "utf-8");
124
152
  return {
125
153
  contents: [{ uri, mimeType: "application/json", text: content }],
126
154
  };
127
155
  }
128
- if (uri === "repo://design-tokens") {
156
+ if (uri === "repo://design-tokens" || uri === "mcp://workspace/design-tokens") {
129
157
  const tokensPath = path.join(config.repoPath, "src/styles/design-tokens.ts");
130
158
  const content = await fs.readFile(tokensPath, "utf-8");
131
159
  return {
132
160
  contents: [{ uri, mimeType: "text/typescript", text: content }],
133
161
  };
134
162
  }
135
- if (uri.startsWith("repo://diff/")) {
163
+ if (uri.startsWith("mcp://git/diff/") || uri.startsWith("repo://diff/")) {
136
164
  const prNumber = parseInt(uri.split("/").pop() || "");
137
165
  const diff = await getPrDiffHandler({ prNumber });
138
166
  return {
139
167
  contents: [{ uri, mimeType: "text/plain", text: diff.diffText }],
140
168
  };
141
169
  }
142
- if (uri.startsWith("repo://ci/")) {
170
+ if (uri.startsWith("mcp://ci/logs/") || uri.startsWith("repo://ci/")) {
143
171
  const prNumber = parseInt(uri.split("/").pop() || "");
144
172
  const logs = await readCiLogsHandler({ prNumber });
145
173
  return {
@@ -263,7 +291,7 @@ export class BoomtickMCPServer {
263
291
  }
264
292
  });
265
293
  }
266
- async run() {
294
+ private prepareEnvironmentAndPreflight() {
267
295
  const projectRoot = config.repoPath || process.cwd();
268
296
  const homeDir = process.env.HOME || "";
269
297
  const extraPaths = [];
@@ -315,9 +343,9 @@ export class BoomtickMCPServer {
315
343
  // 3. SAFE REPOSITORY RESOLUTION
316
344
  const repoPath = config.repoPath || projectRoot;
317
345
 
318
- // 4. SECURE, UNIX-ONLY PRE-FLIGHT CHECK FOR TD-CLI
346
+ // 4. SECURE, UNIX-ONLY PRE-FLIGHT CHECK FOR TD
319
347
  try {
320
- const result = spawnSync("td-cli", ["doctor"], {
348
+ const result = spawnSync("td", ["doctor"], {
321
349
  encoding: "utf-8",
322
350
  cwd: repoPath,
323
351
  shell: false // Explicitly disabled to clear Semgrep rules
@@ -331,16 +359,39 @@ export class BoomtickMCPServer {
331
359
  console.error("status:", result.status);
332
360
  console.error("stdout:", result.stdout);
333
361
  console.error("stderr:", result.stderr);
334
- throw new Error(`td-cli doctor returned exit code: ${result.status}`);
362
+ throw new Error(`td doctor returned exit code: ${result.status}`);
335
363
  }
336
364
 
337
- console.error("✅ td-cli verified on PATH securely");
365
+ console.error("✅ td verified on PATH securely");
338
366
  } catch (error) {
339
- 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.");
340
368
  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));
369
+ console.error("❌ Fatal: td pre-flight check failed:", error instanceof Error ? error.message : String(error));
342
370
  process.exit(1);
343
371
  }
372
+ }
373
+
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;
394
+ }
344
395
 
345
396
  const transport = new StdioServerTransport();
346
397
  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)}`);
@@ -12,7 +12,7 @@ export async function getPrCommitsHandler(args: z.infer<typeof GetPrCommitsInput
12
12
  const params = GetPrCommitsInputSchema.parse(args);
13
13
  const targetRepo = resolveRepo(params.repo);
14
14
 
15
- const result = await runCommand("td-cli", ["gh", "list-pr-commits", "--pr-number", params.prNumber.toString(), "--repo", targetRepo]);
15
+ const result = await runCommand("td", ["gh", "list-pr-commits", "--pr-number", params.prNumber.toString(), "--repo", targetRepo]);
16
16
 
17
17
  if (result.exitCode !== 0) {
18
18
  throw new Error(`Failed to list PR commits: ${sanitizeError(result.stderr)}`);
@@ -11,7 +11,7 @@ export async function getPrDiffHandler(args: z.infer<typeof GetPrDiffInputSchema
11
11
  const params = GetPrDiffInputSchema.parse(args);
12
12
  const targetRepo = resolveRepo(params.repo);
13
13
 
14
- const result = await runCommand("td-cli", [
14
+ const result = await runCommand("td", [
15
15
  "gh", "pr-diff", params.prNumber.toString(), "--repo", targetRepo
16
16
  ]);
17
17