@mushi-mushi/mcp 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -11,15 +11,15 @@ var TOOL_CATALOG = [
11
11
  {
12
12
  name: "get_recent_reports",
13
13
  title: "Recent bug reports",
14
- description: "List recent bug reports with optional filters (status / category / severity). Use this to survey what the triage queue looks like right now.",
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
- useCase: "What landed in my triage queue today?"
17
+ useCase: "What landed in my bug queue today?"
18
18
  },
19
19
  {
20
20
  name: "get_report_detail",
21
21
  title: "Report detail",
22
- description: "Full payload for a single report \u2014 description, console logs, network requests, screenshot URL, classification, fix history.",
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: "Ordered timeline merging reporter comments, fix events, QA runs, skill pipeline steps, and Ask Mushi turns for one report.",
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: "Semantic + keyword search over reports. Uses pgvector similarity server-side \u2014 falls back to description/summary substring only if embeddings are unavailable for the project.",
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 related to a component, page, or description via pgvector nearest-neighbour search. Same backend as search_reports but tuned for "have we seen this before?".',
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 the full context an agent needs to fix a bug: a paste-ready fixPrompt (plain-English diagnosis + reproduction + suggested fix + relevant code + blast radius), plus report detail, reproduction steps, component, root cause, and ontology tags. One call instead of several. No second LLM key needed.",
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: 'Ordered timeline of a fix attempt \u2014 dispatched \u2192 started \u2192 branch \u2192 commit \u2192 PR opened \u2192 CI \u2192 completed/failed. Use this to debug "why did this fix fail?".',
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: "Graph traversal showing other components / pages a bug group touches. Use before dispatching a fix so the agent can scope its changes.",
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 + edges within a depth budget (max 4 hops).",
78
+ description: "Traverse the knowledge graph from a seed component or page. Returns { nodes: [{ id, label, type }], edges } 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: "Natural-language question \u2192 SQL query run against your project data. Read-only, 60/hour rate-limited, no privileged schemas.",
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: "inventory_get",
93
+ name: "get_inventory",
94
94
  title: "Inventory snapshot",
95
- description: "Current inventory.yaml snapshot for a project (latest ingest, validation errors, per-action status summary). Requires inventory_v2 on the project plan.",
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: "inventory_diff",
101
+ name: "diff_inventory",
102
102
  title: "Inventory diff",
103
- description: "Diff two ingested inventory commits (fromSha \u2192 toSha) \u2014 added/removed nodes and edges. Use before merging a PR that touches inventory.yaml.",
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: "inventory_findings",
109
+ name: "list_gate_findings",
110
110
  title: "Gate findings",
111
- description: "Latest gate runs + findings (dead-handler, mock-leak, crawl, status-claim, \u2026). Filter by gate name or severity.",
111
+ description: 'List the most recent inventory gate findings for a project, newest run first. Returns rows of { gate, severity, status, message, node_ref }; "latest" = the most recent run per gate by created_at. 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: "graph_neighborhood",
117
+ name: "get_graph_neighborhood",
118
118
  title: "Graph neighborhood",
119
- description: 'BFS neighborhood around a graph node id or label \u2014 nodes + edges within a depth budget (max 4). Same backend as knowledge-graph traversal, tuned for "what touches this action?".',
119
+ description: `Return the BFS neighborhood around one graph node by id or label: { nodes: [{ id, label, type }], edges } 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: "graph_node_status",
125
+ name: "get_graph_node",
126
126
  title: "Graph node detail",
127
- description: "Fetch a single graph node row (label, type, metadata \u2014 includes v2 derived status on Action nodes).",
127
+ description: "Fetch one knowledge-graph node row by id: { id, label, type, 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: "fix_suggest",
133
+ name: "suggest_fix",
134
134
  title: "Suggested fix (from triage)",
135
- description: "Read-only slice of a report focused on Stage 2 suggested fix + root cause + reproduction \u2014 faster than pulling the full blob when you only need the human-readable hint.",
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, Anthropic BYOK 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: "Single entry point for setup health. mode=full (default) runs ingest + dispatch checks and returns the best next action; mode=ingest runs SDK ingest checks only; mode=dispatch runs fix-dispatch preflight only. Prefer this over setup_check, ingest_setup_check, or diagnose_connection alone.",
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 docs (guides, MCP setup, inventory, QA, skills) by keyword. Returns ranked page titles, URLs, and excerpts \u2014 use before guessing API shapes or RPC names.",
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) from an external agent. Creates a fix_attempt then patches it to completed.",
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: 'Dispatch the Mushi agentic fix orchestrator for a classified report. Set agent="cursor_cloud" to dispatch a Cursor Cloud Agent that opens a signed draft PR. Returns a fix_attempt id; poll get_fix_timeline for progress.',
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: "Run the Sonnet-as-Judge over a batch of classified reports. Returns a batch id; results land in judge_results.",
178
+ description: "Queue the Sonnet-as-Judge to grade the quality of recent fixes across a batch of classified reports. Returns { batchId }; graded scores land asynchronously in the judge_results table (read them back with run_nl_query). Write; consumes LLM budget. Idempotent within a short window \u2014 re-running for the same batch re-grades rather than duplicating. 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: "POST to inventory test-gen: uses the project BYOK LLM to author a Playwright spec from a classified report and opens a draft PR (internal service orchestration). Requires inventory_v2 + GitHub + LLM keys.",
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 between workflow states (new \u2192 classified \u2192 grouped \u2192 fixing \u2192 fixed \u2192 verified \u2192 reopened \u2192 dismissed). Enforces the same transition rules as the admin UI.",
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 PR and mark the linked report fixed.",
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,7 +207,7 @@ var TOOL_CATALOG = [
231
207
  {
232
208
  name: "refresh_ci",
233
209
  title: "Refresh fix CI status",
234
- description: "Pull the latest GitHub check-run status for a fix attempt.",
210
+ description: "Re-poll GitHub for the latest check-run status of a fix attempt's PR and persist it. Returns { ciStatus: pending|success|failure|neutral, checks: [{ name, conclusion }] }. Read-only (mutates nothing on GitHub). Use right before merge_fix to confirm CI is green; use get_fix_timeline for the full attempt lifecycle.",
235
211
  scope: "mcp:read",
236
212
  hints: { readOnly: true, idempotent: true, openWorld: true },
237
213
  useCase: "Check whether CI is green before merging."
@@ -239,7 +215,7 @@ var TOOL_CATALOG = [
239
215
  {
240
216
  name: "reopen_report",
241
217
  title: "Reopen report (operator)",
242
- description: "Move a report to reopened for regression triage.",
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."
@@ -297,10 +273,10 @@ var TOOL_CATALOG = [
297
273
  {
298
274
  name: "get_project_context",
299
275
  title: "Project context snapshot",
300
- description: "Return a rich context snapshot for a project: name, repo URL, SDK heartbeat, ingest status, open report count, autofix readiness, BYOK LLM config, plan tier, and active integration health. Equivalent to a merged preflight + activation + settings read. Agents should call this at the start of a triage session to orient themselves.",
276
+ description: "Return a rich context snapshot for a project: name, repo URL, SDK heartbeat, ingest status, open report count, autofix readiness, your LLM key config, plan tier, and active integration health. Equivalent to a merged preflight + activation + settings read. Agents should call this at the start of a review session to orient themselves.",
301
277
  scope: "mcp:read",
302
278
  hints: { readOnly: true, idempotent: true, openWorld: true },
303
- useCase: "Give me a status overview of this project before I start triaging."
279
+ useCase: "Give me a status overview of this project before I start reviewing bugs."
304
280
  },
305
281
  {
306
282
  name: "get_pipeline_logs",
@@ -321,7 +297,7 @@ var TOOL_CATALOG = [
321
297
  {
322
298
  name: "triage_issue",
323
299
  title: "Triage issue end-to-end",
324
- description: 'Read-only orchestration tool: combines report detail, evidence, similar bugs, fix context, blast radius, and recent pipeline logs for a report into a single structured triage packet. Returns a prioritised list of recommended next actions (investigate, dispatch_fix, group_with, dismiss). Equivalent to a Sentry "Analyze with Seer" flow grounded in user-felt reports. Pass report_id to kick off triage. Call this before dispatch_fix.',
300
+ description: 'Read-only combined tool: merges report detail, evidence, similar bugs, fix context, blast radius, and recent pipeline logs for a report into a single structured review packet. Returns a prioritised list of recommended next actions (investigate, dispatch_fix, group_with, dismiss). Equivalent to a Sentry "Analyze with Seer" flow grounded in user-felt reports. Pass report_id to kick off review. Call this before dispatch_fix.',
325
301
  scope: "mcp:read",
326
302
  hints: { readOnly: true, idempotent: true, openWorld: true },
327
303
  useCase: "Analyze this bug report end-to-end and tell me what to do."
@@ -330,7 +306,7 @@ var TOOL_CATALOG = [
330
306
  {
331
307
  name: "query_lessons",
332
308
  title: "Query lessons for diff context",
333
- description: 'Token-budget retrieval of relevant learning rules (lessons) for a given code diff or PR context. Returns ranked lessons packed within max_tokens using bi-encoder retrieval + severity-weighted scoring. Use this before opening a PR, writing a fix, or asking "what mistakes should I avoid in this area of code?"',
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: "List promoted learning rules (lessons) for the current project. Each lesson represents a named pattern of mistakes that has been encoded from bug reports. Use this to understand what systemic issues have been identified and encoded as heuristics.",
317
+ description: 'List the promoted learning rules ("lessons") for the current project, newest first. Each lesson is a named pattern of mistakes encoded from past bug reports. Returns { lessons: [{ id, title, rule, severity, source_report_ids }] }. Read-only. Use to see the full catalog of encoded heuristics; use query_lessons to retrieve only the 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 with Firecrawl/Browserbase and ask Claude to draft an inventory.yaml with pages and user stories. Creates a story_map_run row for progress tracking, then writes 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 for progress.',
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: "Get the status and results of a story_map_run (pending \u2192 running \u2192 completed/failed). Returns pages_crawled, proposal_id (once done), and cursor_pr_url if Cursor Cloud refined the draft.",
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,15 +348,15 @@ var TDD_TOOL_CATALOG = [
364
348
  {
365
349
  name: "generate_tdd_from_story",
366
350
  title: "Generate TDD test from user story",
367
- description: "Given a user story id (from the accepted inventory), ask Claude to write a full Playwright TypeScript test. Inserts a qa_stories row (source=test_gen_from_story) with approval_status driven by automation_mode. Optionally opens a draft GitHub PR. Returns { qaStoryId, prUrl, approvalStatus, needsHumanReview }.",
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."
371
355
  },
372
356
  {
373
357
  name: "improve_qa_story",
374
- title: "PDCA auto-improve a failing QA story",
375
- description: "Trigger the PDCA improver for a specific project. Finds recently failed qa_story_runs and uses Claude to write improved test scripts. New tests are created with source=pdca and approval gated by the original story's automation_mode.",
358
+ title: "Auto-improve a failing QA story",
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,23 +364,23 @@ var TDD_TOOL_CATALOG = [
380
364
  {
381
365
  name: "run_qa_story",
382
366
  title: "Trigger a manual QA story run",
383
- description: 'Queue a manual run for an enabled + approved qa_story. Returns the run id immediately; poll qa_story_runs or use get_report_detail for progress. Equivalent to "Run now" in the console.',
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."
387
371
  },
388
372
  {
389
373
  name: "list_byok_keys",
390
- title: "List BYOK API key pool",
391
- description: "List all BYOK API keys for the project, grouped by provider. Shows label, priority, status, and cooldown. Never returns the raw key value \u2014 only metadata. Use this to see which keys are active or exhausted.",
374
+ title: "List your API key pool",
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?"
395
379
  },
396
380
  {
397
381
  name: "add_byok_key",
398
- title: "Add a BYOK API key",
399
- description: "Add a new API key to the project's BYOK pool for a given provider (anthropic, openai, firecrawl, browserbase, cursor). Specify label and priority for ordering. The key is stored encrypted in Supabase Vault.",
382
+ title: "Add an API key",
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: "Get the queue of TDD tests that were auto-generated and are waiting for human approval before they run in the QA schedule.",
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 that is in pending_review. Approved stories are enabled in the QA schedule immediately.",
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 available in the catalog, optionally filtered by category. Returns slug, title, description, category, and chain_slugs for each skill. Use before start_skill_pipeline to find the right skill slug for a given type of work.",
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 the full detail for a single agent skill by slug, including the complete SKILL.md body and resolved chain. Use before executing a step to understand what the skill expects you to do.",
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 the full detail for a skill pipeline run: status, context_packet, and all step statuses. Call to retrieve the context_packet after a pipeline was started from the console or another agent.",
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: "Ask a plain-English question about the connected repo. Grounds on pgvector retrieval over project_codebase_files and returns an answer with file:line citations. Requires codebase indexing enabled and an Anthropic or OpenAI BYOK key.",
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: "Lazy-generate (or return cached) plain-English summary for an indexed file or symbol. Cache invalidates when content_hash changes after re-index.",
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 guided tour (~6\u201310 stops) for onboarding \u2014 each stop lists node ids, file paths, layer, and rationale. Cached per index fingerprint.",
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: "Search indexed files by plain-English meaning via embeddings. Returns top-k file chunks with similarity scores.",
540
+ description: 'Search the indexed repo by plain-English meaning via embeddings. Returns top-k { results: [{ path, startLine, endLine, snippet, similarity }] } 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 (reverse import graph). Supports manual paths, last push, GitHub compare, or fix PR files.",
556
+ description: "Find the files that depend on a set of changed paths by walking the reverse import graph. Source the paths from: manual list, the last push, a GitHub compare range, or a fix PR's files. Returns { impacted: [{ path, importedBy }], depth }. 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 graph nodes and sources for the project. Use include_wiki on ask_codebase for RAG merge.",
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,9 +581,17 @@ var FEATURE_GROUPS = [
613
581
  "audit",
614
582
  "docs",
615
583
  "usage",
584
+ "codebase",
616
585
  /** Deprecated aliases kept for one release — hidden unless `legacy` or `all`. */
617
586
  "legacy"
618
587
  ];
588
+ var DEFAULT_FEATURE_GROUPS = [
589
+ "triage",
590
+ "fixes",
591
+ "inventory",
592
+ "setup",
593
+ "docs"
594
+ ];
619
595
  var ALL_FEATURE_GROUPS = FEATURE_GROUPS.filter(
620
596
  (g) => g !== "legacy"
621
597
  );
@@ -635,21 +611,16 @@ var TOOL_FEATURE_MAP = {
635
611
  triage_issue: "triage",
636
612
  query_lessons: "triage",
637
613
  list_lessons: "triage",
638
- fix_suggest: "triage",
639
- graph_neighborhood: "triage",
640
- graph_node_status: "triage",
641
- get_reporter_thread: "legacy",
614
+ suggest_fix: "triage",
615
+ get_graph_neighborhood: "triage",
616
+ get_graph_node: "triage",
642
617
  // inventory
643
- inventory_get: "inventory",
644
- inventory_diff: "inventory",
645
- inventory_findings: "inventory",
618
+ get_inventory: "inventory",
619
+ diff_inventory: "inventory",
620
+ list_gate_findings: "inventory",
646
621
  inventory_current: "admin",
647
622
  // setup
648
- diagnose_connection: "setup",
649
623
  diagnose_setup: "setup",
650
- setup_check: "legacy",
651
- ingest_setup_check: "legacy",
652
- get_activation_status: "legacy",
653
624
  activation_status: "setup",
654
625
  project_integration_health: "setup",
655
626
  setup_repo_for_mushi: "setup",
@@ -702,16 +673,44 @@ var TOOL_FEATURE_MAP = {
702
673
  // audit
703
674
  run_fullstack_audit: "audit",
704
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",
705
684
  // docs
706
685
  search_mushi_docs: "docs"
707
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
+ };
708
702
  function parseFeaturesParam(raw) {
709
703
  if (raw == null || raw.trim() === "") return "all";
710
704
  const normalized = raw.trim().toLowerCase();
711
705
  if (normalized === "all" || normalized === "*") return "all";
712
706
  const groups = normalized.split(",").map((s) => s.trim()).filter(Boolean);
713
707
  const valid = groups.filter((g) => FEATURE_GROUPS.includes(g));
714
- if (valid.length === 0) return "all";
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
+ }
715
714
  return valid;
716
715
  }
717
716
  function parseFeaturesCsv(raw) {
@@ -779,8 +778,8 @@ var MUSHI_DOCS_INDEX = [
779
778
  {
780
779
  title: "Cursor MCP troubleshooting",
781
780
  path: `${BASE}/guides/mcp-setup`,
782
- keywords: ["cursor", "mcp.json", "red badge", "transport error", "stdout", "stderr", "json-rpc", "INVALID_TOKEN", "syntax helper", "diagnose_connection"],
783
- excerpt: "Cursor stdio MCP requires stdout to contain only JSON-RPC. Use the console syntax helper, restart Cursor MCP, then run diagnose_connection."
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."
784
783
  },
785
784
  {
786
785
  title: "Judge loop",
@@ -857,6 +856,7 @@ function searchMushiDocs(query, limit = 8) {
857
856
  }
858
857
 
859
858
  // src/server.ts
859
+ var NO_ARG_INPUT = {};
860
860
  var MushiApiError = class extends Error {
861
861
  constructor(status, code, message) {
862
862
  super(`[${code}] ${message}`);
@@ -1093,7 +1093,7 @@ function createMushiServer(config) {
1093
1093
  title: titleOf("get_two_way_comms_health"),
1094
1094
  description: descOf("get_two_way_comms_health"),
1095
1095
  annotations: annotationsFor("get_two_way_comms_health"),
1096
- inputSchema: {}
1096
+ inputSchema: NO_ARG_INPUT
1097
1097
  },
1098
1098
  async () => jsonText(await apiCall("/v1/sync/two-way-health"))
1099
1099
  );
@@ -1235,14 +1235,14 @@ function createMushiServer(config) {
1235
1235
  }
1236
1236
  );
1237
1237
  server.registerTool(
1238
- "graph_neighborhood",
1238
+ "get_graph_neighborhood",
1239
1239
  {
1240
- title: titleOf("graph_neighborhood"),
1241
- description: descOf("graph_neighborhood"),
1242
- annotations: annotationsFor("graph_neighborhood"),
1240
+ title: titleOf("get_graph_neighborhood"),
1241
+ description: descOf("get_graph_neighborhood"),
1242
+ annotations: annotationsFor("get_graph_neighborhood"),
1243
1243
  inputSchema: {
1244
- seed: z.string().describe("Starting node id or label"),
1245
- depth: z.number().optional().describe("Traversal depth (default 2, max 4)")
1244
+ seed: z.string().describe("Starting graph node id or label to expand around (e.g. an inventory Action id or component name)"),
1245
+ depth: z.number().int().min(1).max(4).optional().describe("BFS hops to traverse outward. Default 2; clamped to a max of 4.")
1246
1246
  }
1247
1247
  },
1248
1248
  async (args) => {
@@ -1254,65 +1254,65 @@ function createMushiServer(config) {
1254
1254
  }
1255
1255
  );
1256
1256
  server.registerTool(
1257
- "graph_node_status",
1257
+ "get_graph_node",
1258
1258
  {
1259
- title: titleOf("graph_node_status"),
1260
- description: descOf("graph_node_status"),
1261
- annotations: annotationsFor("graph_node_status"),
1262
- inputSchema: { nodeId: z.string().describe("graph_nodes.id") }
1259
+ title: titleOf("get_graph_node"),
1260
+ description: descOf("get_graph_node"),
1261
+ annotations: annotationsFor("get_graph_node"),
1262
+ inputSchema: { nodeId: z.string().describe("The graph_nodes.id of the node to fetch (UUID).") }
1263
1263
  },
1264
1264
  async (args) => jsonText(await apiCall(`/v1/admin/graph/node/${args.nodeId}`))
1265
1265
  );
1266
1266
  server.registerTool(
1267
- "inventory_get",
1267
+ "get_inventory",
1268
1268
  {
1269
- title: titleOf("inventory_get"),
1270
- description: descOf("inventory_get"),
1271
- annotations: annotationsFor("inventory_get"),
1269
+ title: titleOf("get_inventory"),
1270
+ description: descOf("get_inventory"),
1271
+ annotations: annotationsFor("get_inventory"),
1272
1272
  inputSchema: {
1273
1273
  projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted")
1274
1274
  }
1275
1275
  },
1276
1276
  async (args) => {
1277
1277
  const pid = args.projectId ?? projectId;
1278
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for inventory_get");
1278
+ if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for get_inventory");
1279
1279
  return jsonText(await apiCall(`/v1/admin/inventory/${pid}`));
1280
1280
  }
1281
1281
  );
1282
1282
  server.registerTool(
1283
- "inventory_diff",
1283
+ "diff_inventory",
1284
1284
  {
1285
- title: titleOf("inventory_diff"),
1286
- description: descOf("inventory_diff"),
1287
- annotations: annotationsFor("inventory_diff"),
1285
+ title: titleOf("diff_inventory"),
1286
+ description: descOf("diff_inventory"),
1287
+ annotations: annotationsFor("diff_inventory"),
1288
1288
  inputSchema: {
1289
1289
  projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project"),
1290
- fromSha: z.string().describe("Older commit SHA (baseline)"),
1291
- toSha: z.string().describe("Newer commit SHA (candidate)")
1290
+ fromSha: z.string().describe("Older commit SHA (the baseline to diff from)"),
1291
+ toSha: z.string().describe("Newer commit SHA (the candidate to diff to)")
1292
1292
  }
1293
1293
  },
1294
1294
  async (args) => {
1295
1295
  const pid = args.projectId ?? projectId;
1296
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for inventory_diff");
1296
+ if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for diff_inventory");
1297
1297
  const q = new URLSearchParams({ from: args.fromSha, to: args.toSha });
1298
1298
  return jsonText(await apiCall(`/v1/admin/inventory/${pid}/diff?${q}`));
1299
1299
  }
1300
1300
  );
1301
1301
  server.registerTool(
1302
- "inventory_findings",
1302
+ "list_gate_findings",
1303
1303
  {
1304
- title: titleOf("inventory_findings"),
1305
- description: descOf("inventory_findings"),
1306
- annotations: annotationsFor("inventory_findings"),
1304
+ title: titleOf("list_gate_findings"),
1305
+ description: descOf("list_gate_findings"),
1306
+ annotations: annotationsFor("list_gate_findings"),
1307
1307
  inputSchema: {
1308
1308
  projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project"),
1309
- gate: z.string().optional().describe("Filter by gate id (e.g. dead_handler, status_claim)"),
1310
- severity: z.string().optional().describe("Filter findings by severity")
1309
+ gate: z.string().optional().describe("Filter to one gate id: dead-handler | mock-leak | crawl | status-claim"),
1310
+ severity: z.string().optional().describe("Minimum severity to include: low | medium | high | critical")
1311
1311
  }
1312
1312
  },
1313
1313
  async (args) => {
1314
1314
  const pid = args.projectId ?? projectId;
1315
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for inventory_findings");
1315
+ if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for list_gate_findings");
1316
1316
  const q = new URLSearchParams();
1317
1317
  if (args.gate) q.set("gate", args.gate);
1318
1318
  if (args.severity) q.set("severity", args.severity);
@@ -1321,17 +1321,25 @@ function createMushiServer(config) {
1321
1321
  }
1322
1322
  );
1323
1323
  server.registerTool(
1324
- "fix_suggest",
1324
+ "suggest_fix",
1325
1325
  {
1326
- title: titleOf("fix_suggest"),
1327
- description: descOf("fix_suggest"),
1328
- annotations: annotationsFor("fix_suggest"),
1329
- inputSchema: { reportId: z.string().describe("Report UUID") }
1326
+ title: titleOf("suggest_fix"),
1327
+ description: descOf("suggest_fix"),
1328
+ annotations: annotationsFor("suggest_fix"),
1329
+ inputSchema: { reportId: z.string().describe("Report UUID to read the Stage-2 suggested-fix slice for") },
1330
+ outputSchema: {
1331
+ reportId: z.string().describe("The report this slice was read from"),
1332
+ rootCause: z.unknown().describe("Stage-2 root-cause hint, or null if not yet classified"),
1333
+ suggestedFix: z.unknown().describe("Stage-2 suggested fix, or null if not yet classified"),
1334
+ reproductionSteps: z.unknown().describe("Reproduction steps recorded on the report (array; [] if none)"),
1335
+ summary: z.unknown().describe("One-line report summary, or null"),
1336
+ component: z.unknown().describe("Component/page the bug was attributed to, or null")
1337
+ }
1330
1338
  },
1331
1339
  async (args) => {
1332
1340
  const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
1333
1341
  const s2 = report.stage2_analysis;
1334
- return jsonText({
1342
+ return jsonResult({
1335
1343
  reportId: args.reportId,
1336
1344
  rootCause: s2?.rootCause ?? null,
1337
1345
  suggestedFix: s2?.suggestedFix ?? null,
@@ -1357,73 +1365,6 @@ function createMushiServer(config) {
1357
1365
  return jsonText(data);
1358
1366
  }
1359
1367
  );
1360
- server.registerTool(
1361
- "setup_check",
1362
- {
1363
- title: titleOf("setup_check"),
1364
- description: descOf("setup_check"),
1365
- annotations: annotationsFor("setup_check"),
1366
- inputSchema: {
1367
- projectId: z.string().optional().describe(
1368
- "Project UUID to check. Falls back to the projectId the server was initialised with."
1369
- )
1370
- }
1371
- },
1372
- async (args) => {
1373
- const resolvedId = args.projectId ?? projectId;
1374
- if (!resolvedId) {
1375
- return jsonText({
1376
- ok: false,
1377
- error: "No projectId provided and none configured on the MCP server. Pass projectId explicitly."
1378
- });
1379
- }
1380
- const data = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
1381
- const summary = data.checks.map((c) => ({
1382
- check: c.key,
1383
- label: c.label,
1384
- passed: c.ready,
1385
- hint: c.hint,
1386
- fixPath: c.fixHref
1387
- }));
1388
- return jsonText({
1389
- ready: data.ready,
1390
- repoUrl: data.repoUrl ?? null,
1391
- checks: summary,
1392
- // Human-readable summary for agents that paste the result into a prompt
1393
- 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(", ")}.`
1394
- });
1395
- }
1396
- );
1397
- server.registerTool(
1398
- "ingest_setup_check",
1399
- {
1400
- title: titleOf("ingest_setup_check"),
1401
- description: descOf("ingest_setup_check"),
1402
- annotations: annotationsFor("ingest_setup_check"),
1403
- inputSchema: {}
1404
- },
1405
- async () => {
1406
- const data = await apiCall("/v1/sync/ingest-setup");
1407
- const failed = data.steps.filter((s) => s.required && !s.complete);
1408
- 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(", ")}.`;
1409
- return jsonText({
1410
- ready: data.ready,
1411
- projectId: data.project_id,
1412
- projectName: data.project_name,
1413
- requiredComplete: data.required_complete,
1414
- requiredTotal: data.required_total,
1415
- steps: data.steps.map((s) => ({
1416
- id: s.id,
1417
- label: s.label,
1418
- passed: s.complete,
1419
- required: s.required,
1420
- hint: s.hint
1421
- })),
1422
- diagnostic: data.diagnostic ?? null,
1423
- summary
1424
- });
1425
- }
1426
- );
1427
1368
  server.registerTool(
1428
1369
  "diagnose_setup",
1429
1370
  {
@@ -1441,7 +1382,8 @@ function createMushiServer(config) {
1441
1382
  summary: z.string(),
1442
1383
  nextAction: z.string().optional(),
1443
1384
  ingest: z.unknown().optional(),
1444
- dispatch: z.unknown().optional()
1385
+ dispatch: z.unknown().optional(),
1386
+ connection: z.unknown().optional()
1445
1387
  }
1446
1388
  },
1447
1389
  async (args) => {
@@ -1481,160 +1423,99 @@ function createMushiServer(config) {
1481
1423
  dispatch: { repoUrl: data.repoUrl ?? null, checks: data.checks }
1482
1424
  });
1483
1425
  }
1484
- const ingest = await apiCall("/v1/sync/ingest-setup");
1485
- let dispatchReady = true;
1486
- let dispatchBlock;
1487
- let dispatchChecks = null;
1488
- if (resolvedId) {
1489
- try {
1490
- const preflight = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
1491
- dispatchReady = preflight.ready;
1492
- dispatchChecks = preflight.checks;
1493
- if (!preflight.ready) {
1494
- const failed = preflight.checks.filter((c) => !c.ready);
1495
- dispatchBlock = failed[0]?.hint;
1496
- }
1497
- } catch {
1498
- dispatchReady = false;
1499
- dispatchBlock = "Could not run dispatch preflight \u2014 verify project_id and API key scope.";
1500
- }
1501
- }
1502
- const ingestFailed = ingest.steps.filter((s) => s.required && !s.complete);
1503
- const ingestBlock = ingestFailed[0]?.hint;
1504
- const ready = ingest.ready && dispatchReady;
1505
- 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.";
1506
- 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.";
1507
- return jsonResult({
1508
- mode: "full",
1509
- ready,
1510
- summary,
1511
- nextAction,
1512
- ingest: { ready: ingest.ready, steps: ingest.steps },
1513
- dispatch: resolvedId ? { ready: dispatchReady, checks: dispatchChecks } : null
1514
- });
1515
- }
1516
- );
1517
- server.registerTool(
1518
- "diagnose_connection",
1519
- {
1520
- title: titleOf("diagnose_connection"),
1521
- description: descOf("diagnose_connection"),
1522
- annotations: annotationsFor("diagnose_connection"),
1523
- inputSchema: {},
1524
- outputSchema: {
1525
- ready: z.boolean(),
1526
- healthOk: z.boolean(),
1527
- ingestReady: z.boolean(),
1528
- dispatchReady: z.boolean(),
1529
- endpoint: z.string().nullable(),
1530
- projectId: z.string().nullable(),
1531
- accessibleProjectCount: z.number().nullable(),
1532
- issues: z.array(z.unknown()),
1533
- nextAction: z.string(),
1534
- summary: z.string()
1535
- }
1536
- },
1537
- async () => {
1538
- const issues = [];
1426
+ const connIssues = [];
1427
+ let healthOk = false;
1539
1428
  if (!apiKey?.startsWith("mushi_")) {
1540
- issues.push({
1429
+ connIssues.push({
1541
1430
  check: "mcp_api_key",
1542
- detail: "MCP server API key missing or malformed",
1543
- fix: "Run `mushi connect` to write MUSHI_API_KEY into .cursor/mcp.json, then restart the MCP server."
1431
+ detail: "MCP server API key missing or malformed (expected prefix: mushi_)",
1432
+ fix: "Run `mushi connect` or set MUSHI_API_KEY to a valid key in your MCP config."
1544
1433
  });
1545
1434
  }
1546
1435
  if (!apiEndpoint) {
1547
- issues.push({
1436
+ connIssues.push({
1548
1437
  check: "mcp_endpoint",
1549
1438
  detail: "No API endpoint configured",
1550
- fix: "Set MUSHI_API_ENDPOINT to your `\u2026/functions/v1/api` URL in .cursor/mcp.json."
1439
+ fix: "Set MUSHI_API_ENDPOINT to your `.../functions/v1/api` URL in your MCP config."
1551
1440
  });
1552
- }
1553
- let healthOk = false;
1554
- if (apiEndpoint) {
1441
+ } else {
1555
1442
  try {
1556
1443
  const healthRes = await doFetch(`${apiEndpoint.replace(/\/$/, "")}/health`, {
1557
1444
  signal: AbortSignal.timeout(5e3)
1558
1445
  });
1559
1446
  healthOk = healthRes.status === 200;
1560
1447
  if (!healthOk) {
1561
- issues.push({
1448
+ connIssues.push({
1562
1449
  check: "endpoint_health",
1563
1450
  detail: `GET /health \u2192 HTTP ${healthRes.status}`,
1564
1451
  fix: "Verify MUSHI_API_ENDPOINT and that the Supabase edge function is deployed."
1565
1452
  });
1566
1453
  }
1567
1454
  } catch (err) {
1568
- issues.push({
1455
+ connIssues.push({
1569
1456
  check: "endpoint_health",
1570
1457
  detail: err instanceof Error ? err.message : String(err),
1571
- fix: "Check network connectivity and the endpoint URL in .cursor/mcp.json."
1458
+ fix: "Check network connectivity and MUSHI_API_ENDPOINT in your MCP config."
1572
1459
  });
1573
1460
  }
1574
1461
  }
1575
- let ingestReady = false;
1576
- let dispatchReady = false;
1577
1462
  let accessibleProjectCount = null;
1578
- if (!projectId && healthOk) {
1463
+ if (!resolvedId && healthOk) {
1579
1464
  try {
1580
- const overview = await apiCall("/v1/admin/mcp/account-overview");
1465
+ const overview = await apiCall(
1466
+ "/v1/admin/mcp/account-overview"
1467
+ );
1581
1468
  accessibleProjectCount = overview.total;
1582
1469
  if (overview.total === 0) {
1583
- issues.push({
1470
+ connIssues.push({
1584
1471
  check: "no_accessible_projects",
1585
1472
  detail: "API key has no accessible projects",
1586
- fix: "Mint an API key on a project (console \u2192 Projects \u2192 API Keys) or add MUSHI_PROJECT_ID to restrict to a single project."
1473
+ fix: "Mint an API key on a project (console \u2192 Projects \u2192 API Keys) or add MUSHI_PROJECT_ID."
1587
1474
  });
1588
1475
  }
1589
1476
  } catch {
1590
1477
  }
1591
1478
  }
1592
- try {
1593
- const ingest = await apiCall("/v1/sync/ingest-setup");
1594
- ingestReady = ingest.ready;
1595
- if (!ingest.ready) {
1596
- const failed = ingest.steps.filter((s) => s.required && !s.complete);
1597
- issues.push({
1598
- check: "ingest_setup",
1599
- detail: `Incomplete: ${failed.map((s) => s.label).join(", ")}`,
1600
- fix: failed[0]?.hint ?? "Paste the SDK snippet, start your dev server, submit a test report."
1601
- });
1602
- }
1603
- } catch (err) {
1604
- issues.push({
1605
- check: "ingest_setup",
1606
- detail: err instanceof Error ? err.message : String(err),
1607
- fix: "Confirm API key is active for this project (Projects \u2192 API Keys)."
1608
- });
1609
- }
1610
- if (projectId) {
1479
+ const ingest = await apiCall("/v1/sync/ingest-setup");
1480
+ let dispatchReady = true;
1481
+ let dispatchBlock;
1482
+ let dispatchChecks = null;
1483
+ if (resolvedId) {
1611
1484
  try {
1612
- const preflight = await apiCall(`/v1/admin/projects/${projectId}/preflight`);
1485
+ const preflight = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
1613
1486
  dispatchReady = preflight.ready;
1487
+ dispatchChecks = preflight.checks;
1614
1488
  if (!preflight.ready) {
1615
1489
  const failed = preflight.checks.filter((c) => !c.ready);
1616
- issues.push({
1617
- check: "dispatch_preflight",
1618
- detail: `Blocked: ${failed.map((c) => c.label).join(", ")}`,
1619
- fix: failed[0]?.hint ?? "Open Settings \u2192 Integrations and complete GitHub + BYOK setup."
1620
- });
1490
+ dispatchBlock = failed[0]?.hint;
1621
1491
  }
1622
- } catch {
1492
+ } catch (err) {
1493
+ dispatchReady = false;
1494
+ const detail = err instanceof Error ? err.message : String(err);
1495
+ dispatchBlock = `Could not run dispatch preflight \u2014 ${detail}`;
1496
+ apiLog.warn("diagnose_setup dispatch preflight failed", { error: detail });
1623
1497
  }
1624
1498
  }
1625
- const ready = issues.length === 0 && healthOk && ingestReady;
1626
- const nextAction = issues[0]?.fix ?? (ready ? "Connection healthy \u2014 SDK ingest is working. Submit a report to confirm end-to-end." : "Run `mushi doctor` in your app repo for a full local checklist.");
1499
+ const ingestFailed = ingest.steps.filter((s) => s.required && !s.complete);
1500
+ const ingestBlock = ingestFailed[0]?.hint;
1501
+ const connOk = connIssues.length === 0;
1502
+ const ready = connOk && ingest.ready && dispatchReady;
1503
+ 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.";
1504
+ 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.";
1627
1505
  return jsonResult({
1506
+ mode: "full",
1628
1507
  ready,
1629
- healthOk,
1630
- ingestReady,
1631
- dispatchReady,
1632
- endpoint: apiEndpoint ?? null,
1633
- projectId: projectId ?? null,
1634
- accessibleProjectCount,
1635
- issues,
1508
+ summary,
1636
1509
  nextAction,
1637
- summary: ready ? "MCP credentials valid; ingest pipeline ready." : `Connection issue \u2014 ${issues[0]?.check ?? "unknown"}: ${nextAction}`
1510
+ connection: {
1511
+ healthOk,
1512
+ endpoint: apiEndpoint ?? null,
1513
+ projectId: resolvedId ?? null,
1514
+ accessibleProjectCount,
1515
+ issues: connIssues
1516
+ },
1517
+ ingest: { ready: ingest.ready, steps: ingest.steps },
1518
+ dispatch: resolvedId ? { ready: dispatchReady, checks: dispatchChecks } : null
1638
1519
  });
1639
1520
  }
1640
1521
  );
@@ -1673,7 +1554,7 @@ function createMushiServer(config) {
1673
1554
  title: titleOf("list_projects"),
1674
1555
  description: descOf("list_projects"),
1675
1556
  annotations: annotationsFor("list_projects"),
1676
- inputSchema: {},
1557
+ inputSchema: NO_ARG_INPUT,
1677
1558
  outputSchema: {
1678
1559
  projects: z.array(z.unknown()),
1679
1560
  total: z.number().optional(),
@@ -1694,7 +1575,7 @@ function createMushiServer(config) {
1694
1575
  title: titleOf("get_account_overview"),
1695
1576
  description: descOf("get_account_overview"),
1696
1577
  annotations: annotationsFor("get_account_overview"),
1697
- inputSchema: {},
1578
+ inputSchema: NO_ARG_INPUT,
1698
1579
  outputSchema: {
1699
1580
  projects: z.array(z.unknown()),
1700
1581
  total: z.number(),
@@ -1813,7 +1694,7 @@ function createMushiServer(config) {
1813
1694
  description: descOf("triage_issue"),
1814
1695
  annotations: annotationsFor("triage_issue"),
1815
1696
  inputSchema: {
1816
- report_id: z.string().describe("Report UUID to triage."),
1697
+ report_id: z.string().uuid().describe("Report UUID to triage."),
1817
1698
  project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
1818
1699
  include_logs: z.boolean().optional().describe("Include recent pipeline logs in triage packet (default: true).")
1819
1700
  },
@@ -1874,11 +1755,31 @@ function createMushiServer(config) {
1874
1755
  args: { reportId: args.report_id }
1875
1756
  });
1876
1757
  }
1758
+ const partial_errors = [];
1759
+ if (reportRes.status === "rejected") {
1760
+ partial_errors.push(`report: ${String(reportRes.reason)}`);
1761
+ }
1762
+ if (evidenceRes.status === "rejected") {
1763
+ partial_errors.push(`timeline: ${String(evidenceRes.reason)}`);
1764
+ }
1765
+ if (similarRes.status === "rejected") {
1766
+ partial_errors.push(`similarity: ${String(similarRes.reason)}`);
1767
+ }
1768
+ if (fixCtxRes.status === "rejected") {
1769
+ partial_errors.push(`fix_context: ${String(fixCtxRes.reason)}`);
1770
+ }
1771
+ if (blastRes.status === "rejected") {
1772
+ partial_errors.push(`blast_radius: ${String(blastRes.reason)}`);
1773
+ }
1774
+ if (includeLogs && logsRes.status === "rejected") {
1775
+ partial_errors.push(`recent_logs: ${String(logsRes.reason)}`);
1776
+ }
1877
1777
  const result = {
1878
1778
  report_id: args.report_id,
1879
1779
  severity,
1880
1780
  category,
1881
1781
  status,
1782
+ partial_errors,
1882
1783
  report: reportRes.status === "fulfilled" ? reportRes.value : { error: String(reportRes.reason) },
1883
1784
  reporter_thread: evidenceRes.status === "fulfilled" ? evidenceRes.value : null,
1884
1785
  similar_bugs: similarRes.status === "fulfilled" ? similarRes.value : null,
@@ -1943,7 +1844,7 @@ function createMushiServer(config) {
1943
1844
  description: descOf("dispatch_fix"),
1944
1845
  annotations: annotationsFor("dispatch_fix"),
1945
1846
  inputSchema: {
1946
- reportId: z.string().describe("Report UUID to fix"),
1847
+ reportId: z.string().uuid().describe("Report UUID to fix"),
1947
1848
  agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
1948
1849
  idempotencyKey: z.string().uuid().optional().describe("Optional RFC 4122 UUID. Resend the same key to safely retry without dispatching a duplicate fix job (Idempotency-Key IETF draft)."),
1949
1850
  inventoryActionNodeId: z.string().uuid().optional().describe("Optional inventory Action node UUID for spec-traceability (\xA72.10). When provided, the fix-worker embeds the expected_outcome contract in the LLM prompt and runs validateAgainstSpec before opening the PR.")
@@ -2027,8 +1928,8 @@ function createMushiServer(config) {
2027
1928
  server.registerTool(
2028
1929
  "merge_fix",
2029
1930
  {
2030
- title: "Merge fix PR",
2031
- description: "Squash-merge a fix attempt PR and mark the linked report fixed.",
1931
+ title: titleOf("merge_fix"),
1932
+ description: descOf("merge_fix"),
2032
1933
  annotations: annotationsFor("merge_fix"),
2033
1934
  inputSchema: {
2034
1935
  fixId: z.string().describe("Fix attempt UUID"),
@@ -2046,8 +1947,8 @@ function createMushiServer(config) {
2046
1947
  server.registerTool(
2047
1948
  "refresh_ci",
2048
1949
  {
2049
- title: "Refresh fix CI status",
2050
- description: "Pull the latest GitHub check-run status for a fix attempt.",
1950
+ title: titleOf("refresh_ci"),
1951
+ description: descOf("refresh_ci"),
2051
1952
  annotations: annotationsFor("refresh_ci"),
2052
1953
  inputSchema: {
2053
1954
  fixId: z.string().describe("Fix attempt UUID")
@@ -2329,6 +2230,22 @@ function createMushiServer(config) {
2329
2230
  };
2330
2231
  }
2331
2232
  );
2233
+ server.registerTool(
2234
+ "activation_status",
2235
+ {
2236
+ title: titleOf("activation_status"),
2237
+ description: descOf("activation_status"),
2238
+ annotations: annotationsFor("activation_status"),
2239
+ inputSchema: {
2240
+ project_id: z.string().optional().describe("Project UUID (defaults to the configured project).")
2241
+ }
2242
+ },
2243
+ async (args) => {
2244
+ const pid = args.project_id ?? projectId;
2245
+ const qs = pid ? `?project_id=${encodeURIComponent(pid)}` : "";
2246
+ return jsonResult(await apiCall(`/v1/admin/activation${qs}`));
2247
+ }
2248
+ );
2332
2249
  server.resource(
2333
2250
  "activation_status",
2334
2251
  "mushi://activation",
@@ -2427,7 +2344,7 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
2427
2344
  text: `You are a Mushi onboarding copilot. Use the MCP tools:
2428
2345
  1. Read mushi://activation for unified setup posture.
2429
2346
  2. Read project://integration-health if GitHub or Sentry looks blocked.
2430
- 3. Call get_activation_status if the resource is unavailable.
2347
+ 3. Call diagnose_setup if the resource is unavailable.
2431
2348
 
2432
2349
  Then output:
2433
2350
  - **Status:** one sentence on what is done vs blocked
@@ -2860,36 +2777,6 @@ Be specific. No generic advice.`
2860
2777
  return jsonText({ ok: true, message: `Step ${step_index} \u2192 ${args.status}` });
2861
2778
  }
2862
2779
  );
2863
- server.registerTool(
2864
- "get_activation_status",
2865
- {
2866
- title: titleOf("get_activation_status"),
2867
- description: descOf("get_activation_status"),
2868
- annotations: annotationsFor("get_activation_status"),
2869
- inputSchema: {
2870
- project_id: z.string().optional().describe("Optional project UUID override")
2871
- }
2872
- },
2873
- async (args) => {
2874
- const qs = args.project_id ? `?project_id=${encodeURIComponent(args.project_id)}` : "";
2875
- const data = await apiCall(`/v1/admin/activation${qs}`);
2876
- return jsonText(data);
2877
- }
2878
- );
2879
- server.registerTool(
2880
- "get_reporter_thread",
2881
- {
2882
- title: titleOf("get_reporter_thread"),
2883
- description: descOf("get_reporter_thread"),
2884
- annotations: annotationsFor("get_reporter_thread"),
2885
- inputSchema: { reportId: z.string().describe("The report UUID") }
2886
- },
2887
- // The reporter thread is the `comments` lane of the unified report
2888
- // timeline. There is no standalone admin `/comments` route (only the
2889
- // reporter-token-gated `/v1/reporter/reports/:id/comments`), so we read the
2890
- // admin-authed timeline, which also carries fix / QA / status lanes.
2891
- async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}/timeline`))
2892
- );
2893
2780
  server.registerTool(
2894
2781
  "ask_codebase",
2895
2782
  {
@@ -3106,10 +2993,44 @@ Be specific. No generic advice.`
3106
2993
  return result;
3107
2994
  });
3108
2995
  }
2996
+ {
2997
+ const llAliasServer = server.server;
2998
+ const innerHandler = llAliasServer?._requestHandlers?.get("tools/call");
2999
+ if (typeof innerHandler === "function") {
3000
+ llAliasServer._requestHandlers.set("tools/call", async (...args) => {
3001
+ const req = args[0];
3002
+ const originalName = req?.params?.name;
3003
+ const canonicalName = originalName ? DEPRECATED_TOOL_ALIASES[originalName] : void 0;
3004
+ if (canonicalName) {
3005
+ const rewritten = {
3006
+ ...args[0],
3007
+ params: { ...req?.params ?? {}, name: canonicalName }
3008
+ };
3009
+ const result = await innerHandler(rewritten, ...args.slice(1));
3010
+ if (result != null && typeof result === "object" && "content" in result && Array.isArray(result.content)) {
3011
+ const r = result;
3012
+ return {
3013
+ ...r,
3014
+ content: [
3015
+ {
3016
+ type: "text",
3017
+ text: `\u26A0\uFE0F Deprecated: \`${originalName}\` was renamed to \`${canonicalName}\`. Update your agent config \u2014 this alias will be removed in the next release.`
3018
+ },
3019
+ ...r.content
3020
+ ]
3021
+ };
3022
+ }
3023
+ return result;
3024
+ }
3025
+ return innerHandler(...args);
3026
+ });
3027
+ }
3028
+ }
3109
3029
  return server;
3110
3030
  }
3111
3031
 
3112
3032
  // src/index.ts
3033
+ import * as Sentry from "@sentry/node";
3113
3034
  var _writeStderr = (...args) => process.stderr.write(
3114
3035
  args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ") + "\n"
3115
3036
  );
@@ -3124,7 +3045,15 @@ var PROJECT_ID = process.env.MUSHI_PROJECT_ID ?? "";
3124
3045
  var SCOPES_RAW = process.env.MUSHI_SCOPES ?? "";
3125
3046
  var parsedScopes = SCOPES_RAW ? SCOPES_RAW.split(",").map((s) => s.trim()).filter((s) => s === "mcp:read" || s === "mcp:write") : ALL_SCOPES;
3126
3047
  var SCOPES = SCOPES_RAW && parsedScopes.length === 0 ? ALL_SCOPES : parsedScopes;
3127
- var FEATURES = parseFeaturesCsv(process.env.MUSHI_FEATURES);
3048
+ var FEATURES = process.env.MUSHI_FEATURES?.trim() ? parseFeaturesCsv(process.env.MUSHI_FEATURES) : DEFAULT_FEATURE_GROUPS;
3049
+ var MCP_SENTRY_DSN = process.env.MUSHI_MCP_SENTRY_DSN?.trim();
3050
+ if (MCP_SENTRY_DSN) {
3051
+ Sentry.init({
3052
+ dsn: MCP_SENTRY_DSN,
3053
+ environment: process.env.MUSHI_SENTRY_ENVIRONMENT ?? process.env.NODE_ENV ?? "development",
3054
+ tracesSampleRate: 0
3055
+ });
3056
+ }
3128
3057
  async function main() {
3129
3058
  if (!API_KEY) {
3130
3059
  log.fatal("MUSHI_API_KEY environment variable is required");
@@ -3180,7 +3109,8 @@ async function main() {
3180
3109
  }
3181
3110
  lastInventoryAt = updatedAt;
3182
3111
  }
3183
- } catch {
3112
+ } catch (pollErr) {
3113
+ log.debug("inventory poll failed", { err: String(pollErr) });
3184
3114
  }
3185
3115
  };
3186
3116
  void pollInventory();