@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.
- package/.mcp/schemas/boomtick.health.json +5 -0
- package/.mcp/schemas/boomtick_health.json +5 -0
- package/.mcp/schemas/github.checkout_branch.json +5 -0
- package/.mcp/schemas/github.comment_triage_summary.json +5 -0
- package/.mcp/schemas/github.create_issue.json +5 -0
- package/.mcp/schemas/github.create_pull_request.json +5 -0
- package/.mcp/schemas/github.get_merge_conflict_files.json +5 -0
- package/.mcp/schemas/github.get_pr.json +5 -0
- package/.mcp/schemas/github.get_pr_commits.json +5 -0
- package/.mcp/schemas/github.get_pr_diff.json +5 -0
- package/.mcp/schemas/github.get_pr_reviews.json +5 -0
- package/.mcp/schemas/github.issue_comment.json +5 -0
- package/.mcp/schemas/github.issue_update.json +5 -0
- package/.mcp/schemas/github.issue_view.json +5 -0
- package/.mcp/schemas/github.open_replacement_pr.json +5 -0
- package/.mcp/schemas/github.search_open_prs.json +5 -0
- package/.mcp/schemas/github_checkout_branch.json +5 -0
- package/.mcp/schemas/github_comment_triage_summary.json +5 -0
- package/.mcp/schemas/github_create_issue.json +5 -0
- package/.mcp/schemas/github_create_pull_request.json +5 -0
- package/.mcp/schemas/github_get_merge_conflict_files.json +5 -0
- package/.mcp/schemas/github_get_pr.json +5 -0
- package/.mcp/schemas/github_get_pr_commits.json +5 -0
- package/.mcp/schemas/github_get_pr_diff.json +5 -0
- package/.mcp/schemas/github_get_pr_reviews.json +5 -0
- package/.mcp/schemas/github_issue_comment.json +5 -0
- package/.mcp/schemas/github_issue_update.json +5 -0
- package/.mcp/schemas/github_issue_view.json +5 -0
- package/.mcp/schemas/github_open_replacement_pr.json +5 -0
- package/.mcp/schemas/github_search_open_prs.json +5 -0
- package/.mcp/schemas/jules.cancel_session.json +5 -0
- package/.mcp/schemas/jules.create_session.json +5 -0
- package/.mcp/schemas/jules.get_messages.json +5 -0
- package/.mcp/schemas/jules.get_pr.json +5 -0
- package/.mcp/schemas/jules.get_session.json +5 -0
- package/.mcp/schemas/jules.list_sessions.json +5 -0
- package/.mcp/schemas/jules.send_message.json +5 -0
- package/.mcp/schemas/jules.trigger_feedback.json +5 -0
- package/.mcp/schemas/jules_cancel_session.json +5 -0
- package/.mcp/schemas/jules_create_session.json +5 -0
- package/.mcp/schemas/jules_get_messages.json +5 -0
- package/.mcp/schemas/jules_get_pr.json +5 -0
- package/.mcp/schemas/jules_get_session.json +5 -0
- package/.mcp/schemas/jules_list_sessions.json +5 -0
- package/.mcp/schemas/jules_send_message.json +5 -0
- package/.mcp/schemas/jules_trigger_feedback.json +5 -0
- package/.mcp/schemas/repo.commit_patch.json +5 -0
- package/.mcp/schemas/repo.create_branch.json +5 -0
- package/.mcp/schemas/repo.create_repair_branch.json +5 -0
- package/.mcp/schemas/repo.get_changed_files.json +5 -0
- package/.mcp/schemas/repo.get_command_schema.json +6 -1
- package/.mcp/schemas/repo.get_package_scripts.json +5 -0
- package/.mcp/schemas/repo.get_workflow_runs.json +5 -0
- package/.mcp/schemas/repo.logs.json +5 -0
- package/.mcp/schemas/repo.read_agent_context.json +5 -0
- package/.mcp/schemas/repo.read_ci_logs.json +5 -0
- package/.mcp/schemas/repo.run_tests.json +5 -0
- package/.mcp/schemas/repo_commit_patch.json +5 -0
- package/.mcp/schemas/repo_create_branch.json +5 -0
- package/.mcp/schemas/repo_create_repair_branch.json +5 -0
- package/.mcp/schemas/repo_get_changed_files.json +5 -0
- package/.mcp/schemas/repo_get_command_schema.json +6 -1
- package/.mcp/schemas/repo_get_package_scripts.json +5 -0
- package/.mcp/schemas/repo_get_workflow_runs.json +5 -0
- package/.mcp/schemas/repo_logs.json +5 -0
- package/.mcp/schemas/repo_read_agent_context.json +5 -0
- package/.mcp/schemas/repo_read_ci_logs.json +5 -0
- package/.mcp/schemas/repo_run_tests.json +5 -0
- package/CHANGELOG.md +14 -0
- package/actions/ai-review/action.yml +2 -2
- package/actions/audit/action.yml +1 -1
- package/actions/ci-validate/action.yml +2 -2
- package/actions/impact-analysis/action.yml +3 -3
- package/actions/run-project-gate/action.yml +5 -5
- package/actions/setup/action.yml +1 -1
- package/actions/test-build/action.yml +1 -1
- package/dist/config.js +1 -1
- package/dist/config.test.js +3 -3
- package/dist/lib/result.js +4 -0
- package/dist/lib/result.test.js +4 -2
- package/dist/lib/shell.js +6 -6
- package/dist/mcp/definitions.js +73 -2
- package/dist/mcp/http.js +91 -0
- package/dist/mcp/http.test.js +58 -0
- package/dist/mcp/server.js +65 -13
- package/dist/mcp/server.test.js +44 -0
- package/dist/tools/contract.js +109 -1
- package/dist/tools/github.checkout_branch.js +1 -1
- package/dist/tools/github.comment_triage_summary.js +1 -1
- package/dist/tools/github.create_issue.js +1 -1
- package/dist/tools/github.create_issue.test.js +6 -6
- package/dist/tools/github.create_pull_request.js +1 -1
- package/dist/tools/github.create_pull_request.test.js +4 -4
- package/dist/tools/github.get_pr.js +1 -1
- package/dist/tools/github.get_pr_commits.js +2 -2
- package/dist/tools/github.get_pr_diff.js +1 -1
- package/dist/tools/github.get_pr_reviews.js +2 -2
- package/dist/tools/github.get_pr_reviews.test.js +9 -9
- package/dist/tools/github.issue_comment.js +1 -1
- package/dist/tools/github.issue_update.js +1 -1
- package/dist/tools/github.issue_view.js +1 -1
- package/dist/tools/github.search_open_prs.js +1 -1
- package/dist/tools/jules/cancel-session.js +1 -1
- package/dist/tools/jules/create-session.test.js +11 -11
- package/dist/tools/jules/get-messages.js +1 -1
- package/dist/tools/jules/get-messages.test.js +3 -3
- package/dist/tools/jules/get-pr.test.js +3 -3
- package/dist/tools/jules/list-sessions.js +1 -1
- package/dist/tools/jules/send-message.js +1 -1
- package/dist/tools/jules/send-message.test.js +5 -5
- package/dist/tools/jules/shared.js +4 -4
- package/dist/tools/jules/trigger-feedback.js +1 -1
- package/dist/tools/jules/trigger-feedback.test.js +5 -5
- package/dist/tools/repo.get_command_schema.js +2 -2
- package/dist/tools/repo.get_workflow_runs.js +2 -2
- package/package.json +1 -1
- package/scripts/sync-mcp-schemas.ts +2 -0
- package/src/config.test.ts +3 -3
- package/src/config.ts +1 -1
- package/src/lib/result.test.ts +4 -2
- package/src/lib/result.ts +6 -0
- package/src/lib/shell.ts +6 -6
- package/src/mcp/definitions.ts +76 -3
- package/src/mcp/http.test.ts +72 -0
- package/src/mcp/http.ts +118 -0
- package/src/mcp/server.test.ts +52 -0
- package/src/mcp/server.ts +75 -12
- package/src/tools/contract.ts +109 -1
- package/src/tools/github.checkout_branch.ts +1 -1
- package/src/tools/github.comment_triage_summary.ts +1 -1
- package/src/tools/github.create_issue.test.ts +6 -6
- package/src/tools/github.create_issue.ts +1 -1
- package/src/tools/github.create_pull_request.test.ts +4 -4
- package/src/tools/github.create_pull_request.ts +1 -1
- package/src/tools/github.get_pr.ts +1 -1
- package/src/tools/github.get_pr_commits.ts +2 -2
- package/src/tools/github.get_pr_diff.ts +1 -1
- package/src/tools/github.get_pr_reviews.test.ts +9 -9
- package/src/tools/github.get_pr_reviews.ts +2 -2
- package/src/tools/github.issue_comment.ts +1 -1
- package/src/tools/github.issue_update.ts +1 -1
- package/src/tools/github.issue_view.ts +1 -1
- package/src/tools/github.search_open_prs.ts +1 -1
- package/src/tools/jules/cancel-session.ts +1 -1
- package/src/tools/jules/create-session.test.ts +11 -11
- package/src/tools/jules/get-messages.test.ts +3 -3
- package/src/tools/jules/get-messages.ts +1 -1
- package/src/tools/jules/get-pr.test.ts +3 -3
- package/src/tools/jules/list-sessions.ts +1 -1
- package/src/tools/jules/send-message.test.ts +5 -5
- package/src/tools/jules/send-message.ts +1 -1
- package/src/tools/jules/shared.ts +4 -4
- package/src/tools/jules/trigger-feedback.test.ts +5 -5
- package/src/tools/jules/trigger-feedback.ts +1 -1
- package/src/tools/repo.get_command_schema.ts +2 -2
- package/src/tools/repo.get_workflow_runs.ts +2 -2
package/src/mcp/http.ts
ADDED
|
@@ -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
|
+
}
|
package/src/mcp/server.test.ts
CHANGED
|
@@ -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 (
|
|
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
|
-
|
|
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
|
|
358
|
+
// 4. SECURE, UNIX-ONLY PRE-FLIGHT CHECK FOR TD
|
|
319
359
|
try {
|
|
320
|
-
const result = spawnSync("td
|
|
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
|
|
374
|
+
throw new Error(`td doctor returned exit code: ${result.status}`);
|
|
335
375
|
}
|
|
336
376
|
|
|
337
|
-
console.error("✅ td
|
|
377
|
+
console.error("✅ td verified on PATH securely");
|
|
338
378
|
} catch (error) {
|
|
339
|
-
console.error("❌ Fatal: td
|
|
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
|
|
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);
|
package/src/tools/contract.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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)}`);
|