@mushi-mushi/mcp 0.12.0 → 0.13.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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/dist/index.js +318 -6
  3. package/package.json +3 -3
package/README.md CHANGED
@@ -132,6 +132,8 @@ The endpoint accepts JSON-RPC 2.0 over POST (returns `application/json` or `text
132
132
  | `get_knowledge_graph` | Traverse the knowledge graph from a seed component or page |
133
133
  | `setup_check` | The 4 **dispatch-readiness** checks (GitHub repo, codebase indexed, Anthropic key, autofix enabled) — run before `dispatch_fix` |
134
134
  | `ingest_setup_check` | The 4 **required ingest** checks (project, active API key, SDK heartbeat, first report) + `last_sdk_seen_at` diagnostics — run after wiring env vars to confirm the SDK is reporting |
135
+ | `get_activation_status` | Unified setup posture for the active project — required steps, SDK heartbeat, dispatch preflight, and the next best action. Run first when a user says setup is broken |
136
+ | `get_reporter_thread` | Unified report timeline — the reporter/admin comment thread (including verify/reopen signals) plus fix, QA, and status lanes. Use when triaging whether an end user still sees a bug as unfixed |
135
137
 
136
138
  ### Write / agentic
137
139
 
package/dist/index.js CHANGED
@@ -24,6 +24,14 @@ var TOOL_CATALOG = [
24
24
  hints: { readOnly: true, idempotent: true, openWorld: true },
25
25
  useCase: "Show me everything you know about this report."
26
26
  },
27
+ {
28
+ name: "get_report_timeline",
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.",
31
+ scope: "mcp:read",
32
+ hints: { readOnly: true, idempotent: true, openWorld: true },
33
+ useCase: "What happened on this report thread end-to-end?"
34
+ },
27
35
  {
28
36
  name: "search_reports",
29
37
  title: "Search reports",
@@ -146,6 +154,14 @@ var TOOL_CATALOG = [
146
154
  hints: { readOnly: true, idempotent: true, openWorld: true },
147
155
  useCase: "Is the SDK installed and ingesting reports? Why is my banner still missing?"
148
156
  },
157
+ {
158
+ name: "diagnose_connection",
159
+ title: "Connection diagnose (CLI + MCP + SDK)",
160
+ description: `Validate the MCP server credentials (endpoint, API key, projectId), ping /health, run ingest-setup and dispatch preflight, and return the single best next action when anything fails. Use when the user asks "why aren't my reports showing up?".`,
161
+ scope: "mcp:read",
162
+ hints: { readOnly: true, idempotent: true, openWorld: true },
163
+ useCase: "Why is my Mushi setup broken \u2014 what exact step should I fix next?"
164
+ },
149
165
  // --- Write / agentic ----------------------------------------------------
150
166
  {
151
167
  name: "submit_fix_result",
@@ -183,14 +199,35 @@ var TOOL_CATALOG = [
183
199
  {
184
200
  name: "transition_status",
185
201
  title: "Move report between states",
186
- description: "Move a report between workflow states (new \u2192 classified \u2192 grouped \u2192 fixing \u2192 fixed \u2192 dismissed). Enforces the same transition rules as the admin UI.",
202
+ 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.",
187
203
  scope: "mcp:write",
188
- // Transitioning to `dismissed` is destructive by intent — it removes the
189
- // report from triage queues. Flag the whole tool as destructive so the
190
- // client prompts the user on every call.
191
204
  hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
192
205
  useCase: "Dismiss this duplicate / mark it fixed."
193
206
  },
207
+ {
208
+ name: "merge_fix",
209
+ title: "Merge fix PR",
210
+ description: "Squash-merge a fix attempt PR and mark the linked report fixed.",
211
+ scope: "mcp:write",
212
+ hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
213
+ useCase: "Merge the draft PR and notify the reporter."
214
+ },
215
+ {
216
+ name: "refresh_ci",
217
+ title: "Refresh fix CI status",
218
+ description: "Pull the latest GitHub check-run status for a fix attempt.",
219
+ scope: "mcp:read",
220
+ hints: { readOnly: true, idempotent: true, openWorld: true },
221
+ useCase: "Check whether CI is green before merging."
222
+ },
223
+ {
224
+ name: "reopen_report",
225
+ title: "Reopen report (operator)",
226
+ description: "Move a report to reopened for regression triage.",
227
+ scope: "mcp:write",
228
+ hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
229
+ useCase: "Reopen a regression the reporter flagged as not fixed."
230
+ },
194
231
  // --- Rewards (P3) -------------------------------------------------------
195
232
  {
196
233
  name: "list_top_contributors",
@@ -306,6 +343,14 @@ var TDD_TOOL_CATALOG = [
306
343
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: false },
307
344
  useCase: "Reply to a reporter asking for more info or confirming a fix."
308
345
  },
346
+ {
347
+ name: "get_two_way_comms_health",
348
+ title: "Two-way communication health",
349
+ description: "Summarize SDK \u2194 admin two-way reporter health for a host app: last SDK heartbeat, app version/platform last seen, unread reporter messages, recent reporter replies, and pending QA/TDD follow-ups. Use after wiring @mushi-mushi/web in a Vite/Capacitor app to confirm reports land in the console and admin/MCP replies reach the in-app widget.",
350
+ scope: "mcp:read",
351
+ hints: { readOnly: true, idempotent: true, openWorld: true },
352
+ useCase: "Is two-way reporter communication working for this host app?"
353
+ },
309
354
  {
310
355
  name: "list_qa_story_runs",
311
356
  title: "Recent QA story runs",
@@ -371,6 +416,22 @@ var TDD_TOOL_CATALOG = [
371
416
  hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
372
417
  useCase: "I just opened the PR \u2014 mark step 2 as passed and link the PR."
373
418
  },
419
+ {
420
+ name: "get_activation_status",
421
+ title: "Activation cockpit status",
422
+ description: "Unified setup posture for the active project: required steps, SDK heartbeat, dispatch preflight, and the next best action. Use this first when the user says setup is broken or they cannot connect.",
423
+ scope: "mcp:read",
424
+ hints: { readOnly: true, idempotent: true, openWorld: true },
425
+ useCase: "What is blocking this project from going live?"
426
+ },
427
+ {
428
+ name: "get_reporter_thread",
429
+ title: "Reporter feedback thread",
430
+ description: "Fetch the unified timeline for a report \u2014 the reporter/admin comment thread (including verify/reopen signals) plus fix, QA, and status lanes. Use when triaging whether an end user still sees a bug as unfixed.",
431
+ scope: "mcp:read",
432
+ hints: { readOnly: true, idempotent: true, openWorld: true },
433
+ useCase: "What did the reporter say after we marked this fixed?"
434
+ },
374
435
  // ── Full-Stack Audit tools (Phase 5) ────────────────────────────────────────
375
436
  {
376
437
  name: "run_fullstack_audit",
@@ -524,6 +585,26 @@ function createMushiServer(config) {
524
585
  },
525
586
  async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}`))
526
587
  );
588
+ server.registerTool(
589
+ "get_report_timeline",
590
+ {
591
+ title: titleOf("get_report_timeline"),
592
+ description: descOf("get_report_timeline"),
593
+ annotations: annotationsFor("get_report_timeline"),
594
+ inputSchema: { reportId: z.string().describe("The report UUID") }
595
+ },
596
+ async (args) => jsonText(await apiCall(`/v1/sync/reports/${args.reportId}/timeline`))
597
+ );
598
+ server.registerTool(
599
+ "get_two_way_comms_health",
600
+ {
601
+ title: titleOf("get_two_way_comms_health"),
602
+ description: descOf("get_two_way_comms_health"),
603
+ annotations: annotationsFor("get_two_way_comms_health"),
604
+ inputSchema: {}
605
+ },
606
+ async () => jsonText(await apiCall("/v1/sync/two-way-health"))
607
+ );
527
608
  server.registerTool(
528
609
  "search_reports",
529
610
  {
@@ -824,6 +905,109 @@ function createMushiServer(config) {
824
905
  });
825
906
  }
826
907
  );
908
+ server.registerTool(
909
+ "diagnose_connection",
910
+ {
911
+ title: titleOf("diagnose_connection"),
912
+ description: descOf("diagnose_connection"),
913
+ annotations: annotationsFor("diagnose_connection"),
914
+ inputSchema: {}
915
+ },
916
+ async () => {
917
+ const issues = [];
918
+ if (!apiKey?.startsWith("mushi_")) {
919
+ issues.push({
920
+ check: "mcp_api_key",
921
+ detail: "MCP server API key missing or malformed",
922
+ fix: "Run `mushi connect` to write MUSHI_API_KEY into .cursor/mcp.json, then restart the MCP server."
923
+ });
924
+ }
925
+ if (!projectId) {
926
+ issues.push({
927
+ check: "mcp_project_id",
928
+ detail: "No projectId configured on the MCP server",
929
+ fix: "Add MUSHI_PROJECT_ID to .cursor/mcp.json (copy UUID from console \u2192 Projects)."
930
+ });
931
+ }
932
+ if (!apiEndpoint) {
933
+ issues.push({
934
+ check: "mcp_endpoint",
935
+ detail: "No API endpoint configured",
936
+ fix: "Set MUSHI_API_ENDPOINT to your `\u2026/functions/v1/api` URL in .cursor/mcp.json."
937
+ });
938
+ }
939
+ let healthOk = false;
940
+ if (apiEndpoint) {
941
+ try {
942
+ const healthRes = await doFetch(`${apiEndpoint.replace(/\/$/, "")}/health`, {
943
+ signal: AbortSignal.timeout(5e3)
944
+ });
945
+ healthOk = healthRes.status === 200;
946
+ if (!healthOk) {
947
+ issues.push({
948
+ check: "endpoint_health",
949
+ detail: `GET /health \u2192 HTTP ${healthRes.status}`,
950
+ fix: "Verify MUSHI_API_ENDPOINT and that the Supabase edge function is deployed."
951
+ });
952
+ }
953
+ } catch (err) {
954
+ issues.push({
955
+ check: "endpoint_health",
956
+ detail: err instanceof Error ? err.message : String(err),
957
+ fix: "Check network connectivity and the endpoint URL in .cursor/mcp.json."
958
+ });
959
+ }
960
+ }
961
+ let ingestReady = false;
962
+ let dispatchReady = false;
963
+ try {
964
+ const ingest = await apiCall("/v1/sync/ingest-setup");
965
+ ingestReady = ingest.ready;
966
+ if (!ingest.ready) {
967
+ const failed = ingest.steps.filter((s) => s.required && !s.complete);
968
+ issues.push({
969
+ check: "ingest_setup",
970
+ detail: `Incomplete: ${failed.map((s) => s.label).join(", ")}`,
971
+ fix: failed[0]?.hint ?? "Paste the SDK snippet, start your dev server, submit a test report."
972
+ });
973
+ }
974
+ } catch (err) {
975
+ issues.push({
976
+ check: "ingest_setup",
977
+ detail: err instanceof Error ? err.message : String(err),
978
+ fix: "Confirm API key is active for this project (Projects \u2192 API Keys)."
979
+ });
980
+ }
981
+ if (projectId) {
982
+ try {
983
+ const preflight = await apiCall(`/v1/admin/projects/${projectId}/preflight`);
984
+ dispatchReady = preflight.ready;
985
+ if (!preflight.ready) {
986
+ const failed = preflight.checks.filter((c) => !c.ready);
987
+ issues.push({
988
+ check: "dispatch_preflight",
989
+ detail: `Blocked: ${failed.map((c) => c.label).join(", ")}`,
990
+ fix: failed[0]?.hint ?? "Open Settings \u2192 Integrations and complete GitHub + BYOK setup."
991
+ });
992
+ }
993
+ } catch {
994
+ }
995
+ }
996
+ const ready = issues.length === 0 && healthOk && ingestReady;
997
+ 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.");
998
+ return jsonText({
999
+ ready,
1000
+ healthOk,
1001
+ ingestReady,
1002
+ dispatchReady,
1003
+ endpoint: apiEndpoint ?? null,
1004
+ projectId: projectId ?? null,
1005
+ issues,
1006
+ nextAction,
1007
+ summary: ready ? "MCP credentials valid; ingest pipeline ready." : `Connection issue \u2014 ${issues[0]?.check ?? "unknown"}: ${nextAction}`
1008
+ });
1009
+ }
1010
+ );
827
1011
  server.registerTool(
828
1012
  "submit_fix_result",
829
1013
  {
@@ -836,12 +1020,15 @@ function createMushiServer(config) {
836
1020
  prUrl: z.string().optional().describe("GitHub PR URL"),
837
1021
  filesChanged: z.array(z.string()).describe("Files modified"),
838
1022
  linesChanged: z.number().describe("Total lines changed"),
839
- summary: z.string().describe("Fix summary")
1023
+ summary: z.string().describe("Fix summary"),
1024
+ idempotencyKey: z.string().uuid().optional().describe("Optional UUID \u2014 resend the same key to safely retry without creating duplicate fix rows")
840
1025
  }
841
1026
  },
842
1027
  async (args) => {
1028
+ const idemKey = args.idempotencyKey ?? crypto.randomUUID();
843
1029
  const created = await apiCall("/v1/admin/fixes", {
844
1030
  method: "POST",
1031
+ headers: { "Idempotency-Key": idemKey },
845
1032
  body: JSON.stringify({ reportId: args.reportId, agent: "mcp" })
846
1033
  });
847
1034
  await apiCall(`/v1/admin/fixes/${created.fixId}`, {
@@ -947,6 +1134,59 @@ function createMushiServer(config) {
947
1134
  return jsonText(data);
948
1135
  }
949
1136
  );
1137
+ server.registerTool(
1138
+ "merge_fix",
1139
+ {
1140
+ title: "Merge fix PR",
1141
+ description: "Squash-merge a fix attempt PR and mark the linked report fixed.",
1142
+ annotations: annotationsFor("merge_fix"),
1143
+ inputSchema: {
1144
+ fixId: z.string().describe("Fix attempt UUID"),
1145
+ mergeMethod: z.enum(["squash", "merge", "rebase"]).optional().describe("GitHub merge method")
1146
+ }
1147
+ },
1148
+ async (args) => {
1149
+ const data = await apiCall(`/v1/admin/fixes/${args.fixId}/merge`, {
1150
+ method: "POST",
1151
+ body: JSON.stringify({ mergeMethod: args.mergeMethod ?? "squash" })
1152
+ });
1153
+ return jsonText(data);
1154
+ }
1155
+ );
1156
+ server.registerTool(
1157
+ "refresh_ci",
1158
+ {
1159
+ title: "Refresh fix CI status",
1160
+ description: "Pull the latest GitHub check-run status for a fix attempt.",
1161
+ annotations: annotationsFor("refresh_ci"),
1162
+ inputSchema: {
1163
+ fixId: z.string().describe("Fix attempt UUID")
1164
+ }
1165
+ },
1166
+ async (args) => {
1167
+ const data = await apiCall(`/v1/admin/fixes/${args.fixId}/refresh-ci`, { method: "POST" });
1168
+ return jsonText(data);
1169
+ }
1170
+ );
1171
+ server.registerTool(
1172
+ "reopen_report",
1173
+ {
1174
+ title: "Reopen report (operator)",
1175
+ description: "Operator alias to move a report back to new for regression triage.",
1176
+ annotations: annotationsFor("reopen_report"),
1177
+ inputSchema: {
1178
+ reportId: z.string().describe("Report UUID"),
1179
+ note: z.string().optional().describe("Triage note")
1180
+ }
1181
+ },
1182
+ async (args) => {
1183
+ const data = await apiCall(`/v1/sync/reports/${args.reportId}`, {
1184
+ method: "PATCH",
1185
+ body: JSON.stringify({ status: "reopened", note: args.note })
1186
+ });
1187
+ return jsonText(data);
1188
+ }
1189
+ );
950
1190
  server.registerTool(
951
1191
  "transition_status",
952
1192
  {
@@ -955,7 +1195,7 @@ function createMushiServer(config) {
955
1195
  annotations: annotationsFor("transition_status"),
956
1196
  inputSchema: {
957
1197
  reportId: z.string().describe("Report UUID"),
958
- status: z.enum(["pending", "classified", "grouped", "fixing", "fixed", "dismissed"]).describe("Target status"),
1198
+ status: z.enum(["pending", "classified", "grouped", "fixing", "fixed", "resolved", "verified", "reopened", "dismissed"]).describe("Target status"),
959
1199
  reason: z.string().optional().describe("Reason for the transition (audit trail)")
960
1200
  }
961
1201
  },
@@ -1138,6 +1378,23 @@ function createMushiServer(config) {
1138
1378
  };
1139
1379
  }
1140
1380
  );
1381
+ server.resource(
1382
+ "activation_status",
1383
+ "mushi://activation",
1384
+ {
1385
+ description: "Unified setup posture \u2014 SDK heartbeat, reports, GitHub, MCP readiness, QA stories, and the next best action."
1386
+ },
1387
+ async () => {
1388
+ const qs = projectId ? `?project_id=${encodeURIComponent(projectId)}` : "";
1389
+ return {
1390
+ contents: [{
1391
+ uri: "mushi://activation",
1392
+ mimeType: "application/json",
1393
+ text: JSON.stringify(await apiCall(`/v1/admin/activation${qs}`), null, 2)
1394
+ }]
1395
+ };
1396
+ }
1397
+ );
1141
1398
  server.prompt(
1142
1399
  "summarize_report_for_fix",
1143
1400
  "Turn a Mushi report into a one-line root cause, smallest file set, repro steps, and blast-radius warnings. Use before asking an agent to write the patch.",
@@ -1207,6 +1464,31 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
1207
1464
  }]
1208
1465
  })
1209
1466
  );
1467
+ server.prompt(
1468
+ "mushi_setup",
1469
+ "Diagnose why Mushi setup is stuck and return the single next command or console step to unblock it.",
1470
+ {},
1471
+ () => ({
1472
+ messages: [{
1473
+ role: "user",
1474
+ content: {
1475
+ type: "text",
1476
+ text: `You are a Mushi onboarding copilot. Use the MCP tools:
1477
+ 1. Read mushi://activation for unified setup posture.
1478
+ 2. Read project://integration-health if GitHub or Sentry looks blocked.
1479
+ 3. Call get_activation_status if the resource is unavailable.
1480
+
1481
+ Then output:
1482
+ - **Status:** one sentence on what is done vs blocked
1483
+ - **Next step:** the single highest-leverage action (console link or CLI command)
1484
+ - **Prove it:** how the user verifies the step worked
1485
+ - **If still stuck:** one diagnostic command (e.g. mushi doctor --fix)
1486
+
1487
+ Be specific. No generic advice.`
1488
+ }
1489
+ }]
1490
+ })
1491
+ );
1210
1492
  server.registerTool(
1211
1493
  "map_user_stories",
1212
1494
  {
@@ -1610,6 +1892,36 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
1610
1892
  return jsonText({ ok: true, message: `Step ${step_index} \u2192 ${args.status}` });
1611
1893
  }
1612
1894
  );
1895
+ server.registerTool(
1896
+ "get_activation_status",
1897
+ {
1898
+ title: titleOf("get_activation_status"),
1899
+ description: descOf("get_activation_status"),
1900
+ annotations: annotationsFor("get_activation_status"),
1901
+ inputSchema: {
1902
+ project_id: z.string().optional().describe("Optional project UUID override")
1903
+ }
1904
+ },
1905
+ async (args) => {
1906
+ const qs = args.project_id ? `?project_id=${encodeURIComponent(args.project_id)}` : "";
1907
+ const data = await apiCall(`/v1/admin/activation${qs}`);
1908
+ return jsonText(data);
1909
+ }
1910
+ );
1911
+ server.registerTool(
1912
+ "get_reporter_thread",
1913
+ {
1914
+ title: titleOf("get_reporter_thread"),
1915
+ description: descOf("get_reporter_thread"),
1916
+ annotations: annotationsFor("get_reporter_thread"),
1917
+ inputSchema: { reportId: z.string().describe("The report UUID") }
1918
+ },
1919
+ // The reporter thread is the `comments` lane of the unified report
1920
+ // timeline. There is no standalone admin `/comments` route (only the
1921
+ // reporter-token-gated `/v1/reporter/reports/:id/comments`), so we read the
1922
+ // admin-authed timeline, which also carries fix / QA / status lanes.
1923
+ async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}/timeline`))
1924
+ );
1613
1925
  const grantedScopes = config.scopes;
1614
1926
  if (grantedScopes !== void 0) {
1615
1927
  const toolRegistry = server._registeredTools;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/mcp",
3
- "version": "0.12.0",
3
+ "version": "0.13.1",
4
4
  "license": "MIT",
5
5
  "description": "MCP server exposing Mushi Mushi reports to coding agents",
6
6
  "type": "module",
@@ -25,8 +25,8 @@
25
25
  ],
26
26
  "dependencies": {
27
27
  "@modelcontextprotocol/sdk": "^1.29.0",
28
- "@mushi-mushi/core": "^1.9.0",
29
- "zod": "^4.4.2"
28
+ "zod": "^4.4.2",
29
+ "@mushi-mushi/core": "^1.12.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^22.19.17",