@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/index.js CHANGED
@@ -375,7 +375,7 @@ var TDD_TOOL_CATALOG = [
375
375
  {
376
376
  name: "list_byok_keys",
377
377
  title: "List your API key pool",
378
- description: "List the project's BYOK API keys grouped by provider (anthropic | openai | firecrawl | browserbase | cursor). Returns { keys: [{ id, provider, label, priority, status, cooldownUntil }] } \u2014 metadata only, never the raw secret. Read-only. Use to see which keys are active vs rate-limited/exhausted; use add_byok_key to add one.",
378
+ description: "List the project's BYOK API keys grouped by provider (anthropic | openai | firecrawl | browserbase | cursor). Returns pooled { keys: [{ id, provider_slug, label, priority, status, cooldown_until, test_status, key_hint, base_url, last_tested_at, last_used_at }] } plus read-only { legacyKeys } metadata for credentials saved before the pooled lifecycle \u2014 never the raw secret. Read-only. Use to see which keys are validated, active, pending, legacy, or rate-limited; use add_byok_key to add one.",
379
379
  scope: "mcp:read",
380
380
  hints: { readOnly: true, idempotent: true, openWorld: true },
381
381
  useCase: "Which API keys are active and which are rate-limited?"
@@ -383,11 +383,27 @@ var TDD_TOOL_CATALOG = [
383
383
  {
384
384
  name: "add_byok_key",
385
385
  title: "Add an API key",
386
- description: "Add a BYOK API key to the project pool for a provider (anthropic | openai | firecrawl | browserbase | cursor), with a label and priority for failover ordering. The raw key is stored encrypted in Supabase Vault and never returned. Returns { id, provider, label }. Write; NOT idempotent \u2014 adds a new row each call. Use to register a backup/rotated key; use list_byok_keys to review the pool.",
386
+ description: "Add and immediately validate a BYOK API key for anthropic | openai | firecrawl | browserbase | cursor. The raw key is stored encrypted in Supabase Vault and never returned; failed probes remain quarantined. Optional baseUrl is accepted only for allow-listed OpenAI-compatible HTTPS providers. Write; NOT idempotent.",
387
387
  scope: "mcp:write",
388
388
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
389
389
  useCase: "Add a backup Anthropic key to the pool."
390
390
  },
391
+ {
392
+ name: "test_byok_key",
393
+ title: "Test an API key",
394
+ description: "Re-test one pooled BYOK credential by key id. A successful provider probe activates the key; auth, quota, and network failures keep it out of the runtime pool. Returns the sanitized validation result and updated key metadata. Write; idempotent.",
395
+ scope: "mcp:write",
396
+ hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
397
+ useCase: "Re-test a quarantined OpenAI key after fixing its account quota."
398
+ },
399
+ {
400
+ name: "remove_byok_key",
401
+ title: "Remove an API key",
402
+ description: "Permanently remove one pooled BYOK credential by key id from the authenticated project, including its Vault secret. Write; destructive; idempotent.",
403
+ scope: "mcp:write",
404
+ hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
405
+ useCase: "Remove a rotated or compromised provider key from the runtime pool."
406
+ },
391
407
  {
392
408
  name: "list_pending_review_stories",
393
409
  title: "List QA stories pending review",
@@ -561,7 +577,9 @@ var USE_MUSHI_INTENTS = {
561
577
  "activation_status",
562
578
  "get_backend_health",
563
579
  "list_byok_keys",
564
- "add_byok_key"
580
+ "add_byok_key",
581
+ "test_byok_key",
582
+ "remove_byok_key"
565
583
  ],
566
584
  hint: "Call mushi_setup first \u2014 it diagnoses setup gaps and returns the next command to run."
567
585
  },
@@ -591,12 +609,7 @@ var USE_MUSHI_INTENTS = {
591
609
  },
592
610
  audit: {
593
611
  label: "Audit / health check",
594
- tools: [
595
- "run_fullstack_audit",
596
- "get_backend_health",
597
- "get_dashboard",
598
- "get_usage"
599
- ],
612
+ tools: ["run_fullstack_audit", "get_backend_health", "get_dashboard", "get_usage"],
600
613
  hint: "Call run_fullstack_audit for a full-stack health scorecard."
601
614
  }
602
615
  };
@@ -759,8 +772,10 @@ var TOOL_FEATURE_MAP = {
759
772
  start_skill_pipeline: "skills",
760
773
  get_pipeline_run: "skills",
761
774
  checkin_pipeline_step: "skills",
762
- list_byok_keys: "skills",
763
- add_byok_key: "skills",
775
+ list_byok_keys: "setup",
776
+ add_byok_key: "setup",
777
+ test_byok_key: "setup",
778
+ remove_byok_key: "setup",
764
779
  // audit
765
780
  run_fullstack_audit: "audit",
766
781
  get_backend_health: "audit",
@@ -1004,7 +1019,7 @@ function createMushiServer(config) {
1004
1019
  // Authorization is accepted by `adminOrApiKey` only as a JWT, but we
1005
1020
  // still send it for endpoints still behind plain `jwtAuth` (legacy)
1006
1021
  // and for transparent proxies that strip X-Mushi-* headers.
1007
- "Authorization": `Bearer ${apiKey}`,
1022
+ Authorization: `Bearer ${apiKey}`,
1008
1023
  "X-Mushi-Api-Key": apiKey,
1009
1024
  "X-Request-Id": requestId,
1010
1025
  ...projectId ? { "X-Mushi-Project-Id": projectId } : {},
@@ -1055,9 +1070,7 @@ function createMushiServer(config) {
1055
1070
  }
1056
1071
  if (projectId) return projectId;
1057
1072
  try {
1058
- const data = await apiCall(
1059
- "/v1/admin/mcp/account-overview"
1060
- );
1073
+ const data = await apiCall("/v1/admin/mcp/account-overview");
1061
1074
  if (data.total === 1 && data.projects[0]) {
1062
1075
  return data.projects[0].id;
1063
1076
  }
@@ -1168,9 +1181,12 @@ function createMushiServer(config) {
1168
1181
  params.set("limit", String(Math.min(args.limit ?? 20, 100)));
1169
1182
  const pid = await resolveProjectId(args.project_id);
1170
1183
  const extraHeaders = pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
1171
- const data = await apiCall(`/v1/admin/reports?${params}`, {
1172
- headers: extraHeaders
1173
- });
1184
+ const data = await apiCall(
1185
+ `/v1/admin/reports?${params}`,
1186
+ {
1187
+ headers: extraHeaders
1188
+ }
1189
+ );
1174
1190
  return jsonResult(data);
1175
1191
  }
1176
1192
  );
@@ -1193,7 +1209,10 @@ function createMushiServer(config) {
1193
1209
  return {
1194
1210
  ...res,
1195
1211
  resource_links: [
1196
- { uri: `project://reports/${args.reportId}`, title: `Report ${args.reportId.slice(0, 8)}\u2026` }
1212
+ {
1213
+ uri: `project://reports/${args.reportId}`,
1214
+ title: `Report ${args.reportId.slice(0, 8)}\u2026`
1215
+ }
1197
1216
  ]
1198
1217
  };
1199
1218
  }
@@ -1297,17 +1316,20 @@ function createMushiServer(config) {
1297
1316
  const report = await apiCall(`/v1/admin/reports/${args.reportId}`, {
1298
1317
  headers
1299
1318
  });
1300
- return wrappedJsonResult({
1301
- report,
1302
- // Paste-ready fix prompt composed server-side by composeFixPacket()
1303
- // (diagnosis + repro + suggested fix + relevant code + blast radius).
1304
- // Hand this straight to the editing agent — no second LLM key needed.
1305
- fixPrompt: report.fix_packet ?? null,
1306
- reproductionSteps: report.reproduction_steps ?? [],
1307
- component: report.component,
1308
- rootCause: report.stage2_analysis?.rootCause,
1309
- bugOntologyTags: report.bug_ontology_tags
1310
- }, "fix context");
1319
+ return wrappedJsonResult(
1320
+ {
1321
+ report,
1322
+ // Paste-ready fix prompt composed server-side by composeFixPacket()
1323
+ // (diagnosis + repro + suggested fix + relevant code + blast radius).
1324
+ // Hand this straight to the editing agent — no second LLM key needed.
1325
+ fixPrompt: report.fix_packet ?? null,
1326
+ reproductionSteps: report.reproduction_steps ?? [],
1327
+ component: report.component,
1328
+ rootCause: report.stage2_analysis?.rootCause,
1329
+ bugOntologyTags: report.bug_ontology_tags
1330
+ },
1331
+ "fix context"
1332
+ );
1311
1333
  }
1312
1334
  );
1313
1335
  server.registerTool(
@@ -1356,7 +1378,9 @@ function createMushiServer(config) {
1356
1378
  seed: args.seed,
1357
1379
  depth: String(Math.min(args.depth ?? 2, 4))
1358
1380
  });
1359
- const data = await apiCall(`/v1/admin/graph/traverse?${params}`);
1381
+ const data = await apiCall(
1382
+ `/v1/admin/graph/traverse?${params}`
1383
+ );
1360
1384
  return jsonResult(data);
1361
1385
  }
1362
1386
  );
@@ -1367,7 +1391,9 @@ function createMushiServer(config) {
1367
1391
  description: descOf("get_graph_neighborhood"),
1368
1392
  annotations: annotationsFor("get_graph_neighborhood"),
1369
1393
  inputSchema: {
1370
- seed: z.string().describe("Starting graph node id or label to expand around (e.g. an inventory Action id or component name)"),
1394
+ seed: z.string().describe(
1395
+ "Starting graph node id or label to expand around (e.g. an inventory Action id or component name)"
1396
+ ),
1371
1397
  depth: z.number().int().min(1).max(4).optional().describe("BFS hops to traverse outward. Default 2; clamped to a max of 4.")
1372
1398
  },
1373
1399
  outputSchema: {
@@ -1380,7 +1406,9 @@ function createMushiServer(config) {
1380
1406
  seed: args.seed,
1381
1407
  depth: String(Math.min(args.depth ?? 2, 4))
1382
1408
  });
1383
- const data = await apiCall(`/v1/admin/graph/traverse?${params}`);
1409
+ const data = await apiCall(
1410
+ `/v1/admin/graph/traverse?${params}`
1411
+ );
1384
1412
  return jsonResult(data);
1385
1413
  }
1386
1414
  );
@@ -1390,7 +1418,9 @@ function createMushiServer(config) {
1390
1418
  title: titleOf("get_graph_node"),
1391
1419
  description: descOf("get_graph_node"),
1392
1420
  annotations: annotationsFor("get_graph_node"),
1393
- inputSchema: { nodeId: z.string().describe("The graph_nodes.id of the node to fetch (UUID).") },
1421
+ inputSchema: {
1422
+ nodeId: z.string().describe("The graph_nodes.id of the node to fetch (UUID).")
1423
+ },
1394
1424
  outputSchema: {
1395
1425
  node: z.unknown().describe("Single graph_nodes row including metadata (Action nodes carry v2 status)")
1396
1426
  }
@@ -1412,7 +1442,8 @@ function createMushiServer(config) {
1412
1442
  },
1413
1443
  async (args) => {
1414
1444
  const pid = args.projectId ?? projectId;
1415
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for get_inventory");
1445
+ if (!pid)
1446
+ throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for get_inventory");
1416
1447
  return jsonText(await apiCall(`/v1/admin/inventory/${pid}`));
1417
1448
  }
1418
1449
  );
@@ -1430,7 +1461,8 @@ function createMushiServer(config) {
1430
1461
  },
1431
1462
  async (args) => {
1432
1463
  const pid = args.projectId ?? projectId;
1433
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for diff_inventory");
1464
+ if (!pid)
1465
+ throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for diff_inventory");
1434
1466
  const q = new URLSearchParams({ from: args.fromSha, to: args.toSha });
1435
1467
  return jsonText(await apiCall(`/v1/admin/inventory/${pid}/diff?${q}`));
1436
1468
  }
@@ -1453,12 +1485,19 @@ function createMushiServer(config) {
1453
1485
  },
1454
1486
  async (args) => {
1455
1487
  const pid = args.projectId ?? projectId;
1456
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for list_gate_findings");
1488
+ if (!pid)
1489
+ throw new MushiApiError(
1490
+ 400,
1491
+ "MISSING_PROJECT",
1492
+ "projectId is required for list_gate_findings"
1493
+ );
1457
1494
  const q = new URLSearchParams();
1458
1495
  if (args.gate) q.set("gate", args.gate);
1459
1496
  if (args.severity) q.set("severity", args.severity);
1460
1497
  const suffix = q.toString() ? `?${q}` : "";
1461
- const data = await apiCall(`/v1/admin/inventory/${pid}/findings${suffix}`);
1498
+ const data = await apiCall(
1499
+ `/v1/admin/inventory/${pid}/findings${suffix}`
1500
+ );
1462
1501
  return jsonResult(data);
1463
1502
  }
1464
1503
  );
@@ -1468,7 +1507,9 @@ function createMushiServer(config) {
1468
1507
  title: titleOf("suggest_fix"),
1469
1508
  description: descOf("suggest_fix"),
1470
1509
  annotations: annotationsFor("suggest_fix"),
1471
- inputSchema: { reportId: z.string().describe("Report UUID to read the Stage-2 suggested-fix slice for") },
1510
+ inputSchema: {
1511
+ reportId: z.string().describe("Report UUID to read the Stage-2 suggested-fix slice for")
1512
+ },
1472
1513
  outputSchema: {
1473
1514
  reportId: z.string().describe("The report this slice was read from"),
1474
1515
  rootCause: z.unknown().describe("Stage-2 root-cause hint, or null if not yet classified"),
@@ -1497,7 +1538,11 @@ function createMushiServer(config) {
1497
1538
  title: titleOf("run_nl_query"),
1498
1539
  description: descOf("run_nl_query"),
1499
1540
  annotations: annotationsFor("run_nl_query"),
1500
- inputSchema: { question: z.string().describe('Question in plain English, e.g. "Which components had the most critical bugs this week?"') }
1541
+ inputSchema: {
1542
+ question: z.string().describe(
1543
+ 'Question in plain English, e.g. "Which components had the most critical bugs this week?"'
1544
+ )
1545
+ }
1501
1546
  },
1502
1547
  async (args) => {
1503
1548
  const data = await apiCall("/v1/admin/query", {
@@ -1514,7 +1559,9 @@ function createMushiServer(config) {
1514
1559
  description: descOf("diagnose_setup"),
1515
1560
  annotations: annotationsFor("diagnose_setup"),
1516
1561
  inputSchema: {
1517
- mode: z.enum(["full", "ingest", "dispatch"]).optional().describe("full (default) = ingest + dispatch; ingest = SDK pipeline only; dispatch = fix preflight only."),
1562
+ mode: z.enum(["full", "ingest", "dispatch"]).optional().describe(
1563
+ "full (default) = ingest + dispatch; ingest = SDK pipeline only; dispatch = fix preflight only."
1564
+ ),
1518
1565
  project_id: z.string().optional().describe("Project UUID for dispatch checks (defaults to configured project)."),
1519
1566
  projectId: z.string().optional().describe("Alias for project_id.")
1520
1567
  },
@@ -1552,7 +1599,11 @@ function createMushiServer(config) {
1552
1599
  }
1553
1600
  if (mode === "dispatch") {
1554
1601
  if (!resolvedId) {
1555
- throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required for dispatch mode");
1602
+ throw new MushiApiError(
1603
+ 400,
1604
+ "MISSING_PROJECT",
1605
+ "project_id is required for dispatch mode"
1606
+ );
1556
1607
  }
1557
1608
  const data = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
1558
1609
  const failed = data.checks.filter((c) => !c.ready);
@@ -1686,7 +1737,12 @@ function createMushiServer(config) {
1686
1737
  async (args) => {
1687
1738
  const query = args.query ?? "";
1688
1739
  const hits = searchMushiDocs(query, args.limit ?? 8);
1689
- const results = hits.map(({ title, path, excerpt, score }) => ({ title, path, excerpt, score }));
1740
+ const results = hits.map(({ title, path, excerpt, score }) => ({
1741
+ title,
1742
+ path,
1743
+ excerpt,
1744
+ score
1745
+ }));
1690
1746
  return jsonResult({ query, results });
1691
1747
  }
1692
1748
  );
@@ -1836,7 +1892,9 @@ function createMushiServer(config) {
1836
1892
  } : { error: String(reportRes.reason) };
1837
1893
  return jsonText({
1838
1894
  evidence,
1839
- reporter_thread: timeline ?? { error: String(timelineRes.reason) }
1895
+ reporter_thread: timeline ?? {
1896
+ error: String(timelineRes.reason)
1897
+ }
1840
1898
  });
1841
1899
  }
1842
1900
  );
@@ -1871,15 +1929,23 @@ function createMushiServer(config) {
1871
1929
  const includeLogs = args.include_logs !== false;
1872
1930
  const [reportRes, evidenceRes, similarRes, fixCtxRes, blastRes, logsRes] = await Promise.allSettled([
1873
1931
  apiCall(`/v1/admin/reports/${args.report_id}`, { headers }),
1874
- apiCall(`/v1/admin/reports/${args.report_id}/timeline`, { headers }),
1932
+ apiCall(`/v1/admin/reports/${args.report_id}/timeline`, {
1933
+ headers
1934
+ }),
1875
1935
  apiCall(`/v1/admin/reports/similarity`, {
1876
1936
  method: "POST",
1877
1937
  headers,
1878
1938
  body: JSON.stringify({ report_id: args.report_id })
1879
1939
  }).catch(() => null),
1880
- apiCall(`/v1/admin/reports/${args.report_id}/fix-context`, { headers }).catch(() => null),
1881
- apiCall(`/v1/admin/reports/${args.report_id}/blast-radius`, { headers }).catch(() => null),
1882
- includeLogs ? apiCall(`/v1/admin/mcp/logs/${pid}?limit=20&level=warn`, { headers }).catch(() => null) : Promise.resolve(null)
1940
+ apiCall(`/v1/admin/reports/${args.report_id}/fix-context`, { headers }).catch(
1941
+ () => null
1942
+ ),
1943
+ apiCall(`/v1/admin/reports/${args.report_id}/blast-radius`, { headers }).catch(
1944
+ () => null
1945
+ ),
1946
+ includeLogs ? apiCall(`/v1/admin/mcp/logs/${pid}?limit=20&level=warn`, { headers }).catch(
1947
+ () => null
1948
+ ) : Promise.resolve(null)
1883
1949
  ]);
1884
1950
  const report = reportRes.status === "fulfilled" ? reportRes.value : null;
1885
1951
  const severity = report ? report.severity : "unknown";
@@ -1946,7 +2012,10 @@ function createMushiServer(config) {
1946
2012
  return {
1947
2013
  ...res,
1948
2014
  resource_links: [
1949
- { uri: `project://reports/${args.report_id}`, title: `Report ${args.report_id.slice(0, 8)}\u2026` },
2015
+ {
2016
+ uri: `project://reports/${args.report_id}`,
2017
+ title: `Report ${args.report_id.slice(0, 8)}\u2026`
2018
+ },
1950
2019
  ...pid ? [{ uri: `project://dashboard`, title: "Project dashboard" }] : []
1951
2020
  ]
1952
2021
  };
@@ -1965,7 +2034,9 @@ function createMushiServer(config) {
1965
2034
  filesChanged: z.array(z.string()).describe("Repo-relative paths modified by the fix"),
1966
2035
  linesChanged: z.number().describe("Total lines added + removed across filesChanged"),
1967
2036
  summary: z.string().describe("One-line human summary of what the fix changed"),
1968
- idempotencyKey: z.string().uuid().optional().describe("Optional UUID \u2014 resend the same key to safely retry without creating duplicate fix rows")
2037
+ idempotencyKey: z.string().uuid().optional().describe(
2038
+ "Optional UUID \u2014 resend the same key to safely retry without creating duplicate fix rows"
2039
+ )
1969
2040
  },
1970
2041
  outputSchema: {
1971
2042
  ok: z.boolean().describe("True when the fix_attempt row was created and marked completed"),
@@ -2003,8 +2074,12 @@ function createMushiServer(config) {
2003
2074
  inputSchema: {
2004
2075
  reportId: z.string().uuid().describe("Report UUID to fix"),
2005
2076
  agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
2006
- 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)."),
2007
- 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.")
2077
+ idempotencyKey: z.string().uuid().optional().describe(
2078
+ "Optional RFC 4122 UUID. Resend the same key to safely retry without dispatching a duplicate fix job (Idempotency-Key IETF draft)."
2079
+ ),
2080
+ inventoryActionNodeId: z.string().uuid().optional().describe(
2081
+ "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."
2082
+ )
2008
2083
  },
2009
2084
  outputSchema: {
2010
2085
  fixId: z.string(),
@@ -2077,11 +2152,19 @@ function createMushiServer(config) {
2077
2152
  },
2078
2153
  async (args) => {
2079
2154
  const pid = args.projectId ?? projectId;
2080
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for test_gen_from_report");
2081
- const data = await apiCall(`/v1/admin/inventory/${pid}/test-gen/from-report/${args.reportId}`, {
2082
- method: "POST",
2083
- body: JSON.stringify({})
2084
- });
2155
+ if (!pid)
2156
+ throw new MushiApiError(
2157
+ 400,
2158
+ "MISSING_PROJECT",
2159
+ "projectId is required for test_gen_from_report"
2160
+ );
2161
+ const data = await apiCall(
2162
+ `/v1/admin/inventory/${pid}/test-gen/from-report/${args.reportId}`,
2163
+ {
2164
+ method: "POST",
2165
+ body: JSON.stringify({})
2166
+ }
2167
+ );
2085
2168
  return jsonText(data);
2086
2169
  }
2087
2170
  );
@@ -2160,7 +2243,17 @@ function createMushiServer(config) {
2160
2243
  annotations: annotationsFor("transition_status"),
2161
2244
  inputSchema: {
2162
2245
  reportId: z.string().describe("Report UUID"),
2163
- status: z.enum(["pending", "classified", "grouped", "fixing", "fixed", "resolved", "verified", "reopened", "dismissed"]).describe("Target status"),
2246
+ status: z.enum([
2247
+ "pending",
2248
+ "classified",
2249
+ "grouped",
2250
+ "fixing",
2251
+ "fixed",
2252
+ "resolved",
2253
+ "verified",
2254
+ "reopened",
2255
+ "dismissed"
2256
+ ]).describe("Target status"),
2164
2257
  reason: z.string().optional().describe("Reason for the transition (audit trail)")
2165
2258
  }
2166
2259
  },
@@ -2184,7 +2277,12 @@ function createMushiServer(config) {
2184
2277
  },
2185
2278
  async (args) => {
2186
2279
  const pid = args.projectId ?? projectId;
2187
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for setup_repo_for_mushi");
2280
+ if (!pid)
2281
+ throw new MushiApiError(
2282
+ 400,
2283
+ "MISSING_PROJECT",
2284
+ "projectId is required for setup_repo_for_mushi"
2285
+ );
2188
2286
  const data = await apiCall(`/v1/admin/projects/${pid}/repo/bootstrap`, {
2189
2287
  method: "POST",
2190
2288
  body: JSON.stringify({})
@@ -2249,23 +2347,45 @@ function createMushiServer(config) {
2249
2347
  "project://stats",
2250
2348
  { description: "Report counts, category breakdown, severity distribution" },
2251
2349
  async () => ({
2252
- contents: [{ uri: "project://stats", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/stats"), null, 2) }]
2350
+ contents: [
2351
+ {
2352
+ uri: "project://stats",
2353
+ mimeType: "application/json",
2354
+ text: JSON.stringify(await apiCall("/v1/admin/stats"), null, 2)
2355
+ }
2356
+ ]
2253
2357
  })
2254
2358
  );
2255
2359
  server.resource(
2256
2360
  "project_settings",
2257
2361
  "project://settings",
2258
- { description: "Project configuration \u2014 autofix agent, plugins enabled, ontology, LLM budgets" },
2362
+ {
2363
+ description: "Project configuration \u2014 autofix agent, plugins enabled, ontology, LLM budgets"
2364
+ },
2259
2365
  async () => ({
2260
- contents: [{ uri: "project://settings", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/settings"), null, 2) }]
2366
+ contents: [
2367
+ {
2368
+ uri: "project://settings",
2369
+ mimeType: "application/json",
2370
+ text: JSON.stringify(await apiCall("/v1/admin/settings"), null, 2)
2371
+ }
2372
+ ]
2261
2373
  })
2262
2374
  );
2263
2375
  server.resource(
2264
2376
  "project_dashboard",
2265
2377
  "project://dashboard",
2266
- { description: "PDCA health snapshot \u2014 stage counts, bottleneck, recent activity (same payload the admin console polls)" },
2378
+ {
2379
+ description: "PDCA health snapshot \u2014 stage counts, bottleneck, recent activity (same payload the admin console polls)"
2380
+ },
2267
2381
  async () => ({
2268
- contents: [{ uri: "project://dashboard", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2) }]
2382
+ contents: [
2383
+ {
2384
+ uri: "project://dashboard",
2385
+ mimeType: "application/json",
2386
+ text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2)
2387
+ }
2388
+ ]
2269
2389
  })
2270
2390
  );
2271
2391
  server.registerTool(
@@ -2280,14 +2400,16 @@ function createMushiServer(config) {
2280
2400
  }
2281
2401
  },
2282
2402
  async ({ limit, range }) => ({
2283
- content: [{
2284
- type: "text",
2285
- text: JSON.stringify(
2286
- await apiCall(`/v1/admin/rewards/leaderboard?range=${range}&limit=${limit}`),
2287
- null,
2288
- 2
2289
- )
2290
- }]
2403
+ content: [
2404
+ {
2405
+ type: "text",
2406
+ text: JSON.stringify(
2407
+ await apiCall(`/v1/admin/rewards/leaderboard?range=${range}&limit=${limit}`),
2408
+ null,
2409
+ 2
2410
+ )
2411
+ }
2412
+ ]
2291
2413
  })
2292
2414
  );
2293
2415
  server.registerTool(
@@ -2303,18 +2425,20 @@ function createMushiServer(config) {
2303
2425
  }
2304
2426
  },
2305
2427
  async ({ external_user_id, points, reason }) => ({
2306
- content: [{
2307
- type: "text",
2308
- text: JSON.stringify(
2309
- await apiCall("/v1/admin/rewards/bonus-points", {
2310
- method: "POST",
2311
- headers: { "Content-Type": "application/json" },
2312
- body: JSON.stringify({ external_user_id, points, reason })
2313
- }),
2314
- null,
2315
- 2
2316
- )
2317
- }]
2428
+ content: [
2429
+ {
2430
+ type: "text",
2431
+ text: JSON.stringify(
2432
+ await apiCall("/v1/admin/rewards/bonus-points", {
2433
+ method: "POST",
2434
+ headers: { "Content-Type": "application/json" },
2435
+ body: JSON.stringify({ external_user_id, points, reason })
2436
+ }),
2437
+ null,
2438
+ 2
2439
+ )
2440
+ }
2441
+ ]
2318
2442
  })
2319
2443
  );
2320
2444
  server.registerTool(
@@ -2330,18 +2454,20 @@ function createMushiServer(config) {
2330
2454
  }
2331
2455
  },
2332
2456
  async ({ external_user_id, tier_slug, reason }) => ({
2333
- content: [{
2334
- type: "text",
2335
- text: JSON.stringify(
2336
- await apiCall("/v1/admin/rewards/set-tier", {
2337
- method: "POST",
2338
- headers: { "Content-Type": "application/json" },
2339
- body: JSON.stringify({ external_user_id, tier_slug, reason })
2340
- }),
2341
- null,
2342
- 2
2343
- )
2344
- }]
2457
+ content: [
2458
+ {
2459
+ type: "text",
2460
+ text: JSON.stringify(
2461
+ await apiCall("/v1/admin/rewards/set-tier", {
2462
+ method: "POST",
2463
+ headers: { "Content-Type": "application/json" },
2464
+ body: JSON.stringify({ external_user_id, tier_slug, reason })
2465
+ }),
2466
+ null,
2467
+ 2
2468
+ )
2469
+ }
2470
+ ]
2345
2471
  })
2346
2472
  );
2347
2473
  server.resource(
@@ -2351,11 +2477,13 @@ function createMushiServer(config) {
2351
2477
  description: "Returns the privacy posture for this project: storage region, LLM provider, whether BYOK is configured, data retention window, and last audit timestamp."
2352
2478
  },
2353
2479
  async () => ({
2354
- contents: [{
2355
- uri: "privacy://status",
2356
- mimeType: "application/json",
2357
- text: JSON.stringify(await apiCall("/v1/admin/privacy-status"), null, 2)
2358
- }]
2480
+ contents: [
2481
+ {
2482
+ uri: "privacy://status",
2483
+ mimeType: "application/json",
2484
+ text: JSON.stringify(await apiCall("/v1/admin/privacy-status"), null, 2)
2485
+ }
2486
+ ]
2359
2487
  })
2360
2488
  );
2361
2489
  server.resource(
@@ -2365,15 +2493,19 @@ function createMushiServer(config) {
2365
2493
  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."
2366
2494
  },
2367
2495
  async () => ({
2368
- contents: [{
2369
- uri: "evolution://history",
2370
- mimeType: "application/json",
2371
- text: projectId ? JSON.stringify(
2372
- await apiCall(`/v1/admin/projects/${encodeURIComponent(projectId)}/evolution-history`),
2373
- null,
2374
- 2
2375
- ) : JSON.stringify({ ok: false, error: "project_id required" })
2376
- }]
2496
+ contents: [
2497
+ {
2498
+ uri: "evolution://history",
2499
+ mimeType: "application/json",
2500
+ text: projectId ? JSON.stringify(
2501
+ await apiCall(
2502
+ `/v1/admin/projects/${encodeURIComponent(projectId)}/evolution-history`
2503
+ ),
2504
+ null,
2505
+ 2
2506
+ ) : JSON.stringify({ ok: false, error: "project_id required" })
2507
+ }
2508
+ ]
2377
2509
  })
2378
2510
  );
2379
2511
  server.resource(
@@ -2383,11 +2515,13 @@ function createMushiServer(config) {
2383
2515
  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."
2384
2516
  },
2385
2517
  async () => ({
2386
- contents: [{
2387
- uri: "project://integration-health",
2388
- mimeType: "application/json",
2389
- text: JSON.stringify(await apiCall("/v1/admin/integrations/health"), null, 2)
2390
- }]
2518
+ contents: [
2519
+ {
2520
+ uri: "project://integration-health",
2521
+ mimeType: "application/json",
2522
+ text: JSON.stringify(await apiCall("/v1/admin/integrations/health"), null, 2)
2523
+ }
2524
+ ]
2391
2525
  })
2392
2526
  );
2393
2527
  server.resource(
@@ -2399,11 +2533,13 @@ function createMushiServer(config) {
2399
2533
  async () => {
2400
2534
  const path = projectId ? `/v1/admin/inventory/${projectId}` : "/v1/admin/inventory";
2401
2535
  return {
2402
- contents: [{
2403
- uri: "inventory://current",
2404
- mimeType: "application/json",
2405
- text: JSON.stringify(await apiCall(path), null, 2)
2406
- }]
2536
+ contents: [
2537
+ {
2538
+ uri: "inventory://current",
2539
+ mimeType: "application/json",
2540
+ text: JSON.stringify(await apiCall(path), null, 2)
2541
+ }
2542
+ ]
2407
2543
  };
2408
2544
  }
2409
2545
  );
@@ -2432,11 +2568,13 @@ function createMushiServer(config) {
2432
2568
  async () => {
2433
2569
  const qs = projectId ? `?project_id=${encodeURIComponent(projectId)}` : "";
2434
2570
  return {
2435
- contents: [{
2436
- uri: "mushi://activation",
2437
- mimeType: "application/json",
2438
- text: JSON.stringify(await apiCall(`/v1/admin/activation${qs}`), null, 2)
2439
- }]
2571
+ contents: [
2572
+ {
2573
+ uri: "mushi://activation",
2574
+ mimeType: "application/json",
2575
+ text: JSON.stringify(await apiCall(`/v1/admin/activation${qs}`), null, 2)
2576
+ }
2577
+ ]
2440
2578
  };
2441
2579
  }
2442
2580
  );
@@ -2445,11 +2583,12 @@ function createMushiServer(config) {
2445
2583
  "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.",
2446
2584
  { reportId: z.string().describe("The report UUID to summarize") },
2447
2585
  ({ reportId }) => ({
2448
- messages: [{
2449
- role: "user",
2450
- content: {
2451
- type: "text",
2452
- text: `You are a senior engineer preparing a fix plan. Use the Mushi MCP tools to:
2586
+ messages: [
2587
+ {
2588
+ role: "user",
2589
+ content: {
2590
+ type: "text",
2591
+ text: `You are a senior engineer preparing a fix plan. Use the Mushi MCP tools to:
2453
2592
  1. Call get_fix_context for reportId "${reportId}".
2454
2593
  2. Call get_blast_radius if the report has a component node id.
2455
2594
  3. Call get_similar_bugs with the component or summary as the query.
@@ -2462,8 +2601,9 @@ Then produce a markdown fix plan with exactly these sections:
2462
2601
  - Confidence (low/medium/high) with a one-line justification
2463
2602
 
2464
2603
  Lead with the fix. Skip the preamble.`
2604
+ }
2465
2605
  }
2466
- }]
2606
+ ]
2467
2607
  })
2468
2608
  );
2469
2609
  server.prompt(
@@ -2471,11 +2611,12 @@ Lead with the fix. Skip the preamble.`
2471
2611
  "Turn raw Sonnet-as-Judge scores into ship / iterate / dismiss guidance. Use after a fix attempt has been judged.",
2472
2612
  { fixId: z.string().describe("The fix_attempt UUID to explain") },
2473
2613
  ({ fixId }) => ({
2474
- messages: [{
2475
- role: "user",
2476
- content: {
2477
- type: "text",
2478
- text: `You are a release engineer. Use the Mushi MCP tools:
2614
+ messages: [
2615
+ {
2616
+ role: "user",
2617
+ content: {
2618
+ type: "text",
2619
+ text: `You are a release engineer. Use the Mushi MCP tools:
2479
2620
  1. Call get_fix_timeline for fixId "${fixId}" to see the full PDCA journey.
2480
2621
 
2481
2622
  Then write a short verdict in this format:
@@ -2484,8 +2625,9 @@ Then write a short verdict in this format:
2484
2625
  - **If iterate:** bullet list of the smallest next patch
2485
2626
 
2486
2627
  No preamble, no score-by-score recap \u2014 the numbers are in the tool output.`
2628
+ }
2487
2629
  }
2488
- }]
2630
+ ]
2489
2631
  })
2490
2632
  );
2491
2633
  server.prompt(
@@ -2493,11 +2635,12 @@ No preamble, no score-by-score recap \u2014 the numbers are in the tool output.`
2493
2635
  'Answer "what should I focus on right now?" \u2014 five-item markdown list drawn from the dashboard + recent classified queue.',
2494
2636
  {},
2495
2637
  () => ({
2496
- messages: [{
2497
- role: "user",
2498
- content: {
2499
- type: "text",
2500
- text: `You are a tech lead looking at the Mushi cockpit. Use the MCP tools:
2638
+ messages: [
2639
+ {
2640
+ role: "user",
2641
+ content: {
2642
+ type: "text",
2643
+ text: `You are a tech lead looking at the Mushi cockpit. Use the MCP tools:
2501
2644
  1. Read the project://dashboard resource for the PDCA snapshot.
2502
2645
  2. Call get_recent_reports with status="classified", limit=10.
2503
2646
 
@@ -2505,8 +2648,9 @@ Then output exactly 5 bullets, in priority order, each formatted as:
2505
2648
  \`**Action** \u2014 why it matters \u2014 suggested tool call\`
2506
2649
 
2507
2650
  Prefer items that are bottlenecks or critical severity. Skip filler.`
2651
+ }
2508
2652
  }
2509
- }]
2653
+ ]
2510
2654
  })
2511
2655
  );
2512
2656
  server.prompt(
@@ -2514,11 +2658,12 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
2514
2658
  "Diagnose why Mushi setup is stuck and return the single next command or console step to unblock it.",
2515
2659
  {},
2516
2660
  () => ({
2517
- messages: [{
2518
- role: "user",
2519
- content: {
2520
- type: "text",
2521
- text: `You are a Mushi onboarding copilot. Use the MCP tools:
2661
+ messages: [
2662
+ {
2663
+ role: "user",
2664
+ content: {
2665
+ type: "text",
2666
+ text: `You are a Mushi onboarding copilot. Use the MCP tools:
2522
2667
  1. Read mushi://activation for unified setup posture.
2523
2668
  2. Read project://integration-health if GitHub or Sentry looks blocked.
2524
2669
  3. Call diagnose_setup if the resource is unavailable.
@@ -2530,8 +2675,9 @@ Then output:
2530
2675
  - **If still stuck:** one diagnostic command (e.g. mushi doctor --fix)
2531
2676
 
2532
2677
  Be specific. No generic advice.`
2678
+ }
2533
2679
  }
2534
- }]
2680
+ ]
2535
2681
  })
2536
2682
  );
2537
2683
  server.registerTool(
@@ -2552,7 +2698,15 @@ Be specific. No generic advice.`
2552
2698
  if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
2553
2699
  const data = await apiCall(
2554
2700
  `/v1/admin/inventory/${projectId2}/map-from-live`,
2555
- { method: "POST", body: JSON.stringify({ base_url: baseUrl, max_pages: maxPages, provider, cursor_cloud_refine: cursorCloudRefine }) }
2701
+ {
2702
+ method: "POST",
2703
+ body: JSON.stringify({
2704
+ base_url: baseUrl,
2705
+ max_pages: maxPages,
2706
+ provider,
2707
+ cursor_cloud_refine: cursorCloudRefine
2708
+ })
2709
+ }
2556
2710
  );
2557
2711
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2558
2712
  }
@@ -2580,7 +2734,9 @@ Be specific. No generic advice.`
2580
2734
  annotations: annotationsFor("generate_tdd_from_story", TDD_TOOL_CATALOG),
2581
2735
  inputSchema: {
2582
2736
  projectId: z.string().describe("Project UUID that owns the inventory story"),
2583
- storyNodeId: z.string().describe("User story node id/slug from accepted inventory (from map_user_stories or inventory.yaml)"),
2737
+ storyNodeId: z.string().describe(
2738
+ "User story node id/slug from accepted inventory (from map_user_stories or inventory.yaml)"
2739
+ ),
2584
2740
  automationMode: z.enum(["auto", "review", "approve"]).optional().describe("Approval gate for the generated qa_story (default review \u2192 pending_review)"),
2585
2741
  baseUrl: z.string().url().optional().describe("Override the live app URL the Playwright test should target"),
2586
2742
  openPr: z.boolean().optional().describe("Open a draft GitHub PR with the generated spec (default true)")
@@ -2594,10 +2750,14 @@ Be specific. No generic advice.`
2594
2750
  },
2595
2751
  async ({ projectId: projectId2, storyNodeId, automationMode, baseUrl, openPr }) => {
2596
2752
  if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
2597
- const data = await apiCall(
2598
- `/v1/admin/inventory/${projectId2}/stories/${storyNodeId}/generate-test`,
2599
- { method: "POST", body: JSON.stringify({ automation_mode: automationMode, base_url: baseUrl, open_pr: openPr }) }
2600
- );
2753
+ const data = await apiCall(`/v1/admin/inventory/${projectId2}/stories/${storyNodeId}/generate-test`, {
2754
+ method: "POST",
2755
+ body: JSON.stringify({
2756
+ automation_mode: automationMode,
2757
+ base_url: baseUrl,
2758
+ open_pr: openPr
2759
+ })
2760
+ });
2601
2761
  return jsonResult(data);
2602
2762
  }
2603
2763
  );
@@ -2612,10 +2772,10 @@ Be specific. No generic advice.`
2612
2772
  }
2613
2773
  },
2614
2774
  async ({ projectId: projectId2 }) => {
2615
- const data = await apiCall(
2616
- "/v1/admin/pdca/improve-qa-stories",
2617
- { method: "POST", body: JSON.stringify({ project_id: projectId2 }) }
2618
- );
2775
+ const data = await apiCall("/v1/admin/pdca/improve-qa-stories", {
2776
+ method: "POST",
2777
+ body: JSON.stringify({ project_id: projectId2 })
2778
+ });
2619
2779
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2620
2780
  }
2621
2781
  );
@@ -2649,7 +2809,9 @@ Be specific. No generic advice.`
2649
2809
  }
2650
2810
  },
2651
2811
  async ({ projectId: projectId2 }) => {
2652
- const data = await apiCall(`/v1/admin/byok/keys?project_id=${encodeURIComponent(projectId2)}`);
2812
+ const data = await apiCall(
2813
+ `/v1/admin/byok/keys?project_id=${encodeURIComponent(projectId2)}`
2814
+ );
2653
2815
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2654
2816
  }
2655
2817
  );
@@ -2662,16 +2824,60 @@ Be specific. No generic advice.`
2662
2824
  inputSchema: {
2663
2825
  projectId: z.string().describe("Project id"),
2664
2826
  provider: z.enum(["anthropic", "openai", "firecrawl", "browserbase", "cursor"]).describe("Provider slug"),
2665
- key: z.string().min(10).describe("The API key value to add"),
2827
+ key: z.string().min(8).max(4096).describe("The API key value to add"),
2666
2828
  label: z.string().optional().describe("Human-readable label for this key"),
2667
- priority: z.number().int().min(1).max(999).optional().describe("Priority for ordering (lower = higher priority)")
2829
+ priority: z.number().int().min(0).max(1e4).optional().describe("Priority for ordering (lower = higher priority)"),
2830
+ baseUrl: z.string().url().max(2048).optional().describe("Allow-listed OpenAI-compatible HTTPS base URL")
2668
2831
  }
2669
2832
  },
2670
- async ({ projectId: projectId2, provider, key, label, priority }) => {
2671
- const data = await apiCall(
2672
- "/v1/admin/byok/keys",
2673
- { method: "POST", body: JSON.stringify({ project_id: projectId2, provider_slug: provider, key, label, priority }) }
2674
- );
2833
+ async ({ projectId: projectId2, provider, key, label, priority, baseUrl }) => {
2834
+ const data = await apiCall("/v1/admin/byok/keys", {
2835
+ method: "POST",
2836
+ body: JSON.stringify({
2837
+ project_id: projectId2,
2838
+ provider_slug: provider,
2839
+ key,
2840
+ label,
2841
+ priority,
2842
+ base_url: baseUrl
2843
+ })
2844
+ });
2845
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2846
+ }
2847
+ );
2848
+ server.registerTool(
2849
+ "test_byok_key",
2850
+ {
2851
+ title: titleOf("test_byok_key", TDD_TOOL_CATALOG),
2852
+ description: descOf("test_byok_key", TDD_TOOL_CATALOG),
2853
+ annotations: annotationsFor("test_byok_key", TDD_TOOL_CATALOG),
2854
+ inputSchema: {
2855
+ projectId: z.string().describe("Project id"),
2856
+ keyId: z.string().uuid().describe("Pooled BYOK key id")
2857
+ }
2858
+ },
2859
+ async ({ keyId }) => {
2860
+ const data = await apiCall(`/v1/admin/byok/keys/${encodeURIComponent(keyId)}/test`, {
2861
+ method: "POST"
2862
+ });
2863
+ return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2864
+ }
2865
+ );
2866
+ server.registerTool(
2867
+ "remove_byok_key",
2868
+ {
2869
+ title: titleOf("remove_byok_key", TDD_TOOL_CATALOG),
2870
+ description: descOf("remove_byok_key", TDD_TOOL_CATALOG),
2871
+ annotations: annotationsFor("remove_byok_key", TDD_TOOL_CATALOG),
2872
+ inputSchema: {
2873
+ projectId: z.string().describe("Project id"),
2874
+ keyId: z.string().uuid().describe("Pooled BYOK key UUID from list_byok_keys")
2875
+ }
2876
+ },
2877
+ async ({ keyId }) => {
2878
+ const data = await apiCall(`/v1/admin/byok/keys/${encodeURIComponent(keyId)}`, {
2879
+ method: "DELETE"
2880
+ });
2675
2881
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2676
2882
  }
2677
2883
  );
@@ -2686,7 +2892,9 @@ Be specific. No generic advice.`
2686
2892
  }
2687
2893
  },
2688
2894
  async ({ projectId: projectId2 }) => {
2689
- const data = await apiCall(`/v1/admin/inventory/${projectId2}/stories/pending-review`);
2895
+ const data = await apiCall(
2896
+ `/v1/admin/inventory/${projectId2}/stories/pending-review`
2897
+ );
2690
2898
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2691
2899
  }
2692
2900
  );
@@ -2723,13 +2931,10 @@ Be specific. No generic advice.`
2723
2931
  }
2724
2932
  },
2725
2933
  async ({ reportId, message, authorName }) => {
2726
- const data = await apiCall(
2727
- `/v1/sync/reports/${reportId}/reply`,
2728
- {
2729
- method: "POST",
2730
- body: JSON.stringify({ message, author_name: authorName })
2731
- }
2732
- );
2934
+ const data = await apiCall(`/v1/sync/reports/${reportId}/reply`, {
2935
+ method: "POST",
2936
+ body: JSON.stringify({ message, author_name: authorName })
2937
+ });
2733
2938
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2734
2939
  }
2735
2940
  );
@@ -2808,10 +3013,10 @@ Be specific. No generic advice.`
2808
3013
  async ({ project_id }) => {
2809
3014
  const pid = project_id ?? config.projectId;
2810
3015
  if (!pid) throw new MushiApiError(400, "MISSING_PROJECT_ID", "project_id is required");
2811
- const data = await apiCall(
2812
- `/v1/admin/projects/${pid}/audit`,
2813
- { method: "POST", body: "{}" }
2814
- );
3016
+ const data = await apiCall(`/v1/admin/projects/${pid}/audit`, {
3017
+ method: "POST",
3018
+ body: "{}"
3019
+ });
2815
3020
  return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
2816
3021
  }
2817
3022
  );
@@ -2907,17 +3112,19 @@ Be specific. No generic advice.`
2907
3112
  inputSchema: {
2908
3113
  root_skill_slug: z.string().describe('Root skill slug to run, e.g. "workflow-fix-and-ship"'),
2909
3114
  report_id: z.string().optional().describe("Report UUID to attach the pipeline to"),
2910
- mode: z.enum(["handoff", "cloud"]).optional().describe("handoff (default): get context packet for local agent. cloud: auto-dispatch via Cursor Cloud."),
3115
+ mode: z.enum(["handoff", "cloud"]).optional().describe(
3116
+ "handoff (default): get context packet for local agent. cloud: auto-dispatch via Cursor Cloud."
3117
+ ),
2911
3118
  project_id: z.string().optional().describe("Project UUID. Falls back to the configured project.")
2912
3119
  }
2913
3120
  },
2914
3121
  async (args) => {
2915
3122
  const resolvedProjectId = args.project_id ?? projectId;
2916
3123
  if (!resolvedProjectId) return jsonText({ error: "No project_id provided or configured." });
2917
- const data = await apiCall(
2918
- "/v1/admin/skills/pipelines",
2919
- { method: "POST", body: JSON.stringify({ ...args, project_id: resolvedProjectId }) }
2920
- );
3124
+ const data = await apiCall("/v1/admin/skills/pipelines", {
3125
+ method: "POST",
3126
+ body: JSON.stringify({ ...args, project_id: resolvedProjectId })
3127
+ });
2921
3128
  return jsonText(data);
2922
3129
  }
2923
3130
  );
@@ -2953,10 +3160,10 @@ Be specific. No generic advice.`
2953
3160
  },
2954
3161
  async (args) => {
2955
3162
  const { run_id, step_index, ...body } = args;
2956
- await apiCall(
2957
- `/v1/admin/skills/pipelines/${run_id}/steps/${step_index}/checkin`,
2958
- { method: "POST", body: JSON.stringify(body) }
2959
- );
3163
+ await apiCall(`/v1/admin/skills/pipelines/${run_id}/steps/${step_index}/checkin`, {
3164
+ method: "POST",
3165
+ body: JSON.stringify(body)
3166
+ });
2960
3167
  return jsonText({ ok: true, message: `Step ${step_index} \u2192 ${args.status}` });
2961
3168
  }
2962
3169
  );
@@ -3124,7 +3331,11 @@ Be specific. No generic advice.`
3124
3331
  if (args.compare) qs.set("compare", args.compare);
3125
3332
  if (args.fix_id) qs.set("fix_id", args.fix_id);
3126
3333
  if (!qs.toString()) {
3127
- throw new MushiApiError(400, "BAD_REQUEST", "Provide paths, source=last_push, compare, or fix_id");
3334
+ throw new MushiApiError(
3335
+ 400,
3336
+ "BAD_REQUEST",
3337
+ "Provide paths, source=last_push, compare, or fix_id"
3338
+ );
3128
3339
  }
3129
3340
  const data = await apiCall(`/v1/admin/projects/${pid}/codebase/impact?${qs}`);
3130
3341
  return jsonResult(data);
@@ -3176,7 +3387,7 @@ Be specific. No generic advice.`
3176
3387
  "### First step",
3177
3388
  `Call \`${cluster.tools[0]}\` to get started.`,
3178
3389
  "",
3179
- "> 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."
3390
+ "> 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."
3180
3391
  ].join("\n");
3181
3392
  return { content: [{ type: "text", text: orientation }] };
3182
3393
  }
@@ -3185,11 +3396,7 @@ Be specific. No generic advice.`
3185
3396
  const featureFilter = config.features ?? "all";
3186
3397
  if (grantedScopes !== void 0 || featureFilter !== "all") {
3187
3398
  const toolRegistry = server._registeredTools;
3188
- const allSpecs = [
3189
- ...TOOL_CATALOG,
3190
- ...TDD_TOOL_CATALOG,
3191
- ...CODEBASE_TOOL_CATALOG
3192
- ];
3399
+ const allSpecs = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG, ...CODEBASE_TOOL_CATALOG];
3193
3400
  for (const spec of allSpecs) {
3194
3401
  if (grantedScopes !== void 0 && !grantedScopes.includes(spec.scope)) {
3195
3402
  toolRegistry[spec.name]?.remove();