@mushi-mushi/mcp 0.20.0 → 0.20.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -3,14 +3,14 @@ import {
3
3
  TDD_TOOL_CATALOG,
4
4
  TOOL_CATALOG,
5
5
  USE_MUSHI_INTENTS
6
- } from "./chunk-OZJEY3QR.js";
6
+ } from "./chunk-TP2QQWDJ.js";
7
7
  import {
8
8
  MUSHI_SERVER_METADATA
9
9
  } from "./chunk-EANW5YTI.js";
10
10
  import {
11
11
  DEPRECATED_TOOL_ALIASES,
12
12
  toolMatchesFeatures
13
- } from "./chunk-VBXXUK5G.js";
13
+ } from "./chunk-ZSVRLSW5.js";
14
14
 
15
15
  // src/server.ts
16
16
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -182,7 +182,7 @@ function createMushiServer(config) {
182
182
  // Authorization is accepted by `adminOrApiKey` only as a JWT, but we
183
183
  // still send it for endpoints still behind plain `jwtAuth` (legacy)
184
184
  // and for transparent proxies that strip X-Mushi-* headers.
185
- "Authorization": `Bearer ${apiKey}`,
185
+ Authorization: `Bearer ${apiKey}`,
186
186
  "X-Mushi-Api-Key": apiKey,
187
187
  "X-Request-Id": requestId,
188
188
  ...projectId ? { "X-Mushi-Project-Id": projectId } : {},
@@ -233,9 +233,7 @@ function createMushiServer(config) {
233
233
  }
234
234
  if (projectId) return projectId;
235
235
  try {
236
- const data = await apiCall(
237
- "/v1/admin/mcp/account-overview"
238
- );
236
+ const data = await apiCall("/v1/admin/mcp/account-overview");
239
237
  if (data.total === 1 && data.projects[0]) {
240
238
  return data.projects[0].id;
241
239
  }
@@ -346,9 +344,12 @@ function createMushiServer(config) {
346
344
  params.set("limit", String(Math.min(args.limit ?? 20, 100)));
347
345
  const pid = await resolveProjectId(args.project_id);
348
346
  const extraHeaders = pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
349
- const data = await apiCall(`/v1/admin/reports?${params}`, {
350
- headers: extraHeaders
351
- });
347
+ const data = await apiCall(
348
+ `/v1/admin/reports?${params}`,
349
+ {
350
+ headers: extraHeaders
351
+ }
352
+ );
352
353
  return jsonResult(data);
353
354
  }
354
355
  );
@@ -371,7 +372,10 @@ function createMushiServer(config) {
371
372
  return {
372
373
  ...res,
373
374
  resource_links: [
374
- { uri: `project://reports/${args.reportId}`, title: `Report ${args.reportId.slice(0, 8)}\u2026` }
375
+ {
376
+ uri: `project://reports/${args.reportId}`,
377
+ title: `Report ${args.reportId.slice(0, 8)}\u2026`
378
+ }
375
379
  ]
376
380
  };
377
381
  }
@@ -475,17 +479,20 @@ function createMushiServer(config) {
475
479
  const report = await apiCall(`/v1/admin/reports/${args.reportId}`, {
476
480
  headers
477
481
  });
478
- return wrappedJsonResult({
479
- report,
480
- // Paste-ready fix prompt composed server-side by composeFixPacket()
481
- // (diagnosis + repro + suggested fix + relevant code + blast radius).
482
- // Hand this straight to the editing agent — no second LLM key needed.
483
- fixPrompt: report.fix_packet ?? null,
484
- reproductionSteps: report.reproduction_steps ?? [],
485
- component: report.component,
486
- rootCause: report.stage2_analysis?.rootCause,
487
- bugOntologyTags: report.bug_ontology_tags
488
- }, "fix context");
482
+ return wrappedJsonResult(
483
+ {
484
+ report,
485
+ // Paste-ready fix prompt composed server-side by composeFixPacket()
486
+ // (diagnosis + repro + suggested fix + relevant code + blast radius).
487
+ // Hand this straight to the editing agent — no second LLM key needed.
488
+ fixPrompt: report.fix_packet ?? null,
489
+ reproductionSteps: report.reproduction_steps ?? [],
490
+ component: report.component,
491
+ rootCause: report.stage2_analysis?.rootCause,
492
+ bugOntologyTags: report.bug_ontology_tags
493
+ },
494
+ "fix context"
495
+ );
489
496
  }
490
497
  );
491
498
  server.registerTool(
@@ -534,7 +541,9 @@ function createMushiServer(config) {
534
541
  seed: args.seed,
535
542
  depth: String(Math.min(args.depth ?? 2, 4))
536
543
  });
537
- const data = await apiCall(`/v1/admin/graph/traverse?${params}`);
544
+ const data = await apiCall(
545
+ `/v1/admin/graph/traverse?${params}`
546
+ );
538
547
  return jsonResult(data);
539
548
  }
540
549
  );
@@ -545,7 +554,9 @@ function createMushiServer(config) {
545
554
  description: descOf("get_graph_neighborhood"),
546
555
  annotations: annotationsFor("get_graph_neighborhood"),
547
556
  inputSchema: {
548
- seed: z.string().describe("Starting graph node id or label to expand around (e.g. an inventory Action id or component name)"),
557
+ seed: z.string().describe(
558
+ "Starting graph node id or label to expand around (e.g. an inventory Action id or component name)"
559
+ ),
549
560
  depth: z.number().int().min(1).max(4).optional().describe("BFS hops to traverse outward. Default 2; clamped to a max of 4.")
550
561
  },
551
562
  outputSchema: {
@@ -558,7 +569,9 @@ function createMushiServer(config) {
558
569
  seed: args.seed,
559
570
  depth: String(Math.min(args.depth ?? 2, 4))
560
571
  });
561
- const data = await apiCall(`/v1/admin/graph/traverse?${params}`);
572
+ const data = await apiCall(
573
+ `/v1/admin/graph/traverse?${params}`
574
+ );
562
575
  return jsonResult(data);
563
576
  }
564
577
  );
@@ -568,7 +581,9 @@ function createMushiServer(config) {
568
581
  title: titleOf("get_graph_node"),
569
582
  description: descOf("get_graph_node"),
570
583
  annotations: annotationsFor("get_graph_node"),
571
- inputSchema: { nodeId: z.string().describe("The graph_nodes.id of the node to fetch (UUID).") },
584
+ inputSchema: {
585
+ nodeId: z.string().describe("The graph_nodes.id of the node to fetch (UUID).")
586
+ },
572
587
  outputSchema: {
573
588
  node: z.unknown().describe("Single graph_nodes row including metadata (Action nodes carry v2 status)")
574
589
  }
@@ -590,7 +605,8 @@ function createMushiServer(config) {
590
605
  },
591
606
  async (args) => {
592
607
  const pid = args.projectId ?? projectId;
593
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for get_inventory");
608
+ if (!pid)
609
+ throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for get_inventory");
594
610
  return jsonText(await apiCall(`/v1/admin/inventory/${pid}`));
595
611
  }
596
612
  );
@@ -608,7 +624,8 @@ function createMushiServer(config) {
608
624
  },
609
625
  async (args) => {
610
626
  const pid = args.projectId ?? projectId;
611
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for diff_inventory");
627
+ if (!pid)
628
+ throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for diff_inventory");
612
629
  const q = new URLSearchParams({ from: args.fromSha, to: args.toSha });
613
630
  return jsonText(await apiCall(`/v1/admin/inventory/${pid}/diff?${q}`));
614
631
  }
@@ -631,12 +648,19 @@ function createMushiServer(config) {
631
648
  },
632
649
  async (args) => {
633
650
  const pid = args.projectId ?? projectId;
634
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for list_gate_findings");
651
+ if (!pid)
652
+ throw new MushiApiError(
653
+ 400,
654
+ "MISSING_PROJECT",
655
+ "projectId is required for list_gate_findings"
656
+ );
635
657
  const q = new URLSearchParams();
636
658
  if (args.gate) q.set("gate", args.gate);
637
659
  if (args.severity) q.set("severity", args.severity);
638
660
  const suffix = q.toString() ? `?${q}` : "";
639
- const data = await apiCall(`/v1/admin/inventory/${pid}/findings${suffix}`);
661
+ const data = await apiCall(
662
+ `/v1/admin/inventory/${pid}/findings${suffix}`
663
+ );
640
664
  return jsonResult(data);
641
665
  }
642
666
  );
@@ -646,7 +670,9 @@ function createMushiServer(config) {
646
670
  title: titleOf("suggest_fix"),
647
671
  description: descOf("suggest_fix"),
648
672
  annotations: annotationsFor("suggest_fix"),
649
- inputSchema: { reportId: z.string().describe("Report UUID to read the Stage-2 suggested-fix slice for") },
673
+ inputSchema: {
674
+ reportId: z.string().describe("Report UUID to read the Stage-2 suggested-fix slice for")
675
+ },
650
676
  outputSchema: {
651
677
  reportId: z.string().describe("The report this slice was read from"),
652
678
  rootCause: z.unknown().describe("Stage-2 root-cause hint, or null if not yet classified"),
@@ -675,7 +701,11 @@ function createMushiServer(config) {
675
701
  title: titleOf("run_nl_query"),
676
702
  description: descOf("run_nl_query"),
677
703
  annotations: annotationsFor("run_nl_query"),
678
- inputSchema: { question: z.string().describe('Question in plain English, e.g. "Which components had the most critical bugs this week?"') }
704
+ inputSchema: {
705
+ question: z.string().describe(
706
+ 'Question in plain English, e.g. "Which components had the most critical bugs this week?"'
707
+ )
708
+ }
679
709
  },
680
710
  async (args) => {
681
711
  const data = await apiCall("/v1/admin/query", {
@@ -692,7 +722,9 @@ function createMushiServer(config) {
692
722
  description: descOf("diagnose_setup"),
693
723
  annotations: annotationsFor("diagnose_setup"),
694
724
  inputSchema: {
695
- mode: z.enum(["full", "ingest", "dispatch"]).optional().describe("full (default) = ingest + dispatch; ingest = SDK pipeline only; dispatch = fix preflight only."),
725
+ mode: z.enum(["full", "ingest", "dispatch"]).optional().describe(
726
+ "full (default) = ingest + dispatch; ingest = SDK pipeline only; dispatch = fix preflight only."
727
+ ),
696
728
  project_id: z.string().optional().describe("Project UUID for dispatch checks (defaults to configured project)."),
697
729
  projectId: z.string().optional().describe("Alias for project_id.")
698
730
  },
@@ -730,7 +762,11 @@ function createMushiServer(config) {
730
762
  }
731
763
  if (mode === "dispatch") {
732
764
  if (!resolvedId) {
733
- throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required for dispatch mode");
765
+ throw new MushiApiError(
766
+ 400,
767
+ "MISSING_PROJECT",
768
+ "project_id is required for dispatch mode"
769
+ );
734
770
  }
735
771
  const data = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
736
772
  const failed = data.checks.filter((c) => !c.ready);
@@ -864,7 +900,12 @@ function createMushiServer(config) {
864
900
  async (args) => {
865
901
  const query = args.query ?? "";
866
902
  const hits = searchMushiDocs(query, args.limit ?? 8);
867
- const results = hits.map(({ title, path, excerpt, score }) => ({ title, path, excerpt, score }));
903
+ const results = hits.map(({ title, path, excerpt, score }) => ({
904
+ title,
905
+ path,
906
+ excerpt,
907
+ score
908
+ }));
868
909
  return jsonResult({ query, results });
869
910
  }
870
911
  );
@@ -1014,7 +1055,9 @@ function createMushiServer(config) {
1014
1055
  } : { error: String(reportRes.reason) };
1015
1056
  return jsonText({
1016
1057
  evidence,
1017
- reporter_thread: timeline ?? { error: String(timelineRes.reason) }
1058
+ reporter_thread: timeline ?? {
1059
+ error: String(timelineRes.reason)
1060
+ }
1018
1061
  });
1019
1062
  }
1020
1063
  );
@@ -1049,15 +1092,23 @@ function createMushiServer(config) {
1049
1092
  const includeLogs = args.include_logs !== false;
1050
1093
  const [reportRes, evidenceRes, similarRes, fixCtxRes, blastRes, logsRes] = await Promise.allSettled([
1051
1094
  apiCall(`/v1/admin/reports/${args.report_id}`, { headers }),
1052
- apiCall(`/v1/admin/reports/${args.report_id}/timeline`, { headers }),
1095
+ apiCall(`/v1/admin/reports/${args.report_id}/timeline`, {
1096
+ headers
1097
+ }),
1053
1098
  apiCall(`/v1/admin/reports/similarity`, {
1054
1099
  method: "POST",
1055
1100
  headers,
1056
1101
  body: JSON.stringify({ report_id: args.report_id })
1057
1102
  }).catch(() => null),
1058
- apiCall(`/v1/admin/reports/${args.report_id}/fix-context`, { headers }).catch(() => null),
1059
- apiCall(`/v1/admin/reports/${args.report_id}/blast-radius`, { headers }).catch(() => null),
1060
- includeLogs ? apiCall(`/v1/admin/mcp/logs/${pid}?limit=20&level=warn`, { headers }).catch(() => null) : Promise.resolve(null)
1103
+ apiCall(`/v1/admin/reports/${args.report_id}/fix-context`, { headers }).catch(
1104
+ () => null
1105
+ ),
1106
+ apiCall(`/v1/admin/reports/${args.report_id}/blast-radius`, { headers }).catch(
1107
+ () => null
1108
+ ),
1109
+ includeLogs ? apiCall(`/v1/admin/mcp/logs/${pid}?limit=20&level=warn`, { headers }).catch(
1110
+ () => null
1111
+ ) : Promise.resolve(null)
1061
1112
  ]);
1062
1113
  const report = reportRes.status === "fulfilled" ? reportRes.value : null;
1063
1114
  const severity = report ? report.severity : "unknown";
@@ -1124,7 +1175,10 @@ function createMushiServer(config) {
1124
1175
  return {
1125
1176
  ...res,
1126
1177
  resource_links: [
1127
- { uri: `project://reports/${args.report_id}`, title: `Report ${args.report_id.slice(0, 8)}\u2026` },
1178
+ {
1179
+ uri: `project://reports/${args.report_id}`,
1180
+ title: `Report ${args.report_id.slice(0, 8)}\u2026`
1181
+ },
1128
1182
  ...pid ? [{ uri: `project://dashboard`, title: "Project dashboard" }] : []
1129
1183
  ]
1130
1184
  };
@@ -1143,7 +1197,9 @@ function createMushiServer(config) {
1143
1197
  filesChanged: z.array(z.string()).describe("Repo-relative paths modified by the fix"),
1144
1198
  linesChanged: z.number().describe("Total lines added + removed across filesChanged"),
1145
1199
  summary: z.string().describe("One-line human summary of what the fix changed"),
1146
- idempotencyKey: z.string().uuid().optional().describe("Optional UUID \u2014 resend the same key to safely retry without creating duplicate fix rows")
1200
+ idempotencyKey: z.string().uuid().optional().describe(
1201
+ "Optional UUID \u2014 resend the same key to safely retry without creating duplicate fix rows"
1202
+ )
1147
1203
  },
1148
1204
  outputSchema: {
1149
1205
  ok: z.boolean().describe("True when the fix_attempt row was created and marked completed"),
@@ -1181,8 +1237,12 @@ function createMushiServer(config) {
1181
1237
  inputSchema: {
1182
1238
  reportId: z.string().uuid().describe("Report UUID to fix"),
1183
1239
  agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
1184
- 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)."),
1185
- 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.")
1240
+ idempotencyKey: z.string().uuid().optional().describe(
1241
+ "Optional RFC 4122 UUID. Resend the same key to safely retry without dispatching a duplicate fix job (Idempotency-Key IETF draft)."
1242
+ ),
1243
+ inventoryActionNodeId: z.string().uuid().optional().describe(
1244
+ "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."
1245
+ )
1186
1246
  },
1187
1247
  outputSchema: {
1188
1248
  fixId: z.string(),
@@ -1255,11 +1315,19 @@ function createMushiServer(config) {
1255
1315
  },
1256
1316
  async (args) => {
1257
1317
  const pid = args.projectId ?? projectId;
1258
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for test_gen_from_report");
1259
- const data = await apiCall(`/v1/admin/inventory/${pid}/test-gen/from-report/${args.reportId}`, {
1260
- method: "POST",
1261
- body: JSON.stringify({})
1262
- });
1318
+ if (!pid)
1319
+ throw new MushiApiError(
1320
+ 400,
1321
+ "MISSING_PROJECT",
1322
+ "projectId is required for test_gen_from_report"
1323
+ );
1324
+ const data = await apiCall(
1325
+ `/v1/admin/inventory/${pid}/test-gen/from-report/${args.reportId}`,
1326
+ {
1327
+ method: "POST",
1328
+ body: JSON.stringify({})
1329
+ }
1330
+ );
1263
1331
  return jsonText(data);
1264
1332
  }
1265
1333
  );
@@ -1338,7 +1406,17 @@ function createMushiServer(config) {
1338
1406
  annotations: annotationsFor("transition_status"),
1339
1407
  inputSchema: {
1340
1408
  reportId: z.string().describe("Report UUID"),
1341
- status: z.enum(["pending", "classified", "grouped", "fixing", "fixed", "resolved", "verified", "reopened", "dismissed"]).describe("Target status"),
1409
+ status: z.enum([
1410
+ "pending",
1411
+ "classified",
1412
+ "grouped",
1413
+ "fixing",
1414
+ "fixed",
1415
+ "resolved",
1416
+ "verified",
1417
+ "reopened",
1418
+ "dismissed"
1419
+ ]).describe("Target status"),
1342
1420
  reason: z.string().optional().describe("Reason for the transition (audit trail)")
1343
1421
  }
1344
1422
  },
@@ -1362,7 +1440,12 @@ function createMushiServer(config) {
1362
1440
  },
1363
1441
  async (args) => {
1364
1442
  const pid = args.projectId ?? projectId;
1365
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for setup_repo_for_mushi");
1443
+ if (!pid)
1444
+ throw new MushiApiError(
1445
+ 400,
1446
+ "MISSING_PROJECT",
1447
+ "projectId is required for setup_repo_for_mushi"
1448
+ );
1366
1449
  const data = await apiCall(`/v1/admin/projects/${pid}/repo/bootstrap`, {
1367
1450
  method: "POST",
1368
1451
  body: JSON.stringify({})
@@ -1427,23 +1510,45 @@ function createMushiServer(config) {
1427
1510
  "project://stats",
1428
1511
  { description: "Report counts, category breakdown, severity distribution" },
1429
1512
  async () => ({
1430
- contents: [{ uri: "project://stats", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/stats"), null, 2) }]
1513
+ contents: [
1514
+ {
1515
+ uri: "project://stats",
1516
+ mimeType: "application/json",
1517
+ text: JSON.stringify(await apiCall("/v1/admin/stats"), null, 2)
1518
+ }
1519
+ ]
1431
1520
  })
1432
1521
  );
1433
1522
  server.resource(
1434
1523
  "project_settings",
1435
1524
  "project://settings",
1436
- { description: "Project configuration \u2014 autofix agent, plugins enabled, ontology, LLM budgets" },
1525
+ {
1526
+ description: "Project configuration \u2014 autofix agent, plugins enabled, ontology, LLM budgets"
1527
+ },
1437
1528
  async () => ({
1438
- contents: [{ uri: "project://settings", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/settings"), null, 2) }]
1529
+ contents: [
1530
+ {
1531
+ uri: "project://settings",
1532
+ mimeType: "application/json",
1533
+ text: JSON.stringify(await apiCall("/v1/admin/settings"), null, 2)
1534
+ }
1535
+ ]
1439
1536
  })
1440
1537
  );
1441
1538
  server.resource(
1442
1539
  "project_dashboard",
1443
1540
  "project://dashboard",
1444
- { description: "PDCA health snapshot \u2014 stage counts, bottleneck, recent activity (same payload the admin console polls)" },
1541
+ {
1542
+ description: "PDCA health snapshot \u2014 stage counts, bottleneck, recent activity (same payload the admin console polls)"
1543
+ },
1445
1544
  async () => ({
1446
- contents: [{ uri: "project://dashboard", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2) }]
1545
+ contents: [
1546
+ {
1547
+ uri: "project://dashboard",
1548
+ mimeType: "application/json",
1549
+ text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2)
1550
+ }
1551
+ ]
1447
1552
  })
1448
1553
  );
1449
1554
  server.registerTool(
@@ -1458,14 +1563,16 @@ function createMushiServer(config) {
1458
1563
  }
1459
1564
  },
1460
1565
  async ({ limit, range }) => ({
1461
- content: [{
1462
- type: "text",
1463
- text: JSON.stringify(
1464
- await apiCall(`/v1/admin/rewards/leaderboard?range=${range}&limit=${limit}`),
1465
- null,
1466
- 2
1467
- )
1468
- }]
1566
+ content: [
1567
+ {
1568
+ type: "text",
1569
+ text: JSON.stringify(
1570
+ await apiCall(`/v1/admin/rewards/leaderboard?range=${range}&limit=${limit}`),
1571
+ null,
1572
+ 2
1573
+ )
1574
+ }
1575
+ ]
1469
1576
  })
1470
1577
  );
1471
1578
  server.registerTool(
@@ -1481,18 +1588,20 @@ function createMushiServer(config) {
1481
1588
  }
1482
1589
  },
1483
1590
  async ({ external_user_id, points, reason }) => ({
1484
- content: [{
1485
- type: "text",
1486
- text: JSON.stringify(
1487
- await apiCall("/v1/admin/rewards/bonus-points", {
1488
- method: "POST",
1489
- headers: { "Content-Type": "application/json" },
1490
- body: JSON.stringify({ external_user_id, points, reason })
1491
- }),
1492
- null,
1493
- 2
1494
- )
1495
- }]
1591
+ content: [
1592
+ {
1593
+ type: "text",
1594
+ text: JSON.stringify(
1595
+ await apiCall("/v1/admin/rewards/bonus-points", {
1596
+ method: "POST",
1597
+ headers: { "Content-Type": "application/json" },
1598
+ body: JSON.stringify({ external_user_id, points, reason })
1599
+ }),
1600
+ null,
1601
+ 2
1602
+ )
1603
+ }
1604
+ ]
1496
1605
  })
1497
1606
  );
1498
1607
  server.registerTool(
@@ -1508,18 +1617,20 @@ function createMushiServer(config) {
1508
1617
  }
1509
1618
  },
1510
1619
  async ({ external_user_id, tier_slug, reason }) => ({
1511
- content: [{
1512
- type: "text",
1513
- text: JSON.stringify(
1514
- await apiCall("/v1/admin/rewards/set-tier", {
1515
- method: "POST",
1516
- headers: { "Content-Type": "application/json" },
1517
- body: JSON.stringify({ external_user_id, tier_slug, reason })
1518
- }),
1519
- null,
1520
- 2
1521
- )
1522
- }]
1620
+ content: [
1621
+ {
1622
+ type: "text",
1623
+ text: JSON.stringify(
1624
+ await apiCall("/v1/admin/rewards/set-tier", {
1625
+ method: "POST",
1626
+ headers: { "Content-Type": "application/json" },
1627
+ body: JSON.stringify({ external_user_id, tier_slug, reason })
1628
+ }),
1629
+ null,
1630
+ 2
1631
+ )
1632
+ }
1633
+ ]
1523
1634
  })
1524
1635
  );
1525
1636
  server.resource(
@@ -1529,11 +1640,13 @@ function createMushiServer(config) {
1529
1640
  description: "Returns the privacy posture for this project: storage region, LLM provider, whether BYOK is configured, data retention window, and last audit timestamp."
1530
1641
  },
1531
1642
  async () => ({
1532
- contents: [{
1533
- uri: "privacy://status",
1534
- mimeType: "application/json",
1535
- text: JSON.stringify(await apiCall("/v1/admin/privacy-status"), null, 2)
1536
- }]
1643
+ contents: [
1644
+ {
1645
+ uri: "privacy://status",
1646
+ mimeType: "application/json",
1647
+ text: JSON.stringify(await apiCall("/v1/admin/privacy-status"), null, 2)
1648
+ }
1649
+ ]
1537
1650
  })
1538
1651
  );
1539
1652
  server.resource(
@@ -1543,15 +1656,19 @@ function createMushiServer(config) {
1543
1656
  description: "Returns the project's last 30 days of judge scores, prompt promotions, fixed-bug count, and lesson inductions. Agents can read this to see whether the loop is converging (rising judge scores, falling recurrence) or stalling."
1544
1657
  },
1545
1658
  async () => ({
1546
- contents: [{
1547
- uri: "evolution://history",
1548
- mimeType: "application/json",
1549
- text: projectId ? JSON.stringify(
1550
- await apiCall(`/v1/admin/projects/${encodeURIComponent(projectId)}/evolution-history`),
1551
- null,
1552
- 2
1553
- ) : JSON.stringify({ ok: false, error: "project_id required" })
1554
- }]
1659
+ contents: [
1660
+ {
1661
+ uri: "evolution://history",
1662
+ mimeType: "application/json",
1663
+ text: projectId ? JSON.stringify(
1664
+ await apiCall(
1665
+ `/v1/admin/projects/${encodeURIComponent(projectId)}/evolution-history`
1666
+ ),
1667
+ null,
1668
+ 2
1669
+ ) : JSON.stringify({ ok: false, error: "project_id required" })
1670
+ }
1671
+ ]
1555
1672
  })
1556
1673
  );
1557
1674
  server.resource(
@@ -1561,11 +1678,13 @@ function createMushiServer(config) {
1561
1678
  description: "Live health status of every configured BYOK channel (Sentry, GitHub, LangFuse, PagerDuty, \u2026). Orchestrators should check this before dispatching a fix to fail-fast on broken channels rather than burning LLM budget and discovering the failure mid-run."
1562
1679
  },
1563
1680
  async () => ({
1564
- contents: [{
1565
- uri: "project://integration-health",
1566
- mimeType: "application/json",
1567
- text: JSON.stringify(await apiCall("/v1/admin/integrations/health"), null, 2)
1568
- }]
1681
+ contents: [
1682
+ {
1683
+ uri: "project://integration-health",
1684
+ mimeType: "application/json",
1685
+ text: JSON.stringify(await apiCall("/v1/admin/integrations/health"), null, 2)
1686
+ }
1687
+ ]
1569
1688
  })
1570
1689
  );
1571
1690
  server.resource(
@@ -1577,11 +1696,13 @@ function createMushiServer(config) {
1577
1696
  async () => {
1578
1697
  const path = projectId ? `/v1/admin/inventory/${projectId}` : "/v1/admin/inventory";
1579
1698
  return {
1580
- contents: [{
1581
- uri: "inventory://current",
1582
- mimeType: "application/json",
1583
- text: JSON.stringify(await apiCall(path), null, 2)
1584
- }]
1699
+ contents: [
1700
+ {
1701
+ uri: "inventory://current",
1702
+ mimeType: "application/json",
1703
+ text: JSON.stringify(await apiCall(path), null, 2)
1704
+ }
1705
+ ]
1585
1706
  };
1586
1707
  }
1587
1708
  );
@@ -1610,11 +1731,13 @@ function createMushiServer(config) {
1610
1731
  async () => {
1611
1732
  const qs = projectId ? `?project_id=${encodeURIComponent(projectId)}` : "";
1612
1733
  return {
1613
- contents: [{
1614
- uri: "mushi://activation",
1615
- mimeType: "application/json",
1616
- text: JSON.stringify(await apiCall(`/v1/admin/activation${qs}`), null, 2)
1617
- }]
1734
+ contents: [
1735
+ {
1736
+ uri: "mushi://activation",
1737
+ mimeType: "application/json",
1738
+ text: JSON.stringify(await apiCall(`/v1/admin/activation${qs}`), null, 2)
1739
+ }
1740
+ ]
1618
1741
  };
1619
1742
  }
1620
1743
  );
@@ -1623,11 +1746,12 @@ function createMushiServer(config) {
1623
1746
  "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.",
1624
1747
  { reportId: z.string().describe("The report UUID to summarize") },
1625
1748
  ({ reportId }) => ({
1626
- messages: [{
1627
- role: "user",
1628
- content: {
1629
- type: "text",
1630
- text: `You are a senior engineer preparing a fix plan. Use the Mushi MCP tools to:
1749
+ messages: [
1750
+ {
1751
+ role: "user",
1752
+ content: {
1753
+ type: "text",
1754
+ text: `You are a senior engineer preparing a fix plan. Use the Mushi MCP tools to:
1631
1755
  1. Call get_fix_context for reportId "${reportId}".
1632
1756
  2. Call get_blast_radius if the report has a component node id.
1633
1757
  3. Call get_similar_bugs with the component or summary as the query.
@@ -1640,8 +1764,9 @@ Then produce a markdown fix plan with exactly these sections:
1640
1764
  - Confidence (low/medium/high) with a one-line justification
1641
1765
 
1642
1766
  Lead with the fix. Skip the preamble.`
1767
+ }
1643
1768
  }
1644
- }]
1769
+ ]
1645
1770
  })
1646
1771
  );
1647
1772
  server.prompt(
@@ -1649,11 +1774,12 @@ Lead with the fix. Skip the preamble.`
1649
1774
  "Turn raw Sonnet-as-Judge scores into ship / iterate / dismiss guidance. Use after a fix attempt has been judged.",
1650
1775
  { fixId: z.string().describe("The fix_attempt UUID to explain") },
1651
1776
  ({ fixId }) => ({
1652
- messages: [{
1653
- role: "user",
1654
- content: {
1655
- type: "text",
1656
- text: `You are a release engineer. Use the Mushi MCP tools:
1777
+ messages: [
1778
+ {
1779
+ role: "user",
1780
+ content: {
1781
+ type: "text",
1782
+ text: `You are a release engineer. Use the Mushi MCP tools:
1657
1783
  1. Call get_fix_timeline for fixId "${fixId}" to see the full PDCA journey.
1658
1784
 
1659
1785
  Then write a short verdict in this format:
@@ -1662,8 +1788,9 @@ Then write a short verdict in this format:
1662
1788
  - **If iterate:** bullet list of the smallest next patch
1663
1789
 
1664
1790
  No preamble, no score-by-score recap \u2014 the numbers are in the tool output.`
1791
+ }
1665
1792
  }
1666
- }]
1793
+ ]
1667
1794
  })
1668
1795
  );
1669
1796
  server.prompt(
@@ -1671,11 +1798,12 @@ No preamble, no score-by-score recap \u2014 the numbers are in the tool output.`
1671
1798
  'Answer "what should I focus on right now?" \u2014 five-item markdown list drawn from the dashboard + recent classified queue.',
1672
1799
  {},
1673
1800
  () => ({
1674
- messages: [{
1675
- role: "user",
1676
- content: {
1677
- type: "text",
1678
- text: `You are a tech lead looking at the Mushi cockpit. Use the MCP tools:
1801
+ messages: [
1802
+ {
1803
+ role: "user",
1804
+ content: {
1805
+ type: "text",
1806
+ text: `You are a tech lead looking at the Mushi cockpit. Use the MCP tools:
1679
1807
  1. Read the project://dashboard resource for the PDCA snapshot.
1680
1808
  2. Call get_recent_reports with status="classified", limit=10.
1681
1809
 
@@ -1683,8 +1811,9 @@ Then output exactly 5 bullets, in priority order, each formatted as:
1683
1811
  \`**Action** \u2014 why it matters \u2014 suggested tool call\`
1684
1812
 
1685
1813
  Prefer items that are bottlenecks or critical severity. Skip filler.`
1814
+ }
1686
1815
  }
1687
- }]
1816
+ ]
1688
1817
  })
1689
1818
  );
1690
1819
  server.prompt(
@@ -1692,11 +1821,12 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
1692
1821
  "Diagnose why Mushi setup is stuck and return the single next command or console step to unblock it.",
1693
1822
  {},
1694
1823
  () => ({
1695
- messages: [{
1696
- role: "user",
1697
- content: {
1698
- type: "text",
1699
- text: `You are a Mushi onboarding copilot. Use the MCP tools:
1824
+ messages: [
1825
+ {
1826
+ role: "user",
1827
+ content: {
1828
+ type: "text",
1829
+ text: `You are a Mushi onboarding copilot. Use the MCP tools:
1700
1830
  1. Read mushi://activation for unified setup posture.
1701
1831
  2. Read project://integration-health if GitHub or Sentry looks blocked.
1702
1832
  3. Call diagnose_setup if the resource is unavailable.
@@ -1708,8 +1838,9 @@ Then output:
1708
1838
  - **If still stuck:** one diagnostic command (e.g. mushi doctor --fix)
1709
1839
 
1710
1840
  Be specific. No generic advice.`
1841
+ }
1711
1842
  }
1712
- }]
1843
+ ]
1713
1844
  })
1714
1845
  );
1715
1846
  server.registerTool(
@@ -1730,7 +1861,15 @@ Be specific. No generic advice.`
1730
1861
  if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
1731
1862
  const data = await apiCall(
1732
1863
  `/v1/admin/inventory/${projectId2}/map-from-live`,
1733
- { method: "POST", body: JSON.stringify({ base_url: baseUrl, max_pages: maxPages, provider, cursor_cloud_refine: cursorCloudRefine }) }
1864
+ {
1865
+ method: "POST",
1866
+ body: JSON.stringify({
1867
+ base_url: baseUrl,
1868
+ max_pages: maxPages,
1869
+ provider,
1870
+ cursor_cloud_refine: cursorCloudRefine
1871
+ })
1872
+ }
1734
1873
  );
1735
1874
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1736
1875
  }
@@ -1758,7 +1897,9 @@ Be specific. No generic advice.`
1758
1897
  annotations: annotationsFor("generate_tdd_from_story", TDD_TOOL_CATALOG),
1759
1898
  inputSchema: {
1760
1899
  projectId: z.string().describe("Project UUID that owns the inventory story"),
1761
- storyNodeId: z.string().describe("User story node id/slug from accepted inventory (from map_user_stories or inventory.yaml)"),
1900
+ storyNodeId: z.string().describe(
1901
+ "User story node id/slug from accepted inventory (from map_user_stories or inventory.yaml)"
1902
+ ),
1762
1903
  automationMode: z.enum(["auto", "review", "approve"]).optional().describe("Approval gate for the generated qa_story (default review \u2192 pending_review)"),
1763
1904
  baseUrl: z.string().url().optional().describe("Override the live app URL the Playwright test should target"),
1764
1905
  openPr: z.boolean().optional().describe("Open a draft GitHub PR with the generated spec (default true)")
@@ -1772,10 +1913,14 @@ Be specific. No generic advice.`
1772
1913
  },
1773
1914
  async ({ projectId: projectId2, storyNodeId, automationMode, baseUrl, openPr }) => {
1774
1915
  if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
1775
- const data = await apiCall(
1776
- `/v1/admin/inventory/${projectId2}/stories/${storyNodeId}/generate-test`,
1777
- { method: "POST", body: JSON.stringify({ automation_mode: automationMode, base_url: baseUrl, open_pr: openPr }) }
1778
- );
1916
+ const data = await apiCall(`/v1/admin/inventory/${projectId2}/stories/${storyNodeId}/generate-test`, {
1917
+ method: "POST",
1918
+ body: JSON.stringify({
1919
+ automation_mode: automationMode,
1920
+ base_url: baseUrl,
1921
+ open_pr: openPr
1922
+ })
1923
+ });
1779
1924
  return jsonResult(data);
1780
1925
  }
1781
1926
  );
@@ -1790,10 +1935,10 @@ Be specific. No generic advice.`
1790
1935
  }
1791
1936
  },
1792
1937
  async ({ projectId: projectId2 }) => {
1793
- const data = await apiCall(
1794
- "/v1/admin/pdca/improve-qa-stories",
1795
- { method: "POST", body: JSON.stringify({ project_id: projectId2 }) }
1796
- );
1938
+ const data = await apiCall("/v1/admin/pdca/improve-qa-stories", {
1939
+ method: "POST",
1940
+ body: JSON.stringify({ project_id: projectId2 })
1941
+ });
1797
1942
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1798
1943
  }
1799
1944
  );
@@ -1827,7 +1972,9 @@ Be specific. No generic advice.`
1827
1972
  }
1828
1973
  },
1829
1974
  async ({ projectId: projectId2 }) => {
1830
- const data = await apiCall(`/v1/admin/byok/keys?project_id=${encodeURIComponent(projectId2)}`);
1975
+ const data = await apiCall(
1976
+ `/v1/admin/byok/keys?project_id=${encodeURIComponent(projectId2)}`
1977
+ );
1831
1978
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1832
1979
  }
1833
1980
  );
@@ -1840,16 +1987,60 @@ Be specific. No generic advice.`
1840
1987
  inputSchema: {
1841
1988
  projectId: z.string().describe("Project id"),
1842
1989
  provider: z.enum(["anthropic", "openai", "firecrawl", "browserbase", "cursor"]).describe("Provider slug"),
1843
- key: z.string().min(10).describe("The API key value to add"),
1990
+ key: z.string().min(8).max(4096).describe("The API key value to add"),
1844
1991
  label: z.string().optional().describe("Human-readable label for this key"),
1845
- priority: z.number().int().min(1).max(999).optional().describe("Priority for ordering (lower = higher priority)")
1992
+ priority: z.number().int().min(0).max(1e4).optional().describe("Priority for ordering (lower = higher priority)"),
1993
+ baseUrl: z.string().url().max(2048).optional().describe("Allow-listed OpenAI-compatible HTTPS base URL")
1846
1994
  }
1847
1995
  },
1848
- async ({ projectId: projectId2, provider, key, label, priority }) => {
1849
- const data = await apiCall(
1850
- "/v1/admin/byok/keys",
1851
- { method: "POST", body: JSON.stringify({ project_id: projectId2, provider_slug: provider, key, label, priority }) }
1852
- );
1996
+ async ({ projectId: projectId2, provider, key, label, priority, baseUrl }) => {
1997
+ const data = await apiCall("/v1/admin/byok/keys", {
1998
+ method: "POST",
1999
+ body: JSON.stringify({
2000
+ project_id: projectId2,
2001
+ provider_slug: provider,
2002
+ key,
2003
+ label,
2004
+ priority,
2005
+ base_url: baseUrl
2006
+ })
2007
+ });
2008
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2009
+ }
2010
+ );
2011
+ server.registerTool(
2012
+ "test_byok_key",
2013
+ {
2014
+ title: titleOf("test_byok_key", TDD_TOOL_CATALOG),
2015
+ description: descOf("test_byok_key", TDD_TOOL_CATALOG),
2016
+ annotations: annotationsFor("test_byok_key", TDD_TOOL_CATALOG),
2017
+ inputSchema: {
2018
+ projectId: z.string().describe("Project id"),
2019
+ keyId: z.string().uuid().describe("Pooled BYOK key id")
2020
+ }
2021
+ },
2022
+ async ({ keyId }) => {
2023
+ const data = await apiCall(`/v1/admin/byok/keys/${encodeURIComponent(keyId)}/test`, {
2024
+ method: "POST"
2025
+ });
2026
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2027
+ }
2028
+ );
2029
+ server.registerTool(
2030
+ "remove_byok_key",
2031
+ {
2032
+ title: titleOf("remove_byok_key", TDD_TOOL_CATALOG),
2033
+ description: descOf("remove_byok_key", TDD_TOOL_CATALOG),
2034
+ annotations: annotationsFor("remove_byok_key", TDD_TOOL_CATALOG),
2035
+ inputSchema: {
2036
+ projectId: z.string().describe("Project id"),
2037
+ keyId: z.string().uuid().describe("Pooled BYOK key UUID from list_byok_keys")
2038
+ }
2039
+ },
2040
+ async ({ keyId }) => {
2041
+ const data = await apiCall(`/v1/admin/byok/keys/${encodeURIComponent(keyId)}`, {
2042
+ method: "DELETE"
2043
+ });
1853
2044
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1854
2045
  }
1855
2046
  );
@@ -1864,7 +2055,9 @@ Be specific. No generic advice.`
1864
2055
  }
1865
2056
  },
1866
2057
  async ({ projectId: projectId2 }) => {
1867
- const data = await apiCall(`/v1/admin/inventory/${projectId2}/stories/pending-review`);
2058
+ const data = await apiCall(
2059
+ `/v1/admin/inventory/${projectId2}/stories/pending-review`
2060
+ );
1868
2061
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1869
2062
  }
1870
2063
  );
@@ -1901,13 +2094,10 @@ Be specific. No generic advice.`
1901
2094
  }
1902
2095
  },
1903
2096
  async ({ reportId, message, authorName }) => {
1904
- const data = await apiCall(
1905
- `/v1/sync/reports/${reportId}/reply`,
1906
- {
1907
- method: "POST",
1908
- body: JSON.stringify({ message, author_name: authorName })
1909
- }
1910
- );
2097
+ const data = await apiCall(`/v1/sync/reports/${reportId}/reply`, {
2098
+ method: "POST",
2099
+ body: JSON.stringify({ message, author_name: authorName })
2100
+ });
1911
2101
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1912
2102
  }
1913
2103
  );
@@ -1986,10 +2176,10 @@ Be specific. No generic advice.`
1986
2176
  async ({ project_id }) => {
1987
2177
  const pid = project_id ?? config.projectId;
1988
2178
  if (!pid) throw new MushiApiError(400, "MISSING_PROJECT_ID", "project_id is required");
1989
- const data = await apiCall(
1990
- `/v1/admin/projects/${pid}/audit`,
1991
- { method: "POST", body: "{}" }
1992
- );
2179
+ const data = await apiCall(`/v1/admin/projects/${pid}/audit`, {
2180
+ method: "POST",
2181
+ body: "{}"
2182
+ });
1993
2183
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1994
2184
  }
1995
2185
  );
@@ -2085,17 +2275,19 @@ Be specific. No generic advice.`
2085
2275
  inputSchema: {
2086
2276
  root_skill_slug: z.string().describe('Root skill slug to run, e.g. "workflow-fix-and-ship"'),
2087
2277
  report_id: z.string().optional().describe("Report UUID to attach the pipeline to"),
2088
- mode: z.enum(["handoff", "cloud"]).optional().describe("handoff (default): get context packet for local agent. cloud: auto-dispatch via Cursor Cloud."),
2278
+ mode: z.enum(["handoff", "cloud"]).optional().describe(
2279
+ "handoff (default): get context packet for local agent. cloud: auto-dispatch via Cursor Cloud."
2280
+ ),
2089
2281
  project_id: z.string().optional().describe("Project UUID. Falls back to the configured project.")
2090
2282
  }
2091
2283
  },
2092
2284
  async (args) => {
2093
2285
  const resolvedProjectId = args.project_id ?? projectId;
2094
2286
  if (!resolvedProjectId) return jsonText({ error: "No project_id provided or configured." });
2095
- const data = await apiCall(
2096
- "/v1/admin/skills/pipelines",
2097
- { method: "POST", body: JSON.stringify({ ...args, project_id: resolvedProjectId }) }
2098
- );
2287
+ const data = await apiCall("/v1/admin/skills/pipelines", {
2288
+ method: "POST",
2289
+ body: JSON.stringify({ ...args, project_id: resolvedProjectId })
2290
+ });
2099
2291
  return jsonText(data);
2100
2292
  }
2101
2293
  );
@@ -2131,10 +2323,10 @@ Be specific. No generic advice.`
2131
2323
  },
2132
2324
  async (args) => {
2133
2325
  const { run_id, step_index, ...body } = args;
2134
- await apiCall(
2135
- `/v1/admin/skills/pipelines/${run_id}/steps/${step_index}/checkin`,
2136
- { method: "POST", body: JSON.stringify(body) }
2137
- );
2326
+ await apiCall(`/v1/admin/skills/pipelines/${run_id}/steps/${step_index}/checkin`, {
2327
+ method: "POST",
2328
+ body: JSON.stringify(body)
2329
+ });
2138
2330
  return jsonText({ ok: true, message: `Step ${step_index} \u2192 ${args.status}` });
2139
2331
  }
2140
2332
  );
@@ -2302,7 +2494,11 @@ Be specific. No generic advice.`
2302
2494
  if (args.compare) qs.set("compare", args.compare);
2303
2495
  if (args.fix_id) qs.set("fix_id", args.fix_id);
2304
2496
  if (!qs.toString()) {
2305
- throw new MushiApiError(400, "BAD_REQUEST", "Provide paths, source=last_push, compare, or fix_id");
2497
+ throw new MushiApiError(
2498
+ 400,
2499
+ "BAD_REQUEST",
2500
+ "Provide paths, source=last_push, compare, or fix_id"
2501
+ );
2306
2502
  }
2307
2503
  const data = await apiCall(`/v1/admin/projects/${pid}/codebase/impact?${qs}`);
2308
2504
  return jsonResult(data);
@@ -2354,7 +2550,7 @@ Be specific. No generic advice.`
2354
2550
  "### First step",
2355
2551
  `Call \`${cluster.tools[0]}\` to get started.`,
2356
2552
  "",
2357
- "> Tip: call any tool by name \u2014 `use_mushi` is a read-only helper that never calls other tools itself. All 68 tools remain available."
2553
+ "> Tip: call any tool by name \u2014 `use_mushi` is a read-only helper that never calls other tools itself. All 71 tools remain available."
2358
2554
  ].join("\n");
2359
2555
  return { content: [{ type: "text", text: orientation }] };
2360
2556
  }
@@ -2363,11 +2559,7 @@ Be specific. No generic advice.`
2363
2559
  const featureFilter = config.features ?? "all";
2364
2560
  if (grantedScopes !== void 0 || featureFilter !== "all") {
2365
2561
  const toolRegistry = server._registeredTools;
2366
- const allSpecs = [
2367
- ...TOOL_CATALOG,
2368
- ...TDD_TOOL_CATALOG,
2369
- ...CODEBASE_TOOL_CATALOG
2370
- ];
2562
+ const allSpecs = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG, ...CODEBASE_TOOL_CATALOG];
2371
2563
  for (const spec of allSpecs) {
2372
2564
  if (grantedScopes !== void 0 && !grantedScopes.includes(spec.scope)) {
2373
2565
  toolRegistry[spec.name]?.remove();