@arii/boomtick-mcp 0.4.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +37 -0
- package/.mcp/schemas/github.get_pr_diff.json +5 -0
- package/.mcp/schemas/github.get_pr_reviews.json +37 -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 +37 -0
- package/.mcp/schemas/github_get_pr_diff.json +5 -0
- package/.mcp/schemas/github_get_pr_reviews.json +37 -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 +55 -0
- package/.mcp/schemas/repo.logs.json +19 -0
- package/.mcp/schemas/repo.read_agent_context.json +5 -0
- package/.mcp/schemas/repo.read_ci_logs.json +19 -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 +55 -0
- package/.mcp/schemas/repo_logs.json +19 -0
- package/.mcp/schemas/repo_read_agent_context.json +5 -0
- package/.mcp/schemas/repo_read_ci_logs.json +19 -0
- package/.mcp/schemas/repo_run_tests.json +5 -0
- package/CHANGELOG.md +14 -0
- package/README.md +0 -1
- 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 +7 -19
- package/actions/run-project-gate/action.yml +7 -14
- package/actions/setup/action.yml +1 -1
- package/actions/test-build/action.yml +2 -20
- package/dist/config.js +1 -1
- package/dist/config.test.js +3 -3
- package/dist/lib/git.js +44 -0
- package/dist/lib/git.test.js +82 -0
- 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 +86 -38
- package/dist/mcp/http.js +91 -0
- package/dist/mcp/http.test.js +58 -0
- package/dist/mcp/server.js +51 -57
- package/dist/mcp/server.test.js +19 -32
- package/dist/tools/contract.js +244 -3
- 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_merge_conflict_files.js +10 -18
- package/dist/tools/github.get_pr.js +1 -1
- package/dist/tools/github.get_pr_commits.js +21 -0
- package/dist/tools/github.get_pr_diff.js +1 -1
- package/dist/tools/github.get_pr_reviews.js +21 -0
- package/dist/tools/github.get_pr_reviews.test.js +99 -0
- 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.js +1 -2
- 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.js +1 -2
- package/dist/tools/jules/get-pr.test.js +3 -3
- package/dist/tools/jules/get-session.js +1 -2
- 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 +24 -0
- package/dist/tools/jules/trigger-feedback.js +1 -1
- package/dist/tools/jules/trigger-feedback.test.js +5 -5
- package/dist/tools/repo.get_changed_files.js +2 -13
- package/dist/tools/repo.get_command_schema.js +2 -2
- package/dist/tools/repo.get_workflow_runs.js +29 -0
- package/dist/tools/repo.logs.js +5 -1
- package/dist/tools/repo.read_agent_context.js +27 -1
- package/dist/tools/repo.read_agent_context.test.js +1 -1
- package/dist/tools/repo.read_ci_logs.js +5 -1
- package/dist/tools/repo.run_tests.js +1 -1
- package/package.json +3 -6
- package/scripts/sync-mcp-schemas.ts +2 -0
- package/src/agents/verifier-agent.prompt.md +0 -4
- package/src/config.test.ts +3 -3
- package/src/config.ts +1 -1
- package/src/lib/git.test.ts +110 -0
- package/src/lib/git.ts +75 -0
- 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 +91 -42
- package/src/mcp/http.test.ts +72 -0
- package/src/mcp/http.ts +118 -0
- package/src/mcp/server.test.ts +20 -38
- package/src/mcp/server.ts +60 -54
- package/src/tools/contract.ts +256 -3
- 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_merge_conflict_files.ts +10 -20
- package/src/tools/github.get_pr.ts +1 -1
- package/src/tools/github.get_pr_commits.ts +27 -0
- package/src/tools/github.get_pr_diff.ts +1 -1
- package/src/tools/github.get_pr_reviews.test.ts +110 -0
- package/src/tools/github.get_pr_reviews.ts +27 -0
- 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/create-session.ts +1 -2
- 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/get-pr.ts +1 -2
- package/src/tools/jules/get-session.ts +1 -2
- 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 +35 -0
- package/src/tools/jules/trigger-feedback.test.ts +5 -5
- package/src/tools/jules/trigger-feedback.ts +1 -1
- package/src/tools/repo.get_changed_files.ts +2 -16
- package/src/tools/repo.get_command_schema.ts +2 -2
- package/src/tools/repo.get_workflow_runs.ts +36 -0
- package/src/tools/repo.logs.ts +5 -1
- package/src/tools/repo.read_agent_context.test.ts +1 -1
- package/src/tools/repo.read_agent_context.ts +35 -1
- package/src/tools/repo.read_ci_logs.ts +5 -1
- package/src/tools/repo.run_tests.ts +1 -1
- package/.mcp/schemas/agent.search_ddgs.json +0 -31
- package/.mcp/schemas/agent_search_ddgs.json +0 -31
- package/.mcp/schemas/repo.get_route_map.json +0 -23
- package/.mcp/schemas/repo.run_lighthouse.json +0 -29
- package/.mcp/schemas/repo.run_playwright.json +0 -36
- package/.mcp/schemas/repo_get_route_map.json +0 -23
- package/.mcp/schemas/repo_run_lighthouse.json +0 -29
- package/.mcp/schemas/repo_run_playwright.json +0 -36
- package/dist/lib/td-cli.js +0 -54
- package/dist/tools/ddgs.search.js +0 -50
- package/dist/tools/ddgs.search.test.js +0 -67
- package/dist/tools/ddgs_search.py +0 -23
- package/dist/tools/repo.get_route_map.js +0 -37
- package/dist/tools/repo.run_lighthouse.js +0 -37
- package/dist/tools/repo.run_playwright.js +0 -34
- package/scripts/export-mcp-schema.ts +0 -7
- package/src/lib/td-cli.ts +0 -73
- package/src/tools/ddgs.search.test.ts +0 -92
- package/src/tools/ddgs.search.ts +0 -58
- package/src/tools/ddgs_search.py +0 -23
- package/src/tools/repo.get_route_map.ts +0 -41
- package/src/tools/repo.run_lighthouse.ts +0 -40
- package/src/tools/repo.run_playwright.ts +0 -42
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
|
@@ -3,11 +3,8 @@ import { BoomtickMCPServer } from "./server.js";
|
|
|
3
3
|
import { config } from "../config.js";
|
|
4
4
|
import fs from "fs/promises";
|
|
5
5
|
import * as healthModule from "./tools.js";
|
|
6
|
-
import * as routeMapModule from "../tools/repo.get_route_map.js";
|
|
7
6
|
import * as prDiffModule from "../tools/github.get_pr_diff.js";
|
|
8
7
|
import * as ciLogsModule from "../tools/repo.read_ci_logs.js";
|
|
9
|
-
import * as lighthouseModule from "../tools/repo.run_lighthouse.js";
|
|
10
|
-
import * as playwrightModule from "../tools/repo.run_playwright.js";
|
|
11
8
|
|
|
12
9
|
vi.mock("fs/promises");
|
|
13
10
|
vi.mock("../config.js", () => ({
|
|
@@ -107,34 +104,44 @@ describe("BoomtickMCPServer", () => {
|
|
|
107
104
|
expect(result.resources).toBeDefined();
|
|
108
105
|
expect(Array.isArray(result.resources)).toBe(true);
|
|
109
106
|
});
|
|
107
|
+
|
|
108
|
+
it("should return resource templates list", async () => {
|
|
109
|
+
const handler = requestHandlers.get("resources/templates/list");
|
|
110
|
+
expect(handler).toBeDefined();
|
|
111
|
+
const result = await handler!({ method: "resources/templates/list" });
|
|
112
|
+
expect(result.resourceTemplates).toBeDefined();
|
|
113
|
+
expect(Array.isArray(result.resourceTemplates)).toBe(true);
|
|
114
|
+
});
|
|
110
115
|
});
|
|
111
116
|
|
|
112
117
|
describe("ReadResource", () => {
|
|
113
|
-
it("should handle
|
|
118
|
+
it("should handle mcp://workspace/agent-contract", async () => {
|
|
114
119
|
const handler = requestHandlers.get("resources/read");
|
|
115
|
-
vi.mocked(fs.readFile).mockResolvedValue(
|
|
120
|
+
vi.mocked(fs.readFile).mockResolvedValue("# Agent Contract\nRule 1");
|
|
116
121
|
|
|
117
122
|
const result = await handler!({
|
|
118
123
|
method: "resources/read",
|
|
119
|
-
params: { uri: "
|
|
124
|
+
params: { uri: "mcp://workspace/agent-contract" },
|
|
120
125
|
});
|
|
121
126
|
|
|
127
|
+
expect(fs.readFile).toHaveBeenCalledWith("/mock/repo/.agents/AGENT_CONTRACT.md", "utf-8");
|
|
122
128
|
expect(result).toEqual({
|
|
123
|
-
contents: [{ uri: "
|
|
129
|
+
contents: [{ uri: "mcp://workspace/agent-contract", mimeType: "text/markdown", text: "# Agent Contract\nRule 1" }],
|
|
124
130
|
});
|
|
125
131
|
});
|
|
126
132
|
|
|
127
|
-
it("should handle repo://
|
|
133
|
+
it("should handle repo://package-json", async () => {
|
|
128
134
|
const handler = requestHandlers.get("resources/read");
|
|
129
|
-
vi.
|
|
135
|
+
vi.mocked(fs.readFile).mockResolvedValue('{"name": "test"}');
|
|
130
136
|
|
|
131
137
|
const result = await handler!({
|
|
132
138
|
method: "resources/read",
|
|
133
|
-
params: { uri: "repo://
|
|
139
|
+
params: { uri: "repo://package-json" },
|
|
134
140
|
});
|
|
135
141
|
|
|
136
|
-
expect(result
|
|
137
|
-
|
|
142
|
+
expect(result).toEqual({
|
|
143
|
+
contents: [{ uri: "repo://package-json", mimeType: "application/json", text: '{"name": "test"}' }],
|
|
144
|
+
});
|
|
138
145
|
});
|
|
139
146
|
|
|
140
147
|
it("should handle repo://design-tokens", async () => {
|
|
@@ -178,32 +185,6 @@ describe("BoomtickMCPServer", () => {
|
|
|
178
185
|
expect(JSON.parse(result.contents[0].text)).toEqual({ status: "success" });
|
|
179
186
|
});
|
|
180
187
|
|
|
181
|
-
it("should handle repo://lighthouse/:branch", async () => {
|
|
182
|
-
const handler = requestHandlers.get("resources/read");
|
|
183
|
-
vi.spyOn(lighthouseModule, "runLighthouseHandler").mockResolvedValue({ score: 100 } as any);
|
|
184
|
-
|
|
185
|
-
const result = await handler!({
|
|
186
|
-
method: "resources/read",
|
|
187
|
-
params: { uri: "repo://lighthouse/feature-x" },
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
expect(result.contents[0].uri).toBe("repo://lighthouse/feature-x");
|
|
191
|
-
expect(JSON.parse(result.contents[0].text)).toEqual({ score: 100 });
|
|
192
|
-
});
|
|
193
|
-
|
|
194
|
-
it("should handle repo://playwright/:branch", async () => {
|
|
195
|
-
const handler = requestHandlers.get("resources/read");
|
|
196
|
-
vi.spyOn(playwrightModule, "runPlaywrightHandler").mockResolvedValue({ passed: true } as any);
|
|
197
|
-
|
|
198
|
-
const result = await handler!({
|
|
199
|
-
method: "resources/read",
|
|
200
|
-
params: { uri: "repo://playwright/feature-x" },
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
expect(result.contents[0].uri).toBe("repo://playwright/feature-x");
|
|
204
|
-
expect(JSON.parse(result.contents[0].text)).toEqual({ passed: true });
|
|
205
|
-
});
|
|
206
|
-
|
|
207
188
|
it("should throw when resource is unknown", async () => {
|
|
208
189
|
const handler = requestHandlers.get("resources/read");
|
|
209
190
|
|
|
@@ -238,6 +219,7 @@ describe("BoomtickMCPServer", () => {
|
|
|
238
219
|
|
|
239
220
|
expect(result).toEqual({
|
|
240
221
|
content: [{ type: "text", text: JSON.stringify({ status: "ok" }, null, 2) }],
|
|
222
|
+
structuredContent: { status: "ok" },
|
|
241
223
|
});
|
|
242
224
|
});
|
|
243
225
|
|
package/src/mcp/server.ts
CHANGED
|
@@ -4,13 +4,14 @@ import {
|
|
|
4
4
|
CallToolRequestSchema,
|
|
5
5
|
ListToolsRequestSchema,
|
|
6
6
|
ListResourcesRequestSchema,
|
|
7
|
+
ListResourceTemplatesRequestSchema,
|
|
7
8
|
ReadResourceRequestSchema,
|
|
8
9
|
ListPromptsRequestSchema,
|
|
9
10
|
GetPromptRequestSchema,
|
|
10
11
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
11
12
|
import { config } from "../config.js";
|
|
12
13
|
import { createSuccessResult, createErrorResult } from "../lib/result.js";
|
|
13
|
-
import { MCP_TOOLS, MCP_PROMPTS, MCP_RESOURCES } from "./definitions.js";
|
|
14
|
+
import { MCP_TOOLS, MCP_PROMPTS, MCP_RESOURCES, MCP_RESOURCE_TEMPLATES } from "./definitions.js";
|
|
14
15
|
import { normalizeArguments } from "../lib/param_utils.js";
|
|
15
16
|
import { healthHandler, HealthCheckInputSchema } from "./tools.js";
|
|
16
17
|
import { searchOpenPrsHandler, SearchOpenPrsInputSchema } from "../tools/github.search_open_prs.js";
|
|
@@ -21,14 +22,11 @@ import { getChangedFilesHandler, GetChangedFilesInputSchema } from "../tools/rep
|
|
|
21
22
|
import { readAgentContextHandler, ReadAgentContextInputSchema } from "../tools/repo.read_agent_context.js";
|
|
22
23
|
import { getCommandSchemaHandler, GetCommandSchemaInputSchema } from "../tools/repo.get_command_schema.js";
|
|
23
24
|
import { getPackageScriptsHandler, GetPackageScriptsInputSchema } from "../tools/repo.get_package_scripts.js";
|
|
24
|
-
import { getRouteMapHandler, GetRouteMapInputSchema } from "../tools/repo.get_route_map.js";
|
|
25
25
|
import { readCiLogsHandler, ReadCiLogsInputSchema } from "../tools/repo.read_ci_logs.js";
|
|
26
26
|
import { repoLogsHandler, RepoLogsInputSchema } from "../tools/repo.logs.js";
|
|
27
27
|
import { createRepairBranchHandler, CreateRepairBranchInputSchema } from "../tools/repo.create_repair_branch.js";
|
|
28
28
|
import { createBranchHandler, CreateBranchInputSchema } from "../tools/repo.create_branch.js";
|
|
29
29
|
import { runTestsHandler, RunTestsInputSchema } from "../tools/repo.run_tests.js";
|
|
30
|
-
import { runLighthouseHandler, RunLighthouseInputSchema } from "../tools/repo.run_lighthouse.js";
|
|
31
|
-
import { runPlaywrightHandler, RunPlaywrightInputSchema } from "../tools/repo.run_playwright.js";
|
|
32
30
|
import { commitPatchHandler, CommitPatchInputSchema } from "../tools/repo.commit_patch.js";
|
|
33
31
|
import { openReplacementPrHandler, OpenReplacementPrInputSchema } from "../tools/github.open_replacement_pr.js";
|
|
34
32
|
import { commentTriageSummaryHandler, CommentTriageSummaryInputSchema } from "../tools/github.comment_triage_summary.js";
|
|
@@ -48,7 +46,6 @@ import { listJulesSessionsHandler, ListJulesSessionsInputSchema } from "../tools
|
|
|
48
46
|
import { cancelJulesSessionHandler, CancelJulesSessionInputSchema } from "../tools/jules/cancel-session.js";
|
|
49
47
|
import { getJulesPullRequestHandler, GetJulesPullRequestInputSchema } from "../tools/jules/get-pr.js";
|
|
50
48
|
import { triggerJulesFeedbackHandler, TriggerJulesFeedbackInputSchema } from "../tools/jules/trigger-feedback.js";
|
|
51
|
-
import { ddgsSearchHandler, DdgsSearchInputSchema } from "../tools/ddgs.search.js";
|
|
52
49
|
|
|
53
50
|
import fs from "fs/promises";
|
|
54
51
|
import { existsSync, readFileSync } from "fs";
|
|
@@ -56,6 +53,7 @@ import path from "path";
|
|
|
56
53
|
|
|
57
54
|
import { spawnSync } from "child_process";
|
|
58
55
|
import { fileURLToPath } from "url";
|
|
56
|
+
import { createMcpHttpServer } from "./http.js";
|
|
59
57
|
|
|
60
58
|
export class BoomtickMCPServer {
|
|
61
59
|
private server: Server;
|
|
@@ -121,55 +119,61 @@ export class BoomtickMCPServer {
|
|
|
121
119
|
};
|
|
122
120
|
});
|
|
123
121
|
|
|
122
|
+
this.server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => {
|
|
123
|
+
return {
|
|
124
|
+
resourceTemplates: MCP_RESOURCE_TEMPLATES,
|
|
125
|
+
};
|
|
126
|
+
});
|
|
127
|
+
|
|
124
128
|
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
125
129
|
const uri = request.params.uri;
|
|
126
|
-
if (
|
|
127
|
-
|
|
130
|
+
if (
|
|
131
|
+
uri === "mcp://workspace/agent-contract" ||
|
|
132
|
+
uri === "mcp://workspace/agent_contract" ||
|
|
133
|
+
uri === "repo://agent-contract"
|
|
134
|
+
) {
|
|
135
|
+
const contractPath = path.join(config.repoPath, ".agents", "AGENT_CONTRACT.md");
|
|
136
|
+
const content = await fs.readFile(contractPath, "utf-8");
|
|
128
137
|
return {
|
|
129
|
-
contents: [{ uri, mimeType: "
|
|
138
|
+
contents: [{ uri, mimeType: "text/markdown", text: content }],
|
|
130
139
|
};
|
|
131
140
|
}
|
|
132
|
-
if (
|
|
133
|
-
|
|
141
|
+
if (
|
|
142
|
+
uri === "mcp://git/changed-files" ||
|
|
143
|
+
uri === "repo://changed-files"
|
|
144
|
+
) {
|
|
145
|
+
const changedFiles = await getChangedFilesHandler(GetChangedFilesInputSchema.parse({}));
|
|
134
146
|
return {
|
|
135
|
-
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(
|
|
147
|
+
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(changedFiles, null, 2) }],
|
|
136
148
|
};
|
|
137
149
|
}
|
|
138
|
-
if (uri === "repo://
|
|
150
|
+
if (uri === "repo://package-json" || uri === "mcp://workspace/package-json") {
|
|
151
|
+
const content = await fs.readFile(path.join(config.repoPath, "package.json"), "utf-8");
|
|
152
|
+
return {
|
|
153
|
+
contents: [{ uri, mimeType: "application/json", text: content }],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (uri === "repo://design-tokens" || uri === "mcp://workspace/design-tokens") {
|
|
139
157
|
const tokensPath = path.join(config.repoPath, "src/styles/design-tokens.ts");
|
|
140
158
|
const content = await fs.readFile(tokensPath, "utf-8");
|
|
141
159
|
return {
|
|
142
160
|
contents: [{ uri, mimeType: "text/typescript", text: content }],
|
|
143
161
|
};
|
|
144
162
|
}
|
|
145
|
-
if (uri.startsWith("repo://diff/")) {
|
|
163
|
+
if (uri.startsWith("mcp://git/diff/") || uri.startsWith("repo://diff/")) {
|
|
146
164
|
const prNumber = parseInt(uri.split("/").pop() || "");
|
|
147
165
|
const diff = await getPrDiffHandler({ prNumber });
|
|
148
166
|
return {
|
|
149
167
|
contents: [{ uri, mimeType: "text/plain", text: diff.diffText }],
|
|
150
168
|
};
|
|
151
169
|
}
|
|
152
|
-
if (uri.startsWith("repo://ci/")) {
|
|
170
|
+
if (uri.startsWith("mcp://ci/logs/") || uri.startsWith("repo://ci/")) {
|
|
153
171
|
const prNumber = parseInt(uri.split("/").pop() || "");
|
|
154
172
|
const logs = await readCiLogsHandler({ prNumber });
|
|
155
173
|
return {
|
|
156
174
|
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(logs, null, 2) }],
|
|
157
175
|
};
|
|
158
176
|
}
|
|
159
|
-
if (uri.startsWith("repo://lighthouse/")) {
|
|
160
|
-
const branch = uri.split("/").pop() || "";
|
|
161
|
-
const report = await runLighthouseHandler({ route: "/", worktreePath: `../boomtick-mcp-rescue-${branch}` });
|
|
162
|
-
return {
|
|
163
|
-
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(report, null, 2) }],
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
if (uri.startsWith("repo://playwright/")) {
|
|
167
|
-
const branch = uri.split("/").pop() || "";
|
|
168
|
-
const report = await runPlaywrightHandler({ worktreePath: `../boomtick-mcp-rescue-${branch}` });
|
|
169
|
-
return {
|
|
170
|
-
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(report, null, 2) }],
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
177
|
throw new Error(`Resource not found: ${uri}`);
|
|
174
178
|
});
|
|
175
179
|
}
|
|
@@ -213,9 +217,6 @@ export class BoomtickMCPServer {
|
|
|
213
217
|
case "repo.get.package.scripts":
|
|
214
218
|
case "repo.get_package_scripts":
|
|
215
219
|
return createSuccessResult(await getPackageScriptsHandler(GetPackageScriptsInputSchema.parse(normArgs)));
|
|
216
|
-
case "repo.get.route.map":
|
|
217
|
-
case "repo.get_route_map":
|
|
218
|
-
return createSuccessResult(await getRouteMapHandler(GetRouteMapInputSchema.parse(normArgs)));
|
|
219
220
|
case "repo.read.ci.logs":
|
|
220
221
|
case "repo.read_ci_logs":
|
|
221
222
|
return createSuccessResult(await readCiLogsHandler(ReadCiLogsInputSchema.parse(normArgs)));
|
|
@@ -230,12 +231,6 @@ export class BoomtickMCPServer {
|
|
|
230
231
|
case "repo.run.tests":
|
|
231
232
|
case "repo.run_tests":
|
|
232
233
|
return createSuccessResult(await runTestsHandler(RunTestsInputSchema.parse(normArgs)));
|
|
233
|
-
case "repo.run.lighthouse":
|
|
234
|
-
case "repo.run_lighthouse":
|
|
235
|
-
return createSuccessResult(await runLighthouseHandler(RunLighthouseInputSchema.parse(normArgs)));
|
|
236
|
-
case "repo.run.playwright":
|
|
237
|
-
case "repo.run_playwright":
|
|
238
|
-
return createSuccessResult(await runPlaywrightHandler(RunPlaywrightInputSchema.parse(normArgs)));
|
|
239
234
|
case "repo.commit.patch":
|
|
240
235
|
case "repo.commit_patch":
|
|
241
236
|
return createSuccessResult(await commitPatchHandler(CommitPatchInputSchema.parse(normArgs)));
|
|
@@ -288,9 +283,6 @@ export class BoomtickMCPServer {
|
|
|
288
283
|
case "jules.trigger.feedback":
|
|
289
284
|
case "jules.trigger_feedback":
|
|
290
285
|
return createSuccessResult(await triggerJulesFeedbackHandler(TriggerJulesFeedbackInputSchema.parse(normArgs)));
|
|
291
|
-
case "agent.search.ddgs":
|
|
292
|
-
case "agent.search_ddgs":
|
|
293
|
-
return createSuccessResult(await ddgsSearchHandler(DdgsSearchInputSchema.parse(normArgs)));
|
|
294
286
|
default:
|
|
295
287
|
return createErrorResult(`Tool not found: ${request.params.name}`);
|
|
296
288
|
}
|
|
@@ -299,7 +291,7 @@ export class BoomtickMCPServer {
|
|
|
299
291
|
}
|
|
300
292
|
});
|
|
301
293
|
}
|
|
302
|
-
|
|
294
|
+
private prepareEnvironmentAndPreflight() {
|
|
303
295
|
const projectRoot = config.repoPath || process.cwd();
|
|
304
296
|
const homeDir = process.env.HOME || "";
|
|
305
297
|
const extraPaths = [];
|
|
@@ -351,9 +343,9 @@ export class BoomtickMCPServer {
|
|
|
351
343
|
// 3. SAFE REPOSITORY RESOLUTION
|
|
352
344
|
const repoPath = config.repoPath || projectRoot;
|
|
353
345
|
|
|
354
|
-
// 4. SECURE, UNIX-ONLY PRE-FLIGHT CHECK FOR TD
|
|
346
|
+
// 4. SECURE, UNIX-ONLY PRE-FLIGHT CHECK FOR TD
|
|
355
347
|
try {
|
|
356
|
-
const result = spawnSync("td
|
|
348
|
+
const result = spawnSync("td", ["doctor"], {
|
|
357
349
|
encoding: "utf-8",
|
|
358
350
|
cwd: repoPath,
|
|
359
351
|
shell: false // Explicitly disabled to clear Semgrep rules
|
|
@@ -367,24 +359,38 @@ export class BoomtickMCPServer {
|
|
|
367
359
|
console.error("status:", result.status);
|
|
368
360
|
console.error("stdout:", result.stdout);
|
|
369
361
|
console.error("stderr:", result.stderr);
|
|
370
|
-
throw new Error(`td
|
|
362
|
+
throw new Error(`td doctor returned exit code: ${result.status}`);
|
|
371
363
|
}
|
|
372
364
|
|
|
373
|
-
console.error("✅ td
|
|
365
|
+
console.error("✅ td verified on PATH securely");
|
|
374
366
|
} catch (error) {
|
|
375
|
-
console.error("❌ Fatal: td
|
|
367
|
+
console.error("❌ Fatal: td is not resolvable on PATH. MCP tools will fail.");
|
|
376
368
|
console.error("Please ensure the project is installed: pip install -e .");
|
|
377
|
-
console.error("❌ Fatal: td
|
|
369
|
+
console.error("❌ Fatal: td pre-flight check failed:", error instanceof Error ? error.message : String(error));
|
|
378
370
|
process.exit(1);
|
|
379
371
|
}
|
|
372
|
+
}
|
|
380
373
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
374
|
+
async startHttp(port = parseInt(process.env.PORT || process.env.HTTP_PORT || "3000", 10), bearerToken?: string) {
|
|
375
|
+
this.prepareEnvironmentAndPreflight();
|
|
376
|
+
const mcpHttp = createMcpHttpServer({ port, bearerToken, mcpServer: this.server });
|
|
377
|
+
return await mcpHttp.listen(port);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
async run() {
|
|
381
|
+
this.prepareEnvironmentAndPreflight();
|
|
382
|
+
|
|
383
|
+
const isHttp =
|
|
384
|
+
process.env.MCP_TRANSPORT === "http" ||
|
|
385
|
+
process.argv.includes("--http") ||
|
|
386
|
+
(process.env.PORT !== undefined && !process.argv.includes("--stdio")) ||
|
|
387
|
+
(process.env.HTTP_PORT !== undefined && !process.argv.includes("--stdio"));
|
|
388
|
+
|
|
389
|
+
if (isHttp) {
|
|
390
|
+
const port = parseInt(process.env.PORT || process.env.HTTP_PORT || "3000", 10);
|
|
391
|
+
const mcpHttp = createMcpHttpServer({ port, mcpServer: this.server });
|
|
392
|
+
await mcpHttp.listen(port);
|
|
393
|
+
return;
|
|
388
394
|
}
|
|
389
395
|
|
|
390
396
|
const transport = new StdioServerTransport();
|