boomtick-cli 0.2.1__py3-none-any.whl
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.
- boomtick_cli-0.2.1.dist-info/METADATA +20 -0
- boomtick_cli-0.2.1.dist-info/RECORD +103 -0
- boomtick_cli-0.2.1.dist-info/WHEEL +5 -0
- boomtick_cli-0.2.1.dist-info/entry_points.txt +3 -0
- boomtick_cli-0.2.1.dist-info/top_level.txt +1 -0
- dev_tools/__init__.py +1 -0
- dev_tools/cli-schema.json +2028 -0
- dev_tools/cli.py +1542 -0
- dev_tools/config.py +235 -0
- dev_tools/constants.py +14 -0
- dev_tools/daemon.py +146 -0
- dev_tools/dist/config.js +108 -0
- dev_tools/dist/index.js +10 -0
- dev_tools/dist/lib/error_utils.js +8 -0
- dev_tools/dist/lib/git.js +31 -0
- dev_tools/dist/lib/result.js +21 -0
- dev_tools/dist/lib/shell.js +91 -0
- dev_tools/dist/lib/shell.test.js +10 -0
- dev_tools/dist/lib/td-cli.js +27 -0
- dev_tools/dist/lib/test-utils.js +20 -0
- dev_tools/dist/mcp/definitions.js +242 -0
- dev_tools/dist/mcp/server.js +317 -0
- dev_tools/dist/mcp/tools.js +18 -0
- dev_tools/dist/tools/contract.js +2069 -0
- dev_tools/dist/tools/ddgs.search.js +50 -0
- dev_tools/dist/tools/ddgs.search.test.js +67 -0
- dev_tools/dist/tools/ddgs_search.py +23 -0
- dev_tools/dist/tools/github.checkout_branch.js +20 -0
- dev_tools/dist/tools/github.comment_triage_summary.js +25 -0
- dev_tools/dist/tools/github.create_issue.js +28 -0
- dev_tools/dist/tools/github.create_issue.test.js +54 -0
- dev_tools/dist/tools/github.create_pull_request.js +44 -0
- dev_tools/dist/tools/github.get_merge_conflict_files.js +26 -0
- dev_tools/dist/tools/github.get_pr.js +18 -0
- dev_tools/dist/tools/github.get_pr_diff.js +24 -0
- dev_tools/dist/tools/github.issue_comment.js +24 -0
- dev_tools/dist/tools/github.issue_update.js +29 -0
- dev_tools/dist/tools/github.issue_view.js +28 -0
- dev_tools/dist/tools/github.open_replacement_pr.js +41 -0
- dev_tools/dist/tools/github.search_open_prs.js +31 -0
- dev_tools/dist/tools/github.search_open_prs.test.js +51 -0
- dev_tools/dist/tools/jules/cancel-session.js +20 -0
- dev_tools/dist/tools/jules/create-session.js +20 -0
- dev_tools/dist/tools/jules/create-session.test.js +99 -0
- dev_tools/dist/tools/jules/get-messages.js +19 -0
- dev_tools/dist/tools/jules/get-messages.test.js +45 -0
- dev_tools/dist/tools/jules/get-pr.js +14 -0
- dev_tools/dist/tools/jules/get-pr.test.js +37 -0
- dev_tools/dist/tools/jules/get-session.js +10 -0
- dev_tools/dist/tools/jules/list-sessions.js +43 -0
- dev_tools/dist/tools/jules/send-message.js +22 -0
- dev_tools/dist/tools/jules/send-message.test.js +56 -0
- dev_tools/dist/tools/jules/shared.js +26 -0
- dev_tools/dist/tools/jules/trigger-feedback.js +16 -0
- dev_tools/dist/tools/jules/trigger-feedback.test.js +42 -0
- dev_tools/dist/tools/repo.commit_patch.js +33 -0
- dev_tools/dist/tools/repo.create_branch.js +21 -0
- dev_tools/dist/tools/repo.create_branch.test.js +42 -0
- dev_tools/dist/tools/repo.create_repair_branch.js +38 -0
- dev_tools/dist/tools/repo.get_changed_files.js +21 -0
- dev_tools/dist/tools/repo.get_command_schema.js +27 -0
- dev_tools/dist/tools/repo.get_package_scripts.js +35 -0
- dev_tools/dist/tools/repo.get_package_scripts.test.js +39 -0
- dev_tools/dist/tools/repo.get_route_map.js +37 -0
- dev_tools/dist/tools/repo.logs.js +16 -0
- dev_tools/dist/tools/repo.read_agent_context.js +20 -0
- dev_tools/dist/tools/repo.read_ci_logs.js +18 -0
- dev_tools/dist/tools/repo.run_lighthouse.js +37 -0
- dev_tools/dist/tools/repo.run_playwright.js +29 -0
- dev_tools/dist/tools/repo.run_tests.js +43 -0
- dev_tools/dist/tools/types.js +1 -0
- dev_tools/get_ai_context.py +63 -0
- dev_tools/handlers/__init__.py +1 -0
- dev_tools/handlers/command_handler.py +69 -0
- dev_tools/mcp_server.py +36 -0
- dev_tools/models.py +354 -0
- dev_tools/orchestrator.py +2841 -0
- dev_tools/pr_overlap.py +139 -0
- dev_tools/resources/__init__.py +1 -0
- dev_tools/resources/build-repo-context.py +182 -0
- dev_tools/resources/prompt_constants.json +5 -0
- dev_tools/resources/review_template.md +41 -0
- dev_tools/resources/ux-audit.config.json +15 -0
- dev_tools/resources/visual_guidelines.json +3 -0
- dev_tools/review_read_pass.py +232 -0
- dev_tools/schema_gen.py +55 -0
- dev_tools/schema_utils.py +125 -0
- dev_tools/scope_check.py +85 -0
- dev_tools/services/__init__.py +1 -0
- dev_tools/services/ai_service.py +816 -0
- dev_tools/services/dependency_graph.py +123 -0
- dev_tools/services/github.py +783 -0
- dev_tools/services/jules.py +181 -0
- dev_tools/services/repair_service.py +199 -0
- dev_tools/services/vector_store.py +82 -0
- dev_tools/services/vision_service.py +91 -0
- dev_tools/td_cli.py +28 -0
- dev_tools/utils/__init__.py +1035 -0
- dev_tools/utils/git.py +68 -0
- dev_tools/ux_report.py +213 -0
- dev_tools/verify_infra.py +91 -0
- dev_tools/verify_versions.py +191 -0
- dev_tools/version_utils.py +175 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { runCommand } from "../lib/shell.js";
|
|
2
|
+
function tryParseJson(text) {
|
|
3
|
+
try {
|
|
4
|
+
return JSON.parse(text);
|
|
5
|
+
}
|
|
6
|
+
catch {
|
|
7
|
+
return null;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export async function runTDCli(args) {
|
|
11
|
+
const result = await runCommand("td-cli", args);
|
|
12
|
+
const stdout = result.stdout.trim();
|
|
13
|
+
const output = stdout.startsWith("{") ? tryParseJson(stdout) : null;
|
|
14
|
+
// Handle structured errors (status: "error") regardless of exit code
|
|
15
|
+
if (output?.status === "error") {
|
|
16
|
+
throw new Error(`td-cli returned error: ${output.message ?? "Unknown error"}`);
|
|
17
|
+
}
|
|
18
|
+
// Handle non-zero exit codes (shell failure)
|
|
19
|
+
if (result.exitCode !== 0) {
|
|
20
|
+
throw new Error(`td-cli command failed (${args.join(" ")}): ${result.stderr || stdout}`);
|
|
21
|
+
}
|
|
22
|
+
// Ensure we have a valid JSON response for successful exit
|
|
23
|
+
if (!output) {
|
|
24
|
+
throw new Error(`td-cli returned non-JSON output with exit code 0: ${stdout}`);
|
|
25
|
+
}
|
|
26
|
+
return output;
|
|
27
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { vi } from "vitest";
|
|
2
|
+
/**
|
|
3
|
+
* Shared test utilities for boomtick-mcp
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Ensures JULES_API_KEY is set in the environment for tests that require it.
|
|
7
|
+
* Should be called in beforeEach.
|
|
8
|
+
*/
|
|
9
|
+
export function setupTestEnv() {
|
|
10
|
+
vi.stubEnv("JULES_API_KEY", "test-key");
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Resets the test environment.
|
|
14
|
+
* Should be called in afterEach.
|
|
15
|
+
*/
|
|
16
|
+
export function teardownTestEnv() {
|
|
17
|
+
vi.unstubAllEnvs();
|
|
18
|
+
// Explicitly delete to satisfy strict isolation requirements if vi.unstubAllEnvs is not enough
|
|
19
|
+
delete process.env.JULES_API_KEY;
|
|
20
|
+
}
|
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { HealthCheckInputJsonSchema, SearchPRsInputJsonSchema, GetPrDiffInputJsonSchema, CheckoutBranchInputJsonSchema, GetMergeConflictFilesInputJsonSchema, GetChangedFilesInputJsonSchema, GetPackageScriptsInputJsonSchema, GetRouteMapInputJsonSchema, ReadCiLogsInputJsonSchema, RepoLogsInputJsonSchema, CreateBranchInputJsonSchema, CreateRepairBranchInputJsonSchema, RunTestsInputJsonSchema, RunLighthouseInputJsonSchema, RunPlaywrightInputJsonSchema, CommitPatchInputJsonSchema, OpenReplacementPrInputJsonSchema, CreatePullRequestInputJsonSchema, CommentTriageSummaryInputJsonSchema, GetPrInputJsonSchema, IssueViewInputJsonSchema, IssueUpdateInputJsonSchema, IssueCommentInputJsonSchema, CreateIssueInputJsonSchema, GetCommandSchemaInputJsonSchema, CreateJulesSessionInputJsonSchema, JulesSessionIdInputJsonSchema, JulesSendMessageInputJsonSchema, JulesListSessionsInputJsonSchema, SearchDdgsInputJsonSchema, } from "../tools/contract.js";
|
|
2
|
+
export const MCP_PROMPTS = [
|
|
3
|
+
{
|
|
4
|
+
name: "conflict-scout",
|
|
5
|
+
description: "Find PRs worth rescuing.",
|
|
6
|
+
},
|
|
7
|
+
{
|
|
8
|
+
name: "pr-consolidation",
|
|
9
|
+
description: "Guidelines for analyzing and proposing consolidation of overlapping PRs.",
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
name: "repo-context",
|
|
13
|
+
description: "Gather repository context for a PR.",
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
name: "repair-agent",
|
|
17
|
+
description: "Apply the smallest safe fix for a PR.",
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: "verifier-agent",
|
|
21
|
+
description: "Verify that a repair works.",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
name: "pr-writer",
|
|
25
|
+
description: "Write a summary for a replacement PR.",
|
|
26
|
+
},
|
|
27
|
+
];
|
|
28
|
+
export const MCP_RESOURCES = [
|
|
29
|
+
{
|
|
30
|
+
uri: "repo://package-json",
|
|
31
|
+
name: "package.json",
|
|
32
|
+
mimeType: "application/json",
|
|
33
|
+
description: "The root package.json file of the repository.",
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
uri: "repo://routes",
|
|
37
|
+
name: "Route Map",
|
|
38
|
+
mimeType: "application/json",
|
|
39
|
+
description: "The mapping of application routes to content files.",
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
uri: "repo://design-tokens",
|
|
43
|
+
name: "Design Tokens",
|
|
44
|
+
mimeType: "application/json",
|
|
45
|
+
description: "The design tokens used in the repository.",
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
uri: "repo://repair-report/{branch}",
|
|
49
|
+
name: "Repair Report",
|
|
50
|
+
mimeType: "application/json",
|
|
51
|
+
description: "The validation report for a specific repair branch.",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
uri: "repo://lighthouse/{branch}",
|
|
55
|
+
name: "Lighthouse Report",
|
|
56
|
+
mimeType: "application/json",
|
|
57
|
+
description: "Lighthouse CI report for a specific branch.",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
uri: "repo://playwright/{branch}",
|
|
61
|
+
name: "Playwright Report",
|
|
62
|
+
mimeType: "application/json",
|
|
63
|
+
description: "Playwright test report for a specific branch.",
|
|
64
|
+
},
|
|
65
|
+
];
|
|
66
|
+
export const MCP_TOOLS = [
|
|
67
|
+
{
|
|
68
|
+
name: "boomtick.health",
|
|
69
|
+
description: "Check the health and configuration of the MCP server.",
|
|
70
|
+
inputSchema: HealthCheckInputJsonSchema,
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
name: "github.search_open_prs",
|
|
74
|
+
description: "Search for open pull requests in the repository.",
|
|
75
|
+
inputSchema: SearchPRsInputJsonSchema,
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: "repo.read_agent_context",
|
|
79
|
+
description: "Read the full repository index including file tree, CLI schema, and MCP tools.",
|
|
80
|
+
inputSchema: { type: "object", properties: {} },
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "github.get_pr_diff",
|
|
84
|
+
description: "Get the diff and changed files for a pull request.",
|
|
85
|
+
inputSchema: GetPrDiffInputJsonSchema,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: "github.checkout_branch",
|
|
89
|
+
description: "Checkout a specific branch in the repository or worktree.",
|
|
90
|
+
inputSchema: CheckoutBranchInputJsonSchema,
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "github.get_merge_conflict_files",
|
|
94
|
+
description: "Detect files that conflict when a PR is merged with the base branch.",
|
|
95
|
+
inputSchema: GetMergeConflictFilesInputJsonSchema,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "repo.get_changed_files",
|
|
99
|
+
description: "Get the list of changed files between two refs.",
|
|
100
|
+
inputSchema: GetChangedFilesInputJsonSchema,
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
name: "repo.get_command_schema",
|
|
104
|
+
description: "Retrieve a lightweight, targeted schema for a specific td-cli subcommand. Use this instead of reading the entire cli-schema.json to save tokens.",
|
|
105
|
+
inputSchema: GetCommandSchemaInputJsonSchema,
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
name: "repo.get_package_scripts",
|
|
109
|
+
description: "Get the scripts defined in package.json.",
|
|
110
|
+
inputSchema: GetPackageScriptsInputJsonSchema,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "repo.get_route_map",
|
|
114
|
+
description: "Get the mapping of routes to content files.",
|
|
115
|
+
inputSchema: GetRouteMapInputJsonSchema,
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
name: "repo.read_ci_logs",
|
|
119
|
+
description: "Read CI logs for a given pull request.",
|
|
120
|
+
inputSchema: ReadCiLogsInputJsonSchema,
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "repo.logs",
|
|
124
|
+
description: "Stream or grep combined CI logs for all jobs in a pull request.",
|
|
125
|
+
inputSchema: RepoLogsInputJsonSchema,
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "repo.create_branch",
|
|
129
|
+
description: "Creates a new clean git branch from a target base branch.",
|
|
130
|
+
inputSchema: CreateBranchInputJsonSchema,
|
|
131
|
+
},
|
|
132
|
+
{
|
|
133
|
+
name: "repo.create_repair_branch",
|
|
134
|
+
description: "Creates a fresh worktree and a new repair branch for an existing PR. This isolates the repair work from the main codebase.",
|
|
135
|
+
inputSchema: CreateRepairBranchInputJsonSchema,
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
name: "repo.run_tests",
|
|
139
|
+
description: "Run repository tests and checks.",
|
|
140
|
+
inputSchema: RunTestsInputJsonSchema,
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "repo.run_lighthouse",
|
|
144
|
+
description: "Run Lighthouse CI audits.",
|
|
145
|
+
inputSchema: RunLighthouseInputJsonSchema,
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
name: "repo.run_playwright",
|
|
149
|
+
description: "Run Playwright E2E tests.",
|
|
150
|
+
inputSchema: RunPlaywrightInputJsonSchema,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
name: "repo.commit_patch",
|
|
154
|
+
description: "Commit verified repair changes in a worktree.",
|
|
155
|
+
inputSchema: CommitPatchInputJsonSchema,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: "github.open_replacement_pr",
|
|
159
|
+
description: "Open a new PR that replaces or repairs the original PR.",
|
|
160
|
+
inputSchema: OpenReplacementPrInputJsonSchema,
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
name: "github.create_pull_request",
|
|
164
|
+
description: "Creates a pull request on GitHub using the MCP server's integrated credentials. Bypasses terminal CLI constraints.",
|
|
165
|
+
inputSchema: CreatePullRequestInputJsonSchema,
|
|
166
|
+
},
|
|
167
|
+
{
|
|
168
|
+
name: "github.comment_triage_summary",
|
|
169
|
+
description: "Comment on the original PR with a diagnosis and replacement link.",
|
|
170
|
+
inputSchema: CommentTriageSummaryInputJsonSchema,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: "github.get_pr",
|
|
174
|
+
description: "Get details of a GitHub PR including title, state, and URLs.",
|
|
175
|
+
inputSchema: GetPrInputJsonSchema,
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: "github.issue_view",
|
|
179
|
+
description: "View details of a GitHub issue including title, body, and state.",
|
|
180
|
+
inputSchema: IssueViewInputJsonSchema,
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
name: "github.issue_update",
|
|
184
|
+
description: "Update a GitHub issue's body, labels, and/or state.",
|
|
185
|
+
inputSchema: IssueUpdateInputJsonSchema,
|
|
186
|
+
},
|
|
187
|
+
{
|
|
188
|
+
name: "github.issue_comment",
|
|
189
|
+
description: "Add a new comment to a GitHub issue.",
|
|
190
|
+
inputSchema: IssueCommentInputJsonSchema,
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
name: "github.create_issue",
|
|
194
|
+
description: "Create a new GitHub issue.",
|
|
195
|
+
inputSchema: CreateIssueInputJsonSchema,
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
name: "jules.create_session",
|
|
199
|
+
description: "Create a Jules session that performs work externally and may generate a GitHub pull request.",
|
|
200
|
+
inputSchema: CreateJulesSessionInputJsonSchema,
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
name: "jules.get_session",
|
|
204
|
+
description: "Get the status and details of a Jules session.",
|
|
205
|
+
inputSchema: JulesSessionIdInputJsonSchema,
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
name: "jules.send_message",
|
|
209
|
+
description: "Send a message to an active Jules session.",
|
|
210
|
+
inputSchema: JulesSendMessageInputJsonSchema,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: "jules.get_messages",
|
|
214
|
+
description: "Get the message history of a Jules session.",
|
|
215
|
+
inputSchema: JulesSessionIdInputJsonSchema,
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: "jules.list_sessions",
|
|
219
|
+
description: "List all Jules sessions.",
|
|
220
|
+
inputSchema: JulesListSessionsInputJsonSchema,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: "jules.cancel_session",
|
|
224
|
+
description: "Cancel an ongoing Jules session.",
|
|
225
|
+
inputSchema: JulesSessionIdInputJsonSchema,
|
|
226
|
+
},
|
|
227
|
+
{
|
|
228
|
+
name: "jules.get_pr",
|
|
229
|
+
description: "Get the generated pull request url associated with an active Jules agent session.",
|
|
230
|
+
inputSchema: JulesSessionIdInputJsonSchema,
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: "jules.trigger_feedback",
|
|
234
|
+
description: "Automatically collect CI status/logs for the PR associated with a Jules session and send them back as feedback.",
|
|
235
|
+
inputSchema: JulesSessionIdInputJsonSchema,
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: "agent.search_ddgs",
|
|
239
|
+
description: "Search the web using DuckDuckGo (via ddgs python library).",
|
|
240
|
+
inputSchema: SearchDdgsInputJsonSchema,
|
|
241
|
+
},
|
|
242
|
+
];
|
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
4
|
+
import { config } from "../config.js";
|
|
5
|
+
import { createSuccessResult, createErrorResult } from "../lib/result.js";
|
|
6
|
+
import { MCP_TOOLS, MCP_PROMPTS, MCP_RESOURCES } from "./definitions.js";
|
|
7
|
+
import { healthHandler, HealthCheckInputSchema } from "./tools.js";
|
|
8
|
+
import { searchOpenPrsHandler, SearchOpenPrsInputSchema } from "../tools/github.search_open_prs.js";
|
|
9
|
+
import { getPrDiffHandler, GetPrDiffInputSchema } from "../tools/github.get_pr_diff.js";
|
|
10
|
+
import { getMergeConflictFilesHandler, GetMergeConflictFilesInputSchema } from "../tools/github.get_merge_conflict_files.js";
|
|
11
|
+
import { checkoutBranchHandler, CheckoutBranchInputSchema } from "../tools/github.checkout_branch.js";
|
|
12
|
+
import { getChangedFilesHandler, GetChangedFilesInputSchema } from "../tools/repo.get_changed_files.js";
|
|
13
|
+
import { readAgentContextHandler, ReadAgentContextInputSchema } from "../tools/repo.read_agent_context.js";
|
|
14
|
+
import { getCommandSchemaHandler, GetCommandSchemaInputSchema } from "../tools/repo.get_command_schema.js";
|
|
15
|
+
import { getPackageScriptsHandler, GetPackageScriptsInputSchema } from "../tools/repo.get_package_scripts.js";
|
|
16
|
+
import { getRouteMapHandler, GetRouteMapInputSchema } from "../tools/repo.get_route_map.js";
|
|
17
|
+
import { readCiLogsHandler, ReadCiLogsInputSchema } from "../tools/repo.read_ci_logs.js";
|
|
18
|
+
import { repoLogsHandler, RepoLogsInputSchema } from "../tools/repo.logs.js";
|
|
19
|
+
import { createRepairBranchHandler, CreateRepairBranchInputSchema } from "../tools/repo.create_repair_branch.js";
|
|
20
|
+
import { createBranchHandler, CreateBranchInputSchema } from "../tools/repo.create_branch.js";
|
|
21
|
+
import { runTestsHandler, RunTestsInputSchema } from "../tools/repo.run_tests.js";
|
|
22
|
+
import { runLighthouseHandler, RunLighthouseInputSchema } from "../tools/repo.run_lighthouse.js";
|
|
23
|
+
import { runPlaywrightHandler, RunPlaywrightInputSchema } from "../tools/repo.run_playwright.js";
|
|
24
|
+
import { commitPatchHandler, CommitPatchInputSchema } from "../tools/repo.commit_patch.js";
|
|
25
|
+
import { openReplacementPrHandler, OpenReplacementPrInputSchema } from "../tools/github.open_replacement_pr.js";
|
|
26
|
+
import { commentTriageSummaryHandler, CommentTriageSummaryInputSchema } from "../tools/github.comment_triage_summary.js";
|
|
27
|
+
import { createPullRequestHandler, CreatePullRequestInputSchema } from "../tools/github.create_pull_request.js";
|
|
28
|
+
import { getPrHandler, GetPrInputSchema } from "../tools/github.get_pr.js";
|
|
29
|
+
import { issueViewHandler, IssueViewInputSchema } from "../tools/github.issue_view.js";
|
|
30
|
+
import { issueUpdateHandler, IssueUpdateInputSchema } from "../tools/github.issue_update.js";
|
|
31
|
+
import { issueCommentHandler, IssueCommentInputSchema } from "../tools/github.issue_comment.js";
|
|
32
|
+
import { createIssueHandler, CreateIssueInputSchema } from "../tools/github.create_issue.js";
|
|
33
|
+
import { createJulesSessionHandler, CreateJulesSessionInputSchema } from "../tools/jules/create-session.js";
|
|
34
|
+
import { getJulesSessionHandler, GetJulesSessionInputSchema } from "../tools/jules/get-session.js";
|
|
35
|
+
import { sendJulesMessageHandler, SendJulesMessageInputSchema } from "../tools/jules/send-message.js";
|
|
36
|
+
import { getJulesMessagesHandler, GetJulesMessagesInputSchema } from "../tools/jules/get-messages.js";
|
|
37
|
+
import { listJulesSessionsHandler, ListJulesSessionsInputSchema } from "../tools/jules/list-sessions.js";
|
|
38
|
+
import { cancelJulesSessionHandler, CancelJulesSessionInputSchema } from "../tools/jules/cancel-session.js";
|
|
39
|
+
import { getJulesPullRequestHandler, GetJulesPullRequestInputSchema } from "../tools/jules/get-pr.js";
|
|
40
|
+
import { triggerJulesFeedbackHandler, TriggerJulesFeedbackInputSchema } from "../tools/jules/trigger-feedback.js";
|
|
41
|
+
import { ddgsSearchHandler, DdgsSearchInputSchema } from "../tools/ddgs.search.js";
|
|
42
|
+
import fs from "fs/promises";
|
|
43
|
+
import { existsSync, readFileSync } from "fs";
|
|
44
|
+
import path from "path";
|
|
45
|
+
import { spawnSync } from "child_process";
|
|
46
|
+
import { fileURLToPath } from "url";
|
|
47
|
+
export class BoomtickMCPServer {
|
|
48
|
+
server;
|
|
49
|
+
constructor() {
|
|
50
|
+
this.server = new Server({
|
|
51
|
+
name: "boomtick-mcp",
|
|
52
|
+
version: "0.2.0",
|
|
53
|
+
}, {
|
|
54
|
+
capabilities: {
|
|
55
|
+
tools: {},
|
|
56
|
+
resources: {},
|
|
57
|
+
prompts: {},
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
this.setupToolHandlers();
|
|
61
|
+
this.setupResourceHandlers();
|
|
62
|
+
this.setupPromptHandlers();
|
|
63
|
+
}
|
|
64
|
+
setupPromptHandlers() {
|
|
65
|
+
this.server.setRequestHandler(ListPromptsRequestSchema, async () => {
|
|
66
|
+
return {
|
|
67
|
+
prompts: MCP_PROMPTS,
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
this.server.setRequestHandler(GetPromptRequestSchema, async (request) => {
|
|
71
|
+
const name = request.params.name;
|
|
72
|
+
const agentsDir = path.resolve(config.repoPath, "boomtick-pkg/mcp/src/agents");
|
|
73
|
+
// nosemgrep
|
|
74
|
+
const promptPath = path.resolve(agentsDir, `${name}.prompt.md`);
|
|
75
|
+
if (!promptPath.startsWith(agentsDir + path.sep)) {
|
|
76
|
+
throw new Error("Path traversal detected");
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
const content = await fs.readFile(promptPath, "utf-8");
|
|
80
|
+
return {
|
|
81
|
+
messages: [
|
|
82
|
+
{
|
|
83
|
+
role: "user",
|
|
84
|
+
content: { type: "text", text: content },
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (e) {
|
|
90
|
+
throw new Error(`Prompt not found: ${name}`);
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
setupResourceHandlers() {
|
|
95
|
+
this.server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
96
|
+
return {
|
|
97
|
+
resources: MCP_RESOURCES,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
this.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
|
101
|
+
const uri = request.params.uri;
|
|
102
|
+
if (uri === "repo://package-json") {
|
|
103
|
+
const content = await fs.readFile(path.join(config.repoPath, "package.json"), "utf-8");
|
|
104
|
+
return {
|
|
105
|
+
contents: [{ uri, mimeType: "application/json", text: content }],
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
if (uri === "repo://routes") {
|
|
109
|
+
const routeMap = await getRouteMapHandler({});
|
|
110
|
+
return {
|
|
111
|
+
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(routeMap, null, 2) }],
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
if (uri === "repo://design-tokens") {
|
|
115
|
+
const tokensPath = path.join(config.repoPath, "src/styles/design-tokens.ts");
|
|
116
|
+
const content = await fs.readFile(tokensPath, "utf-8");
|
|
117
|
+
return {
|
|
118
|
+
contents: [{ uri, mimeType: "text/typescript", text: content }],
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (uri.startsWith("repo://diff/")) {
|
|
122
|
+
const prNumber = parseInt(uri.split("/").pop() || "");
|
|
123
|
+
const diff = await getPrDiffHandler({ prNumber });
|
|
124
|
+
return {
|
|
125
|
+
contents: [{ uri, mimeType: "text/plain", text: diff.diffText }],
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
if (uri.startsWith("repo://ci/")) {
|
|
129
|
+
const prNumber = parseInt(uri.split("/").pop() || "");
|
|
130
|
+
const logs = await readCiLogsHandler({ prNumber });
|
|
131
|
+
return {
|
|
132
|
+
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(logs, null, 2) }],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
if (uri.startsWith("repo://lighthouse/")) {
|
|
136
|
+
const branch = uri.split("/").pop() || "";
|
|
137
|
+
const report = await runLighthouseHandler({ route: "/", worktreePath: `../boomtick-mcp-rescue-${branch}` });
|
|
138
|
+
return {
|
|
139
|
+
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(report, null, 2) }],
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (uri.startsWith("repo://playwright/")) {
|
|
143
|
+
const branch = uri.split("/").pop() || "";
|
|
144
|
+
const report = await runPlaywrightHandler({ worktreePath: `../boomtick-mcp-rescue-${branch}` });
|
|
145
|
+
return {
|
|
146
|
+
contents: [{ uri, mimeType: "application/json", text: JSON.stringify(report, null, 2) }],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
throw new Error(`Resource not found: ${uri}`);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
setupToolHandlers() {
|
|
153
|
+
this.server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
154
|
+
return {
|
|
155
|
+
tools: MCP_TOOLS,
|
|
156
|
+
};
|
|
157
|
+
});
|
|
158
|
+
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
159
|
+
try {
|
|
160
|
+
switch (request.params.name) {
|
|
161
|
+
case "boomtick.health":
|
|
162
|
+
return createSuccessResult(await healthHandler(HealthCheckInputSchema.parse(request.params.arguments || {})));
|
|
163
|
+
case "github.search_open_prs":
|
|
164
|
+
return createSuccessResult(await searchOpenPrsHandler(SearchOpenPrsInputSchema.parse(request.params.arguments || {})));
|
|
165
|
+
case "repo.read_agent_context":
|
|
166
|
+
return createSuccessResult(await readAgentContextHandler(ReadAgentContextInputSchema.parse(request.params.arguments || {})));
|
|
167
|
+
case "github.get_pr_diff":
|
|
168
|
+
return createSuccessResult(await getPrDiffHandler(GetPrDiffInputSchema.parse(request.params.arguments)));
|
|
169
|
+
case "github.get_merge_conflict_files":
|
|
170
|
+
return createSuccessResult(await getMergeConflictFilesHandler(GetMergeConflictFilesInputSchema.parse(request.params.arguments)));
|
|
171
|
+
case "github.checkout_branch":
|
|
172
|
+
return createSuccessResult(await checkoutBranchHandler(CheckoutBranchInputSchema.parse(request.params.arguments)));
|
|
173
|
+
case "repo.get_changed_files":
|
|
174
|
+
return createSuccessResult(await getChangedFilesHandler(GetChangedFilesInputSchema.parse(request.params.arguments || {})));
|
|
175
|
+
case "repo.get_command_schema":
|
|
176
|
+
return createSuccessResult(await getCommandSchemaHandler(GetCommandSchemaInputSchema.parse(request.params.arguments)));
|
|
177
|
+
case "repo.get_package_scripts":
|
|
178
|
+
return createSuccessResult(await getPackageScriptsHandler(GetPackageScriptsInputSchema.parse(request.params.arguments || {})));
|
|
179
|
+
case "repo.get_route_map":
|
|
180
|
+
return createSuccessResult(await getRouteMapHandler(GetRouteMapInputSchema.parse(request.params.arguments || {})));
|
|
181
|
+
case "repo.read_ci_logs":
|
|
182
|
+
return createSuccessResult(await readCiLogsHandler(ReadCiLogsInputSchema.parse(request.params.arguments)));
|
|
183
|
+
case "repo.logs":
|
|
184
|
+
return createSuccessResult(await repoLogsHandler(RepoLogsInputSchema.parse(request.params.arguments)));
|
|
185
|
+
case "repo.create_branch":
|
|
186
|
+
return createSuccessResult(await createBranchHandler(CreateBranchInputSchema.parse(request.params.arguments)));
|
|
187
|
+
case "repo.create_repair_branch":
|
|
188
|
+
return createSuccessResult(await createRepairBranchHandler(CreateRepairBranchInputSchema.parse(request.params.arguments)));
|
|
189
|
+
case "repo.run_tests":
|
|
190
|
+
return createSuccessResult(await runTestsHandler(RunTestsInputSchema.parse(request.params.arguments || {})));
|
|
191
|
+
case "repo.run_lighthouse":
|
|
192
|
+
return createSuccessResult(await runLighthouseHandler(RunLighthouseInputSchema.parse(request.params.arguments || {})));
|
|
193
|
+
case "repo.run_playwright":
|
|
194
|
+
return createSuccessResult(await runPlaywrightHandler(RunPlaywrightInputSchema.parse(request.params.arguments || {})));
|
|
195
|
+
case "repo.commit_patch":
|
|
196
|
+
return createSuccessResult(await commitPatchHandler(CommitPatchInputSchema.parse(request.params.arguments)));
|
|
197
|
+
case "github.open_replacement_pr":
|
|
198
|
+
return createSuccessResult(await openReplacementPrHandler(OpenReplacementPrInputSchema.parse(request.params.arguments)));
|
|
199
|
+
case "github.comment_triage_summary":
|
|
200
|
+
return createSuccessResult(await commentTriageSummaryHandler(CommentTriageSummaryInputSchema.parse(request.params.arguments)));
|
|
201
|
+
case "github.create_pull_request":
|
|
202
|
+
return createSuccessResult(await createPullRequestHandler(CreatePullRequestInputSchema.parse(request.params.arguments)));
|
|
203
|
+
case "github.get_pr":
|
|
204
|
+
return createSuccessResult(await getPrHandler(GetPrInputSchema.parse(request.params.arguments)));
|
|
205
|
+
case "github.issue_view":
|
|
206
|
+
return createSuccessResult(await issueViewHandler(IssueViewInputSchema.parse(request.params.arguments)));
|
|
207
|
+
case "github.issue_update":
|
|
208
|
+
return createSuccessResult(await issueUpdateHandler(IssueUpdateInputSchema.parse(request.params.arguments)));
|
|
209
|
+
case "github.issue_comment":
|
|
210
|
+
return createSuccessResult(await issueCommentHandler(IssueCommentInputSchema.parse(request.params.arguments)));
|
|
211
|
+
case "github.create_issue":
|
|
212
|
+
return createSuccessResult(await createIssueHandler(CreateIssueInputSchema.parse(request.params.arguments)));
|
|
213
|
+
case "jules.create_session":
|
|
214
|
+
return createSuccessResult(await createJulesSessionHandler(CreateJulesSessionInputSchema.parse(request.params.arguments)));
|
|
215
|
+
case "jules.get_session":
|
|
216
|
+
return createSuccessResult(await getJulesSessionHandler(GetJulesSessionInputSchema.parse(request.params.arguments)));
|
|
217
|
+
case "jules.send_message":
|
|
218
|
+
return createSuccessResult(await sendJulesMessageHandler(SendJulesMessageInputSchema.parse(request.params.arguments)));
|
|
219
|
+
case "jules.get_messages":
|
|
220
|
+
return createSuccessResult(await getJulesMessagesHandler(GetJulesMessagesInputSchema.parse(request.params.arguments)));
|
|
221
|
+
case "jules.list_sessions":
|
|
222
|
+
return createSuccessResult(await listJulesSessionsHandler(ListJulesSessionsInputSchema.parse(request.params.arguments || {})));
|
|
223
|
+
case "jules.cancel_session":
|
|
224
|
+
return createSuccessResult(await cancelJulesSessionHandler(CancelJulesSessionInputSchema.parse(request.params.arguments)));
|
|
225
|
+
case "jules.get_pr":
|
|
226
|
+
return createSuccessResult(await getJulesPullRequestHandler(GetJulesPullRequestInputSchema.parse(request.params.arguments)));
|
|
227
|
+
case "jules.trigger_feedback":
|
|
228
|
+
return createSuccessResult(await triggerJulesFeedbackHandler(TriggerJulesFeedbackInputSchema.parse(request.params.arguments)));
|
|
229
|
+
case "agent.search_ddgs":
|
|
230
|
+
return createSuccessResult(await ddgsSearchHandler(DdgsSearchInputSchema.parse(request.params.arguments)));
|
|
231
|
+
default:
|
|
232
|
+
return createErrorResult(`Tool not found: ${request.params.name}`);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
return createErrorResult(error instanceof Error ? error.message : String(error));
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
async run() {
|
|
241
|
+
const projectRoot = config.repoPath || process.cwd();
|
|
242
|
+
const homeDir = process.env.HOME || "";
|
|
243
|
+
const extraPaths = [];
|
|
244
|
+
// 1. DYNAMIC PYTHON PATH (pip / virtualenv discovery)
|
|
245
|
+
const venvDirs = [".venv", "venv", "env"];
|
|
246
|
+
for (const venvDir of venvDirs) {
|
|
247
|
+
const venvBin = path.join(projectRoot, venvDir, "bin");
|
|
248
|
+
if (existsSync(venvBin)) {
|
|
249
|
+
extraPaths.push(venvBin);
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// 2. DYNAMIC NODE PATH (.nvmrc parsing)
|
|
254
|
+
const nvmrcPath = path.join(projectRoot, ".nvmrc");
|
|
255
|
+
let targetNodeVersion = "";
|
|
256
|
+
if (existsSync(nvmrcPath)) {
|
|
257
|
+
const rawVersion = readFileSync(nvmrcPath, "utf-8").trim();
|
|
258
|
+
targetNodeVersion = rawVersion.startsWith("v") ? rawVersion : `v${rawVersion}`;
|
|
259
|
+
}
|
|
260
|
+
if (homeDir) {
|
|
261
|
+
extraPaths.push(path.join(homeDir, ".local", "bin"), path.join(homeDir, "miniforge3", "bin"), path.join(homeDir, "anaconda3", "bin"), path.join(homeDir, "miniconda3", "bin"));
|
|
262
|
+
if (process.env.NVM_BIN) {
|
|
263
|
+
extraPaths.push(process.env.NVM_BIN);
|
|
264
|
+
}
|
|
265
|
+
else if (targetNodeVersion) {
|
|
266
|
+
extraPaths.push(path.join(homeDir, ".nvm", "versions", "node", targetNodeVersion, "bin"));
|
|
267
|
+
}
|
|
268
|
+
else {
|
|
269
|
+
extraPaths.push(path.join(homeDir, ".nvm", "versions", "node", process.version, "bin"));
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
extraPaths.push(path.dirname(process.execPath));
|
|
273
|
+
const currentPath = process.env.PATH || "";
|
|
274
|
+
const updatedPaths = [...extraPaths, ...currentPath.split(path.delimiter)];
|
|
275
|
+
process.env.PATH = Array.from(new Set(updatedPaths))
|
|
276
|
+
.filter(Boolean)
|
|
277
|
+
.join(path.delimiter);
|
|
278
|
+
// 3. SAFE REPOSITORY RESOLUTION
|
|
279
|
+
const repoPath = config.repoPath || projectRoot;
|
|
280
|
+
// 4. SECURE, UNIX-ONLY PRE-FLIGHT CHECK FOR TD-CLI
|
|
281
|
+
try {
|
|
282
|
+
const result = spawnSync("td-cli", ["doctor"], {
|
|
283
|
+
encoding: "utf-8",
|
|
284
|
+
cwd: repoPath,
|
|
285
|
+
shell: false // Explicitly disabled to clear Semgrep rules
|
|
286
|
+
});
|
|
287
|
+
if (result.error) {
|
|
288
|
+
throw result.error;
|
|
289
|
+
}
|
|
290
|
+
if (result.status !== 0) {
|
|
291
|
+
console.error("status:", result.status);
|
|
292
|
+
console.error("stdout:", result.stdout);
|
|
293
|
+
console.error("stderr:", result.stderr);
|
|
294
|
+
throw new Error(`td-cli doctor returned exit code: ${result.status}`);
|
|
295
|
+
}
|
|
296
|
+
console.error("✅ td-cli verified on PATH securely");
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
console.error("❌ Fatal: td-cli is not resolvable on PATH. MCP tools will fail.");
|
|
300
|
+
console.error("Please ensure the project is installed: pip install -e .");
|
|
301
|
+
console.error("❌ Fatal: td-cli pre-flight check failed:", error instanceof Error ? error.message : String(error));
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
305
|
+
const ddgsScriptPath = path.join(__dirname, "..", "tools", "ddgs_search.py");
|
|
306
|
+
try {
|
|
307
|
+
await fs.access(ddgsScriptPath);
|
|
308
|
+
}
|
|
309
|
+
catch (err) {
|
|
310
|
+
console.error(`❌ Fatal: ddgs_search.py not found at ${ddgsScriptPath}`);
|
|
311
|
+
process.exit(1);
|
|
312
|
+
}
|
|
313
|
+
const transport = new StdioServerTransport();
|
|
314
|
+
await this.server.connect(transport);
|
|
315
|
+
console.error("Boomtick MCP Server running on stdio");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { config } from "../config.js";
|
|
3
|
+
export const HealthCheckInputSchema = z.object({
|
|
4
|
+
checkDeep: z.boolean().optional().default(false),
|
|
5
|
+
});
|
|
6
|
+
export async function healthHandler(args) {
|
|
7
|
+
HealthCheckInputSchema.parse(args);
|
|
8
|
+
return {
|
|
9
|
+
status: "ok",
|
|
10
|
+
config: {
|
|
11
|
+
githubOwner: config.githubOwner,
|
|
12
|
+
githubRepo: config.githubRepo,
|
|
13
|
+
repoPath: config.repoPath,
|
|
14
|
+
readOnly: !config.githubToken,
|
|
15
|
+
ghPath: config.ghPath
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|