@mushi-mushi/mcp 0.13.0 → 0.14.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
@@ -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",
@@ -244,6 +260,64 @@ var TOOL_CATALOG = [
244
260
  scope: "mcp:write",
245
261
  hints: { readOnly: false, destructive: false, idempotent: true, openWorld: false },
246
262
  useCase: "Set up this repo for the Mushi evolution loop in one step."
263
+ },
264
+ // ── Sentry-like triage and project context ────────────────────────────────
265
+ {
266
+ name: "list_projects",
267
+ title: "List accessible projects",
268
+ description: "List the Mushi projects accessible to this API key. For project-scoped keys (the typical case), returns a single-item list with the bound project's id, name, slug, plan tier, and ingest status. For org-level tokens, returns all projects in the org. Use this when MUSHI_PROJECT_ID is not configured \u2014 call list_projects first, then pass the id to other tools.",
269
+ scope: "mcp:read",
270
+ hints: { readOnly: true, idempotent: true, openWorld: true },
271
+ useCase: "Which projects can I access with this API key?"
272
+ },
273
+ {
274
+ name: "get_project_context",
275
+ title: "Project context snapshot",
276
+ 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.",
277
+ scope: "mcp:read",
278
+ hints: { readOnly: true, idempotent: true, openWorld: true },
279
+ useCase: "Give me a status overview of this project before I start triaging."
280
+ },
281
+ {
282
+ name: "get_pipeline_logs",
283
+ title: "Recent pipeline logs",
284
+ description: "Pull recent log entries from the Mushi pipeline services: classify-report, fix-worker, qa-story-runner, mcp, webhook, api, or all. Accepts project_id, service, since (ISO-8601), limit (max 200), and level (info | warn | error | fatal) filters. Returns structured log rows with timestamp, level, service, message, and a trace_id/report_id when available. Use this when a fix failed, a QA story keeps erroring, or an ingest pipeline went silent.",
285
+ scope: "mcp:read",
286
+ hints: { readOnly: true, idempotent: true, openWorld: true },
287
+ useCase: "Why did the last fix attempt fail? Show me recent pipeline errors."
288
+ },
289
+ {
290
+ name: "get_report_evidence",
291
+ title: "Bug report evidence",
292
+ description: "Return the full evidence package for a single bug report: screenshot URL, console logs, network excerpts, browser environment (user agent, URL, viewport, SDK version), user replay link if available, breadcrumb trail, and the reporter's own comments thread. This is the same data an engineer would collect for a root-cause investigation. Faster than calling get_report_detail + report timeline separately.",
293
+ scope: "mcp:read",
294
+ hints: { readOnly: true, idempotent: true, openWorld: true },
295
+ useCase: "I need all the evidence for this bug report to diagnose the root cause."
296
+ },
297
+ {
298
+ name: "triage_issue",
299
+ title: "Triage issue end-to-end",
300
+ 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.',
301
+ scope: "mcp:read",
302
+ hints: { readOnly: true, idempotent: true, openWorld: true },
303
+ useCase: "Analyze this bug report end-to-end and tell me what to do."
304
+ },
305
+ // ── Lessons / evolution loop ─────────────────────────────────────────────
306
+ {
307
+ name: "query_lessons",
308
+ title: "Query lessons for diff context",
309
+ 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?"',
310
+ scope: "mcp:read",
311
+ hints: { readOnly: true, idempotent: true, openWorld: true },
312
+ useCase: "What past mistakes should I avoid when making this change?"
313
+ },
314
+ {
315
+ name: "list_lessons",
316
+ title: "List project lessons",
317
+ 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.",
318
+ scope: "mcp:read",
319
+ hints: { readOnly: true, idempotent: true, openWorld: true },
320
+ useCase: "What systemic patterns has Mushi identified for this project?"
247
321
  }
248
322
  ];
249
323
  var TDD_TOOL_CATALOG = [
@@ -327,6 +401,14 @@ var TDD_TOOL_CATALOG = [
327
401
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: false },
328
402
  useCase: "Reply to a reporter asking for more info or confirming a fix."
329
403
  },
404
+ {
405
+ name: "get_two_way_comms_health",
406
+ title: "Two-way communication health",
407
+ 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.",
408
+ scope: "mcp:read",
409
+ hints: { readOnly: true, idempotent: true, openWorld: true },
410
+ useCase: "Is two-way reporter communication working for this host app?"
411
+ },
330
412
  {
331
413
  name: "list_qa_story_runs",
332
414
  title: "Recent QA story runs",
@@ -392,6 +474,22 @@ var TDD_TOOL_CATALOG = [
392
474
  hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
393
475
  useCase: "I just opened the PR \u2014 mark step 2 as passed and link the PR."
394
476
  },
477
+ {
478
+ name: "get_activation_status",
479
+ title: "Activation cockpit status",
480
+ 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.",
481
+ scope: "mcp:read",
482
+ hints: { readOnly: true, idempotent: true, openWorld: true },
483
+ useCase: "What is blocking this project from going live?"
484
+ },
485
+ {
486
+ name: "get_reporter_thread",
487
+ title: "Reporter feedback thread",
488
+ 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.",
489
+ scope: "mcp:read",
490
+ hints: { readOnly: true, idempotent: true, openWorld: true },
491
+ useCase: "What did the reporter say after we marked this fixed?"
492
+ },
395
493
  // ── Full-Stack Audit tools (Phase 5) ────────────────────────────────────────
396
494
  {
397
495
  name: "run_fullstack_audit",
@@ -545,6 +643,26 @@ function createMushiServer(config) {
545
643
  },
546
644
  async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}`))
547
645
  );
646
+ server.registerTool(
647
+ "get_report_timeline",
648
+ {
649
+ title: titleOf("get_report_timeline"),
650
+ description: descOf("get_report_timeline"),
651
+ annotations: annotationsFor("get_report_timeline"),
652
+ inputSchema: { reportId: z.string().describe("The report UUID") }
653
+ },
654
+ async (args) => jsonText(await apiCall(`/v1/sync/reports/${args.reportId}/timeline`))
655
+ );
656
+ server.registerTool(
657
+ "get_two_way_comms_health",
658
+ {
659
+ title: titleOf("get_two_way_comms_health"),
660
+ description: descOf("get_two_way_comms_health"),
661
+ annotations: annotationsFor("get_two_way_comms_health"),
662
+ inputSchema: {}
663
+ },
664
+ async () => jsonText(await apiCall("/v1/sync/two-way-health"))
665
+ );
548
666
  server.registerTool(
549
667
  "search_reports",
550
668
  {
@@ -845,6 +963,278 @@ function createMushiServer(config) {
845
963
  });
846
964
  }
847
965
  );
966
+ server.registerTool(
967
+ "diagnose_connection",
968
+ {
969
+ title: titleOf("diagnose_connection"),
970
+ description: descOf("diagnose_connection"),
971
+ annotations: annotationsFor("diagnose_connection"),
972
+ inputSchema: {}
973
+ },
974
+ async () => {
975
+ const issues = [];
976
+ if (!apiKey?.startsWith("mushi_")) {
977
+ issues.push({
978
+ check: "mcp_api_key",
979
+ detail: "MCP server API key missing or malformed",
980
+ fix: "Run `mushi connect` to write MUSHI_API_KEY into .cursor/mcp.json, then restart the MCP server."
981
+ });
982
+ }
983
+ if (!projectId) {
984
+ issues.push({
985
+ check: "mcp_project_id",
986
+ detail: "No projectId configured on the MCP server",
987
+ fix: "Add MUSHI_PROJECT_ID to .cursor/mcp.json (copy UUID from console \u2192 Projects)."
988
+ });
989
+ }
990
+ if (!apiEndpoint) {
991
+ issues.push({
992
+ check: "mcp_endpoint",
993
+ detail: "No API endpoint configured",
994
+ fix: "Set MUSHI_API_ENDPOINT to your `\u2026/functions/v1/api` URL in .cursor/mcp.json."
995
+ });
996
+ }
997
+ let healthOk = false;
998
+ if (apiEndpoint) {
999
+ try {
1000
+ const healthRes = await doFetch(`${apiEndpoint.replace(/\/$/, "")}/health`, {
1001
+ signal: AbortSignal.timeout(5e3)
1002
+ });
1003
+ healthOk = healthRes.status === 200;
1004
+ if (!healthOk) {
1005
+ issues.push({
1006
+ check: "endpoint_health",
1007
+ detail: `GET /health \u2192 HTTP ${healthRes.status}`,
1008
+ fix: "Verify MUSHI_API_ENDPOINT and that the Supabase edge function is deployed."
1009
+ });
1010
+ }
1011
+ } catch (err) {
1012
+ issues.push({
1013
+ check: "endpoint_health",
1014
+ detail: err instanceof Error ? err.message : String(err),
1015
+ fix: "Check network connectivity and the endpoint URL in .cursor/mcp.json."
1016
+ });
1017
+ }
1018
+ }
1019
+ let ingestReady = false;
1020
+ let dispatchReady = false;
1021
+ try {
1022
+ const ingest = await apiCall("/v1/sync/ingest-setup");
1023
+ ingestReady = ingest.ready;
1024
+ if (!ingest.ready) {
1025
+ const failed = ingest.steps.filter((s) => s.required && !s.complete);
1026
+ issues.push({
1027
+ check: "ingest_setup",
1028
+ detail: `Incomplete: ${failed.map((s) => s.label).join(", ")}`,
1029
+ fix: failed[0]?.hint ?? "Paste the SDK snippet, start your dev server, submit a test report."
1030
+ });
1031
+ }
1032
+ } catch (err) {
1033
+ issues.push({
1034
+ check: "ingest_setup",
1035
+ detail: err instanceof Error ? err.message : String(err),
1036
+ fix: "Confirm API key is active for this project (Projects \u2192 API Keys)."
1037
+ });
1038
+ }
1039
+ if (projectId) {
1040
+ try {
1041
+ const preflight = await apiCall(`/v1/admin/projects/${projectId}/preflight`);
1042
+ dispatchReady = preflight.ready;
1043
+ if (!preflight.ready) {
1044
+ const failed = preflight.checks.filter((c) => !c.ready);
1045
+ issues.push({
1046
+ check: "dispatch_preflight",
1047
+ detail: `Blocked: ${failed.map((c) => c.label).join(", ")}`,
1048
+ fix: failed[0]?.hint ?? "Open Settings \u2192 Integrations and complete GitHub + BYOK setup."
1049
+ });
1050
+ }
1051
+ } catch {
1052
+ }
1053
+ }
1054
+ const ready = issues.length === 0 && healthOk && ingestReady;
1055
+ 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.");
1056
+ return jsonText({
1057
+ ready,
1058
+ healthOk,
1059
+ ingestReady,
1060
+ dispatchReady,
1061
+ endpoint: apiEndpoint ?? null,
1062
+ projectId: projectId ?? null,
1063
+ issues,
1064
+ nextAction,
1065
+ summary: ready ? "MCP credentials valid; ingest pipeline ready." : `Connection issue \u2014 ${issues[0]?.check ?? "unknown"}: ${nextAction}`
1066
+ });
1067
+ }
1068
+ );
1069
+ server.registerTool(
1070
+ "list_projects",
1071
+ {
1072
+ title: titleOf("list_projects"),
1073
+ description: descOf("list_projects"),
1074
+ annotations: annotationsFor("list_projects"),
1075
+ inputSchema: {}
1076
+ },
1077
+ async () => {
1078
+ const data = await apiCall("/v1/admin/mcp/projects");
1079
+ return jsonText(data);
1080
+ }
1081
+ );
1082
+ server.registerTool(
1083
+ "get_project_context",
1084
+ {
1085
+ title: titleOf("get_project_context"),
1086
+ description: descOf("get_project_context"),
1087
+ annotations: annotationsFor("get_project_context"),
1088
+ inputSchema: {
1089
+ project_id: z.string().optional().describe("Project UUID. Defaults to configured project.")
1090
+ }
1091
+ },
1092
+ async (args) => {
1093
+ const pid = args.project_id ?? projectId;
1094
+ if (!pid) throw new MushiApiError(400, "MISSING_PROJECT_ID", "project_id is required");
1095
+ const [preflightRes, activationRes] = await Promise.allSettled([
1096
+ apiCall(`/v1/admin/projects/${pid}/preflight`),
1097
+ apiCall(`/v1/admin/activation?project_id=${encodeURIComponent(pid)}`)
1098
+ ]);
1099
+ return jsonText({
1100
+ project_id: pid,
1101
+ preflight: preflightRes.status === "fulfilled" ? preflightRes.value : { error: String(preflightRes.reason) },
1102
+ activation: activationRes.status === "fulfilled" ? activationRes.value : { error: String(activationRes.reason) }
1103
+ });
1104
+ }
1105
+ );
1106
+ server.registerTool(
1107
+ "get_pipeline_logs",
1108
+ {
1109
+ title: titleOf("get_pipeline_logs"),
1110
+ description: descOf("get_pipeline_logs"),
1111
+ annotations: annotationsFor("get_pipeline_logs"),
1112
+ inputSchema: {
1113
+ project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
1114
+ service: z.enum(["classify-report", "fix-worker", "qa-story-runner", "mcp", "webhook", "api", "all"]).optional().describe("Pipeline service to filter (default: all)."),
1115
+ since: z.string().optional().describe("ISO-8601 timestamp \u2014 return only entries after this time."),
1116
+ limit: z.number().optional().describe("Max entries to return (default 50, max 200)."),
1117
+ level: z.enum(["info", "warn", "error", "fatal"]).optional().describe("Minimum severity level (default: warn).")
1118
+ }
1119
+ },
1120
+ async (args) => {
1121
+ const pid = args.project_id ?? projectId;
1122
+ if (!pid) throw new MushiApiError(400, "MISSING_PROJECT_ID", "project_id is required");
1123
+ const qs = new URLSearchParams();
1124
+ if (args.service && args.service !== "all") qs.set("service", args.service);
1125
+ if (args.since) qs.set("since", args.since);
1126
+ qs.set("limit", String(Math.min(args.limit ?? 50, 200)));
1127
+ if (args.level) qs.set("level", args.level);
1128
+ const data = await apiCall(`/v1/admin/mcp/logs/${pid}?${qs}`);
1129
+ return jsonText(data);
1130
+ }
1131
+ );
1132
+ server.registerTool(
1133
+ "get_report_evidence",
1134
+ {
1135
+ title: titleOf("get_report_evidence"),
1136
+ description: descOf("get_report_evidence"),
1137
+ annotations: annotationsFor("get_report_evidence"),
1138
+ inputSchema: {
1139
+ report_id: z.string().describe("Report UUID.")
1140
+ }
1141
+ },
1142
+ async (args) => {
1143
+ const [reportRes, timelineRes] = await Promise.allSettled([
1144
+ apiCall(`/v1/admin/reports/${args.report_id}`),
1145
+ apiCall(`/v1/admin/reports/${args.report_id}/timeline`)
1146
+ ]);
1147
+ const report = reportRes.status === "fulfilled" ? reportRes.value : null;
1148
+ const timeline = timelineRes.status === "fulfilled" ? timelineRes.value : null;
1149
+ const evidence = report ? {
1150
+ report_id: args.report_id,
1151
+ description: report.description,
1152
+ summary: report.summary,
1153
+ screenshot_url: report.screenshot_url ?? null,
1154
+ environment: report.environment ?? null,
1155
+ breadcrumbs: report.breadcrumbs ?? null,
1156
+ sentry_replay_id: report.sentry_replay_id ?? null,
1157
+ sentry_trace_id: report.sentry_trace_id ?? null,
1158
+ sentry_event_id: report.sentry_event_id ?? null,
1159
+ session_id: report.session_id ?? null,
1160
+ created_at: report.created_at,
1161
+ tags: report.tags ?? null
1162
+ } : { error: String(reportRes.reason) };
1163
+ return jsonText({
1164
+ evidence,
1165
+ reporter_thread: timeline ?? { error: String(timelineRes.reason) }
1166
+ });
1167
+ }
1168
+ );
1169
+ server.registerTool(
1170
+ "triage_issue",
1171
+ {
1172
+ title: titleOf("triage_issue"),
1173
+ description: descOf("triage_issue"),
1174
+ annotations: annotationsFor("triage_issue"),
1175
+ inputSchema: {
1176
+ report_id: z.string().describe("Report UUID to triage."),
1177
+ project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
1178
+ include_logs: z.boolean().optional().describe("Include recent pipeline logs in triage packet (default: true).")
1179
+ }
1180
+ },
1181
+ async (args) => {
1182
+ const pid = args.project_id ?? projectId;
1183
+ const includeLogs = args.include_logs !== false;
1184
+ const [reportRes, evidenceRes, similarRes, fixCtxRes, blastRes, logsRes] = await Promise.allSettled([
1185
+ apiCall(`/v1/admin/reports/${args.report_id}`),
1186
+ apiCall(`/v1/admin/reports/${args.report_id}/timeline`),
1187
+ apiCall(`/v1/admin/reports/similarity`, {
1188
+ method: "POST",
1189
+ body: JSON.stringify({ report_id: args.report_id })
1190
+ }).catch(() => null),
1191
+ pid ? apiCall(`/v1/admin/reports/${args.report_id}/fix-context`).catch(() => null) : Promise.resolve(null),
1192
+ pid ? apiCall(`/v1/admin/reports/${args.report_id}/blast-radius`).catch(() => null) : Promise.resolve(null),
1193
+ includeLogs && pid ? apiCall(`/v1/admin/mcp/logs/${pid}?limit=20&level=warn`).catch(() => null) : Promise.resolve(null)
1194
+ ]);
1195
+ const report = reportRes.status === "fulfilled" ? reportRes.value : null;
1196
+ const severity = report ? report.severity : "unknown";
1197
+ const category = report ? report.category : "unknown";
1198
+ const status = report ? report.status : "unknown";
1199
+ const actions = [];
1200
+ if (status === "new" || status === "classified") {
1201
+ actions.push({
1202
+ action: "dispatch_fix",
1203
+ reason: "Report is classified but no fix has been attempted",
1204
+ tool: "dispatch_fix",
1205
+ args: { reportId: args.report_id, agent: "cursor_cloud" }
1206
+ });
1207
+ } else if (status === "fixing") {
1208
+ actions.push({
1209
+ action: "check_fix_progress",
1210
+ reason: "Fix is in progress \u2014 check the fix timeline for latest status",
1211
+ tool: "get_fix_timeline",
1212
+ args: { reportId: args.report_id }
1213
+ });
1214
+ } else if (status === "fixed") {
1215
+ actions.push({
1216
+ action: "verify_fix",
1217
+ reason: "Fix was applied \u2014 verify it resolved the issue",
1218
+ tool: "get_fix_context",
1219
+ args: { reportId: args.report_id }
1220
+ });
1221
+ }
1222
+ return jsonText({
1223
+ report_id: args.report_id,
1224
+ severity,
1225
+ category,
1226
+ status,
1227
+ report: reportRes.status === "fulfilled" ? reportRes.value : { error: String(reportRes.reason) },
1228
+ reporter_thread: evidenceRes.status === "fulfilled" ? evidenceRes.value : null,
1229
+ similar_bugs: similarRes.status === "fulfilled" ? similarRes.value : null,
1230
+ fix_context: fixCtxRes.status === "fulfilled" ? fixCtxRes.value : null,
1231
+ blast_radius: blastRes.status === "fulfilled" ? blastRes.value : null,
1232
+ recent_logs: logsRes.status === "fulfilled" ? logsRes.value : null,
1233
+ recommended_actions: actions,
1234
+ triage_summary: `[${severity}] ${category} \u2014 status: ${status}. ${actions.length > 0 ? `Recommended: ${actions[0]?.action}.` : "No action required."}`
1235
+ });
1236
+ }
1237
+ );
848
1238
  server.registerTool(
849
1239
  "submit_fix_result",
850
1240
  {
@@ -1008,8 +1398,8 @@ function createMushiServer(config) {
1008
1398
  server.registerTool(
1009
1399
  "reopen_report",
1010
1400
  {
1011
- title: "Reopen report (operator)",
1012
- description: "Operator alias to move a report back to new for regression triage.",
1401
+ title: titleOf("reopen_report"),
1402
+ description: descOf("reopen_report"),
1013
1403
  annotations: annotationsFor("reopen_report"),
1014
1404
  inputSchema: {
1015
1405
  reportId: z.string().describe("Report UUID"),
@@ -1064,6 +1454,52 @@ function createMushiServer(config) {
1064
1454
  return jsonText(data);
1065
1455
  }
1066
1456
  );
1457
+ server.registerTool(
1458
+ "query_lessons",
1459
+ {
1460
+ title: titleOf("query_lessons"),
1461
+ description: descOf("query_lessons"),
1462
+ annotations: annotationsFor("query_lessons"),
1463
+ inputSchema: {
1464
+ diff_text: z.string().describe("The PR diff, code snippet, or description of the change being made."),
1465
+ max_tokens: z.number().optional().describe("Maximum tokens for returned lessons context (default 3000, max 8000)."),
1466
+ top_k: z.number().optional().describe("Max number of lessons to return (default 15, max 50)."),
1467
+ project_id: z.string().optional().describe("Project UUID. Defaults to configured project.")
1468
+ }
1469
+ },
1470
+ async (args) => {
1471
+ const pid = args.project_id ?? projectId;
1472
+ const params = new URLSearchParams();
1473
+ params.set("diff_text", args.diff_text);
1474
+ if (args.max_tokens !== void 0) params.set("max_tokens", String(args.max_tokens));
1475
+ if (args.top_k !== void 0) params.set("top_k", String(args.top_k));
1476
+ if (pid) params.set("projectId", pid);
1477
+ const data = await apiCall(`/v1/admin/lessons/query?${params}`);
1478
+ return jsonText(data);
1479
+ }
1480
+ );
1481
+ server.registerTool(
1482
+ "list_lessons",
1483
+ {
1484
+ title: titleOf("list_lessons"),
1485
+ description: descOf("list_lessons"),
1486
+ annotations: annotationsFor("list_lessons"),
1487
+ inputSchema: {
1488
+ severity: z.enum(["info", "warn", "critical"]).optional().describe("Filter by severity level."),
1489
+ limit: z.number().optional().describe("Max number of lessons to return (default 50, max 200)."),
1490
+ project_id: z.string().optional().describe("Project UUID. Defaults to configured project.")
1491
+ }
1492
+ },
1493
+ async (args) => {
1494
+ const pid = args.project_id ?? projectId;
1495
+ const params = new URLSearchParams();
1496
+ if (args.severity) params.set("severity", args.severity);
1497
+ params.set("limit", String(Math.min(args.limit ?? 50, 200)));
1498
+ if (pid) params.set("projectId", pid);
1499
+ const data = await apiCall(`/v1/admin/lessons?${params}`);
1500
+ return jsonText(data);
1501
+ }
1502
+ );
1067
1503
  server.resource(
1068
1504
  "project_stats",
1069
1505
  "project://stats",
@@ -1088,15 +1524,17 @@ function createMushiServer(config) {
1088
1524
  contents: [{ uri: "project://dashboard", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2) }]
1089
1525
  })
1090
1526
  );
1091
- const rewardsMeta = (name) => annotationsFor(name);
1092
- server.tool(
1527
+ server.registerTool(
1093
1528
  "list_top_contributors",
1094
- rewardsMeta("list_top_contributors").title,
1095
1529
  {
1096
- limit: z.number().int().min(1).max(100).optional().default(10).describe("Max rows to return (default 10, max 100)"),
1097
- range: z.enum(["30d", "90d", "all"]).optional().default("30d").describe("Time window for points calculation")
1530
+ title: titleOf("list_top_contributors"),
1531
+ description: descOf("list_top_contributors"),
1532
+ annotations: annotationsFor("list_top_contributors"),
1533
+ inputSchema: {
1534
+ limit: z.number().int().min(1).max(100).optional().default(10).describe("Max rows to return (default 10, max 100)"),
1535
+ range: z.enum(["30d", "90d", "all"]).optional().default("30d").describe("Time window for points calculation")
1536
+ }
1098
1537
  },
1099
- rewardsMeta("list_top_contributors"),
1100
1538
  async ({ limit, range }) => ({
1101
1539
  content: [{
1102
1540
  type: "text",
@@ -1108,15 +1546,18 @@ function createMushiServer(config) {
1108
1546
  }]
1109
1547
  })
1110
1548
  );
1111
- server.tool(
1549
+ server.registerTool(
1112
1550
  "award_bonus_points",
1113
- rewardsMeta("award_bonus_points").title,
1114
1551
  {
1115
- external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
1116
- points: z.number().int().min(1).max(5e4).describe("Bonus points to award (max 50,000 per call)"),
1117
- reason: z.string().max(200).describe("Human-readable reason, logged to end_user_activity")
1552
+ title: titleOf("award_bonus_points"),
1553
+ description: descOf("award_bonus_points"),
1554
+ annotations: annotationsFor("award_bonus_points"),
1555
+ inputSchema: {
1556
+ external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
1557
+ points: z.number().int().min(1).max(5e4).describe("Bonus points to award (max 50,000 per call)"),
1558
+ reason: z.string().max(200).describe("Human-readable reason, logged to end_user_activity")
1559
+ }
1118
1560
  },
1119
- rewardsMeta("award_bonus_points"),
1120
1561
  async ({ external_user_id, points, reason }) => ({
1121
1562
  content: [{
1122
1563
  type: "text",
@@ -1132,15 +1573,18 @@ function createMushiServer(config) {
1132
1573
  }]
1133
1574
  })
1134
1575
  );
1135
- server.tool(
1576
+ server.registerTool(
1136
1577
  "set_tier",
1137
- rewardsMeta("set_tier").title,
1138
1578
  {
1139
- external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
1140
- tier_slug: z.string().describe('Tier slug to assign, e.g. "champion", "contributor", "explorer"'),
1141
- reason: z.string().max(200).optional().describe("Optional reason for manual override")
1579
+ title: titleOf("set_tier"),
1580
+ description: descOf("set_tier"),
1581
+ annotations: annotationsFor("set_tier"),
1582
+ inputSchema: {
1583
+ external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
1584
+ tier_slug: z.string().describe('Tier slug to assign, e.g. "champion", "contributor", "explorer"'),
1585
+ reason: z.string().max(200).optional().describe("Optional reason for manual override")
1586
+ }
1142
1587
  },
1143
- rewardsMeta("set_tier"),
1144
1588
  async ({ external_user_id, tier_slug, reason }) => ({
1145
1589
  content: [{
1146
1590
  type: "text",
@@ -1215,6 +1659,23 @@ function createMushiServer(config) {
1215
1659
  };
1216
1660
  }
1217
1661
  );
1662
+ server.resource(
1663
+ "activation_status",
1664
+ "mushi://activation",
1665
+ {
1666
+ description: "Unified setup posture \u2014 SDK heartbeat, reports, GitHub, MCP readiness, QA stories, and the next best action."
1667
+ },
1668
+ async () => {
1669
+ const qs = projectId ? `?project_id=${encodeURIComponent(projectId)}` : "";
1670
+ return {
1671
+ contents: [{
1672
+ uri: "mushi://activation",
1673
+ mimeType: "application/json",
1674
+ text: JSON.stringify(await apiCall(`/v1/admin/activation${qs}`), null, 2)
1675
+ }]
1676
+ };
1677
+ }
1678
+ );
1218
1679
  server.prompt(
1219
1680
  "summarize_report_for_fix",
1220
1681
  "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.",
@@ -1284,6 +1745,31 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
1284
1745
  }]
1285
1746
  })
1286
1747
  );
1748
+ server.prompt(
1749
+ "mushi_setup",
1750
+ "Diagnose why Mushi setup is stuck and return the single next command or console step to unblock it.",
1751
+ {},
1752
+ () => ({
1753
+ messages: [{
1754
+ role: "user",
1755
+ content: {
1756
+ type: "text",
1757
+ text: `You are a Mushi onboarding copilot. Use the MCP tools:
1758
+ 1. Read mushi://activation for unified setup posture.
1759
+ 2. Read project://integration-health if GitHub or Sentry looks blocked.
1760
+ 3. Call get_activation_status if the resource is unavailable.
1761
+
1762
+ Then output:
1763
+ - **Status:** one sentence on what is done vs blocked
1764
+ - **Next step:** the single highest-leverage action (console link or CLI command)
1765
+ - **Prove it:** how the user verifies the step worked
1766
+ - **If still stuck:** one diagnostic command (e.g. mushi doctor --fix)
1767
+
1768
+ Be specific. No generic advice.`
1769
+ }
1770
+ }]
1771
+ })
1772
+ );
1287
1773
  server.registerTool(
1288
1774
  "map_user_stories",
1289
1775
  {
@@ -1687,6 +2173,36 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
1687
2173
  return jsonText({ ok: true, message: `Step ${step_index} \u2192 ${args.status}` });
1688
2174
  }
1689
2175
  );
2176
+ server.registerTool(
2177
+ "get_activation_status",
2178
+ {
2179
+ title: titleOf("get_activation_status"),
2180
+ description: descOf("get_activation_status"),
2181
+ annotations: annotationsFor("get_activation_status"),
2182
+ inputSchema: {
2183
+ project_id: z.string().optional().describe("Optional project UUID override")
2184
+ }
2185
+ },
2186
+ async (args) => {
2187
+ const qs = args.project_id ? `?project_id=${encodeURIComponent(args.project_id)}` : "";
2188
+ const data = await apiCall(`/v1/admin/activation${qs}`);
2189
+ return jsonText(data);
2190
+ }
2191
+ );
2192
+ server.registerTool(
2193
+ "get_reporter_thread",
2194
+ {
2195
+ title: titleOf("get_reporter_thread"),
2196
+ description: descOf("get_reporter_thread"),
2197
+ annotations: annotationsFor("get_reporter_thread"),
2198
+ inputSchema: { reportId: z.string().describe("The report UUID") }
2199
+ },
2200
+ // The reporter thread is the `comments` lane of the unified report
2201
+ // timeline. There is no standalone admin `/comments` route (only the
2202
+ // reporter-token-gated `/v1/reporter/reports/:id/comments`), so we read the
2203
+ // admin-authed timeline, which also carries fix / QA / status lanes.
2204
+ async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}/timeline`))
2205
+ );
1690
2206
  const grantedScopes = config.scopes;
1691
2207
  if (grantedScopes !== void 0) {
1692
2208
  const toolRegistry = server._registeredTools;