@mushi-mushi/mcp 0.17.1 → 0.18.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
@@ -2,13 +2,14 @@ import {
2
2
  CODEBASE_TOOL_CATALOG,
3
3
  TDD_TOOL_CATALOG,
4
4
  TOOL_CATALOG
5
- } from "./chunk-7UOTJ2LI.js";
5
+ } from "./chunk-TJSPX5CD.js";
6
6
  import {
7
7
  MUSHI_SERVER_METADATA
8
8
  } from "./chunk-EANW5YTI.js";
9
9
  import {
10
+ DEPRECATED_TOOL_ALIASES,
10
11
  toolMatchesFeatures
11
- } from "./chunk-PW4AK6SV.js";
12
+ } from "./chunk-VBXXUK5G.js";
12
13
 
13
14
  // src/server.ts
14
15
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -45,8 +46,8 @@ var MUSHI_DOCS_INDEX = [
45
46
  {
46
47
  title: "Cursor MCP troubleshooting",
47
48
  path: `${BASE}/guides/mcp-setup`,
48
- keywords: ["cursor", "mcp.json", "red badge", "transport error", "stdout", "stderr", "json-rpc", "INVALID_TOKEN", "syntax helper", "diagnose_connection"],
49
- excerpt: "Cursor stdio MCP requires stdout to contain only JSON-RPC. Use the console syntax helper, restart Cursor MCP, then run diagnose_connection."
49
+ keywords: ["cursor", "mcp.json", "red badge", "transport error", "stdout", "stderr", "json-rpc", "INVALID_TOKEN", "syntax helper", "diagnose_setup"],
50
+ excerpt: "Cursor stdio MCP requires stdout to contain only JSON-RPC. Use the console syntax helper, restart Cursor MCP, then run diagnose_setup."
50
51
  },
51
52
  {
52
53
  title: "Judge loop",
@@ -489,8 +490,12 @@ function createMushiServer(config) {
489
490
  description: descOf("get_knowledge_graph"),
490
491
  annotations: annotationsFor("get_knowledge_graph"),
491
492
  inputSchema: {
492
- seed: z.string().describe("Starting node id or label"),
493
- depth: z.number().optional().describe("Traversal depth (default 2, max 4)")
493
+ seed: z.string().describe("Starting graph node id or human-readable label to traverse from"),
494
+ depth: z.number().optional().describe("BFS hops outward from seed \u2014 clamped to 4 in the handler (default 2)")
495
+ },
496
+ outputSchema: {
497
+ nodes: z.array(z.unknown()).describe("Graph nodes within the depth budget"),
498
+ edges: z.array(z.unknown()).describe("Edges connecting the returned nodes")
494
499
  }
495
500
  },
496
501
  async (args) => {
@@ -498,18 +503,23 @@ function createMushiServer(config) {
498
503
  seed: args.seed,
499
504
  depth: String(Math.min(args.depth ?? 2, 4))
500
505
  });
501
- return jsonText(await apiCall(`/v1/admin/graph/traverse?${params}`));
506
+ const data = await apiCall(`/v1/admin/graph/traverse?${params}`);
507
+ return jsonResult(data);
502
508
  }
503
509
  );
504
510
  server.registerTool(
505
- "graph_neighborhood",
511
+ "get_graph_neighborhood",
506
512
  {
507
- title: titleOf("graph_neighborhood"),
508
- description: descOf("graph_neighborhood"),
509
- annotations: annotationsFor("graph_neighborhood"),
513
+ title: titleOf("get_graph_neighborhood"),
514
+ description: descOf("get_graph_neighborhood"),
515
+ annotations: annotationsFor("get_graph_neighborhood"),
510
516
  inputSchema: {
511
- seed: z.string().describe("Starting node id or label"),
512
- depth: z.number().optional().describe("Traversal depth (default 2, max 4)")
517
+ seed: z.string().describe("Starting graph node id or label to expand around (e.g. an inventory Action id or component name)"),
518
+ depth: z.number().int().min(1).max(4).optional().describe("BFS hops to traverse outward. Default 2; clamped to a max of 4.")
519
+ },
520
+ outputSchema: {
521
+ nodes: z.array(z.unknown()).describe("Graph nodes within the depth budget"),
522
+ edges: z.array(z.unknown()).describe("Edges connecting the returned nodes")
513
523
  }
514
524
  },
515
525
  async (args) => {
@@ -517,88 +527,108 @@ function createMushiServer(config) {
517
527
  seed: args.seed,
518
528
  depth: String(Math.min(args.depth ?? 2, 4))
519
529
  });
520
- return jsonText(await apiCall(`/v1/admin/graph/traverse?${params}`));
530
+ const data = await apiCall(`/v1/admin/graph/traverse?${params}`);
531
+ return jsonResult(data);
521
532
  }
522
533
  );
523
534
  server.registerTool(
524
- "graph_node_status",
535
+ "get_graph_node",
525
536
  {
526
- title: titleOf("graph_node_status"),
527
- description: descOf("graph_node_status"),
528
- annotations: annotationsFor("graph_node_status"),
529
- inputSchema: { nodeId: z.string().describe("graph_nodes.id") }
537
+ title: titleOf("get_graph_node"),
538
+ description: descOf("get_graph_node"),
539
+ annotations: annotationsFor("get_graph_node"),
540
+ inputSchema: { nodeId: z.string().describe("The graph_nodes.id of the node to fetch (UUID).") },
541
+ outputSchema: {
542
+ node: z.unknown().describe("Single graph_nodes row including metadata (Action nodes carry v2 status)")
543
+ }
530
544
  },
531
- async (args) => jsonText(await apiCall(`/v1/admin/graph/node/${args.nodeId}`))
545
+ async (args) => {
546
+ const data = await apiCall(`/v1/admin/graph/node/${args.nodeId}`);
547
+ return jsonResult(data);
548
+ }
532
549
  );
533
550
  server.registerTool(
534
- "inventory_get",
551
+ "get_inventory",
535
552
  {
536
- title: titleOf("inventory_get"),
537
- description: descOf("inventory_get"),
538
- annotations: annotationsFor("inventory_get"),
553
+ title: titleOf("get_inventory"),
554
+ description: descOf("get_inventory"),
555
+ annotations: annotationsFor("get_inventory"),
539
556
  inputSchema: {
540
557
  projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted")
541
558
  }
542
559
  },
543
560
  async (args) => {
544
561
  const pid = args.projectId ?? projectId;
545
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for inventory_get");
562
+ if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for get_inventory");
546
563
  return jsonText(await apiCall(`/v1/admin/inventory/${pid}`));
547
564
  }
548
565
  );
549
566
  server.registerTool(
550
- "inventory_diff",
567
+ "diff_inventory",
551
568
  {
552
- title: titleOf("inventory_diff"),
553
- description: descOf("inventory_diff"),
554
- annotations: annotationsFor("inventory_diff"),
569
+ title: titleOf("diff_inventory"),
570
+ description: descOf("diff_inventory"),
571
+ annotations: annotationsFor("diff_inventory"),
555
572
  inputSchema: {
556
573
  projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project"),
557
- fromSha: z.string().describe("Older commit SHA (baseline)"),
558
- toSha: z.string().describe("Newer commit SHA (candidate)")
574
+ fromSha: z.string().describe("Older commit SHA (the baseline to diff from)"),
575
+ toSha: z.string().describe("Newer commit SHA (the candidate to diff to)")
559
576
  }
560
577
  },
561
578
  async (args) => {
562
579
  const pid = args.projectId ?? projectId;
563
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for inventory_diff");
580
+ if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for diff_inventory");
564
581
  const q = new URLSearchParams({ from: args.fromSha, to: args.toSha });
565
582
  return jsonText(await apiCall(`/v1/admin/inventory/${pid}/diff?${q}`));
566
583
  }
567
584
  );
568
585
  server.registerTool(
569
- "inventory_findings",
586
+ "list_gate_findings",
570
587
  {
571
- title: titleOf("inventory_findings"),
572
- description: descOf("inventory_findings"),
573
- annotations: annotationsFor("inventory_findings"),
588
+ title: titleOf("list_gate_findings"),
589
+ description: descOf("list_gate_findings"),
590
+ annotations: annotationsFor("list_gate_findings"),
574
591
  inputSchema: {
575
592
  projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project"),
576
- gate: z.string().optional().describe("Filter by gate id (e.g. dead_handler, status_claim)"),
577
- severity: z.string().optional().describe("Filter findings by severity")
593
+ gate: z.string().optional().describe("Filter to one gate id: dead-handler | mock-leak | crawl | status-claim"),
594
+ severity: z.string().optional().describe("Minimum severity to include: low | medium | high | critical")
595
+ },
596
+ outputSchema: {
597
+ runs: z.array(z.unknown()).describe("Recent gate_runs rows, newest first"),
598
+ findings: z.array(z.unknown()).describe("gate_findings rows for those runs")
578
599
  }
579
600
  },
580
601
  async (args) => {
581
602
  const pid = args.projectId ?? projectId;
582
- if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for inventory_findings");
603
+ if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for list_gate_findings");
583
604
  const q = new URLSearchParams();
584
605
  if (args.gate) q.set("gate", args.gate);
585
606
  if (args.severity) q.set("severity", args.severity);
586
607
  const suffix = q.toString() ? `?${q}` : "";
587
- return jsonText(await apiCall(`/v1/admin/inventory/${pid}/findings${suffix}`));
608
+ const data = await apiCall(`/v1/admin/inventory/${pid}/findings${suffix}`);
609
+ return jsonResult(data);
588
610
  }
589
611
  );
590
612
  server.registerTool(
591
- "fix_suggest",
613
+ "suggest_fix",
592
614
  {
593
- title: titleOf("fix_suggest"),
594
- description: descOf("fix_suggest"),
595
- annotations: annotationsFor("fix_suggest"),
596
- inputSchema: { reportId: z.string().describe("Report UUID") }
615
+ title: titleOf("suggest_fix"),
616
+ description: descOf("suggest_fix"),
617
+ annotations: annotationsFor("suggest_fix"),
618
+ inputSchema: { reportId: z.string().describe("Report UUID to read the Stage-2 suggested-fix slice for") },
619
+ outputSchema: {
620
+ reportId: z.string().describe("The report this slice was read from"),
621
+ rootCause: z.unknown().describe("Stage-2 root-cause hint, or null if not yet classified"),
622
+ suggestedFix: z.unknown().describe("Stage-2 suggested fix, or null if not yet classified"),
623
+ reproductionSteps: z.unknown().describe("Reproduction steps recorded on the report (array; [] if none)"),
624
+ summary: z.unknown().describe("One-line report summary, or null"),
625
+ component: z.unknown().describe("Component/page the bug was attributed to, or null")
626
+ }
597
627
  },
598
628
  async (args) => {
599
629
  const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
600
630
  const s2 = report.stage2_analysis;
601
- return jsonText({
631
+ return jsonResult({
602
632
  reportId: args.reportId,
603
633
  rootCause: s2?.rootCause ?? null,
604
634
  suggestedFix: s2?.suggestedFix ?? null,
@@ -624,73 +654,6 @@ function createMushiServer(config) {
624
654
  return jsonText(data);
625
655
  }
626
656
  );
627
- server.registerTool(
628
- "setup_check",
629
- {
630
- title: titleOf("setup_check"),
631
- description: descOf("setup_check"),
632
- annotations: annotationsFor("setup_check"),
633
- inputSchema: {
634
- projectId: z.string().optional().describe(
635
- "Project UUID to check. Falls back to the projectId the server was initialised with."
636
- )
637
- }
638
- },
639
- async (args) => {
640
- const resolvedId = args.projectId ?? projectId;
641
- if (!resolvedId) {
642
- return jsonText({
643
- ok: false,
644
- error: "No projectId provided and none configured on the MCP server. Pass projectId explicitly."
645
- });
646
- }
647
- const data = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
648
- const summary = data.checks.map((c) => ({
649
- check: c.key,
650
- label: c.label,
651
- passed: c.ready,
652
- hint: c.hint,
653
- fixPath: c.fixHref
654
- }));
655
- return jsonText({
656
- ready: data.ready,
657
- repoUrl: data.repoUrl ?? null,
658
- checks: summary,
659
- // Human-readable summary for agents that paste the result into a prompt
660
- summary: data.ready ? `Project ${resolvedId} is ready to dispatch auto-fixes${data.repoUrl ? ` (target: ${data.repoUrl})` : ""}.` : `Project ${resolvedId} cannot dispatch yet \u2014 ${summary.filter((c) => !c.passed).map((c) => c.label).join(", ")}.`
661
- });
662
- }
663
- );
664
- server.registerTool(
665
- "ingest_setup_check",
666
- {
667
- title: titleOf("ingest_setup_check"),
668
- description: descOf("ingest_setup_check"),
669
- annotations: annotationsFor("ingest_setup_check"),
670
- inputSchema: NO_ARG_INPUT
671
- },
672
- async () => {
673
- const data = await apiCall("/v1/sync/ingest-setup");
674
- const failed = data.steps.filter((s) => s.required && !s.complete);
675
- const summary = data.ready ? `Ingest setup complete (${data.required_complete}/${data.required_total}) for ${data.project_name}.` : `Ingest incomplete (${data.required_complete}/${data.required_total}) \u2014 still need: ${failed.map((s) => s.label).join(", ")}.`;
676
- return jsonText({
677
- ready: data.ready,
678
- projectId: data.project_id,
679
- projectName: data.project_name,
680
- requiredComplete: data.required_complete,
681
- requiredTotal: data.required_total,
682
- steps: data.steps.map((s) => ({
683
- id: s.id,
684
- label: s.label,
685
- passed: s.complete,
686
- required: s.required,
687
- hint: s.hint
688
- })),
689
- diagnostic: data.diagnostic ?? null,
690
- summary
691
- });
692
- }
693
- );
694
657
  server.registerTool(
695
658
  "diagnose_setup",
696
659
  {
@@ -708,7 +671,8 @@ function createMushiServer(config) {
708
671
  summary: z.string(),
709
672
  nextAction: z.string().optional(),
710
673
  ingest: z.unknown().optional(),
711
- dispatch: z.unknown().optional()
674
+ dispatch: z.unknown().optional(),
675
+ connection: z.unknown().optional()
712
676
  }
713
677
  },
714
678
  async (args) => {
@@ -748,162 +712,99 @@ function createMushiServer(config) {
748
712
  dispatch: { repoUrl: data.repoUrl ?? null, checks: data.checks }
749
713
  });
750
714
  }
751
- const ingest = await apiCall("/v1/sync/ingest-setup");
752
- let dispatchReady = true;
753
- let dispatchBlock;
754
- let dispatchChecks = null;
755
- if (resolvedId) {
756
- try {
757
- const preflight = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
758
- dispatchReady = preflight.ready;
759
- dispatchChecks = preflight.checks;
760
- if (!preflight.ready) {
761
- const failed = preflight.checks.filter((c) => !c.ready);
762
- dispatchBlock = failed[0]?.hint;
763
- }
764
- } catch (err) {
765
- dispatchReady = false;
766
- const detail = err instanceof Error ? err.message : String(err);
767
- dispatchBlock = `Could not run dispatch preflight \u2014 ${detail}`;
768
- apiLog.warn("diagnose_setup dispatch preflight failed", { error: detail });
769
- }
770
- }
771
- const ingestFailed = ingest.steps.filter((s) => s.required && !s.complete);
772
- const ingestBlock = ingestFailed[0]?.hint;
773
- const ready = ingest.ready && dispatchReady;
774
- const nextAction = !ingest.ready ? ingestBlock ?? "Complete SDK ingest setup." : !dispatchReady ? dispatchBlock ?? "Complete dispatch preflight in Settings \u2192 Integrations." : "All setup checks pass \u2014 submit a test report to confirm end-to-end.";
775
- const summary = ready ? "Mushi ingest and dispatch setup look healthy." : !ingest.ready ? `Ingest incomplete \u2014 ${ingestFailed.map((s) => s.label).join(", ")}.` : "Ingest OK; dispatch preflight still has blockers.";
776
- return jsonResult({
777
- mode: "full",
778
- ready,
779
- summary,
780
- nextAction,
781
- ingest: { ready: ingest.ready, steps: ingest.steps },
782
- dispatch: resolvedId ? { ready: dispatchReady, checks: dispatchChecks } : null
783
- });
784
- }
785
- );
786
- server.registerTool(
787
- "diagnose_connection",
788
- {
789
- title: titleOf("diagnose_connection"),
790
- description: descOf("diagnose_connection"),
791
- annotations: annotationsFor("diagnose_connection"),
792
- inputSchema: NO_ARG_INPUT,
793
- outputSchema: {
794
- ready: z.boolean(),
795
- healthOk: z.boolean(),
796
- ingestReady: z.boolean(),
797
- dispatchReady: z.boolean(),
798
- endpoint: z.string().nullable(),
799
- projectId: z.string().nullable(),
800
- accessibleProjectCount: z.number().nullable(),
801
- issues: z.array(z.unknown()),
802
- nextAction: z.string(),
803
- summary: z.string()
804
- }
805
- },
806
- async () => {
807
- const issues = [];
715
+ const connIssues = [];
716
+ let healthOk = false;
808
717
  if (!apiKey?.startsWith("mushi_")) {
809
- issues.push({
718
+ connIssues.push({
810
719
  check: "mcp_api_key",
811
- detail: "MCP server API key missing or malformed",
812
- fix: "Run `mushi connect` to write MUSHI_API_KEY into .cursor/mcp.json, then restart the MCP server."
720
+ detail: "MCP server API key missing or malformed (expected prefix: mushi_)",
721
+ fix: "Run `mushi connect` or set MUSHI_API_KEY to a valid key in your MCP config."
813
722
  });
814
723
  }
815
724
  if (!apiEndpoint) {
816
- issues.push({
725
+ connIssues.push({
817
726
  check: "mcp_endpoint",
818
727
  detail: "No API endpoint configured",
819
- fix: "Set MUSHI_API_ENDPOINT to your `\u2026/functions/v1/api` URL in .cursor/mcp.json."
728
+ fix: "Set MUSHI_API_ENDPOINT to your `.../functions/v1/api` URL in your MCP config."
820
729
  });
821
- }
822
- let healthOk = false;
823
- if (apiEndpoint) {
730
+ } else {
824
731
  try {
825
732
  const healthRes = await doFetch(`${apiEndpoint.replace(/\/$/, "")}/health`, {
826
733
  signal: AbortSignal.timeout(5e3)
827
734
  });
828
735
  healthOk = healthRes.status === 200;
829
736
  if (!healthOk) {
830
- issues.push({
737
+ connIssues.push({
831
738
  check: "endpoint_health",
832
739
  detail: `GET /health \u2192 HTTP ${healthRes.status}`,
833
740
  fix: "Verify MUSHI_API_ENDPOINT and that the Supabase edge function is deployed."
834
741
  });
835
742
  }
836
743
  } catch (err) {
837
- issues.push({
744
+ connIssues.push({
838
745
  check: "endpoint_health",
839
746
  detail: err instanceof Error ? err.message : String(err),
840
- fix: "Check network connectivity and the endpoint URL in .cursor/mcp.json."
747
+ fix: "Check network connectivity and MUSHI_API_ENDPOINT in your MCP config."
841
748
  });
842
749
  }
843
750
  }
844
- let ingestReady = false;
845
- let dispatchReady = false;
846
751
  let accessibleProjectCount = null;
847
- if (!projectId && healthOk) {
752
+ if (!resolvedId && healthOk) {
848
753
  try {
849
- const overview = await apiCall("/v1/admin/mcp/account-overview");
754
+ const overview = await apiCall(
755
+ "/v1/admin/mcp/account-overview"
756
+ );
850
757
  accessibleProjectCount = overview.total;
851
758
  if (overview.total === 0) {
852
- issues.push({
759
+ connIssues.push({
853
760
  check: "no_accessible_projects",
854
761
  detail: "API key has no accessible projects",
855
- fix: "Mint an API key on a project (console \u2192 Projects \u2192 API Keys) or add MUSHI_PROJECT_ID to restrict to a single project."
762
+ fix: "Mint an API key on a project (console \u2192 Projects \u2192 API Keys) or add MUSHI_PROJECT_ID."
856
763
  });
857
764
  }
858
765
  } catch {
859
766
  }
860
767
  }
861
- try {
862
- const ingest = await apiCall("/v1/sync/ingest-setup");
863
- ingestReady = ingest.ready;
864
- if (!ingest.ready) {
865
- const failed = ingest.steps.filter((s) => s.required && !s.complete);
866
- issues.push({
867
- check: "ingest_setup",
868
- detail: `Incomplete: ${failed.map((s) => s.label).join(", ")}`,
869
- fix: failed[0]?.hint ?? "Paste the SDK snippet, start your dev server, submit a test report."
870
- });
871
- }
872
- } catch (err) {
873
- issues.push({
874
- check: "ingest_setup",
875
- detail: err instanceof Error ? err.message : String(err),
876
- fix: "Confirm API key is active for this project (Projects \u2192 API Keys)."
877
- });
878
- }
879
- if (projectId) {
768
+ const ingest = await apiCall("/v1/sync/ingest-setup");
769
+ let dispatchReady = true;
770
+ let dispatchBlock;
771
+ let dispatchChecks = null;
772
+ if (resolvedId) {
880
773
  try {
881
- const preflight = await apiCall(`/v1/admin/projects/${projectId}/preflight`);
774
+ const preflight = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
882
775
  dispatchReady = preflight.ready;
776
+ dispatchChecks = preflight.checks;
883
777
  if (!preflight.ready) {
884
778
  const failed = preflight.checks.filter((c) => !c.ready);
885
- issues.push({
886
- check: "dispatch_preflight",
887
- detail: `Blocked: ${failed.map((c) => c.label).join(", ")}`,
888
- fix: failed[0]?.hint ?? "Open Settings \u2192 Integrations and complete GitHub + BYOK setup."
889
- });
779
+ dispatchBlock = failed[0]?.hint;
890
780
  }
891
- } catch {
781
+ } catch (err) {
782
+ dispatchReady = false;
783
+ const detail = err instanceof Error ? err.message : String(err);
784
+ dispatchBlock = `Could not run dispatch preflight \u2014 ${detail}`;
785
+ apiLog.warn("diagnose_setup dispatch preflight failed", { error: detail });
892
786
  }
893
787
  }
894
- const ready = issues.length === 0 && healthOk && ingestReady;
895
- 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.");
788
+ const ingestFailed = ingest.steps.filter((s) => s.required && !s.complete);
789
+ const ingestBlock = ingestFailed[0]?.hint;
790
+ const connOk = connIssues.length === 0;
791
+ const ready = connOk && ingest.ready && dispatchReady;
792
+ const nextAction = !connOk ? connIssues[0]?.fix ?? "Fix the connection issue above." : !ingest.ready ? ingestBlock ?? "Complete SDK ingest setup." : !dispatchReady ? dispatchBlock ?? "Complete dispatch preflight in Settings \u2192 Integrations." : "All setup checks pass \u2014 submit a test report to confirm end-to-end.";
793
+ const summary = ready ? "Mushi connection, ingest, and dispatch setup look healthy." : !connOk ? `Connection issue \u2014 ${connIssues[0]?.check}: ${connIssues[0]?.detail}` : !ingest.ready ? `Ingest incomplete \u2014 ${ingestFailed.map((s) => s.label).join(", ")}.` : "Ingest OK; dispatch preflight still has blockers.";
896
794
  return jsonResult({
795
+ mode: "full",
897
796
  ready,
898
- healthOk,
899
- ingestReady,
900
- dispatchReady,
901
- endpoint: apiEndpoint ?? null,
902
- projectId: projectId ?? null,
903
- accessibleProjectCount,
904
- issues,
797
+ summary,
905
798
  nextAction,
906
- summary: ready ? "MCP credentials valid; ingest pipeline ready." : `Connection issue \u2014 ${issues[0]?.check ?? "unknown"}: ${nextAction}`
799
+ connection: {
800
+ healthOk,
801
+ endpoint: apiEndpoint ?? null,
802
+ projectId: resolvedId ?? null,
803
+ accessibleProjectCount,
804
+ issues: connIssues
805
+ },
806
+ ingest: { ready: ingest.ready, steps: ingest.steps },
807
+ dispatch: resolvedId ? { ready: dispatchReady, checks: dispatchChecks } : null
907
808
  });
908
809
  }
909
810
  );
@@ -1194,13 +1095,17 @@ function createMushiServer(config) {
1194
1095
  description: descOf("submit_fix_result"),
1195
1096
  annotations: annotationsFor("submit_fix_result"),
1196
1097
  inputSchema: {
1197
- reportId: z.string().describe("The report UUID"),
1198
- branch: z.string().describe("Git branch name"),
1199
- prUrl: z.string().optional().describe("GitHub PR URL"),
1200
- filesChanged: z.array(z.string()).describe("Files modified"),
1201
- linesChanged: z.number().describe("Total lines changed"),
1202
- summary: z.string().describe("Fix summary"),
1098
+ reportId: z.string().describe("Report UUID to attach this external fix attempt to"),
1099
+ branch: z.string().describe("Git branch name where the fix was implemented"),
1100
+ prUrl: z.string().optional().describe("GitHub pull request URL, if a PR was opened"),
1101
+ filesChanged: z.array(z.string()).describe("Repo-relative paths modified by the fix"),
1102
+ linesChanged: z.number().describe("Total lines added + removed across filesChanged"),
1103
+ summary: z.string().describe("One-line human summary of what the fix changed"),
1203
1104
  idempotencyKey: z.string().uuid().optional().describe("Optional UUID \u2014 resend the same key to safely retry without creating duplicate fix rows")
1105
+ },
1106
+ outputSchema: {
1107
+ ok: z.boolean().describe("True when the fix_attempt row was created and marked completed"),
1108
+ fixId: z.string().describe("UUID of the new fix_attempt row")
1204
1109
  }
1205
1110
  },
1206
1111
  async (args) => {
@@ -1222,7 +1127,7 @@ function createMushiServer(config) {
1222
1127
  completed_at: (/* @__PURE__ */ new Date()).toISOString()
1223
1128
  })
1224
1129
  });
1225
- return jsonText({ ok: true, fixId: created.fixId });
1130
+ return jsonResult({ ok: true, fixId: created.fixId });
1226
1131
  }
1227
1132
  );
1228
1133
  server.registerTool(
@@ -1279,6 +1184,9 @@ function createMushiServer(config) {
1279
1184
  inputSchema: {
1280
1185
  limit: z.number().optional().describe("Max reports to judge in this batch (default 25, max 100)"),
1281
1186
  projectId: z.string().optional().describe("Restrict to one project when the API key owns multiple")
1187
+ },
1188
+ outputSchema: {
1189
+ dispatched: z.number().describe("Number of judge-batch jobs dispatched (one per accessible project)")
1282
1190
  }
1283
1191
  },
1284
1192
  async (args) => {
@@ -1289,7 +1197,7 @@ function createMushiServer(config) {
1289
1197
  projectId: args.projectId ?? projectId ?? void 0
1290
1198
  })
1291
1199
  });
1292
- return jsonText(data);
1200
+ return jsonResult(data);
1293
1201
  }
1294
1202
  );
1295
1203
  server.registerTool(
@@ -1316,12 +1224,18 @@ function createMushiServer(config) {
1316
1224
  server.registerTool(
1317
1225
  "merge_fix",
1318
1226
  {
1319
- title: "Merge fix PR",
1320
- description: "Squash-merge a fix attempt PR and mark the linked report fixed.",
1227
+ title: titleOf("merge_fix"),
1228
+ description: descOf("merge_fix"),
1321
1229
  annotations: annotationsFor("merge_fix"),
1322
1230
  inputSchema: {
1323
- fixId: z.string().describe("Fix attempt UUID"),
1324
- mergeMethod: z.enum(["squash", "merge", "rebase"]).optional().describe("GitHub merge method")
1231
+ fixId: z.string().describe("Fix attempt UUID whose GitHub PR should be squash-merged"),
1232
+ mergeMethod: z.enum(["squash", "merge", "rebase"]).optional().describe("GitHub merge method (default squash)")
1233
+ },
1234
+ outputSchema: {
1235
+ merged: z.boolean().optional().describe("True when GitHub accepted the merge in this call"),
1236
+ alreadyMerged: z.boolean().optional().describe("True when the PR was already merged (idempotent no-op)"),
1237
+ reportId: z.string().describe("Report UUID linked to this fix attempt"),
1238
+ reportStatus: z.string().describe("Report workflow status after merge bookkeeping")
1325
1239
  }
1326
1240
  },
1327
1241
  async (args) => {
@@ -1329,22 +1243,27 @@ function createMushiServer(config) {
1329
1243
  method: "POST",
1330
1244
  body: JSON.stringify({ mergeMethod: args.mergeMethod ?? "squash" })
1331
1245
  });
1332
- return jsonText(data);
1246
+ return jsonResult(data);
1333
1247
  }
1334
1248
  );
1335
1249
  server.registerTool(
1336
1250
  "refresh_ci",
1337
1251
  {
1338
- title: "Refresh fix CI status",
1339
- description: "Pull the latest GitHub check-run status for a fix attempt.",
1252
+ title: titleOf("refresh_ci"),
1253
+ description: descOf("refresh_ci"),
1340
1254
  annotations: annotationsFor("refresh_ci"),
1341
1255
  inputSchema: {
1342
- fixId: z.string().describe("Fix attempt UUID")
1256
+ fixId: z.string().describe("Fix attempt UUID whose PR check-runs should be re-polled")
1257
+ },
1258
+ outputSchema: {
1259
+ check_run_status: z.string().nullable().describe("GitHub check run status after refresh"),
1260
+ check_run_conclusion: z.string().nullable().describe("success | failure | neutral | null while pending"),
1261
+ check_run_updated_at: z.string().nullable().describe("ISO timestamp when CI status was last persisted")
1343
1262
  }
1344
1263
  },
1345
1264
  async (args) => {
1346
1265
  const data = await apiCall(`/v1/admin/fixes/${args.fixId}/refresh-ci`, { method: "POST" });
1347
- return jsonText(data);
1266
+ return jsonResult(data);
1348
1267
  }
1349
1268
  );
1350
1269
  server.registerTool(
@@ -1354,16 +1273,19 @@ function createMushiServer(config) {
1354
1273
  description: descOf("reopen_report"),
1355
1274
  annotations: annotationsFor("reopen_report"),
1356
1275
  inputSchema: {
1357
- reportId: z.string().describe("Report UUID"),
1358
- note: z.string().optional().describe("Triage note")
1276
+ reportId: z.string().describe("Report UUID to move back to reopened status"),
1277
+ note: z.string().optional().describe("Operator note recorded on the reopen transition")
1278
+ },
1279
+ outputSchema: {
1280
+ report: z.unknown().describe("Updated report row with status=reopened")
1359
1281
  }
1360
1282
  },
1361
1283
  async (args) => {
1362
- const data = await apiCall(`/v1/sync/reports/${args.reportId}`, {
1284
+ const report = await apiCall(`/v1/sync/reports/${args.reportId}`, {
1363
1285
  method: "PATCH",
1364
1286
  body: JSON.stringify({ status: "reopened", note: args.note })
1365
1287
  });
1366
- return jsonText(data);
1288
+ return jsonResult({ report });
1367
1289
  }
1368
1290
  );
1369
1291
  server.registerTool(
@@ -1440,9 +1362,12 @@ function createMushiServer(config) {
1440
1362
  description: descOf("list_lessons"),
1441
1363
  annotations: annotationsFor("list_lessons"),
1442
1364
  inputSchema: {
1443
- severity: z.enum(["info", "warn", "critical"]).optional().describe("Filter by severity level."),
1444
- limit: z.number().optional().describe("Max number of lessons to return (default 50, max 200)."),
1445
- project_id: z.string().optional().describe("Project UUID. Defaults to configured project.")
1365
+ severity: z.enum(["info", "warn", "critical"]).optional().describe("Filter to one severity level"),
1366
+ limit: z.number().optional().describe("Max lessons to return (default 50, max 200)"),
1367
+ project_id: z.string().optional().describe("Project UUID \u2014 defaults to configured project")
1368
+ },
1369
+ outputSchema: {
1370
+ lessons: z.array(z.unknown()).describe("Promoted lesson rows ordered by frequency")
1446
1371
  }
1447
1372
  },
1448
1373
  async (args) => {
@@ -1451,8 +1376,8 @@ function createMushiServer(config) {
1451
1376
  if (args.severity) params.set("severity", args.severity);
1452
1377
  params.set("limit", String(Math.min(args.limit ?? 50, 200)));
1453
1378
  if (pid) params.set("projectId", pid);
1454
- const data = await apiCall(`/v1/admin/lessons?${params}`);
1455
- return jsonText(data);
1379
+ const lessons = await apiCall(`/v1/admin/lessons?${params}`);
1380
+ return jsonResult({ lessons });
1456
1381
  }
1457
1382
  );
1458
1383
  server.resource(
@@ -1618,6 +1543,22 @@ function createMushiServer(config) {
1618
1543
  };
1619
1544
  }
1620
1545
  );
1546
+ server.registerTool(
1547
+ "activation_status",
1548
+ {
1549
+ title: titleOf("activation_status"),
1550
+ description: descOf("activation_status"),
1551
+ annotations: annotationsFor("activation_status"),
1552
+ inputSchema: {
1553
+ project_id: z.string().optional().describe("Project UUID (defaults to the configured project).")
1554
+ }
1555
+ },
1556
+ async (args) => {
1557
+ const pid = args.project_id ?? projectId;
1558
+ const qs = pid ? `?project_id=${encodeURIComponent(pid)}` : "";
1559
+ return jsonResult(await apiCall(`/v1/admin/activation${qs}`));
1560
+ }
1561
+ );
1621
1562
  server.resource(
1622
1563
  "activation_status",
1623
1564
  "mushi://activation",
@@ -1716,7 +1657,7 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
1716
1657
  text: `You are a Mushi onboarding copilot. Use the MCP tools:
1717
1658
  1. Read mushi://activation for unified setup posture.
1718
1659
  2. Read project://integration-health if GitHub or Sentry looks blocked.
1719
- 3. Call get_activation_status if the resource is unavailable.
1660
+ 3. Call diagnose_setup if the resource is unavailable.
1720
1661
 
1721
1662
  Then output:
1722
1663
  - **Status:** one sentence on what is done vs blocked
@@ -1774,11 +1715,17 @@ Be specific. No generic advice.`
1774
1715
  description: descOf("generate_tdd_from_story", TDD_TOOL_CATALOG),
1775
1716
  annotations: annotationsFor("generate_tdd_from_story", TDD_TOOL_CATALOG),
1776
1717
  inputSchema: {
1777
- projectId: z.string().describe("Project id"),
1778
- storyNodeId: z.string().describe("User story id slug from the accepted inventory"),
1779
- automationMode: z.enum(["auto", "review", "approve"]).optional().describe("Gate mode for the generated test (default: review)"),
1780
- baseUrl: z.string().url().optional().describe("Override the app base URL"),
1781
- openPr: z.boolean().optional().describe("Open a draft GitHub PR (default: true)")
1718
+ projectId: z.string().describe("Project UUID that owns the inventory story"),
1719
+ storyNodeId: z.string().describe("User story node id/slug from accepted inventory (from map_user_stories or inventory.yaml)"),
1720
+ automationMode: z.enum(["auto", "review", "approve"]).optional().describe("Approval gate for the generated qa_story (default review \u2192 pending_review)"),
1721
+ baseUrl: z.string().url().optional().describe("Override the live app URL the Playwright test should target"),
1722
+ openPr: z.boolean().optional().describe("Open a draft GitHub PR with the generated spec (default true)")
1723
+ },
1724
+ outputSchema: {
1725
+ qaStoryId: z.string().describe("UUID of the inserted qa_stories row"),
1726
+ prUrl: z.string().nullable().describe("GitHub PR URL when openPr=true and GitHub is connected"),
1727
+ approvalStatus: z.string().describe("pending_review | approved \u2014 follows automationMode and project defaults"),
1728
+ needsHumanReview: z.boolean().describe("True when the story requires operator approval before scheduled runs")
1782
1729
  }
1783
1730
  },
1784
1731
  async ({ projectId: projectId2, storyNodeId, automationMode, baseUrl, openPr }) => {
@@ -1787,7 +1734,7 @@ Be specific. No generic advice.`
1787
1734
  `/v1/admin/inventory/${projectId2}/stories/${storyNodeId}/generate-test`,
1788
1735
  { method: "POST", body: JSON.stringify({ automation_mode: automationMode, base_url: baseUrl, open_pr: openPr }) }
1789
1736
  );
1790
- return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
1737
+ return jsonResult(data);
1791
1738
  }
1792
1739
  );
1793
1740
  server.registerTool(
@@ -2149,36 +2096,6 @@ Be specific. No generic advice.`
2149
2096
  return jsonText({ ok: true, message: `Step ${step_index} \u2192 ${args.status}` });
2150
2097
  }
2151
2098
  );
2152
- server.registerTool(
2153
- "get_activation_status",
2154
- {
2155
- title: titleOf("get_activation_status"),
2156
- description: descOf("get_activation_status"),
2157
- annotations: annotationsFor("get_activation_status"),
2158
- inputSchema: {
2159
- project_id: z.string().optional().describe("Optional project UUID override")
2160
- }
2161
- },
2162
- async (args) => {
2163
- const qs = args.project_id ? `?project_id=${encodeURIComponent(args.project_id)}` : "";
2164
- const data = await apiCall(`/v1/admin/activation${qs}`);
2165
- return jsonText(data);
2166
- }
2167
- );
2168
- server.registerTool(
2169
- "get_reporter_thread",
2170
- {
2171
- title: titleOf("get_reporter_thread"),
2172
- description: descOf("get_reporter_thread"),
2173
- annotations: annotationsFor("get_reporter_thread"),
2174
- inputSchema: { reportId: z.string().describe("The report UUID") }
2175
- },
2176
- // The reporter thread is the `comments` lane of the unified report
2177
- // timeline. There is no standalone admin `/comments` route (only the
2178
- // reporter-token-gated `/v1/reporter/reports/:id/comments`), so we read the
2179
- // admin-authed timeline, which also carries fix / QA / status lanes.
2180
- async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}/timeline`))
2181
- );
2182
2099
  server.registerTool(
2183
2100
  "ask_codebase",
2184
2101
  {
@@ -2263,23 +2180,31 @@ Be specific. No generic advice.`
2263
2180
  annotations: annotationsFor("search_codebase", CODEBASE_TOOL_CATALOG),
2264
2181
  inputSchema: {
2265
2182
  project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
2266
- query: z.string().describe("Plain-English search query"),
2267
- k: z.number().optional().describe("Number of results (default 8, max 20)"),
2268
- scope_prefix: z.string().optional().describe("Optional subdirectory scope prefix")
2183
+ query: z.string().describe("Plain-English search query against indexed source files"),
2184
+ k: z.number().int().min(1).max(20).optional().describe("Max hits to return (default 8, max 20)"),
2185
+ scope_prefix: z.string().optional().describe("Optional repo subdirectory prefix to limit search scope")
2186
+ },
2187
+ outputSchema: {
2188
+ results: z.array(z.unknown()).describe("Ranked file/symbol hits with paths, line ranges, and similarity"),
2189
+ query: z.string().describe("Normalized query string that was searched"),
2190
+ mode: z.string().optional().describe("semantic (embeddings) or name (path/symbol match)")
2269
2191
  }
2270
2192
  },
2271
2193
  async (args) => {
2272
2194
  const pid = args.project_id ?? projectId;
2273
2195
  if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
2274
- const data = await apiCall(`/v1/admin/projects/${pid}/codebase/search`, {
2275
- method: "POST",
2276
- body: JSON.stringify({
2277
- query: args.query,
2278
- k: args.k ?? 8,
2279
- scope_prefix: args.scope_prefix ?? void 0
2280
- })
2281
- });
2282
- return jsonText(data);
2196
+ const data = await apiCall(
2197
+ `/v1/admin/projects/${pid}/codebase/search`,
2198
+ {
2199
+ method: "POST",
2200
+ body: JSON.stringify({
2201
+ query: args.query,
2202
+ k: args.k ?? 8,
2203
+ scope_prefix: args.scope_prefix ?? void 0
2204
+ })
2205
+ }
2206
+ );
2207
+ return jsonResult(data);
2283
2208
  }
2284
2209
  );
2285
2210
  server.registerTool(
@@ -2313,10 +2238,17 @@ Be specific. No generic advice.`
2313
2238
  annotations: annotationsFor("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
2314
2239
  inputSchema: {
2315
2240
  project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
2316
- paths: z.array(z.string()).optional().describe("Changed file paths"),
2317
- source: z.enum(["last_push", "compare", "fix"]).optional().describe("Auto-resolve changed paths"),
2318
- compare: z.string().optional().describe("GitHub compare range base...head"),
2319
- fix_id: z.string().optional().describe("Fix attempt UUID for PR changed files")
2241
+ paths: z.array(z.string()).optional().describe("Changed repo-relative file paths to analyze"),
2242
+ source: z.enum(["last_push", "compare", "fix"]).optional().describe("Auto-resolve changed paths instead of manual paths"),
2243
+ compare: z.string().optional().describe("GitHub compare range base...head when source=compare"),
2244
+ fix_id: z.string().optional().describe("Fix attempt UUID \u2014 uses that PR's changed files when source=fix")
2245
+ },
2246
+ outputSchema: {
2247
+ changed_paths: z.array(z.string()).describe("Resolved set of changed paths analyzed"),
2248
+ source: z.string().describe("How changed_paths were resolved (manual, last_push, compare, fix, \u2026)"),
2249
+ affected_file_paths: z.array(z.string()).describe("Indexed files that import or depend on changed paths"),
2250
+ affected_node_ids: z.array(z.string()).describe("Codebase graph node ids impacted by the change"),
2251
+ meta: z.unknown().nullable().optional().describe("Extra resolution metadata when auto-sourced")
2320
2252
  }
2321
2253
  },
2322
2254
  async (args) => {
@@ -2331,7 +2263,7 @@ Be specific. No generic advice.`
2331
2263
  throw new MushiApiError(400, "BAD_REQUEST", "Provide paths, source=last_push, compare, or fix_id");
2332
2264
  }
2333
2265
  const data = await apiCall(`/v1/admin/projects/${pid}/codebase/impact?${qs}`);
2334
- return jsonText(data);
2266
+ return jsonResult(data);
2335
2267
  }
2336
2268
  );
2337
2269
  server.registerTool(
@@ -2395,6 +2327,39 @@ Be specific. No generic advice.`
2395
2327
  return result;
2396
2328
  });
2397
2329
  }
2330
+ {
2331
+ const llAliasServer = server.server;
2332
+ const innerHandler = llAliasServer?._requestHandlers?.get("tools/call");
2333
+ if (typeof innerHandler === "function") {
2334
+ llAliasServer._requestHandlers.set("tools/call", async (...args) => {
2335
+ const req = args[0];
2336
+ const originalName = req?.params?.name;
2337
+ const canonicalName = originalName ? DEPRECATED_TOOL_ALIASES[originalName] : void 0;
2338
+ if (canonicalName) {
2339
+ const rewritten = {
2340
+ ...args[0],
2341
+ params: { ...req?.params ?? {}, name: canonicalName }
2342
+ };
2343
+ const result = await innerHandler(rewritten, ...args.slice(1));
2344
+ if (result != null && typeof result === "object" && "content" in result && Array.isArray(result.content)) {
2345
+ const r = result;
2346
+ return {
2347
+ ...r,
2348
+ content: [
2349
+ {
2350
+ type: "text",
2351
+ text: `\u26A0\uFE0F Deprecated: \`${originalName}\` was renamed to \`${canonicalName}\`. Update your agent config \u2014 this alias will be removed in the next release.`
2352
+ },
2353
+ ...r.content
2354
+ ]
2355
+ };
2356
+ }
2357
+ return result;
2358
+ }
2359
+ return innerHandler(...args);
2360
+ });
2361
+ }
2362
+ }
2398
2363
  return server;
2399
2364
  }
2400
2365
  export {