@mushi-mushi/mcp 0.17.1 → 0.18.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/README.md +18 -9
- package/dist/catalog.js +1 -1
- package/dist/{chunk-7UOTJ2LI.js → chunk-TJSPX5CD.js} +63 -95
- package/dist/{chunk-PW4AK6SV.js → chunk-VBXXUK5G.js} +31 -14
- package/dist/clients.js +1 -1
- package/dist/feature-groups.d.ts +8 -2
- package/dist/feature-groups.js +1 -1
- package/dist/index.js +373 -411
- package/dist/server.js +269 -304
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -11,7 +11,7 @@ var TOOL_CATALOG = [
|
|
|
11
11
|
{
|
|
12
12
|
name: "get_recent_reports",
|
|
13
13
|
title: "Recent bug reports",
|
|
14
|
-
description: "List recent bug reports
|
|
14
|
+
description: "List recent bug reports for a project, newest first. Returns { reports: [{ id, status, category, severity, summary, created_at }], total }. Optional filters: status (new|classified|grouped|fixing|fixed|verified|reopened|dismissed), category (bug|slow|visual|confusing|other), severity (critical|high|medium|low), limit (default 20, max 100). Use to survey open reports; for one report use get_report_detail, to find a bug by text use search_reports.",
|
|
15
15
|
scope: "mcp:read",
|
|
16
16
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
17
17
|
useCase: "What landed in my bug queue today?"
|
|
@@ -19,7 +19,7 @@ var TOOL_CATALOG = [
|
|
|
19
19
|
{
|
|
20
20
|
name: "get_report_detail",
|
|
21
21
|
title: "Report detail",
|
|
22
|
-
description: "
|
|
22
|
+
description: "Fetch the full record for one bug report by id: description, console logs, network requests, screenshot URL, classification (stage 1/2), and fix history. Returns { report }. Read-only. Use when you have a reportId and need everything about it; for evidence only use get_report_evidence, for the activity thread use get_report_timeline, for a one-call fix bundle use get_fix_context.",
|
|
23
23
|
scope: "mcp:read",
|
|
24
24
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
25
25
|
useCase: "Show me everything you know about this report."
|
|
@@ -27,7 +27,7 @@ var TOOL_CATALOG = [
|
|
|
27
27
|
{
|
|
28
28
|
name: "get_report_timeline",
|
|
29
29
|
title: "Unified report timeline",
|
|
30
|
-
description: "
|
|
30
|
+
description: "Return the ordered activity timeline for one report (oldest to newest), merging reporter comments, fix events, QA runs, skill-pipeline steps, and Ask Mushi turns into one lane. Returns { events: [{ ts, kind, actor, summary }] }. Read-only. Use to see what happened end-to-end on a report thread; use get_report_detail for the static record or get_fix_timeline to debug one fix attempt.",
|
|
31
31
|
scope: "mcp:read",
|
|
32
32
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
33
33
|
useCase: "What happened on this report thread end-to-end?"
|
|
@@ -35,7 +35,7 @@ var TOOL_CATALOG = [
|
|
|
35
35
|
{
|
|
36
36
|
name: "search_reports",
|
|
37
37
|
title: "Search reports",
|
|
38
|
-
description:
|
|
38
|
+
description: 'Search reports by meaning and keyword (pgvector similarity server-side; falls back to summary/description substring if embeddings are unavailable). Returns ranked { results: [{ id, summary, similarity }] }. Read-only. Use to find reports by free text ("checkout flakiness"); use get_similar_bugs to dedupe a known component/bug, or get_recent_reports to list without a query.',
|
|
39
39
|
scope: "mcp:read",
|
|
40
40
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
41
41
|
useCase: 'Find reports mentioning "checkout flakiness".'
|
|
@@ -43,7 +43,7 @@ var TOOL_CATALOG = [
|
|
|
43
43
|
{
|
|
44
44
|
name: "get_similar_bugs",
|
|
45
45
|
title: "Similar bugs",
|
|
46
|
-
description: 'Find bugs
|
|
46
|
+
description: 'Find existing bugs similar to a component, page, or description via pgvector nearest-neighbour search (same backend as search_reports, tuned for "have we seen this before?"). Returns ranked { reports: [{ id, summary, similarity }] }. Read-only. Use to dedupe before filing or group regressions; use search_reports for general free-text search.',
|
|
47
47
|
scope: "mcp:read",
|
|
48
48
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
49
49
|
useCase: "Have we seen a bug like this before?"
|
|
@@ -51,7 +51,7 @@ var TOOL_CATALOG = [
|
|
|
51
51
|
{
|
|
52
52
|
name: "get_fix_context",
|
|
53
53
|
title: "Fix context bundle",
|
|
54
|
-
description: "Bundle
|
|
54
|
+
description: "Bundle everything an agent needs to fix one bug in a single call: a paste-ready fixPrompt (plain-English diagnosis + reproduction + suggested fix + relevant code + blast radius), plus report detail, repro steps, component, root cause, and ontology tags. Returns { fixPrompt, report, reproduction, component, rootCause, tags }. Read-only; no second LLM key needed. Use before writing a fix; use triage_issue for a multi-report review packet, or suggest_fix for just the Stage-2 hint.",
|
|
55
55
|
scope: "mcp:read",
|
|
56
56
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
57
57
|
useCase: "Give me everything I need to fix this in one payload."
|
|
@@ -59,7 +59,7 @@ var TOOL_CATALOG = [
|
|
|
59
59
|
{
|
|
60
60
|
name: "get_fix_timeline",
|
|
61
61
|
title: "Fix timeline",
|
|
62
|
-
description: '
|
|
62
|
+
description: 'Return the ordered lifecycle of one fix attempt: dispatched, started, branch, commit, PR opened, CI, completed/failed, with timestamps and the PR URL. Returns { events: [{ ts, stage, detail }] }. Read-only. Use to debug "why did this fix fail?" after dispatch_fix; use refresh_ci to re-poll GitHub CI, or get_report_timeline for the whole report thread.',
|
|
63
63
|
scope: "mcp:read",
|
|
64
64
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
65
65
|
useCase: "Why did this fix attempt fail \u2014 show me every step."
|
|
@@ -67,7 +67,7 @@ var TOOL_CATALOG = [
|
|
|
67
67
|
{
|
|
68
68
|
name: "get_blast_radius",
|
|
69
69
|
title: "Blast radius",
|
|
70
|
-
description: "
|
|
70
|
+
description: "Return the other components/pages a bug group touches, via knowledge-graph traversal from the report node. Returns { nodes: [{ id, label, type }], edges }. Read-only. Use before dispatch_fix to scope a change safely; use get_knowledge_graph to traverse from an arbitrary seed, or analyze_codebase_impact for file-level import impact.",
|
|
71
71
|
scope: "mcp:read",
|
|
72
72
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
73
73
|
useCase: "What else might break if I change this component?"
|
|
@@ -75,7 +75,7 @@ var TOOL_CATALOG = [
|
|
|
75
75
|
{
|
|
76
76
|
name: "get_knowledge_graph",
|
|
77
77
|
title: "Knowledge graph traversal",
|
|
78
|
-
description: "Traverse the knowledge graph from a seed component or page. Returns nodes
|
|
78
|
+
description: "Traverse the knowledge graph from a seed component or page. Returns { nodes: [{ id, label, node_type }], edges: [{ source_node_id, target_node_id, edge_type }] } within a depth budget (default 2, max 4 hops). Read-only. Use to see how a component connects to the rest of the app; use get_blast_radius for a bug's impact area, or get_graph_neighborhood for a tighter BFS around one node.",
|
|
79
79
|
scope: "mcp:read",
|
|
80
80
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
81
81
|
useCase: "Show me how this component connects to the rest of the app."
|
|
@@ -83,97 +83,73 @@ var TOOL_CATALOG = [
|
|
|
83
83
|
{
|
|
84
84
|
name: "run_nl_query",
|
|
85
85
|
title: "Ask your data (NL \u2192 SQL)",
|
|
86
|
-
description:
|
|
86
|
+
description: 'Answer a natural-language question about your project data by generating and running a read-only SQL query (no privileged schemas, rate-limited to 60/hour). Returns { sql, rows }. Use for ad-hoc analytics ("which components had the most critical bugs this week?"); use get_recent_reports/search_reports for plain report lookups, or search_mushi_docs for documentation questions.',
|
|
87
87
|
scope: "mcp:read",
|
|
88
88
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
89
89
|
useCase: "Which components had the most critical bugs this week?"
|
|
90
90
|
},
|
|
91
91
|
// --- Inventory v2 (whitepaper §6.8) -------------------------------------
|
|
92
92
|
{
|
|
93
|
-
name: "
|
|
93
|
+
name: "get_inventory",
|
|
94
94
|
title: "Inventory snapshot",
|
|
95
|
-
description: "
|
|
95
|
+
description: "Return the current inventory.yaml snapshot for a project: latest ingest, validation errors, and a per-action status summary. Returns { snapshot, validationErrors, actions: [{ id, status }] }. Requires the inventory_v2 plan. Read-only. Use for the full current state; use diff_inventory to compare two commits, or list_gate_findings for the latest gate results.",
|
|
96
96
|
scope: "mcp:read",
|
|
97
97
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
98
98
|
useCase: "What does the live inventory claim for this repo right now?"
|
|
99
99
|
},
|
|
100
100
|
{
|
|
101
|
-
name: "
|
|
101
|
+
name: "diff_inventory",
|
|
102
102
|
title: "Inventory diff",
|
|
103
|
-
description: "Diff two ingested inventory commits (fromSha
|
|
103
|
+
description: "Diff two ingested inventory commits (fromSha to toSha): added/removed nodes and edges. Returns { added, removed, changed }. Requires inventory_v2. Read-only. Use before merging a PR that touches inventory.yaml to see what changed; use get_inventory for the current snapshot.",
|
|
104
104
|
scope: "mcp:read",
|
|
105
105
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
106
106
|
useCase: "What changed in inventory between these two SHAs?"
|
|
107
107
|
},
|
|
108
108
|
{
|
|
109
|
-
name: "
|
|
109
|
+
name: "list_gate_findings",
|
|
110
110
|
title: "Gate findings",
|
|
111
|
-
description: "
|
|
111
|
+
description: "List recent inventory gate runs and their findings for a project, newest first. Returns { runs: [{ id, gate, status, findings_count, \u2026 }], findings: [{ severity, rule_id, message, file_path, node_id, \u2026 }] }. Filter by gate (dead-handler | mock-leak | crawl | status-claim) or minimum severity (low|medium|high|critical). Read-only. Use to see which CI gates failed on the last crawl; use diff_inventory to compare two commits, or get_inventory for the full snapshot.",
|
|
112
112
|
scope: "mcp:read",
|
|
113
113
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
114
114
|
useCase: "Show me what CI gates failed on the last run."
|
|
115
115
|
},
|
|
116
116
|
{
|
|
117
|
-
name: "
|
|
117
|
+
name: "get_graph_neighborhood",
|
|
118
118
|
title: "Graph neighborhood",
|
|
119
|
-
description:
|
|
119
|
+
description: `Return the BFS neighborhood around one graph node by id or label: { nodes: [{ id, label, node_type }], edges: [{ source_node_id, target_node_id, edge_type }] } within a depth budget (default 2, max 4). Read-only. Tuned for "what touches this action?"; use get_knowledge_graph to traverse from a component seed, or get_graph_node for a single node's row.`,
|
|
120
120
|
scope: "mcp:read",
|
|
121
121
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
122
122
|
useCase: "What nodes connect to this inventory Action within 2 hops?"
|
|
123
123
|
},
|
|
124
124
|
{
|
|
125
|
-
name: "
|
|
125
|
+
name: "get_graph_node",
|
|
126
126
|
title: "Graph node detail",
|
|
127
|
-
description: "Fetch
|
|
127
|
+
description: "Fetch one knowledge-graph node row by id. Returns { node: { id, node_type, label, metadata } } including the v2 derived status on Action nodes (ok | stale | broken). Read-only. Use to inspect a single node's status; use get_graph_neighborhood to see what connects to it.",
|
|
128
128
|
scope: "mcp:read",
|
|
129
129
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
130
130
|
useCase: "What status does the graph store on this node id?"
|
|
131
131
|
},
|
|
132
132
|
{
|
|
133
|
-
name: "
|
|
133
|
+
name: "suggest_fix",
|
|
134
134
|
title: "Suggested fix (from triage)",
|
|
135
|
-
description:
|
|
135
|
+
description: 'Return the Stage-2 suggested-fix slice for one report: root cause, suggested fix, repro steps, summary, and component \u2014 faster than get_report_detail when you only need the human-readable hint. Returns { reportId, rootCause, suggestedFix, reproductionSteps, summary, component }. Read-only; reads the existing classification (run triage_issue first if unclassified). Use for a quick "what should we try?"; use get_fix_context for the full paste-ready bundle.',
|
|
136
136
|
scope: "mcp:read",
|
|
137
137
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
138
138
|
useCase: "What did Stage 2 say we should try for this report?"
|
|
139
139
|
},
|
|
140
140
|
// --- Setup / admin -------------------------------------------------------
|
|
141
|
-
{
|
|
142
|
-
name: "setup_check",
|
|
143
|
-
title: "Dispatch preflight check",
|
|
144
|
-
description: "[Deprecated \u2014 use diagnose_setup with mode=dispatch or diagnose_connection.] Run the 4 **dispatch-readiness** checks for a project and return their pass/fail status (GitHub repo connected, codebase indexed, your Anthropic API key present, autofix enabled). Also returns the target repo URL when GitHub is connected.",
|
|
145
|
-
scope: "mcp:read",
|
|
146
|
-
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
147
|
-
useCase: "Is this project ready to auto-fix bugs? What is blocking dispatch?"
|
|
148
|
-
},
|
|
149
|
-
{
|
|
150
|
-
name: "ingest_setup_check",
|
|
151
|
-
title: "Ingest setup check",
|
|
152
|
-
description: "[Deprecated \u2014 use diagnose_setup with mode=ingest or diagnose_connection.] Run the 4 **required ingest** checks for the project tied to this API key: project exists, active API key, SDK heartbeat (or real report), and at least one ingested report.",
|
|
153
|
-
scope: "mcp:read",
|
|
154
|
-
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
155
|
-
useCase: "Is the SDK installed and ingesting reports? Why is my banner still missing?"
|
|
156
|
-
},
|
|
157
141
|
{
|
|
158
142
|
name: "diagnose_setup",
|
|
159
143
|
title: "Unified setup diagnose",
|
|
160
|
-
description: "
|
|
144
|
+
description: "Diagnose Mushi setup health and return the single best next action. mode=full (default) runs both SDK-ingest and fix-dispatch preflight checks; mode=ingest runs ingest checks only (project exists, active API key, SDK heartbeat, at least one report); mode=dispatch runs dispatch readiness only (GitHub connected, codebase indexed, LLM key present, autofix enabled). Returns { ready, steps: [{ label, complete, required, hint }], nextAction }. Read-only. The one setup-diagnosis entry point \u2014 use this instead of separate connection/ingest checks.",
|
|
161
145
|
scope: "mcp:read",
|
|
162
146
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
163
147
|
useCase: "Why is Mushi setup broken \u2014 one call, clear next step."
|
|
164
148
|
},
|
|
165
|
-
{
|
|
166
|
-
name: "diagnose_connection",
|
|
167
|
-
title: "Connection diagnose (CLI + MCP + SDK)",
|
|
168
|
-
description: `Validate MCP credentials, ping /health, run ingest-setup and dispatch preflight, return the single best next action. For mode-specific checks prefer diagnose_setup. Use when the user asks "why aren't my reports showing up?".`,
|
|
169
|
-
scope: "mcp:read",
|
|
170
|
-
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
171
|
-
useCase: "Why is my Mushi setup broken \u2014 what exact step should I fix next?"
|
|
172
|
-
},
|
|
173
149
|
{
|
|
174
150
|
name: "search_mushi_docs",
|
|
175
151
|
title: "Search Mushi documentation",
|
|
176
|
-
description: "Search official Mushi
|
|
152
|
+
description: "Search the official Mushi documentation (guides, MCP setup, inventory, QA, skills) by keyword. Returns ranked { results: [{ title, url, excerpt }] }. Read-only. Use before guessing API shapes, tool names, or RPC names; use run_nl_query for questions about your own project data, not the docs.",
|
|
177
153
|
scope: "mcp:read",
|
|
178
154
|
hints: { readOnly: true, idempotent: true, openWorld: false },
|
|
179
155
|
useCase: "How do I configure MCP scopes / dispatch a fix / wire QA stories?"
|
|
@@ -182,7 +158,7 @@ var TOOL_CATALOG = [
|
|
|
182
158
|
{
|
|
183
159
|
name: "submit_fix_result",
|
|
184
160
|
title: "Record a fix outcome",
|
|
185
|
-
description: "Record a fix outcome (branch, PR, files, lines
|
|
161
|
+
description: "Record a fix outcome from an external agent (e.g. your own Cursor/Claude run): branch, PR URL, files changed, lines added/removed. Creates a fix_attempt row then patches it to completed and links it to the report. Returns { fixAttemptId }. Write; NOT idempotent \u2014 each call creates a new fix_attempt, so call once per PR. Use after you opened a PR outside Mushi; use dispatch_fix to have Mushi open the PR instead, or merge_fix once CI is green.",
|
|
186
162
|
scope: "mcp:write",
|
|
187
163
|
// Not idempotent: re-running creates a second fix_attempt row.
|
|
188
164
|
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
@@ -191,7 +167,7 @@ var TOOL_CATALOG = [
|
|
|
191
167
|
{
|
|
192
168
|
name: "dispatch_fix",
|
|
193
169
|
title: "Dispatch Mushi fix agent",
|
|
194
|
-
description: 'Start
|
|
170
|
+
description: 'Start a Mushi fix agent for a classified report; it writes a branch and opens a signed draft PR. Set agent="cursor_cloud" to dispatch a Cursor Cloud Agent (default uses the in-repo worker). Requires GitHub connected + an LLM key (run diagnose_setup mode=dispatch first). Returns { fixAttemptId }; poll get_fix_timeline for progress and merge_fix when CI is green. Write; NOT idempotent \u2014 each call starts a new attempt. Report must be classified \u2014 run triage_issue if not.',
|
|
195
171
|
scope: "mcp:write",
|
|
196
172
|
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
197
173
|
useCase: "Let the in-repo agent attempt this fix for me (or: dispatch a Cursor Cloud Agent)."
|
|
@@ -199,7 +175,7 @@ var TOOL_CATALOG = [
|
|
|
199
175
|
{
|
|
200
176
|
name: "trigger_judge",
|
|
201
177
|
title: "Run Sonnet-as-Judge",
|
|
202
|
-
description: "
|
|
178
|
+
description: "Queue the Sonnet-as-Judge to grade recent fix quality across accessible projects. Returns { dispatched: number } \u2014 one judge-batch job per project; scores land asynchronously in judge_results (read back with run_nl_query). Write; consumes LLM budget. Idempotent within a short window. Use before shipping to vet fix quality; use get_fix_timeline to inspect a single attempt instead.",
|
|
203
179
|
scope: "mcp:write",
|
|
204
180
|
hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
|
|
205
181
|
useCase: "Grade the latest batch of fixes before I ship."
|
|
@@ -207,7 +183,7 @@ var TOOL_CATALOG = [
|
|
|
207
183
|
{
|
|
208
184
|
name: "test_gen_from_report",
|
|
209
185
|
title: "Generate Playwright test from report",
|
|
210
|
-
description: "Generate a Playwright
|
|
186
|
+
description: "Generate a Playwright regression test from a classified report using your project LLM key, then open a draft GitHub PR with the spec. Requires the inventory_v2 plan plus GitHub and LLM keys configured. Returns { qaStoryId, prUrl }. Write; consumes LLM budget; NOT idempotent \u2014 each call opens a new PR. Use to lock in a regression as an E2E test; use generate_tdd_from_story to build a test from a mapped user story instead.",
|
|
211
187
|
scope: "mcp:write",
|
|
212
188
|
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
213
189
|
useCase: "Turn this regression report into an E2E test PR."
|
|
@@ -215,7 +191,7 @@ var TOOL_CATALOG = [
|
|
|
215
191
|
{
|
|
216
192
|
name: "transition_status",
|
|
217
193
|
title: "Move report between states",
|
|
218
|
-
description: "Move a report
|
|
194
|
+
description: "Move a report to a new workflow state, enforcing the same transition rules as the admin UI. Valid targets: classified, grouped, fixing, fixed, verified, reopened, dismissed. Returns { report } with the updated status. Write; idempotent (setting the current status is a no-op); rejects illegal transitions. Use to dismiss a duplicate or mark fixed; use merge_fix to mark fixed via a merged PR, or reopen_report for the reopened path.",
|
|
219
195
|
scope: "mcp:write",
|
|
220
196
|
hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
|
|
221
197
|
useCase: "Dismiss this duplicate / mark it fixed."
|
|
@@ -223,7 +199,7 @@ var TOOL_CATALOG = [
|
|
|
223
199
|
{
|
|
224
200
|
name: "merge_fix",
|
|
225
201
|
title: "Merge fix PR",
|
|
226
|
-
description: "Squash-merge a fix attempt
|
|
202
|
+
description: "Squash-merge the GitHub PR for a fix attempt, mark the linked report fixed, and notify the reporter. Re-readies the PR first if it is still a draft. Returns { merged, reportStatus }. Write; destructive (mutates the target repo's default branch); idempotent \u2014 re-running an already-merged attempt is a safe no-op. Prerequisite: CI green (check with refresh_ci). Use to ship a fix opened by dispatch_fix; use transition_status to change state without merging.",
|
|
227
203
|
scope: "mcp:write",
|
|
228
204
|
hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
|
|
229
205
|
useCase: "Merge the draft PR and notify the reporter."
|
|
@@ -231,15 +207,15 @@ var TOOL_CATALOG = [
|
|
|
231
207
|
{
|
|
232
208
|
name: "refresh_ci",
|
|
233
209
|
title: "Refresh fix CI status",
|
|
234
|
-
description: "
|
|
235
|
-
scope: "mcp:
|
|
236
|
-
hints: { readOnly:
|
|
210
|
+
description: "Re-poll GitHub for the latest check-run status of a fix attempt's PR and persist it on the fix_attempt row (does not merge or mutate GitHub). Returns { check_run_status, check_run_conclusion, check_run_updated_at }. Write; idempotent. Use right before merge_fix to confirm CI is green; use get_fix_timeline for the full attempt lifecycle.",
|
|
211
|
+
scope: "mcp:write",
|
|
212
|
+
hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
|
|
237
213
|
useCase: "Check whether CI is green before merging."
|
|
238
214
|
},
|
|
239
215
|
{
|
|
240
216
|
name: "reopen_report",
|
|
241
217
|
title: "Reopen report (operator)",
|
|
242
|
-
description:
|
|
218
|
+
description: 'Move a previously fixed/verified/dismissed report back to the reopened state for regression review, recording an operator note. Returns { report } with status=reopened. Write; idempotent \u2014 reopening an already-reopened report is a no-op. Use when a reporter says "still broken" after a fix shipped; use transition_status for any other state change.',
|
|
243
219
|
scope: "mcp:write",
|
|
244
220
|
hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
|
|
245
221
|
useCase: "Reopen a regression the reporter flagged as not fixed."
|
|
@@ -330,7 +306,7 @@ var TOOL_CATALOG = [
|
|
|
330
306
|
{
|
|
331
307
|
name: "query_lessons",
|
|
332
308
|
title: "Query lessons for diff context",
|
|
333
|
-
description: '
|
|
309
|
+
description: 'Retrieve the learning rules ("lessons") most relevant to a given code diff or PR context, packed within a token budget. Uses bi-encoder retrieval + severity-weighted scoring; pass the diff/description as the query and max_tokens (default 2000). Returns ranked { lessons: [{ title, rule, severity }] }. Read-only. Use before writing a fix or opening a PR; use list_lessons to browse all lessons unfiltered.',
|
|
334
310
|
scope: "mcp:read",
|
|
335
311
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
336
312
|
useCase: "What past mistakes should I avoid when making this change?"
|
|
@@ -338,17 +314,25 @@ var TOOL_CATALOG = [
|
|
|
338
314
|
{
|
|
339
315
|
name: "list_lessons",
|
|
340
316
|
title: "List project lessons",
|
|
341
|
-
description:
|
|
317
|
+
description: 'List promoted learning rules ("lessons") for the current project, highest-frequency first. Returns { lessons: [{ id, rule_text, severity, frequency, anti_pattern, \u2026 }] }. Read-only. Use to browse the full catalog of encoded heuristics; use query_lessons to retrieve only lessons relevant to a specific diff or PR within a token budget.',
|
|
342
318
|
scope: "mcp:read",
|
|
343
319
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
344
320
|
useCase: "What systemic patterns has Mushi identified for this project?"
|
|
321
|
+
},
|
|
322
|
+
{
|
|
323
|
+
name: "activation_status",
|
|
324
|
+
title: "Activation cockpit status",
|
|
325
|
+
description: "Return the unified activation posture \u2014 SDK heartbeat, ingested reports, GitHub, MCP readiness, QA stories, and the next best action. Read this before guessing which onboarding step is blocking the user. Also available as the mushi://activation resource for resource-reader clients. Returns { sdkActive, reportsIngested, githubConnected, mcpConnected, qaStoriesCreated, nextBestAction }.",
|
|
326
|
+
scope: "mcp:read",
|
|
327
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
328
|
+
useCase: "Is Mushi fully set up and active for this project?"
|
|
345
329
|
}
|
|
346
330
|
];
|
|
347
331
|
var TDD_TOOL_CATALOG = [
|
|
348
332
|
{
|
|
349
333
|
name: "map_user_stories",
|
|
350
334
|
title: "Map user stories from live app",
|
|
351
|
-
description: 'Crawl a live application URL
|
|
335
|
+
description: 'Crawl a live application URL (Firecrawl/Browserbase) and have Claude draft an inventory.yaml of pages and user stories, written to an inventory_proposals row (source=live_crawl). Optionally dispatches a Cursor Cloud agent to refine the draft and open a PR. Returns { runId, status: "pending" } immediately \u2014 poll get_map_run_status. Write; consumes crawl + LLM budget; NOT idempotent. Use to bootstrap test coverage without hand-writing YAML; then generate_tdd_from_story per accepted story.',
|
|
352
336
|
scope: "mcp:write",
|
|
353
337
|
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
354
338
|
useCase: "Map the user stories in my live app automatically without writing YAML by hand."
|
|
@@ -356,7 +340,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
356
340
|
{
|
|
357
341
|
name: "get_map_run_status",
|
|
358
342
|
title: "Story map run status",
|
|
359
|
-
description: "
|
|
343
|
+
description: "Poll the status and results of a story_map_run started by map_user_stories. Returns { status: pending|running|completed|failed, pages_crawled, proposal_id (once done), cursor_pr_url (if Cursor Cloud refined the draft) }. Read-only. Use to know when a crawl has finished and which inventory_proposals row to review next.",
|
|
360
344
|
scope: "mcp:read",
|
|
361
345
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
362
346
|
useCase: "Is my story mapping crawl done yet?"
|
|
@@ -364,7 +348,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
364
348
|
{
|
|
365
349
|
name: "generate_tdd_from_story",
|
|
366
350
|
title: "Generate TDD test from user story",
|
|
367
|
-
description: "
|
|
351
|
+
description: "Generate a full Playwright TypeScript test from a mapped user story id (from accepted inventory) using Claude, and insert a qa_stories row (source=test_gen_from_story). approval_status follows the project automation_mode (auto = enabled immediately; review/approve = pending_review). Optionally opens a draft GitHub PR. Returns { qaStoryId, prUrl, approvalStatus, needsHumanReview }. Write; consumes LLM budget; NOT idempotent. Run map_user_stories first; use test_gen_from_report to build a test from a bug report instead.",
|
|
368
352
|
scope: "mcp:write",
|
|
369
353
|
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
370
354
|
useCase: "Generate a Playwright test for this user story."
|
|
@@ -372,7 +356,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
372
356
|
{
|
|
373
357
|
name: "improve_qa_story",
|
|
374
358
|
title: "Auto-improve a failing QA story",
|
|
375
|
-
description: "
|
|
359
|
+
description: "Analyze recently failed qa_story_runs and use Claude to write improved test scripts that address the failures. New tests are created with source=pdca, parent_story_id chained to the original, and approval gated by the original story's automation_mode. Returns { improvedStoryIds }. Write; consumes LLM budget; NOT idempotent. Use to repair flaky/broken tests; use run_qa_story to re-run as-is, or list_qa_story_runs to inspect failures first.",
|
|
376
360
|
scope: "mcp:write",
|
|
377
361
|
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
378
362
|
useCase: "Fix my failing QA tests automatically."
|
|
@@ -380,7 +364,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
380
364
|
{
|
|
381
365
|
name: "run_qa_story",
|
|
382
366
|
title: "Trigger a manual QA story run",
|
|
383
|
-
description: 'Queue
|
|
367
|
+
description: 'Queue an immediate manual run for an enabled + approved qa_story (equivalent to "Run now" in the console). Returns { runId } right away; poll list_qa_story_runs or get_qa_story_run for progress and results. Write; NOT idempotent \u2014 each call creates a new run; returns 409 if the story is disabled or pending review. Use to verify a flow on demand; use improve_qa_story to repair a failing story.',
|
|
384
368
|
scope: "mcp:write",
|
|
385
369
|
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
386
370
|
useCase: "Run the login flow test right now."
|
|
@@ -388,7 +372,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
388
372
|
{
|
|
389
373
|
name: "list_byok_keys",
|
|
390
374
|
title: "List your API key pool",
|
|
391
|
-
description: "List
|
|
375
|
+
description: "List the project's BYOK API keys grouped by provider (anthropic | openai | firecrawl | browserbase | cursor). Returns { keys: [{ id, provider, label, priority, status, cooldownUntil }] } \u2014 metadata only, never the raw secret. Read-only. Use to see which keys are active vs rate-limited/exhausted; use add_byok_key to add one.",
|
|
392
376
|
scope: "mcp:read",
|
|
393
377
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
394
378
|
useCase: "Which API keys are active and which are rate-limited?"
|
|
@@ -396,7 +380,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
396
380
|
{
|
|
397
381
|
name: "add_byok_key",
|
|
398
382
|
title: "Add an API key",
|
|
399
|
-
description: "Add a
|
|
383
|
+
description: "Add a BYOK API key to the project pool for a provider (anthropic | openai | firecrawl | browserbase | cursor), with a label and priority for failover ordering. The raw key is stored encrypted in Supabase Vault and never returned. Returns { id, provider, label }. Write; NOT idempotent \u2014 adds a new row each call. Use to register a backup/rotated key; use list_byok_keys to review the pool.",
|
|
400
384
|
scope: "mcp:write",
|
|
401
385
|
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
402
386
|
useCase: "Add a backup Anthropic key to the pool."
|
|
@@ -404,7 +388,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
404
388
|
{
|
|
405
389
|
name: "list_pending_review_stories",
|
|
406
390
|
title: "List QA stories pending review",
|
|
407
|
-
description: "
|
|
391
|
+
description: "List auto-generated QA/TDD stories in approval_status=pending_review \u2014 the queue waiting for human sign-off before they run on schedule. Returns { stories: [{ id, title, source, target_url, created_at }] }. Read-only. Use to find what needs review today; then approve_qa_story to approve or reject each.",
|
|
408
392
|
scope: "mcp:read",
|
|
409
393
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
410
394
|
useCase: "What TDD tests need my approval today?"
|
|
@@ -412,7 +396,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
412
396
|
{
|
|
413
397
|
name: "approve_qa_story",
|
|
414
398
|
title: "Approve or reject a pending QA story",
|
|
415
|
-
description: "Approve or reject a qa_story
|
|
399
|
+
description: "Approve or reject a qa_story currently in pending_review (pass approve=true|false and an optional note). Approved stories are enabled in the QA schedule immediately; rejected ones are disabled. Returns { id, approval_status }. Write; idempotent \u2014 re-approving an approved story is a no-op. Use to clear the review queue from list_pending_review_stories.",
|
|
416
400
|
scope: "mcp:write",
|
|
417
401
|
hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
|
|
418
402
|
useCase: "Approve this auto-generated test."
|
|
@@ -470,7 +454,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
470
454
|
{
|
|
471
455
|
name: "list_skills",
|
|
472
456
|
title: "List agent skills",
|
|
473
|
-
description: "List the agent skills
|
|
457
|
+
description: "List the agent skills in the catalog, optionally filtered by category or search text. Returns { skills: [{ slug, title, description, category, chain_slugs }] }. Read-only. Use to find the right skill slug before start_skill_pipeline; use get_skill to read one skill's full instructions.",
|
|
474
458
|
scope: "mcp:read",
|
|
475
459
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
476
460
|
useCase: "What skills are available for debugging production errors?"
|
|
@@ -478,7 +462,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
478
462
|
{
|
|
479
463
|
name: "get_skill",
|
|
480
464
|
title: "Get skill detail",
|
|
481
|
-
description: "Fetch
|
|
465
|
+
description: "Fetch one agent skill by slug, including the complete SKILL.md body and the resolved chain of sub-skills. Returns { slug, title, body, chain: [{ slug, title }] }. Read-only. Use to read what a skill instructs before executing a pipeline step; use list_skills to discover slugs.",
|
|
482
466
|
scope: "mcp:read",
|
|
483
467
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
484
468
|
useCase: "What does workflow-fix-and-ship actually instruct me to do?"
|
|
@@ -494,7 +478,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
494
478
|
{
|
|
495
479
|
name: "get_pipeline_run",
|
|
496
480
|
title: "Get pipeline run detail",
|
|
497
|
-
description: "Fetch
|
|
481
|
+
description: "Fetch a skill pipeline run by id: { status, context_packet, steps: [{ index, slug, status }] }. Read-only. Use to retrieve the context_packet when a pipeline was started from the console or another agent; then checkin_pipeline_step as you complete each step. Use start_skill_pipeline to begin a new run.",
|
|
498
482
|
scope: "mcp:read",
|
|
499
483
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
500
484
|
useCase: "Give me the context packet and step status for this pipeline run."
|
|
@@ -507,22 +491,6 @@ var TDD_TOOL_CATALOG = [
|
|
|
507
491
|
hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
|
|
508
492
|
useCase: "I just opened the PR \u2014 mark step 2 as passed and link the PR."
|
|
509
493
|
},
|
|
510
|
-
{
|
|
511
|
-
name: "get_activation_status",
|
|
512
|
-
title: "Activation cockpit status",
|
|
513
|
-
description: "[Deprecated \u2014 use activation_status resource or tool on hosted MCP.] Unified setup posture for the active project: required steps, SDK heartbeat, dispatch preflight, and the next best action.",
|
|
514
|
-
scope: "mcp:read",
|
|
515
|
-
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
516
|
-
useCase: "What is blocking this project from going live?"
|
|
517
|
-
},
|
|
518
|
-
{
|
|
519
|
-
name: "get_reporter_thread",
|
|
520
|
-
title: "Reporter feedback thread",
|
|
521
|
-
description: "[Deprecated \u2014 use get_report_timeline instead.] Fetch the unified timeline for a report \u2014 reporter comments plus fix, QA, and status lanes.",
|
|
522
|
-
scope: "mcp:read",
|
|
523
|
-
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
524
|
-
useCase: "What did the reporter say after we marked this fixed?"
|
|
525
|
-
},
|
|
526
494
|
// ── Full-Stack Audit tools (Phase 5) ────────────────────────────────────────
|
|
527
495
|
{
|
|
528
496
|
name: "run_fullstack_audit",
|
|
@@ -545,7 +513,7 @@ var CODEBASE_TOOL_CATALOG = [
|
|
|
545
513
|
{
|
|
546
514
|
name: "ask_codebase",
|
|
547
515
|
title: "Ask about the indexed codebase",
|
|
548
|
-
description:
|
|
516
|
+
description: 'Answer a plain-English question about the connected repo, grounded on pgvector retrieval over project_codebase_files. Returns { answer, citations: [{ path, line }] }. Set include_wiki=true to merge docs/wiki knowledge. Requires codebase indexing + your Anthropic or OpenAI key; consumes LLM budget (write scope). Use for "how does X work?"; use search_codebase for raw file matches without synthesis, or get_file_summary for one file.',
|
|
549
517
|
scope: "mcp:write",
|
|
550
518
|
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
551
519
|
useCase: "How does authentication work in this repo?"
|
|
@@ -553,7 +521,7 @@ var CODEBASE_TOOL_CATALOG = [
|
|
|
553
521
|
{
|
|
554
522
|
name: "get_file_summary",
|
|
555
523
|
title: "Plain-English file summary",
|
|
556
|
-
description: "
|
|
524
|
+
description: "Return a plain-English summary of one indexed file or symbol (lazily generated on first call, then cached until content_hash changes on re-index). Returns { path, summary, symbols }. Requires codebase indexing. Read-only. Use to understand a single file fast; use ask_codebase for cross-file questions, or get_codebase_tour for an onboarding walkthrough.",
|
|
557
525
|
scope: "mcp:read",
|
|
558
526
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
559
527
|
useCase: "Explain what lib/auth.ts does in plain English."
|
|
@@ -561,7 +529,7 @@ var CODEBASE_TOOL_CATALOG = [
|
|
|
561
529
|
{
|
|
562
530
|
name: "get_codebase_tour",
|
|
563
531
|
title: "Guided codebase tour",
|
|
564
|
-
description: "Return a dependency-ordered
|
|
532
|
+
description: "Return a dependency-ordered onboarding tour of the repo (~6-10 stops); each stop lists node ids, file paths, architectural layer, and why it matters. Returns { stops: [{ title, paths, layer, rationale }] }. Cached per index fingerprint. Requires codebase indexing. Read-only. Use to get oriented in an unfamiliar repo; use get_codebase_domains for a business-domain map, or get_file_summary for one file.",
|
|
565
533
|
scope: "mcp:read",
|
|
566
534
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
567
535
|
useCase: "Give me an onboarding walkthrough of this codebase."
|
|
@@ -569,7 +537,7 @@ var CODEBASE_TOOL_CATALOG = [
|
|
|
569
537
|
{
|
|
570
538
|
name: "search_codebase",
|
|
571
539
|
title: "Semantic codebase search",
|
|
572
|
-
description:
|
|
540
|
+
description: 'Search the indexed repo by plain-English meaning via embeddings. Returns { results: [{ file_path, line_start, line_end, content_preview, similarity, \u2026 }], query, mode } ordered by similarity. Requires codebase indexing enabled. Read-only. Use to locate where something lives ("where do we verify webhooks?"); use ask_codebase for a synthesized answer with citations, or analyze_codebase_impact to find dependents of a file.',
|
|
573
541
|
scope: "mcp:read",
|
|
574
542
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
575
543
|
useCase: "Find files related to authentication or payment handling."
|
|
@@ -577,7 +545,7 @@ var CODEBASE_TOOL_CATALOG = [
|
|
|
577
545
|
{
|
|
578
546
|
name: "get_codebase_domains",
|
|
579
547
|
title: "Business domain map",
|
|
580
|
-
description: "Extract business domains, flows, and steps mapped to file paths. Cached per index fingerprint.",
|
|
548
|
+
description: "Extract the business domains, flows, and steps in the repo, each mapped to the file paths that implement it. Returns { domains: [{ name, flows: [{ name, steps, paths }] }] }. Cached per index fingerprint. Requires codebase indexing. Read-only. Use to see what the app does at a product level; use get_codebase_tour for a dependency-ordered code walkthrough instead.",
|
|
581
549
|
scope: "mcp:read",
|
|
582
550
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
583
551
|
useCase: "What business domains does this codebase implement?"
|
|
@@ -585,7 +553,7 @@ var CODEBASE_TOOL_CATALOG = [
|
|
|
585
553
|
{
|
|
586
554
|
name: "analyze_codebase_impact",
|
|
587
555
|
title: "Diff impact analysis",
|
|
588
|
-
description: "Find files that depend on changed paths
|
|
556
|
+
description: "Find files that depend on a set of changed paths by walking the reverse import graph. Source paths from: manual list, last push, a GitHub compare range, or a fix PR's files. Returns { changed_paths, source, affected_file_paths, affected_node_ids, meta }. Requires codebase indexing. Read-only. Use to gauge a diff's blast radius before merging; use get_blast_radius for a bug's component impact, or search_codebase to locate files.",
|
|
589
557
|
scope: "mcp:read",
|
|
590
558
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
591
559
|
useCase: "What breaks if I change lib/auth.ts?"
|
|
@@ -593,7 +561,7 @@ var CODEBASE_TOOL_CATALOG = [
|
|
|
593
561
|
{
|
|
594
562
|
name: "analyze_wiki_knowledge",
|
|
595
563
|
title: "Wiki knowledge graph",
|
|
596
|
-
description: "Return wiki/docs knowledge
|
|
564
|
+
description: "Return the wiki/docs knowledge-graph nodes and their sources for the project. Returns { nodes: [{ id, label, source_url }], sources }. Read-only. Use to see what doc entities exist; pass include_wiki=true to ask_codebase to merge this corpus into a grounded answer.",
|
|
597
565
|
scope: "mcp:read",
|
|
598
566
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
599
567
|
useCase: "What docs entities exist for onboarding?"
|
|
@@ -613,10 +581,17 @@ var FEATURE_GROUPS = [
|
|
|
613
581
|
"audit",
|
|
614
582
|
"docs",
|
|
615
583
|
"usage",
|
|
616
|
-
"
|
|
584
|
+
"codebase",
|
|
617
585
|
/** Deprecated aliases kept for one release — hidden unless `legacy` or `all`. */
|
|
618
586
|
"legacy"
|
|
619
587
|
];
|
|
588
|
+
var DEFAULT_FEATURE_GROUPS = [
|
|
589
|
+
"triage",
|
|
590
|
+
"fixes",
|
|
591
|
+
"inventory",
|
|
592
|
+
"setup",
|
|
593
|
+
"docs"
|
|
594
|
+
];
|
|
620
595
|
var ALL_FEATURE_GROUPS = FEATURE_GROUPS.filter(
|
|
621
596
|
(g) => g !== "legacy"
|
|
622
597
|
);
|
|
@@ -636,21 +611,16 @@ var TOOL_FEATURE_MAP = {
|
|
|
636
611
|
triage_issue: "triage",
|
|
637
612
|
query_lessons: "triage",
|
|
638
613
|
list_lessons: "triage",
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
get_reporter_thread: "legacy",
|
|
614
|
+
suggest_fix: "triage",
|
|
615
|
+
get_graph_neighborhood: "triage",
|
|
616
|
+
get_graph_node: "triage",
|
|
643
617
|
// inventory
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
618
|
+
get_inventory: "inventory",
|
|
619
|
+
diff_inventory: "inventory",
|
|
620
|
+
list_gate_findings: "inventory",
|
|
647
621
|
inventory_current: "admin",
|
|
648
622
|
// setup
|
|
649
|
-
diagnose_connection: "setup",
|
|
650
623
|
diagnose_setup: "setup",
|
|
651
|
-
setup_check: "legacy",
|
|
652
|
-
ingest_setup_check: "legacy",
|
|
653
|
-
get_activation_status: "legacy",
|
|
654
624
|
activation_status: "setup",
|
|
655
625
|
project_integration_health: "setup",
|
|
656
626
|
setup_repo_for_mushi: "setup",
|
|
@@ -703,16 +673,44 @@ var TOOL_FEATURE_MAP = {
|
|
|
703
673
|
// audit
|
|
704
674
|
run_fullstack_audit: "audit",
|
|
705
675
|
get_backend_health: "audit",
|
|
676
|
+
// codebase understand
|
|
677
|
+
ask_codebase: "codebase",
|
|
678
|
+
get_file_summary: "codebase",
|
|
679
|
+
get_codebase_tour: "codebase",
|
|
680
|
+
search_codebase: "codebase",
|
|
681
|
+
get_codebase_domains: "codebase",
|
|
682
|
+
analyze_codebase_impact: "codebase",
|
|
683
|
+
analyze_wiki_knowledge: "codebase",
|
|
706
684
|
// docs
|
|
707
685
|
search_mushi_docs: "docs"
|
|
708
686
|
};
|
|
687
|
+
var DEPRECATED_TOOL_ALIASES = {
|
|
688
|
+
// Removed deprecated tools → successor.
|
|
689
|
+
setup_check: "diagnose_setup",
|
|
690
|
+
ingest_setup_check: "diagnose_setup",
|
|
691
|
+
diagnose_connection: "diagnose_setup",
|
|
692
|
+
get_activation_status: "activation_status",
|
|
693
|
+
get_reporter_thread: "get_report_timeline",
|
|
694
|
+
// Renamed to verb_object → new name.
|
|
695
|
+
fix_suggest: "suggest_fix",
|
|
696
|
+
inventory_get: "get_inventory",
|
|
697
|
+
inventory_diff: "diff_inventory",
|
|
698
|
+
inventory_findings: "list_gate_findings",
|
|
699
|
+
graph_neighborhood: "get_graph_neighborhood",
|
|
700
|
+
graph_node_status: "get_graph_node"
|
|
701
|
+
};
|
|
709
702
|
function parseFeaturesParam(raw) {
|
|
710
703
|
if (raw == null || raw.trim() === "") return "all";
|
|
711
704
|
const normalized = raw.trim().toLowerCase();
|
|
712
705
|
if (normalized === "all" || normalized === "*") return "all";
|
|
713
706
|
const groups = normalized.split(",").map((s) => s.trim()).filter(Boolean);
|
|
714
707
|
const valid = groups.filter((g) => FEATURE_GROUPS.includes(g));
|
|
715
|
-
if (valid.length === 0)
|
|
708
|
+
if (valid.length === 0) {
|
|
709
|
+
console.warn(
|
|
710
|
+
`[mushi-mcp] MUSHI_FEATURES="${raw}" contains no recognised groups; falling back to DEFAULT_FEATURE_GROUPS. Valid groups: ${FEATURE_GROUPS.join(", ")}`
|
|
711
|
+
);
|
|
712
|
+
return DEFAULT_FEATURE_GROUPS;
|
|
713
|
+
}
|
|
716
714
|
return valid;
|
|
717
715
|
}
|
|
718
716
|
function parseFeaturesCsv(raw) {
|
|
@@ -780,8 +778,8 @@ var MUSHI_DOCS_INDEX = [
|
|
|
780
778
|
{
|
|
781
779
|
title: "Cursor MCP troubleshooting",
|
|
782
780
|
path: `${BASE}/guides/mcp-setup`,
|
|
783
|
-
keywords: ["cursor", "mcp.json", "red badge", "transport error", "stdout", "stderr", "json-rpc", "INVALID_TOKEN", "syntax helper", "
|
|
784
|
-
excerpt: "Cursor stdio MCP requires stdout to contain only JSON-RPC. Use the console syntax helper, restart Cursor MCP, then run
|
|
781
|
+
keywords: ["cursor", "mcp.json", "red badge", "transport error", "stdout", "stderr", "json-rpc", "INVALID_TOKEN", "syntax helper", "diagnose_setup"],
|
|
782
|
+
excerpt: "Cursor stdio MCP requires stdout to contain only JSON-RPC. Use the console syntax helper, restart Cursor MCP, then run diagnose_setup."
|
|
785
783
|
},
|
|
786
784
|
{
|
|
787
785
|
title: "Judge loop",
|
|
@@ -1224,8 +1222,12 @@ function createMushiServer(config) {
|
|
|
1224
1222
|
description: descOf("get_knowledge_graph"),
|
|
1225
1223
|
annotations: annotationsFor("get_knowledge_graph"),
|
|
1226
1224
|
inputSchema: {
|
|
1227
|
-
seed: z.string().describe("Starting node id or label"),
|
|
1228
|
-
depth: z.number().optional().describe("
|
|
1225
|
+
seed: z.string().describe("Starting graph node id or human-readable label to traverse from"),
|
|
1226
|
+
depth: z.number().optional().describe("BFS hops outward from seed \u2014 clamped to 4 in the handler (default 2)")
|
|
1227
|
+
},
|
|
1228
|
+
outputSchema: {
|
|
1229
|
+
nodes: z.array(z.unknown()).describe("Graph nodes within the depth budget"),
|
|
1230
|
+
edges: z.array(z.unknown()).describe("Edges connecting the returned nodes")
|
|
1229
1231
|
}
|
|
1230
1232
|
},
|
|
1231
1233
|
async (args) => {
|
|
@@ -1233,18 +1235,23 @@ function createMushiServer(config) {
|
|
|
1233
1235
|
seed: args.seed,
|
|
1234
1236
|
depth: String(Math.min(args.depth ?? 2, 4))
|
|
1235
1237
|
});
|
|
1236
|
-
|
|
1238
|
+
const data = await apiCall(`/v1/admin/graph/traverse?${params}`);
|
|
1239
|
+
return jsonResult(data);
|
|
1237
1240
|
}
|
|
1238
1241
|
);
|
|
1239
1242
|
server.registerTool(
|
|
1240
|
-
"
|
|
1243
|
+
"get_graph_neighborhood",
|
|
1241
1244
|
{
|
|
1242
|
-
title: titleOf("
|
|
1243
|
-
description: descOf("
|
|
1244
|
-
annotations: annotationsFor("
|
|
1245
|
+
title: titleOf("get_graph_neighborhood"),
|
|
1246
|
+
description: descOf("get_graph_neighborhood"),
|
|
1247
|
+
annotations: annotationsFor("get_graph_neighborhood"),
|
|
1245
1248
|
inputSchema: {
|
|
1246
|
-
seed: z.string().describe("Starting node id or label"),
|
|
1247
|
-
depth: z.number().optional().describe("
|
|
1249
|
+
seed: z.string().describe("Starting graph node id or label to expand around (e.g. an inventory Action id or component name)"),
|
|
1250
|
+
depth: z.number().int().min(1).max(4).optional().describe("BFS hops to traverse outward. Default 2; clamped to a max of 4.")
|
|
1251
|
+
},
|
|
1252
|
+
outputSchema: {
|
|
1253
|
+
nodes: z.array(z.unknown()).describe("Graph nodes within the depth budget"),
|
|
1254
|
+
edges: z.array(z.unknown()).describe("Edges connecting the returned nodes")
|
|
1248
1255
|
}
|
|
1249
1256
|
},
|
|
1250
1257
|
async (args) => {
|
|
@@ -1252,88 +1259,108 @@ function createMushiServer(config) {
|
|
|
1252
1259
|
seed: args.seed,
|
|
1253
1260
|
depth: String(Math.min(args.depth ?? 2, 4))
|
|
1254
1261
|
});
|
|
1255
|
-
|
|
1262
|
+
const data = await apiCall(`/v1/admin/graph/traverse?${params}`);
|
|
1263
|
+
return jsonResult(data);
|
|
1256
1264
|
}
|
|
1257
1265
|
);
|
|
1258
1266
|
server.registerTool(
|
|
1259
|
-
"
|
|
1267
|
+
"get_graph_node",
|
|
1260
1268
|
{
|
|
1261
|
-
title: titleOf("
|
|
1262
|
-
description: descOf("
|
|
1263
|
-
annotations: annotationsFor("
|
|
1264
|
-
inputSchema: { nodeId: z.string().describe("graph_nodes.id") }
|
|
1269
|
+
title: titleOf("get_graph_node"),
|
|
1270
|
+
description: descOf("get_graph_node"),
|
|
1271
|
+
annotations: annotationsFor("get_graph_node"),
|
|
1272
|
+
inputSchema: { nodeId: z.string().describe("The graph_nodes.id of the node to fetch (UUID).") },
|
|
1273
|
+
outputSchema: {
|
|
1274
|
+
node: z.unknown().describe("Single graph_nodes row including metadata (Action nodes carry v2 status)")
|
|
1275
|
+
}
|
|
1265
1276
|
},
|
|
1266
|
-
async (args) =>
|
|
1277
|
+
async (args) => {
|
|
1278
|
+
const data = await apiCall(`/v1/admin/graph/node/${args.nodeId}`);
|
|
1279
|
+
return jsonResult(data);
|
|
1280
|
+
}
|
|
1267
1281
|
);
|
|
1268
1282
|
server.registerTool(
|
|
1269
|
-
"
|
|
1283
|
+
"get_inventory",
|
|
1270
1284
|
{
|
|
1271
|
-
title: titleOf("
|
|
1272
|
-
description: descOf("
|
|
1273
|
-
annotations: annotationsFor("
|
|
1285
|
+
title: titleOf("get_inventory"),
|
|
1286
|
+
description: descOf("get_inventory"),
|
|
1287
|
+
annotations: annotationsFor("get_inventory"),
|
|
1274
1288
|
inputSchema: {
|
|
1275
1289
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted")
|
|
1276
1290
|
}
|
|
1277
1291
|
},
|
|
1278
1292
|
async (args) => {
|
|
1279
1293
|
const pid = args.projectId ?? projectId;
|
|
1280
|
-
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for
|
|
1294
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for get_inventory");
|
|
1281
1295
|
return jsonText(await apiCall(`/v1/admin/inventory/${pid}`));
|
|
1282
1296
|
}
|
|
1283
1297
|
);
|
|
1284
1298
|
server.registerTool(
|
|
1285
|
-
"
|
|
1299
|
+
"diff_inventory",
|
|
1286
1300
|
{
|
|
1287
|
-
title: titleOf("
|
|
1288
|
-
description: descOf("
|
|
1289
|
-
annotations: annotationsFor("
|
|
1301
|
+
title: titleOf("diff_inventory"),
|
|
1302
|
+
description: descOf("diff_inventory"),
|
|
1303
|
+
annotations: annotationsFor("diff_inventory"),
|
|
1290
1304
|
inputSchema: {
|
|
1291
1305
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project"),
|
|
1292
|
-
fromSha: z.string().describe("Older commit SHA (baseline)"),
|
|
1293
|
-
toSha: z.string().describe("Newer commit SHA (candidate)")
|
|
1306
|
+
fromSha: z.string().describe("Older commit SHA (the baseline to diff from)"),
|
|
1307
|
+
toSha: z.string().describe("Newer commit SHA (the candidate to diff to)")
|
|
1294
1308
|
}
|
|
1295
1309
|
},
|
|
1296
1310
|
async (args) => {
|
|
1297
1311
|
const pid = args.projectId ?? projectId;
|
|
1298
|
-
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for
|
|
1312
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for diff_inventory");
|
|
1299
1313
|
const q = new URLSearchParams({ from: args.fromSha, to: args.toSha });
|
|
1300
1314
|
return jsonText(await apiCall(`/v1/admin/inventory/${pid}/diff?${q}`));
|
|
1301
1315
|
}
|
|
1302
1316
|
);
|
|
1303
1317
|
server.registerTool(
|
|
1304
|
-
"
|
|
1318
|
+
"list_gate_findings",
|
|
1305
1319
|
{
|
|
1306
|
-
title: titleOf("
|
|
1307
|
-
description: descOf("
|
|
1308
|
-
annotations: annotationsFor("
|
|
1320
|
+
title: titleOf("list_gate_findings"),
|
|
1321
|
+
description: descOf("list_gate_findings"),
|
|
1322
|
+
annotations: annotationsFor("list_gate_findings"),
|
|
1309
1323
|
inputSchema: {
|
|
1310
1324
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project"),
|
|
1311
|
-
gate: z.string().optional().describe("Filter
|
|
1312
|
-
severity: z.string().optional().describe("
|
|
1325
|
+
gate: z.string().optional().describe("Filter to one gate id: dead-handler | mock-leak | crawl | status-claim"),
|
|
1326
|
+
severity: z.string().optional().describe("Minimum severity to include: low | medium | high | critical")
|
|
1327
|
+
},
|
|
1328
|
+
outputSchema: {
|
|
1329
|
+
runs: z.array(z.unknown()).describe("Recent gate_runs rows, newest first"),
|
|
1330
|
+
findings: z.array(z.unknown()).describe("gate_findings rows for those runs")
|
|
1313
1331
|
}
|
|
1314
1332
|
},
|
|
1315
1333
|
async (args) => {
|
|
1316
1334
|
const pid = args.projectId ?? projectId;
|
|
1317
|
-
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for
|
|
1335
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for list_gate_findings");
|
|
1318
1336
|
const q = new URLSearchParams();
|
|
1319
1337
|
if (args.gate) q.set("gate", args.gate);
|
|
1320
1338
|
if (args.severity) q.set("severity", args.severity);
|
|
1321
1339
|
const suffix = q.toString() ? `?${q}` : "";
|
|
1322
|
-
|
|
1340
|
+
const data = await apiCall(`/v1/admin/inventory/${pid}/findings${suffix}`);
|
|
1341
|
+
return jsonResult(data);
|
|
1323
1342
|
}
|
|
1324
1343
|
);
|
|
1325
1344
|
server.registerTool(
|
|
1326
|
-
"
|
|
1345
|
+
"suggest_fix",
|
|
1327
1346
|
{
|
|
1328
|
-
title: titleOf("
|
|
1329
|
-
description: descOf("
|
|
1330
|
-
annotations: annotationsFor("
|
|
1331
|
-
inputSchema: { reportId: z.string().describe("Report UUID") }
|
|
1347
|
+
title: titleOf("suggest_fix"),
|
|
1348
|
+
description: descOf("suggest_fix"),
|
|
1349
|
+
annotations: annotationsFor("suggest_fix"),
|
|
1350
|
+
inputSchema: { reportId: z.string().describe("Report UUID to read the Stage-2 suggested-fix slice for") },
|
|
1351
|
+
outputSchema: {
|
|
1352
|
+
reportId: z.string().describe("The report this slice was read from"),
|
|
1353
|
+
rootCause: z.unknown().describe("Stage-2 root-cause hint, or null if not yet classified"),
|
|
1354
|
+
suggestedFix: z.unknown().describe("Stage-2 suggested fix, or null if not yet classified"),
|
|
1355
|
+
reproductionSteps: z.unknown().describe("Reproduction steps recorded on the report (array; [] if none)"),
|
|
1356
|
+
summary: z.unknown().describe("One-line report summary, or null"),
|
|
1357
|
+
component: z.unknown().describe("Component/page the bug was attributed to, or null")
|
|
1358
|
+
}
|
|
1332
1359
|
},
|
|
1333
1360
|
async (args) => {
|
|
1334
1361
|
const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
1335
1362
|
const s2 = report.stage2_analysis;
|
|
1336
|
-
return
|
|
1363
|
+
return jsonResult({
|
|
1337
1364
|
reportId: args.reportId,
|
|
1338
1365
|
rootCause: s2?.rootCause ?? null,
|
|
1339
1366
|
suggestedFix: s2?.suggestedFix ?? null,
|
|
@@ -1359,73 +1386,6 @@ function createMushiServer(config) {
|
|
|
1359
1386
|
return jsonText(data);
|
|
1360
1387
|
}
|
|
1361
1388
|
);
|
|
1362
|
-
server.registerTool(
|
|
1363
|
-
"setup_check",
|
|
1364
|
-
{
|
|
1365
|
-
title: titleOf("setup_check"),
|
|
1366
|
-
description: descOf("setup_check"),
|
|
1367
|
-
annotations: annotationsFor("setup_check"),
|
|
1368
|
-
inputSchema: {
|
|
1369
|
-
projectId: z.string().optional().describe(
|
|
1370
|
-
"Project UUID to check. Falls back to the projectId the server was initialised with."
|
|
1371
|
-
)
|
|
1372
|
-
}
|
|
1373
|
-
},
|
|
1374
|
-
async (args) => {
|
|
1375
|
-
const resolvedId = args.projectId ?? projectId;
|
|
1376
|
-
if (!resolvedId) {
|
|
1377
|
-
return jsonText({
|
|
1378
|
-
ok: false,
|
|
1379
|
-
error: "No projectId provided and none configured on the MCP server. Pass projectId explicitly."
|
|
1380
|
-
});
|
|
1381
|
-
}
|
|
1382
|
-
const data = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
1383
|
-
const summary = data.checks.map((c) => ({
|
|
1384
|
-
check: c.key,
|
|
1385
|
-
label: c.label,
|
|
1386
|
-
passed: c.ready,
|
|
1387
|
-
hint: c.hint,
|
|
1388
|
-
fixPath: c.fixHref
|
|
1389
|
-
}));
|
|
1390
|
-
return jsonText({
|
|
1391
|
-
ready: data.ready,
|
|
1392
|
-
repoUrl: data.repoUrl ?? null,
|
|
1393
|
-
checks: summary,
|
|
1394
|
-
// Human-readable summary for agents that paste the result into a prompt
|
|
1395
|
-
summary: data.ready ? `Project ${resolvedId} is ready to dispatch auto-fixes${data.repoUrl ? ` (target: ${data.repoUrl})` : ""}.` : `Project ${resolvedId} cannot dispatch yet \u2014 ${summary.filter((c) => !c.passed).map((c) => c.label).join(", ")}.`
|
|
1396
|
-
});
|
|
1397
|
-
}
|
|
1398
|
-
);
|
|
1399
|
-
server.registerTool(
|
|
1400
|
-
"ingest_setup_check",
|
|
1401
|
-
{
|
|
1402
|
-
title: titleOf("ingest_setup_check"),
|
|
1403
|
-
description: descOf("ingest_setup_check"),
|
|
1404
|
-
annotations: annotationsFor("ingest_setup_check"),
|
|
1405
|
-
inputSchema: NO_ARG_INPUT
|
|
1406
|
-
},
|
|
1407
|
-
async () => {
|
|
1408
|
-
const data = await apiCall("/v1/sync/ingest-setup");
|
|
1409
|
-
const failed = data.steps.filter((s) => s.required && !s.complete);
|
|
1410
|
-
const summary = data.ready ? `Ingest setup complete (${data.required_complete}/${data.required_total}) for ${data.project_name}.` : `Ingest incomplete (${data.required_complete}/${data.required_total}) \u2014 still need: ${failed.map((s) => s.label).join(", ")}.`;
|
|
1411
|
-
return jsonText({
|
|
1412
|
-
ready: data.ready,
|
|
1413
|
-
projectId: data.project_id,
|
|
1414
|
-
projectName: data.project_name,
|
|
1415
|
-
requiredComplete: data.required_complete,
|
|
1416
|
-
requiredTotal: data.required_total,
|
|
1417
|
-
steps: data.steps.map((s) => ({
|
|
1418
|
-
id: s.id,
|
|
1419
|
-
label: s.label,
|
|
1420
|
-
passed: s.complete,
|
|
1421
|
-
required: s.required,
|
|
1422
|
-
hint: s.hint
|
|
1423
|
-
})),
|
|
1424
|
-
diagnostic: data.diagnostic ?? null,
|
|
1425
|
-
summary
|
|
1426
|
-
});
|
|
1427
|
-
}
|
|
1428
|
-
);
|
|
1429
1389
|
server.registerTool(
|
|
1430
1390
|
"diagnose_setup",
|
|
1431
1391
|
{
|
|
@@ -1443,7 +1403,8 @@ function createMushiServer(config) {
|
|
|
1443
1403
|
summary: z.string(),
|
|
1444
1404
|
nextAction: z.string().optional(),
|
|
1445
1405
|
ingest: z.unknown().optional(),
|
|
1446
|
-
dispatch: z.unknown().optional()
|
|
1406
|
+
dispatch: z.unknown().optional(),
|
|
1407
|
+
connection: z.unknown().optional()
|
|
1447
1408
|
}
|
|
1448
1409
|
},
|
|
1449
1410
|
async (args) => {
|
|
@@ -1483,162 +1444,99 @@ function createMushiServer(config) {
|
|
|
1483
1444
|
dispatch: { repoUrl: data.repoUrl ?? null, checks: data.checks }
|
|
1484
1445
|
});
|
|
1485
1446
|
}
|
|
1486
|
-
const
|
|
1487
|
-
let
|
|
1488
|
-
let dispatchBlock;
|
|
1489
|
-
let dispatchChecks = null;
|
|
1490
|
-
if (resolvedId) {
|
|
1491
|
-
try {
|
|
1492
|
-
const preflight = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
1493
|
-
dispatchReady = preflight.ready;
|
|
1494
|
-
dispatchChecks = preflight.checks;
|
|
1495
|
-
if (!preflight.ready) {
|
|
1496
|
-
const failed = preflight.checks.filter((c) => !c.ready);
|
|
1497
|
-
dispatchBlock = failed[0]?.hint;
|
|
1498
|
-
}
|
|
1499
|
-
} catch (err) {
|
|
1500
|
-
dispatchReady = false;
|
|
1501
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
1502
|
-
dispatchBlock = `Could not run dispatch preflight \u2014 ${detail}`;
|
|
1503
|
-
apiLog.warn("diagnose_setup dispatch preflight failed", { error: detail });
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
const ingestFailed = ingest.steps.filter((s) => s.required && !s.complete);
|
|
1507
|
-
const ingestBlock = ingestFailed[0]?.hint;
|
|
1508
|
-
const ready = ingest.ready && dispatchReady;
|
|
1509
|
-
const nextAction = !ingest.ready ? ingestBlock ?? "Complete SDK ingest setup." : !dispatchReady ? dispatchBlock ?? "Complete dispatch preflight in Settings \u2192 Integrations." : "All setup checks pass \u2014 submit a test report to confirm end-to-end.";
|
|
1510
|
-
const summary = ready ? "Mushi ingest and dispatch setup look healthy." : !ingest.ready ? `Ingest incomplete \u2014 ${ingestFailed.map((s) => s.label).join(", ")}.` : "Ingest OK; dispatch preflight still has blockers.";
|
|
1511
|
-
return jsonResult({
|
|
1512
|
-
mode: "full",
|
|
1513
|
-
ready,
|
|
1514
|
-
summary,
|
|
1515
|
-
nextAction,
|
|
1516
|
-
ingest: { ready: ingest.ready, steps: ingest.steps },
|
|
1517
|
-
dispatch: resolvedId ? { ready: dispatchReady, checks: dispatchChecks } : null
|
|
1518
|
-
});
|
|
1519
|
-
}
|
|
1520
|
-
);
|
|
1521
|
-
server.registerTool(
|
|
1522
|
-
"diagnose_connection",
|
|
1523
|
-
{
|
|
1524
|
-
title: titleOf("diagnose_connection"),
|
|
1525
|
-
description: descOf("diagnose_connection"),
|
|
1526
|
-
annotations: annotationsFor("diagnose_connection"),
|
|
1527
|
-
inputSchema: NO_ARG_INPUT,
|
|
1528
|
-
outputSchema: {
|
|
1529
|
-
ready: z.boolean(),
|
|
1530
|
-
healthOk: z.boolean(),
|
|
1531
|
-
ingestReady: z.boolean(),
|
|
1532
|
-
dispatchReady: z.boolean(),
|
|
1533
|
-
endpoint: z.string().nullable(),
|
|
1534
|
-
projectId: z.string().nullable(),
|
|
1535
|
-
accessibleProjectCount: z.number().nullable(),
|
|
1536
|
-
issues: z.array(z.unknown()),
|
|
1537
|
-
nextAction: z.string(),
|
|
1538
|
-
summary: z.string()
|
|
1539
|
-
}
|
|
1540
|
-
},
|
|
1541
|
-
async () => {
|
|
1542
|
-
const issues = [];
|
|
1447
|
+
const connIssues = [];
|
|
1448
|
+
let healthOk = false;
|
|
1543
1449
|
if (!apiKey?.startsWith("mushi_")) {
|
|
1544
|
-
|
|
1450
|
+
connIssues.push({
|
|
1545
1451
|
check: "mcp_api_key",
|
|
1546
|
-
detail: "MCP server API key missing or malformed",
|
|
1547
|
-
fix: "Run `mushi connect`
|
|
1452
|
+
detail: "MCP server API key missing or malformed (expected prefix: mushi_)",
|
|
1453
|
+
fix: "Run `mushi connect` or set MUSHI_API_KEY to a valid key in your MCP config."
|
|
1548
1454
|
});
|
|
1549
1455
|
}
|
|
1550
1456
|
if (!apiEndpoint) {
|
|
1551
|
-
|
|
1457
|
+
connIssues.push({
|
|
1552
1458
|
check: "mcp_endpoint",
|
|
1553
1459
|
detail: "No API endpoint configured",
|
|
1554
|
-
fix: "Set MUSHI_API_ENDPOINT to your
|
|
1460
|
+
fix: "Set MUSHI_API_ENDPOINT to your `.../functions/v1/api` URL in your MCP config."
|
|
1555
1461
|
});
|
|
1556
|
-
}
|
|
1557
|
-
let healthOk = false;
|
|
1558
|
-
if (apiEndpoint) {
|
|
1462
|
+
} else {
|
|
1559
1463
|
try {
|
|
1560
1464
|
const healthRes = await doFetch(`${apiEndpoint.replace(/\/$/, "")}/health`, {
|
|
1561
1465
|
signal: AbortSignal.timeout(5e3)
|
|
1562
1466
|
});
|
|
1563
1467
|
healthOk = healthRes.status === 200;
|
|
1564
1468
|
if (!healthOk) {
|
|
1565
|
-
|
|
1469
|
+
connIssues.push({
|
|
1566
1470
|
check: "endpoint_health",
|
|
1567
1471
|
detail: `GET /health \u2192 HTTP ${healthRes.status}`,
|
|
1568
1472
|
fix: "Verify MUSHI_API_ENDPOINT and that the Supabase edge function is deployed."
|
|
1569
1473
|
});
|
|
1570
1474
|
}
|
|
1571
1475
|
} catch (err) {
|
|
1572
|
-
|
|
1476
|
+
connIssues.push({
|
|
1573
1477
|
check: "endpoint_health",
|
|
1574
1478
|
detail: err instanceof Error ? err.message : String(err),
|
|
1575
|
-
fix: "Check network connectivity and
|
|
1479
|
+
fix: "Check network connectivity and MUSHI_API_ENDPOINT in your MCP config."
|
|
1576
1480
|
});
|
|
1577
1481
|
}
|
|
1578
1482
|
}
|
|
1579
|
-
let ingestReady = false;
|
|
1580
|
-
let dispatchReady = false;
|
|
1581
1483
|
let accessibleProjectCount = null;
|
|
1582
|
-
if (!
|
|
1484
|
+
if (!resolvedId && healthOk) {
|
|
1583
1485
|
try {
|
|
1584
|
-
const overview = await apiCall(
|
|
1486
|
+
const overview = await apiCall(
|
|
1487
|
+
"/v1/admin/mcp/account-overview"
|
|
1488
|
+
);
|
|
1585
1489
|
accessibleProjectCount = overview.total;
|
|
1586
1490
|
if (overview.total === 0) {
|
|
1587
|
-
|
|
1491
|
+
connIssues.push({
|
|
1588
1492
|
check: "no_accessible_projects",
|
|
1589
1493
|
detail: "API key has no accessible projects",
|
|
1590
|
-
fix: "Mint an API key on a project (console \u2192 Projects \u2192 API Keys) or add MUSHI_PROJECT_ID
|
|
1494
|
+
fix: "Mint an API key on a project (console \u2192 Projects \u2192 API Keys) or add MUSHI_PROJECT_ID."
|
|
1591
1495
|
});
|
|
1592
1496
|
}
|
|
1593
1497
|
} catch {
|
|
1594
1498
|
}
|
|
1595
1499
|
}
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
issues.push({
|
|
1602
|
-
check: "ingest_setup",
|
|
1603
|
-
detail: `Incomplete: ${failed.map((s) => s.label).join(", ")}`,
|
|
1604
|
-
fix: failed[0]?.hint ?? "Paste the SDK snippet, start your dev server, submit a test report."
|
|
1605
|
-
});
|
|
1606
|
-
}
|
|
1607
|
-
} catch (err) {
|
|
1608
|
-
issues.push({
|
|
1609
|
-
check: "ingest_setup",
|
|
1610
|
-
detail: err instanceof Error ? err.message : String(err),
|
|
1611
|
-
fix: "Confirm API key is active for this project (Projects \u2192 API Keys)."
|
|
1612
|
-
});
|
|
1613
|
-
}
|
|
1614
|
-
if (projectId) {
|
|
1500
|
+
const ingest = await apiCall("/v1/sync/ingest-setup");
|
|
1501
|
+
let dispatchReady = true;
|
|
1502
|
+
let dispatchBlock;
|
|
1503
|
+
let dispatchChecks = null;
|
|
1504
|
+
if (resolvedId) {
|
|
1615
1505
|
try {
|
|
1616
|
-
const preflight = await apiCall(`/v1/admin/projects/${
|
|
1506
|
+
const preflight = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
1617
1507
|
dispatchReady = preflight.ready;
|
|
1508
|
+
dispatchChecks = preflight.checks;
|
|
1618
1509
|
if (!preflight.ready) {
|
|
1619
1510
|
const failed = preflight.checks.filter((c) => !c.ready);
|
|
1620
|
-
|
|
1621
|
-
check: "dispatch_preflight",
|
|
1622
|
-
detail: `Blocked: ${failed.map((c) => c.label).join(", ")}`,
|
|
1623
|
-
fix: failed[0]?.hint ?? "Open Settings \u2192 Integrations and complete GitHub + BYOK setup."
|
|
1624
|
-
});
|
|
1511
|
+
dispatchBlock = failed[0]?.hint;
|
|
1625
1512
|
}
|
|
1626
|
-
} catch {
|
|
1513
|
+
} catch (err) {
|
|
1514
|
+
dispatchReady = false;
|
|
1515
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
1516
|
+
dispatchBlock = `Could not run dispatch preflight \u2014 ${detail}`;
|
|
1517
|
+
apiLog.warn("diagnose_setup dispatch preflight failed", { error: detail });
|
|
1627
1518
|
}
|
|
1628
1519
|
}
|
|
1629
|
-
const
|
|
1630
|
-
const
|
|
1520
|
+
const ingestFailed = ingest.steps.filter((s) => s.required && !s.complete);
|
|
1521
|
+
const ingestBlock = ingestFailed[0]?.hint;
|
|
1522
|
+
const connOk = connIssues.length === 0;
|
|
1523
|
+
const ready = connOk && ingest.ready && dispatchReady;
|
|
1524
|
+
const nextAction = !connOk ? connIssues[0]?.fix ?? "Fix the connection issue above." : !ingest.ready ? ingestBlock ?? "Complete SDK ingest setup." : !dispatchReady ? dispatchBlock ?? "Complete dispatch preflight in Settings \u2192 Integrations." : "All setup checks pass \u2014 submit a test report to confirm end-to-end.";
|
|
1525
|
+
const summary = ready ? "Mushi connection, ingest, and dispatch setup look healthy." : !connOk ? `Connection issue \u2014 ${connIssues[0]?.check}: ${connIssues[0]?.detail}` : !ingest.ready ? `Ingest incomplete \u2014 ${ingestFailed.map((s) => s.label).join(", ")}.` : "Ingest OK; dispatch preflight still has blockers.";
|
|
1631
1526
|
return jsonResult({
|
|
1527
|
+
mode: "full",
|
|
1632
1528
|
ready,
|
|
1633
|
-
|
|
1634
|
-
ingestReady,
|
|
1635
|
-
dispatchReady,
|
|
1636
|
-
endpoint: apiEndpoint ?? null,
|
|
1637
|
-
projectId: projectId ?? null,
|
|
1638
|
-
accessibleProjectCount,
|
|
1639
|
-
issues,
|
|
1529
|
+
summary,
|
|
1640
1530
|
nextAction,
|
|
1641
|
-
|
|
1531
|
+
connection: {
|
|
1532
|
+
healthOk,
|
|
1533
|
+
endpoint: apiEndpoint ?? null,
|
|
1534
|
+
projectId: resolvedId ?? null,
|
|
1535
|
+
accessibleProjectCount,
|
|
1536
|
+
issues: connIssues
|
|
1537
|
+
},
|
|
1538
|
+
ingest: { ready: ingest.ready, steps: ingest.steps },
|
|
1539
|
+
dispatch: resolvedId ? { ready: dispatchReady, checks: dispatchChecks } : null
|
|
1642
1540
|
});
|
|
1643
1541
|
}
|
|
1644
1542
|
);
|
|
@@ -1929,13 +1827,17 @@ function createMushiServer(config) {
|
|
|
1929
1827
|
description: descOf("submit_fix_result"),
|
|
1930
1828
|
annotations: annotationsFor("submit_fix_result"),
|
|
1931
1829
|
inputSchema: {
|
|
1932
|
-
reportId: z.string().describe("
|
|
1933
|
-
branch: z.string().describe("Git branch name"),
|
|
1934
|
-
prUrl: z.string().optional().describe("GitHub PR
|
|
1935
|
-
filesChanged: z.array(z.string()).describe("
|
|
1936
|
-
linesChanged: z.number().describe("Total lines
|
|
1937
|
-
summary: z.string().describe("
|
|
1830
|
+
reportId: z.string().describe("Report UUID to attach this external fix attempt to"),
|
|
1831
|
+
branch: z.string().describe("Git branch name where the fix was implemented"),
|
|
1832
|
+
prUrl: z.string().optional().describe("GitHub pull request URL, if a PR was opened"),
|
|
1833
|
+
filesChanged: z.array(z.string()).describe("Repo-relative paths modified by the fix"),
|
|
1834
|
+
linesChanged: z.number().describe("Total lines added + removed across filesChanged"),
|
|
1835
|
+
summary: z.string().describe("One-line human summary of what the fix changed"),
|
|
1938
1836
|
idempotencyKey: z.string().uuid().optional().describe("Optional UUID \u2014 resend the same key to safely retry without creating duplicate fix rows")
|
|
1837
|
+
},
|
|
1838
|
+
outputSchema: {
|
|
1839
|
+
ok: z.boolean().describe("True when the fix_attempt row was created and marked completed"),
|
|
1840
|
+
fixId: z.string().describe("UUID of the new fix_attempt row")
|
|
1939
1841
|
}
|
|
1940
1842
|
},
|
|
1941
1843
|
async (args) => {
|
|
@@ -1957,7 +1859,7 @@ function createMushiServer(config) {
|
|
|
1957
1859
|
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1958
1860
|
})
|
|
1959
1861
|
});
|
|
1960
|
-
return
|
|
1862
|
+
return jsonResult({ ok: true, fixId: created.fixId });
|
|
1961
1863
|
}
|
|
1962
1864
|
);
|
|
1963
1865
|
server.registerTool(
|
|
@@ -2014,6 +1916,9 @@ function createMushiServer(config) {
|
|
|
2014
1916
|
inputSchema: {
|
|
2015
1917
|
limit: z.number().optional().describe("Max reports to judge in this batch (default 25, max 100)"),
|
|
2016
1918
|
projectId: z.string().optional().describe("Restrict to one project when the API key owns multiple")
|
|
1919
|
+
},
|
|
1920
|
+
outputSchema: {
|
|
1921
|
+
dispatched: z.number().describe("Number of judge-batch jobs dispatched (one per accessible project)")
|
|
2017
1922
|
}
|
|
2018
1923
|
},
|
|
2019
1924
|
async (args) => {
|
|
@@ -2024,7 +1929,7 @@ function createMushiServer(config) {
|
|
|
2024
1929
|
projectId: args.projectId ?? projectId ?? void 0
|
|
2025
1930
|
})
|
|
2026
1931
|
});
|
|
2027
|
-
return
|
|
1932
|
+
return jsonResult(data);
|
|
2028
1933
|
}
|
|
2029
1934
|
);
|
|
2030
1935
|
server.registerTool(
|
|
@@ -2051,12 +1956,18 @@ function createMushiServer(config) {
|
|
|
2051
1956
|
server.registerTool(
|
|
2052
1957
|
"merge_fix",
|
|
2053
1958
|
{
|
|
2054
|
-
title: "
|
|
2055
|
-
description: "
|
|
1959
|
+
title: titleOf("merge_fix"),
|
|
1960
|
+
description: descOf("merge_fix"),
|
|
2056
1961
|
annotations: annotationsFor("merge_fix"),
|
|
2057
1962
|
inputSchema: {
|
|
2058
|
-
fixId: z.string().describe("Fix attempt UUID"),
|
|
2059
|
-
mergeMethod: z.enum(["squash", "merge", "rebase"]).optional().describe("GitHub merge method")
|
|
1963
|
+
fixId: z.string().describe("Fix attempt UUID whose GitHub PR should be squash-merged"),
|
|
1964
|
+
mergeMethod: z.enum(["squash", "merge", "rebase"]).optional().describe("GitHub merge method (default squash)")
|
|
1965
|
+
},
|
|
1966
|
+
outputSchema: {
|
|
1967
|
+
merged: z.boolean().optional().describe("True when GitHub accepted the merge in this call"),
|
|
1968
|
+
alreadyMerged: z.boolean().optional().describe("True when the PR was already merged (idempotent no-op)"),
|
|
1969
|
+
reportId: z.string().describe("Report UUID linked to this fix attempt"),
|
|
1970
|
+
reportStatus: z.string().describe("Report workflow status after merge bookkeeping")
|
|
2060
1971
|
}
|
|
2061
1972
|
},
|
|
2062
1973
|
async (args) => {
|
|
@@ -2064,22 +1975,27 @@ function createMushiServer(config) {
|
|
|
2064
1975
|
method: "POST",
|
|
2065
1976
|
body: JSON.stringify({ mergeMethod: args.mergeMethod ?? "squash" })
|
|
2066
1977
|
});
|
|
2067
|
-
return
|
|
1978
|
+
return jsonResult(data);
|
|
2068
1979
|
}
|
|
2069
1980
|
);
|
|
2070
1981
|
server.registerTool(
|
|
2071
1982
|
"refresh_ci",
|
|
2072
1983
|
{
|
|
2073
|
-
title: "
|
|
2074
|
-
description: "
|
|
1984
|
+
title: titleOf("refresh_ci"),
|
|
1985
|
+
description: descOf("refresh_ci"),
|
|
2075
1986
|
annotations: annotationsFor("refresh_ci"),
|
|
2076
1987
|
inputSchema: {
|
|
2077
|
-
fixId: z.string().describe("Fix attempt UUID")
|
|
1988
|
+
fixId: z.string().describe("Fix attempt UUID whose PR check-runs should be re-polled")
|
|
1989
|
+
},
|
|
1990
|
+
outputSchema: {
|
|
1991
|
+
check_run_status: z.string().nullable().describe("GitHub check run status after refresh"),
|
|
1992
|
+
check_run_conclusion: z.string().nullable().describe("success | failure | neutral | null while pending"),
|
|
1993
|
+
check_run_updated_at: z.string().nullable().describe("ISO timestamp when CI status was last persisted")
|
|
2078
1994
|
}
|
|
2079
1995
|
},
|
|
2080
1996
|
async (args) => {
|
|
2081
1997
|
const data = await apiCall(`/v1/admin/fixes/${args.fixId}/refresh-ci`, { method: "POST" });
|
|
2082
|
-
return
|
|
1998
|
+
return jsonResult(data);
|
|
2083
1999
|
}
|
|
2084
2000
|
);
|
|
2085
2001
|
server.registerTool(
|
|
@@ -2089,16 +2005,19 @@ function createMushiServer(config) {
|
|
|
2089
2005
|
description: descOf("reopen_report"),
|
|
2090
2006
|
annotations: annotationsFor("reopen_report"),
|
|
2091
2007
|
inputSchema: {
|
|
2092
|
-
reportId: z.string().describe("Report UUID"),
|
|
2093
|
-
note: z.string().optional().describe("
|
|
2008
|
+
reportId: z.string().describe("Report UUID to move back to reopened status"),
|
|
2009
|
+
note: z.string().optional().describe("Operator note recorded on the reopen transition")
|
|
2010
|
+
},
|
|
2011
|
+
outputSchema: {
|
|
2012
|
+
report: z.unknown().describe("Updated report row with status=reopened")
|
|
2094
2013
|
}
|
|
2095
2014
|
},
|
|
2096
2015
|
async (args) => {
|
|
2097
|
-
const
|
|
2016
|
+
const report = await apiCall(`/v1/sync/reports/${args.reportId}`, {
|
|
2098
2017
|
method: "PATCH",
|
|
2099
2018
|
body: JSON.stringify({ status: "reopened", note: args.note })
|
|
2100
2019
|
});
|
|
2101
|
-
return
|
|
2020
|
+
return jsonResult({ report });
|
|
2102
2021
|
}
|
|
2103
2022
|
);
|
|
2104
2023
|
server.registerTool(
|
|
@@ -2175,9 +2094,12 @@ function createMushiServer(config) {
|
|
|
2175
2094
|
description: descOf("list_lessons"),
|
|
2176
2095
|
annotations: annotationsFor("list_lessons"),
|
|
2177
2096
|
inputSchema: {
|
|
2178
|
-
severity: z.enum(["info", "warn", "critical"]).optional().describe("Filter
|
|
2179
|
-
limit: z.number().optional().describe("Max
|
|
2180
|
-
project_id: z.string().optional().describe("Project UUID
|
|
2097
|
+
severity: z.enum(["info", "warn", "critical"]).optional().describe("Filter to one severity level"),
|
|
2098
|
+
limit: z.number().optional().describe("Max lessons to return (default 50, max 200)"),
|
|
2099
|
+
project_id: z.string().optional().describe("Project UUID \u2014 defaults to configured project")
|
|
2100
|
+
},
|
|
2101
|
+
outputSchema: {
|
|
2102
|
+
lessons: z.array(z.unknown()).describe("Promoted lesson rows ordered by frequency")
|
|
2181
2103
|
}
|
|
2182
2104
|
},
|
|
2183
2105
|
async (args) => {
|
|
@@ -2186,8 +2108,8 @@ function createMushiServer(config) {
|
|
|
2186
2108
|
if (args.severity) params.set("severity", args.severity);
|
|
2187
2109
|
params.set("limit", String(Math.min(args.limit ?? 50, 200)));
|
|
2188
2110
|
if (pid) params.set("projectId", pid);
|
|
2189
|
-
const
|
|
2190
|
-
return
|
|
2111
|
+
const lessons = await apiCall(`/v1/admin/lessons?${params}`);
|
|
2112
|
+
return jsonResult({ lessons });
|
|
2191
2113
|
}
|
|
2192
2114
|
);
|
|
2193
2115
|
server.resource(
|
|
@@ -2353,6 +2275,22 @@ function createMushiServer(config) {
|
|
|
2353
2275
|
};
|
|
2354
2276
|
}
|
|
2355
2277
|
);
|
|
2278
|
+
server.registerTool(
|
|
2279
|
+
"activation_status",
|
|
2280
|
+
{
|
|
2281
|
+
title: titleOf("activation_status"),
|
|
2282
|
+
description: descOf("activation_status"),
|
|
2283
|
+
annotations: annotationsFor("activation_status"),
|
|
2284
|
+
inputSchema: {
|
|
2285
|
+
project_id: z.string().optional().describe("Project UUID (defaults to the configured project).")
|
|
2286
|
+
}
|
|
2287
|
+
},
|
|
2288
|
+
async (args) => {
|
|
2289
|
+
const pid = args.project_id ?? projectId;
|
|
2290
|
+
const qs = pid ? `?project_id=${encodeURIComponent(pid)}` : "";
|
|
2291
|
+
return jsonResult(await apiCall(`/v1/admin/activation${qs}`));
|
|
2292
|
+
}
|
|
2293
|
+
);
|
|
2356
2294
|
server.resource(
|
|
2357
2295
|
"activation_status",
|
|
2358
2296
|
"mushi://activation",
|
|
@@ -2451,7 +2389,7 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
|
|
|
2451
2389
|
text: `You are a Mushi onboarding copilot. Use the MCP tools:
|
|
2452
2390
|
1. Read mushi://activation for unified setup posture.
|
|
2453
2391
|
2. Read project://integration-health if GitHub or Sentry looks blocked.
|
|
2454
|
-
3. Call
|
|
2392
|
+
3. Call diagnose_setup if the resource is unavailable.
|
|
2455
2393
|
|
|
2456
2394
|
Then output:
|
|
2457
2395
|
- **Status:** one sentence on what is done vs blocked
|
|
@@ -2509,11 +2447,17 @@ Be specific. No generic advice.`
|
|
|
2509
2447
|
description: descOf("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
2510
2448
|
annotations: annotationsFor("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
2511
2449
|
inputSchema: {
|
|
2512
|
-
projectId: z.string().describe("Project
|
|
2513
|
-
storyNodeId: z.string().describe("User story id
|
|
2514
|
-
automationMode: z.enum(["auto", "review", "approve"]).optional().describe("
|
|
2515
|
-
baseUrl: z.string().url().optional().describe("Override the app
|
|
2516
|
-
openPr: z.boolean().optional().describe("Open a draft GitHub PR (default
|
|
2450
|
+
projectId: z.string().describe("Project UUID that owns the inventory story"),
|
|
2451
|
+
storyNodeId: z.string().describe("User story node id/slug from accepted inventory (from map_user_stories or inventory.yaml)"),
|
|
2452
|
+
automationMode: z.enum(["auto", "review", "approve"]).optional().describe("Approval gate for the generated qa_story (default review \u2192 pending_review)"),
|
|
2453
|
+
baseUrl: z.string().url().optional().describe("Override the live app URL the Playwright test should target"),
|
|
2454
|
+
openPr: z.boolean().optional().describe("Open a draft GitHub PR with the generated spec (default true)")
|
|
2455
|
+
},
|
|
2456
|
+
outputSchema: {
|
|
2457
|
+
qaStoryId: z.string().describe("UUID of the inserted qa_stories row"),
|
|
2458
|
+
prUrl: z.string().nullable().describe("GitHub PR URL when openPr=true and GitHub is connected"),
|
|
2459
|
+
approvalStatus: z.string().describe("pending_review | approved \u2014 follows automationMode and project defaults"),
|
|
2460
|
+
needsHumanReview: z.boolean().describe("True when the story requires operator approval before scheduled runs")
|
|
2517
2461
|
}
|
|
2518
2462
|
},
|
|
2519
2463
|
async ({ projectId: projectId2, storyNodeId, automationMode, baseUrl, openPr }) => {
|
|
@@ -2522,7 +2466,7 @@ Be specific. No generic advice.`
|
|
|
2522
2466
|
`/v1/admin/inventory/${projectId2}/stories/${storyNodeId}/generate-test`,
|
|
2523
2467
|
{ method: "POST", body: JSON.stringify({ automation_mode: automationMode, base_url: baseUrl, open_pr: openPr }) }
|
|
2524
2468
|
);
|
|
2525
|
-
return
|
|
2469
|
+
return jsonResult(data);
|
|
2526
2470
|
}
|
|
2527
2471
|
);
|
|
2528
2472
|
server.registerTool(
|
|
@@ -2884,36 +2828,6 @@ Be specific. No generic advice.`
|
|
|
2884
2828
|
return jsonText({ ok: true, message: `Step ${step_index} \u2192 ${args.status}` });
|
|
2885
2829
|
}
|
|
2886
2830
|
);
|
|
2887
|
-
server.registerTool(
|
|
2888
|
-
"get_activation_status",
|
|
2889
|
-
{
|
|
2890
|
-
title: titleOf("get_activation_status"),
|
|
2891
|
-
description: descOf("get_activation_status"),
|
|
2892
|
-
annotations: annotationsFor("get_activation_status"),
|
|
2893
|
-
inputSchema: {
|
|
2894
|
-
project_id: z.string().optional().describe("Optional project UUID override")
|
|
2895
|
-
}
|
|
2896
|
-
},
|
|
2897
|
-
async (args) => {
|
|
2898
|
-
const qs = args.project_id ? `?project_id=${encodeURIComponent(args.project_id)}` : "";
|
|
2899
|
-
const data = await apiCall(`/v1/admin/activation${qs}`);
|
|
2900
|
-
return jsonText(data);
|
|
2901
|
-
}
|
|
2902
|
-
);
|
|
2903
|
-
server.registerTool(
|
|
2904
|
-
"get_reporter_thread",
|
|
2905
|
-
{
|
|
2906
|
-
title: titleOf("get_reporter_thread"),
|
|
2907
|
-
description: descOf("get_reporter_thread"),
|
|
2908
|
-
annotations: annotationsFor("get_reporter_thread"),
|
|
2909
|
-
inputSchema: { reportId: z.string().describe("The report UUID") }
|
|
2910
|
-
},
|
|
2911
|
-
// The reporter thread is the `comments` lane of the unified report
|
|
2912
|
-
// timeline. There is no standalone admin `/comments` route (only the
|
|
2913
|
-
// reporter-token-gated `/v1/reporter/reports/:id/comments`), so we read the
|
|
2914
|
-
// admin-authed timeline, which also carries fix / QA / status lanes.
|
|
2915
|
-
async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}/timeline`))
|
|
2916
|
-
);
|
|
2917
2831
|
server.registerTool(
|
|
2918
2832
|
"ask_codebase",
|
|
2919
2833
|
{
|
|
@@ -2998,23 +2912,31 @@ Be specific. No generic advice.`
|
|
|
2998
2912
|
annotations: annotationsFor("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2999
2913
|
inputSchema: {
|
|
3000
2914
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
3001
|
-
query: z.string().describe("Plain-English search query"),
|
|
3002
|
-
k: z.number().optional().describe("
|
|
3003
|
-
scope_prefix: z.string().optional().describe("Optional subdirectory scope
|
|
2915
|
+
query: z.string().describe("Plain-English search query against indexed source files"),
|
|
2916
|
+
k: z.number().int().min(1).max(20).optional().describe("Max hits to return (default 8, max 20)"),
|
|
2917
|
+
scope_prefix: z.string().optional().describe("Optional repo subdirectory prefix to limit search scope")
|
|
2918
|
+
},
|
|
2919
|
+
outputSchema: {
|
|
2920
|
+
results: z.array(z.unknown()).describe("Ranked file/symbol hits with paths, line ranges, and similarity"),
|
|
2921
|
+
query: z.string().describe("Normalized query string that was searched"),
|
|
2922
|
+
mode: z.string().optional().describe("semantic (embeddings) or name (path/symbol match)")
|
|
3004
2923
|
}
|
|
3005
2924
|
},
|
|
3006
2925
|
async (args) => {
|
|
3007
2926
|
const pid = args.project_id ?? projectId;
|
|
3008
2927
|
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
3009
|
-
const data = await apiCall(
|
|
3010
|
-
|
|
3011
|
-
|
|
3012
|
-
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
2928
|
+
const data = await apiCall(
|
|
2929
|
+
`/v1/admin/projects/${pid}/codebase/search`,
|
|
2930
|
+
{
|
|
2931
|
+
method: "POST",
|
|
2932
|
+
body: JSON.stringify({
|
|
2933
|
+
query: args.query,
|
|
2934
|
+
k: args.k ?? 8,
|
|
2935
|
+
scope_prefix: args.scope_prefix ?? void 0
|
|
2936
|
+
})
|
|
2937
|
+
}
|
|
2938
|
+
);
|
|
2939
|
+
return jsonResult(data);
|
|
3018
2940
|
}
|
|
3019
2941
|
);
|
|
3020
2942
|
server.registerTool(
|
|
@@ -3048,10 +2970,17 @@ Be specific. No generic advice.`
|
|
|
3048
2970
|
annotations: annotationsFor("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
3049
2971
|
inputSchema: {
|
|
3050
2972
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
3051
|
-
paths: z.array(z.string()).optional().describe("Changed file paths"),
|
|
3052
|
-
source: z.enum(["last_push", "compare", "fix"]).optional().describe("Auto-resolve changed paths"),
|
|
3053
|
-
compare: z.string().optional().describe("GitHub compare range base...head"),
|
|
3054
|
-
fix_id: z.string().optional().describe("Fix attempt UUID
|
|
2973
|
+
paths: z.array(z.string()).optional().describe("Changed repo-relative file paths to analyze"),
|
|
2974
|
+
source: z.enum(["last_push", "compare", "fix"]).optional().describe("Auto-resolve changed paths instead of manual paths"),
|
|
2975
|
+
compare: z.string().optional().describe("GitHub compare range base...head when source=compare"),
|
|
2976
|
+
fix_id: z.string().optional().describe("Fix attempt UUID \u2014 uses that PR's changed files when source=fix")
|
|
2977
|
+
},
|
|
2978
|
+
outputSchema: {
|
|
2979
|
+
changed_paths: z.array(z.string()).describe("Resolved set of changed paths analyzed"),
|
|
2980
|
+
source: z.string().describe("How changed_paths were resolved (manual, last_push, compare, fix, \u2026)"),
|
|
2981
|
+
affected_file_paths: z.array(z.string()).describe("Indexed files that import or depend on changed paths"),
|
|
2982
|
+
affected_node_ids: z.array(z.string()).describe("Codebase graph node ids impacted by the change"),
|
|
2983
|
+
meta: z.unknown().nullable().optional().describe("Extra resolution metadata when auto-sourced")
|
|
3055
2984
|
}
|
|
3056
2985
|
},
|
|
3057
2986
|
async (args) => {
|
|
@@ -3066,7 +2995,7 @@ Be specific. No generic advice.`
|
|
|
3066
2995
|
throw new MushiApiError(400, "BAD_REQUEST", "Provide paths, source=last_push, compare, or fix_id");
|
|
3067
2996
|
}
|
|
3068
2997
|
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/impact?${qs}`);
|
|
3069
|
-
return
|
|
2998
|
+
return jsonResult(data);
|
|
3070
2999
|
}
|
|
3071
3000
|
);
|
|
3072
3001
|
server.registerTool(
|
|
@@ -3130,6 +3059,39 @@ Be specific. No generic advice.`
|
|
|
3130
3059
|
return result;
|
|
3131
3060
|
});
|
|
3132
3061
|
}
|
|
3062
|
+
{
|
|
3063
|
+
const llAliasServer = server.server;
|
|
3064
|
+
const innerHandler = llAliasServer?._requestHandlers?.get("tools/call");
|
|
3065
|
+
if (typeof innerHandler === "function") {
|
|
3066
|
+
llAliasServer._requestHandlers.set("tools/call", async (...args) => {
|
|
3067
|
+
const req = args[0];
|
|
3068
|
+
const originalName = req?.params?.name;
|
|
3069
|
+
const canonicalName = originalName ? DEPRECATED_TOOL_ALIASES[originalName] : void 0;
|
|
3070
|
+
if (canonicalName) {
|
|
3071
|
+
const rewritten = {
|
|
3072
|
+
...args[0],
|
|
3073
|
+
params: { ...req?.params ?? {}, name: canonicalName }
|
|
3074
|
+
};
|
|
3075
|
+
const result = await innerHandler(rewritten, ...args.slice(1));
|
|
3076
|
+
if (result != null && typeof result === "object" && "content" in result && Array.isArray(result.content)) {
|
|
3077
|
+
const r = result;
|
|
3078
|
+
return {
|
|
3079
|
+
...r,
|
|
3080
|
+
content: [
|
|
3081
|
+
{
|
|
3082
|
+
type: "text",
|
|
3083
|
+
text: `\u26A0\uFE0F Deprecated: \`${originalName}\` was renamed to \`${canonicalName}\`. Update your agent config \u2014 this alias will be removed in the next release.`
|
|
3084
|
+
},
|
|
3085
|
+
...r.content
|
|
3086
|
+
]
|
|
3087
|
+
};
|
|
3088
|
+
}
|
|
3089
|
+
return result;
|
|
3090
|
+
}
|
|
3091
|
+
return innerHandler(...args);
|
|
3092
|
+
});
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3133
3095
|
return server;
|
|
3134
3096
|
}
|
|
3135
3097
|
|
|
@@ -3149,7 +3111,7 @@ var PROJECT_ID = process.env.MUSHI_PROJECT_ID ?? "";
|
|
|
3149
3111
|
var SCOPES_RAW = process.env.MUSHI_SCOPES ?? "";
|
|
3150
3112
|
var parsedScopes = SCOPES_RAW ? SCOPES_RAW.split(",").map((s) => s.trim()).filter((s) => s === "mcp:read" || s === "mcp:write") : ALL_SCOPES;
|
|
3151
3113
|
var SCOPES = SCOPES_RAW && parsedScopes.length === 0 ? ALL_SCOPES : parsedScopes;
|
|
3152
|
-
var FEATURES = parseFeaturesCsv(process.env.MUSHI_FEATURES);
|
|
3114
|
+
var FEATURES = process.env.MUSHI_FEATURES?.trim() ? parseFeaturesCsv(process.env.MUSHI_FEATURES) : DEFAULT_FEATURE_GROUPS;
|
|
3153
3115
|
var MCP_SENTRY_DSN = process.env.MUSHI_MCP_SENTRY_DSN?.trim();
|
|
3154
3116
|
if (MCP_SENTRY_DSN) {
|
|
3155
3117
|
Sentry.init({
|