@mushi-mushi/mcp 0.14.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +23 -1
- package/README.md +81 -10
- package/dist/branding.d.ts +31 -0
- package/dist/branding.js +16 -0
- package/dist/catalog.d.ts +2 -1
- package/dist/catalog.js +3 -1
- package/dist/chunk-B2QEG35D.js +158 -0
- package/dist/chunk-EHB2BGWX.js +28 -0
- package/dist/{chunk-FQ5WR3PS.js → chunk-PZEGN4XD.js} +90 -7
- package/dist/feature-groups.d.ts +23 -0
- package/dist/feature-groups.js +24 -0
- package/dist/index.js +850 -42
- package/dist/server.d.ts +6 -0
- package/dist/server.js +614 -31
- package/package.json +18 -8
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// src/index.ts
|
|
4
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
5
|
import { createRequire } from "module";
|
|
6
|
-
import { createLogger } from "@mushi-mushi/core";
|
|
6
|
+
import { createLogger as createLogger2 } from "@mushi-mushi/core";
|
|
7
7
|
|
|
8
8
|
// src/catalog.ts
|
|
9
9
|
var ALL_SCOPES = ["mcp:read", "mcp:write"];
|
|
@@ -141,7 +141,7 @@ var TOOL_CATALOG = [
|
|
|
141
141
|
{
|
|
142
142
|
name: "setup_check",
|
|
143
143
|
title: "Dispatch preflight check",
|
|
144
|
-
description: "Run the 4 **dispatch-readiness** checks for a project and return their pass/fail status (GitHub repo connected, codebase indexed, Anthropic BYOK key present, autofix enabled). Also returns the target repo URL when GitHub is connected.
|
|
144
|
+
description: "[Deprecated \u2014 use diagnose_setup with mode=dispatch or diagnose_connection.] Run the 4 **dispatch-readiness** checks for a project and return their pass/fail status (GitHub repo connected, codebase indexed, Anthropic BYOK key present, autofix enabled). Also returns the target repo URL when GitHub is connected.",
|
|
145
145
|
scope: "mcp:read",
|
|
146
146
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
147
147
|
useCase: "Is this project ready to auto-fix bugs? What is blocking dispatch?"
|
|
@@ -149,19 +149,35 @@ var TOOL_CATALOG = [
|
|
|
149
149
|
{
|
|
150
150
|
name: "ingest_setup_check",
|
|
151
151
|
title: "Ingest setup check",
|
|
152
|
-
description: "Run the 4 **required ingest** checks for the project tied to this API key: project exists, active API key, SDK heartbeat (or real report), and at least one ingested report.
|
|
152
|
+
description: "[Deprecated \u2014 use diagnose_setup with mode=ingest or diagnose_connection.] Run the 4 **required ingest** checks for the project tied to this API key: project exists, active API key, SDK heartbeat (or real report), and at least one ingested report.",
|
|
153
153
|
scope: "mcp:read",
|
|
154
154
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
155
155
|
useCase: "Is the SDK installed and ingesting reports? Why is my banner still missing?"
|
|
156
156
|
},
|
|
157
|
+
{
|
|
158
|
+
name: "diagnose_setup",
|
|
159
|
+
title: "Unified setup diagnose",
|
|
160
|
+
description: "Single entry point for setup health. mode=full (default) runs ingest + dispatch checks and returns the best next action; mode=ingest runs SDK ingest checks only; mode=dispatch runs fix-dispatch preflight only. Prefer this over setup_check, ingest_setup_check, or diagnose_connection alone.",
|
|
161
|
+
scope: "mcp:read",
|
|
162
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
163
|
+
useCase: "Why is Mushi setup broken \u2014 one call, clear next step."
|
|
164
|
+
},
|
|
157
165
|
{
|
|
158
166
|
name: "diagnose_connection",
|
|
159
167
|
title: "Connection diagnose (CLI + MCP + SDK)",
|
|
160
|
-
description: `Validate
|
|
168
|
+
description: `Validate MCP credentials, ping /health, run ingest-setup and dispatch preflight, return the single best next action. For mode-specific checks prefer diagnose_setup. Use when the user asks "why aren't my reports showing up?".`,
|
|
161
169
|
scope: "mcp:read",
|
|
162
170
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
163
171
|
useCase: "Why is my Mushi setup broken \u2014 what exact step should I fix next?"
|
|
164
172
|
},
|
|
173
|
+
{
|
|
174
|
+
name: "search_mushi_docs",
|
|
175
|
+
title: "Search Mushi documentation",
|
|
176
|
+
description: "Search official Mushi docs (guides, MCP setup, inventory, QA, skills) by keyword. Returns ranked page titles, URLs, and excerpts \u2014 use before guessing API shapes or RPC names.",
|
|
177
|
+
scope: "mcp:read",
|
|
178
|
+
hints: { readOnly: true, idempotent: true, openWorld: false },
|
|
179
|
+
useCase: "How do I configure MCP scopes / dispatch a fix / wire QA stories?"
|
|
180
|
+
},
|
|
165
181
|
// --- Write / agentic ----------------------------------------------------
|
|
166
182
|
{
|
|
167
183
|
name: "submit_fix_result",
|
|
@@ -265,11 +281,19 @@ var TOOL_CATALOG = [
|
|
|
265
281
|
{
|
|
266
282
|
name: "list_projects",
|
|
267
283
|
title: "List accessible projects",
|
|
268
|
-
description: "List
|
|
284
|
+
description: "List all Mushi projects accessible to this API key. For project-scoped keys returns a single-item list; for org-scoped keys (account mode) returns every project owned by this account. Call this first when MUSHI_PROJECT_ID is not configured \u2014 use the returned id with subsequent tool calls. If multiple projects are returned, pass the target projectId explicitly to project-scoped tools. Multi-project tip: run `mushi setup --all-projects` in your terminal to create one named MCP server entry per project in .cursor/mcp.json.",
|
|
269
285
|
scope: "mcp:read",
|
|
270
286
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
271
287
|
useCase: "Which projects can I access with this API key?"
|
|
272
288
|
},
|
|
289
|
+
{
|
|
290
|
+
name: "get_account_overview",
|
|
291
|
+
title: "Account overview \u2014 all projects",
|
|
292
|
+
description: "Return an enriched summary of every Mushi project accessible to this API key: id, name, recent report count (last 30 days), number of connected MCP keys, and last-seen heartbeat timestamp. For project-scoped keys this is a one-item list; for org-scoped (account) keys it lists every owned project. Also includes toolCount, resourceCount, promptCount so agents know how many tools are available. Call this at the start of a multi-repo or multi-app triage session to orient yourself.",
|
|
293
|
+
scope: "mcp:read",
|
|
294
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
295
|
+
useCase: "Show me all my Mushi projects and their health at a glance."
|
|
296
|
+
},
|
|
273
297
|
{
|
|
274
298
|
name: "get_project_context",
|
|
275
299
|
title: "Project context snapshot",
|
|
@@ -477,7 +501,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
477
501
|
{
|
|
478
502
|
name: "get_activation_status",
|
|
479
503
|
title: "Activation cockpit status",
|
|
480
|
-
description: "Unified setup posture for the active project: required steps, SDK heartbeat, dispatch preflight, and the next best action.
|
|
504
|
+
description: "[Deprecated \u2014 use activation_status resource or tool on hosted MCP.] Unified setup posture for the active project: required steps, SDK heartbeat, dispatch preflight, and the next best action.",
|
|
481
505
|
scope: "mcp:read",
|
|
482
506
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
483
507
|
useCase: "What is blocking this project from going live?"
|
|
@@ -485,7 +509,7 @@ var TDD_TOOL_CATALOG = [
|
|
|
485
509
|
{
|
|
486
510
|
name: "get_reporter_thread",
|
|
487
511
|
title: "Reporter feedback thread",
|
|
488
|
-
description: "Fetch the unified timeline for a report \u2014
|
|
512
|
+
description: "[Deprecated \u2014 use get_report_timeline instead.] Fetch the unified timeline for a report \u2014 reporter comments plus fix, QA, and status lanes.",
|
|
489
513
|
scope: "mcp:read",
|
|
490
514
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
491
515
|
useCase: "What did the reporter say after we marked this fixed?"
|
|
@@ -508,10 +532,319 @@ var TDD_TOOL_CATALOG = [
|
|
|
508
532
|
useCase: "Are there any tables without RLS? Show me recent backend errors for this project."
|
|
509
533
|
}
|
|
510
534
|
];
|
|
535
|
+
var CODEBASE_TOOL_CATALOG = [
|
|
536
|
+
{
|
|
537
|
+
name: "ask_codebase",
|
|
538
|
+
title: "Ask about the indexed codebase",
|
|
539
|
+
description: "Ask a plain-English question about the connected repo. Grounds on pgvector retrieval over project_codebase_files and returns an answer with file:line citations. Requires codebase indexing enabled and an Anthropic or OpenAI BYOK key.",
|
|
540
|
+
scope: "mcp:write",
|
|
541
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
542
|
+
useCase: "How does authentication work in this repo?"
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
name: "get_file_summary",
|
|
546
|
+
title: "Plain-English file summary",
|
|
547
|
+
description: "Lazy-generate (or return cached) plain-English summary for an indexed file or symbol. Cache invalidates when content_hash changes after re-index.",
|
|
548
|
+
scope: "mcp:read",
|
|
549
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
550
|
+
useCase: "Explain what lib/auth.ts does in plain English."
|
|
551
|
+
},
|
|
552
|
+
{
|
|
553
|
+
name: "get_codebase_tour",
|
|
554
|
+
title: "Guided codebase tour",
|
|
555
|
+
description: "Return a dependency-ordered guided tour (~6\u201310 stops) for onboarding \u2014 each stop lists node ids, file paths, layer, and rationale. Cached per index fingerprint.",
|
|
556
|
+
scope: "mcp:read",
|
|
557
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
558
|
+
useCase: "Give me an onboarding walkthrough of this codebase."
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
name: "search_codebase",
|
|
562
|
+
title: "Semantic codebase search",
|
|
563
|
+
description: "Search indexed files by plain-English meaning via embeddings. Returns top-k file chunks with similarity scores.",
|
|
564
|
+
scope: "mcp:read",
|
|
565
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
566
|
+
useCase: "Find files related to authentication or payment handling."
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
name: "get_codebase_domains",
|
|
570
|
+
title: "Business domain map",
|
|
571
|
+
description: "Extract business domains, flows, and steps mapped to file paths. Cached per index fingerprint.",
|
|
572
|
+
scope: "mcp:read",
|
|
573
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
574
|
+
useCase: "What business domains does this codebase implement?"
|
|
575
|
+
},
|
|
576
|
+
{
|
|
577
|
+
name: "analyze_codebase_impact",
|
|
578
|
+
title: "Diff impact analysis",
|
|
579
|
+
description: "Find files that depend on changed paths (reverse import graph). Supports manual paths, last push, GitHub compare, or fix PR files.",
|
|
580
|
+
scope: "mcp:read",
|
|
581
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
582
|
+
useCase: "What breaks if I change lib/auth.ts?"
|
|
583
|
+
},
|
|
584
|
+
{
|
|
585
|
+
name: "analyze_wiki_knowledge",
|
|
586
|
+
title: "Wiki knowledge graph",
|
|
587
|
+
description: "Return wiki/docs knowledge graph nodes and sources for the project. Use include_wiki on ask_codebase for RAG merge.",
|
|
588
|
+
scope: "mcp:read",
|
|
589
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
590
|
+
useCase: "What docs entities exist for onboarding?"
|
|
591
|
+
}
|
|
592
|
+
];
|
|
593
|
+
|
|
594
|
+
// src/feature-groups.ts
|
|
595
|
+
var FEATURE_GROUPS = [
|
|
596
|
+
"triage",
|
|
597
|
+
"fixes",
|
|
598
|
+
"inventory",
|
|
599
|
+
"setup",
|
|
600
|
+
"qa",
|
|
601
|
+
"skills",
|
|
602
|
+
"rewards",
|
|
603
|
+
"admin",
|
|
604
|
+
"audit",
|
|
605
|
+
"docs",
|
|
606
|
+
/** Deprecated aliases kept for one release — hidden unless `legacy` or `all`. */
|
|
607
|
+
"legacy"
|
|
608
|
+
];
|
|
609
|
+
var ALL_FEATURE_GROUPS = FEATURE_GROUPS.filter(
|
|
610
|
+
(g) => g !== "legacy"
|
|
611
|
+
);
|
|
612
|
+
var TOOL_FEATURE_MAP = {
|
|
613
|
+
// triage
|
|
614
|
+
get_recent_reports: "triage",
|
|
615
|
+
get_report_detail: "triage",
|
|
616
|
+
get_report_timeline: "triage",
|
|
617
|
+
search_reports: "triage",
|
|
618
|
+
get_similar_bugs: "triage",
|
|
619
|
+
get_fix_context: "triage",
|
|
620
|
+
get_fix_timeline: "triage",
|
|
621
|
+
get_blast_radius: "triage",
|
|
622
|
+
get_knowledge_graph: "triage",
|
|
623
|
+
run_nl_query: "triage",
|
|
624
|
+
get_report_evidence: "triage",
|
|
625
|
+
triage_issue: "triage",
|
|
626
|
+
query_lessons: "triage",
|
|
627
|
+
list_lessons: "triage",
|
|
628
|
+
fix_suggest: "triage",
|
|
629
|
+
graph_neighborhood: "triage",
|
|
630
|
+
graph_node_status: "triage",
|
|
631
|
+
get_reporter_thread: "legacy",
|
|
632
|
+
// inventory
|
|
633
|
+
inventory_get: "inventory",
|
|
634
|
+
inventory_diff: "inventory",
|
|
635
|
+
inventory_findings: "inventory",
|
|
636
|
+
inventory_current: "admin",
|
|
637
|
+
// setup
|
|
638
|
+
diagnose_connection: "setup",
|
|
639
|
+
diagnose_setup: "setup",
|
|
640
|
+
setup_check: "legacy",
|
|
641
|
+
ingest_setup_check: "legacy",
|
|
642
|
+
get_activation_status: "legacy",
|
|
643
|
+
activation_status: "setup",
|
|
644
|
+
project_integration_health: "setup",
|
|
645
|
+
setup_repo_for_mushi: "setup",
|
|
646
|
+
get_two_way_comms_health: "setup",
|
|
647
|
+
// fixes
|
|
648
|
+
submit_fix_result: "fixes",
|
|
649
|
+
dispatch_fix: "fixes",
|
|
650
|
+
trigger_judge: "fixes",
|
|
651
|
+
test_gen_from_report: "fixes",
|
|
652
|
+
transition_status: "fixes",
|
|
653
|
+
merge_fix: "fixes",
|
|
654
|
+
refresh_ci: "fixes",
|
|
655
|
+
reopen_report: "fixes",
|
|
656
|
+
reply_to_reporter: "fixes",
|
|
657
|
+
// rewards
|
|
658
|
+
list_top_contributors: "rewards",
|
|
659
|
+
award_bonus_points: "rewards",
|
|
660
|
+
set_tier: "rewards",
|
|
661
|
+
// admin / project context
|
|
662
|
+
list_projects: "admin",
|
|
663
|
+
get_account_overview: "admin",
|
|
664
|
+
get_project_context: "admin",
|
|
665
|
+
get_pipeline_logs: "admin",
|
|
666
|
+
project_dashboard: "admin",
|
|
667
|
+
project_stats: "admin",
|
|
668
|
+
project_settings: "admin",
|
|
669
|
+
privacy_status: "admin",
|
|
670
|
+
evolution_history: "admin",
|
|
671
|
+
// qa / tdd
|
|
672
|
+
map_user_stories: "qa",
|
|
673
|
+
get_map_run_status: "qa",
|
|
674
|
+
generate_tdd_from_story: "qa",
|
|
675
|
+
improve_qa_story: "qa",
|
|
676
|
+
run_qa_story: "qa",
|
|
677
|
+
list_pending_review_stories: "qa",
|
|
678
|
+
approve_qa_story: "qa",
|
|
679
|
+
list_qa_story_runs: "qa",
|
|
680
|
+
get_qa_story_run: "qa",
|
|
681
|
+
test_notification_channel: "qa",
|
|
682
|
+
// skills
|
|
683
|
+
list_skills: "skills",
|
|
684
|
+
get_skill: "skills",
|
|
685
|
+
start_skill_pipeline: "skills",
|
|
686
|
+
get_pipeline_run: "skills",
|
|
687
|
+
checkin_pipeline_step: "skills",
|
|
688
|
+
list_byok_keys: "skills",
|
|
689
|
+
add_byok_key: "skills",
|
|
690
|
+
// audit
|
|
691
|
+
run_fullstack_audit: "audit",
|
|
692
|
+
get_backend_health: "audit",
|
|
693
|
+
// docs
|
|
694
|
+
search_mushi_docs: "docs"
|
|
695
|
+
};
|
|
696
|
+
function parseFeaturesParam(raw) {
|
|
697
|
+
if (raw == null || raw.trim() === "") return "all";
|
|
698
|
+
const normalized = raw.trim().toLowerCase();
|
|
699
|
+
if (normalized === "all" || normalized === "*") return "all";
|
|
700
|
+
const groups = normalized.split(",").map((s) => s.trim()).filter(Boolean);
|
|
701
|
+
const valid = groups.filter((g) => FEATURE_GROUPS.includes(g));
|
|
702
|
+
if (valid.length === 0) return "all";
|
|
703
|
+
return valid;
|
|
704
|
+
}
|
|
705
|
+
function parseFeaturesCsv(raw) {
|
|
706
|
+
if (!raw?.trim()) return "all";
|
|
707
|
+
return parseFeaturesParam(raw);
|
|
708
|
+
}
|
|
709
|
+
function toolMatchesFeatures(toolName, filter) {
|
|
710
|
+
if (filter === "all") return true;
|
|
711
|
+
const group = TOOL_FEATURE_MAP[toolName];
|
|
712
|
+
if (!group) return true;
|
|
713
|
+
if (group === "legacy") {
|
|
714
|
+
return filter.includes("legacy");
|
|
715
|
+
}
|
|
716
|
+
return filter.includes(group);
|
|
717
|
+
}
|
|
511
718
|
|
|
512
719
|
// src/server.ts
|
|
513
720
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
514
721
|
import { z } from "zod";
|
|
722
|
+
import { createLogger } from "@mushi-mushi/core";
|
|
723
|
+
|
|
724
|
+
// src/branding.ts
|
|
725
|
+
var MUSHI_WEBSITE_URL = "https://kensaur.us/mushi-mushi";
|
|
726
|
+
var MUSHI_ICON_PNG_URL = `${MUSHI_WEBSITE_URL}/integrations/mushi-mark-512.png`;
|
|
727
|
+
var MUSHI_ICON_SVG_URL = `${MUSHI_WEBSITE_URL}/favicon.svg`;
|
|
728
|
+
var MUSHI_SERVER_ICONS = [
|
|
729
|
+
{ src: MUSHI_ICON_PNG_URL, mimeType: "image/png", sizes: ["512x512"] },
|
|
730
|
+
{ src: MUSHI_ICON_SVG_URL, mimeType: "image/svg+xml", sizes: ["any"] }
|
|
731
|
+
];
|
|
732
|
+
var MUSHI_SERVER_METADATA = {
|
|
733
|
+
name: "mushi-mushi",
|
|
734
|
+
title: "Mushi Mushi",
|
|
735
|
+
description: "User-felt bug triage, fix dispatch, QA stories, and inventory spec traceability for AI coding agents.",
|
|
736
|
+
websiteUrl: MUSHI_WEBSITE_URL,
|
|
737
|
+
icons: MUSHI_SERVER_ICONS
|
|
738
|
+
};
|
|
739
|
+
|
|
740
|
+
// src/docs-index.ts
|
|
741
|
+
var BASE = "https://kensaur.us/mushi-mushi/docs";
|
|
742
|
+
var MUSHI_DOCS_INDEX = [
|
|
743
|
+
{
|
|
744
|
+
title: "Architecture overview",
|
|
745
|
+
path: `${BASE}/concepts/architecture`,
|
|
746
|
+
keywords: ["architecture", "evolution loop", "pdca", "overview"],
|
|
747
|
+
excerpt: "How Mushi captures user-felt bugs, triages, dispatches fixes, and learns."
|
|
748
|
+
},
|
|
749
|
+
{
|
|
750
|
+
title: "MCP setup",
|
|
751
|
+
path: `${BASE}/guides/mcp-setup`,
|
|
752
|
+
keywords: ["mcp", "cursor", "api key", "stdio", "http", "multi-project", "account mode", "install", "setup", "deeplink"],
|
|
753
|
+
excerpt: "Wire Mushi MCP into Cursor, Claude, or any MCP client. Supports per-project and account-level keys."
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
title: "API key scopes",
|
|
757
|
+
path: `${BASE}/guides/api-keys`,
|
|
758
|
+
keywords: ["api key", "mcp:read", "mcp:write", "scopes", "security", "account key", "org-scoped", "multi-project"],
|
|
759
|
+
excerpt: "Separate SDK ingest keys from MCP read/write keys. Mint an org-scoped account key for multi-project access."
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
title: "Account key (org-scoped)",
|
|
763
|
+
path: `${BASE}/guides/mcp-setup`,
|
|
764
|
+
keywords: ["account key", "org-scoped", "multi-project", "MUSHI_PROJECT_ID", "all projects", "mint-org-key", "get_account_overview"],
|
|
765
|
+
excerpt: "Mint one org-scoped key that covers all your projects; no MUSHI_PROJECT_ID needed. The agent calls get_account_overview to discover projects."
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
title: "Cursor MCP troubleshooting",
|
|
769
|
+
path: `${BASE}/guides/mcp-setup`,
|
|
770
|
+
keywords: ["cursor", "mcp.json", "red badge", "transport error", "stdout", "stderr", "json-rpc", "INVALID_TOKEN", "syntax helper", "diagnose_connection"],
|
|
771
|
+
excerpt: "Cursor stdio MCP requires stdout to contain only JSON-RPC. Use the console syntax helper, restart Cursor MCP, then run diagnose_connection."
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
title: "Judge loop",
|
|
775
|
+
path: `${BASE}/concepts/judge-loop`,
|
|
776
|
+
keywords: ["judge", "sonnet", "quality", "fix scoring"],
|
|
777
|
+
excerpt: "Sonnet-as-Judge scores fix quality and feeds the lesson library."
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
title: "Inventory v2",
|
|
781
|
+
path: `${BASE}/concepts/inventory`,
|
|
782
|
+
keywords: ["inventory", "user stories", "spec", "yaml", "actions"],
|
|
783
|
+
excerpt: "inventory.yaml maps pages, stories, and expected outcomes for spec traceability."
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
title: "QA coverage stories",
|
|
787
|
+
path: `${BASE}/guides/qa-stories`,
|
|
788
|
+
keywords: ["qa", "playwright", "tdd", "cron", "browserbase", "firecrawl"],
|
|
789
|
+
excerpt: "Author, approve, and schedule user-story tests."
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
title: "Skill pipelines",
|
|
793
|
+
path: `${BASE}/guides/skill-pipelines`,
|
|
794
|
+
keywords: ["skills", "pipeline", "cursor", "workflow"],
|
|
795
|
+
excerpt: "Attach agent skills to reports and run handoff or cloud pipelines."
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
title: "Dispatch fix",
|
|
799
|
+
path: `${BASE}/guides/dispatch-fix`,
|
|
800
|
+
keywords: ["dispatch", "fix", "pr", "cursor cloud", "autofix"],
|
|
801
|
+
excerpt: "Dispatch the Mushi fix agent or Cursor Cloud for a classified report."
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
title: "SDK installation",
|
|
805
|
+
path: `${BASE}/guides/sdk`,
|
|
806
|
+
keywords: ["sdk", "install", "react", "next", "ingest"],
|
|
807
|
+
excerpt: "Install @mushi-mushi/web and start capturing user reports."
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
title: "Configuration reference",
|
|
811
|
+
path: `${BASE}/reference/config`,
|
|
812
|
+
keywords: ["env", "config", "settings", "byok"],
|
|
813
|
+
excerpt: "Environment variables and project settings reference."
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
title: "Evolution loop",
|
|
817
|
+
path: `${BASE}/concepts/evolution`,
|
|
818
|
+
keywords: ["evolution", "lessons", "convergence", "prompt promotion"],
|
|
819
|
+
excerpt: "Closed-loop learning from bugs, fixes, and judge scores."
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
title: "Privacy & BYOK",
|
|
823
|
+
path: `${BASE}/guides/privacy`,
|
|
824
|
+
keywords: ["privacy", "byok", "data region", "llm"],
|
|
825
|
+
excerpt: "Data boundaries, BYOK LLM keys, and retention."
|
|
826
|
+
}
|
|
827
|
+
];
|
|
828
|
+
function searchMushiDocs(query, limit = 8) {
|
|
829
|
+
const q = query.trim().toLowerCase();
|
|
830
|
+
if (!q) {
|
|
831
|
+
return MUSHI_DOCS_INDEX.slice(0, limit).map((e) => ({ ...e, score: 0 }));
|
|
832
|
+
}
|
|
833
|
+
const terms = q.split(/\s+/).filter(Boolean);
|
|
834
|
+
const scored = MUSHI_DOCS_INDEX.map((entry) => {
|
|
835
|
+
const hay = `${entry.title} ${entry.keywords.join(" ")} ${entry.excerpt}`.toLowerCase();
|
|
836
|
+
let score = 0;
|
|
837
|
+
for (const term of terms) {
|
|
838
|
+
if (entry.title.toLowerCase().includes(term)) score += 4;
|
|
839
|
+
if (entry.keywords.some((k) => k.includes(term))) score += 3;
|
|
840
|
+
if (hay.includes(term)) score += 1;
|
|
841
|
+
}
|
|
842
|
+
return { ...entry, score };
|
|
843
|
+
}).filter((e) => e.score > 0).sort((a, b) => b.score - a.score);
|
|
844
|
+
return scored.slice(0, limit);
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
// src/server.ts
|
|
515
848
|
var MushiApiError = class extends Error {
|
|
516
849
|
constructor(status, code, message) {
|
|
517
850
|
super(`[${code}] ${message}`);
|
|
@@ -525,10 +858,23 @@ var MushiApiError = class extends Error {
|
|
|
525
858
|
function createMushiServer(config) {
|
|
526
859
|
const { version, apiEndpoint, apiKey, projectId } = config;
|
|
527
860
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
861
|
+
const apiLog = createLogger({
|
|
862
|
+
scope: "mushi:mcp:api",
|
|
863
|
+
level: process.env.MUSHI_LOG_LEVEL ?? "info",
|
|
864
|
+
destination: "stderr"
|
|
865
|
+
});
|
|
528
866
|
if (config.scopes !== void 0 && config.scopes.length === 0) {
|
|
529
|
-
return new McpServer({
|
|
867
|
+
return new McpServer({
|
|
868
|
+
name: MUSHI_SERVER_METADATA.name,
|
|
869
|
+
version,
|
|
870
|
+
title: MUSHI_SERVER_METADATA.title,
|
|
871
|
+
websiteUrl: MUSHI_SERVER_METADATA.websiteUrl,
|
|
872
|
+
icons: [...MUSHI_SERVER_METADATA.icons]
|
|
873
|
+
});
|
|
530
874
|
}
|
|
531
875
|
async function apiCall(path, options) {
|
|
876
|
+
const requestId = crypto.randomUUID().slice(0, 12);
|
|
877
|
+
const started = Date.now();
|
|
532
878
|
const res = await doFetch(`${apiEndpoint}${path}`, {
|
|
533
879
|
...options,
|
|
534
880
|
headers: {
|
|
@@ -538,10 +884,12 @@ function createMushiServer(config) {
|
|
|
538
884
|
// and for transparent proxies that strip X-Mushi-* headers.
|
|
539
885
|
"Authorization": `Bearer ${apiKey}`,
|
|
540
886
|
"X-Mushi-Api-Key": apiKey,
|
|
541
|
-
|
|
887
|
+
"X-Request-Id": requestId,
|
|
888
|
+
...projectId ? { "X-Mushi-Project-Id": projectId } : {},
|
|
542
889
|
...options?.headers ?? {}
|
|
543
890
|
}
|
|
544
891
|
});
|
|
892
|
+
const durationMs = Date.now() - started;
|
|
545
893
|
const text = await res.text();
|
|
546
894
|
let body = null;
|
|
547
895
|
if (text) {
|
|
@@ -555,6 +903,7 @@ function createMushiServer(config) {
|
|
|
555
903
|
const envelope2 = body;
|
|
556
904
|
const code = envelope2?.error?.code ?? `HTTP_${res.status}`;
|
|
557
905
|
const message = envelope2?.error?.message ?? text.slice(0, 500) ?? `Request failed with ${res.status}`;
|
|
906
|
+
apiLog.warn("api.failed", { path, requestId, status: res.status, durationMs, code });
|
|
558
907
|
throw new MushiApiError(res.status, code, message);
|
|
559
908
|
}
|
|
560
909
|
const envelope = body;
|
|
@@ -562,12 +911,47 @@ function createMushiServer(config) {
|
|
|
562
911
|
if (!envelope.ok) {
|
|
563
912
|
const code = envelope.error?.code ?? "API_ERROR";
|
|
564
913
|
const message = envelope.error?.message ?? "API returned ok=false";
|
|
914
|
+
apiLog.warn("api.failed", { path, requestId, status: res.status, durationMs, code });
|
|
565
915
|
throw new MushiApiError(res.status, code, message);
|
|
566
916
|
}
|
|
917
|
+
apiLog.info("api.done", { path, requestId, status: res.status, durationMs });
|
|
567
918
|
return envelope.data ?? {};
|
|
568
919
|
}
|
|
920
|
+
apiLog.info("api.done", { path, requestId, status: res.status, durationMs });
|
|
569
921
|
return body;
|
|
570
922
|
}
|
|
923
|
+
async function resolveProjectId(explicitId) {
|
|
924
|
+
if (explicitId) return explicitId;
|
|
925
|
+
if (projectId) return projectId;
|
|
926
|
+
try {
|
|
927
|
+
const data = await apiCall(
|
|
928
|
+
"/v1/admin/mcp/account-overview"
|
|
929
|
+
);
|
|
930
|
+
if (data.total === 1 && data.projects[0]) {
|
|
931
|
+
return data.projects[0].id;
|
|
932
|
+
}
|
|
933
|
+
if (data.total === 0) {
|
|
934
|
+
throw new MushiApiError(
|
|
935
|
+
400,
|
|
936
|
+
"NO_ACCESSIBLE_PROJECTS",
|
|
937
|
+
"This API key has no accessible projects. Mint an API key on a project in the admin console."
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
const projectList = data.projects.map((p) => `${p.name ?? "unnamed"} (${p.id})`).join(", ");
|
|
941
|
+
throw new MushiApiError(
|
|
942
|
+
400,
|
|
943
|
+
"PROJECT_ID_REQUIRED",
|
|
944
|
+
`Multiple projects are accessible with this key: ${projectList}. Pass projectId explicitly to target a specific project, or set MUSHI_PROJECT_ID in your MCP env config.`
|
|
945
|
+
);
|
|
946
|
+
} catch (err) {
|
|
947
|
+
if (err instanceof MushiApiError) throw err;
|
|
948
|
+
throw new MushiApiError(
|
|
949
|
+
400,
|
|
950
|
+
"MISSING_PROJECT_ID",
|
|
951
|
+
"projectId is required. Set MUSHI_PROJECT_ID in .cursor/mcp.json or pass it explicitly."
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
571
955
|
function jsonText(value) {
|
|
572
956
|
return {
|
|
573
957
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
@@ -580,10 +964,13 @@ function createMushiServer(config) {
|
|
|
580
964
|
};
|
|
581
965
|
}
|
|
582
966
|
const server = new McpServer({
|
|
583
|
-
name:
|
|
584
|
-
version
|
|
967
|
+
name: MUSHI_SERVER_METADATA.name,
|
|
968
|
+
version,
|
|
969
|
+
title: MUSHI_SERVER_METADATA.title,
|
|
970
|
+
websiteUrl: MUSHI_SERVER_METADATA.websiteUrl,
|
|
971
|
+
icons: [...MUSHI_SERVER_METADATA.icons]
|
|
585
972
|
});
|
|
586
|
-
const ALL_TOOL_CATALOG = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG];
|
|
973
|
+
const ALL_TOOL_CATALOG = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG, ...CODEBASE_TOOL_CATALOG];
|
|
587
974
|
function annotationsFor(name, catalog = ALL_TOOL_CATALOG) {
|
|
588
975
|
const spec = catalog.find((t) => t.name === name);
|
|
589
976
|
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
@@ -616,7 +1003,10 @@ function createMushiServer(config) {
|
|
|
616
1003
|
status: z.string().optional().describe("Filter by status: new, classified, grouped, fixing, fixed, dismissed"),
|
|
617
1004
|
category: z.string().optional().describe("Filter by category: bug, slow, visual, confusing, other"),
|
|
618
1005
|
severity: z.string().optional().describe("Filter by severity: critical, high, medium, low"),
|
|
619
|
-
limit: z.number().optional().describe("Max reports to return (default 20, max 100)")
|
|
1006
|
+
limit: z.number().optional().describe("Max reports to return (default 20, max 100)"),
|
|
1007
|
+
project_id: z.string().optional().describe(
|
|
1008
|
+
"Project UUID \u2014 defaults to the server-configured project. Useful when you have multiple projects and want to query a specific one by its ID. Get IDs by calling list_projects or get_account_overview first."
|
|
1009
|
+
)
|
|
620
1010
|
},
|
|
621
1011
|
outputSchema: {
|
|
622
1012
|
reports: z.array(z.unknown()),
|
|
@@ -629,7 +1019,11 @@ function createMushiServer(config) {
|
|
|
629
1019
|
if (args.category) params.set("category", args.category);
|
|
630
1020
|
if (args.severity) params.set("severity", args.severity);
|
|
631
1021
|
params.set("limit", String(Math.min(args.limit ?? 20, 100)));
|
|
632
|
-
const
|
|
1022
|
+
const pid = await resolveProjectId(args.project_id);
|
|
1023
|
+
const extraHeaders = pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
|
|
1024
|
+
const data = await apiCall(`/v1/admin/reports?${params}`, {
|
|
1025
|
+
headers: extraHeaders
|
|
1026
|
+
});
|
|
633
1027
|
return jsonResult(data);
|
|
634
1028
|
}
|
|
635
1029
|
);
|
|
@@ -639,9 +1033,19 @@ function createMushiServer(config) {
|
|
|
639
1033
|
title: titleOf("get_report_detail"),
|
|
640
1034
|
description: descOf("get_report_detail"),
|
|
641
1035
|
annotations: annotationsFor("get_report_detail"),
|
|
642
|
-
inputSchema: { reportId: z.string().describe("The report UUID") }
|
|
1036
|
+
inputSchema: { reportId: z.string().describe("The report UUID") },
|
|
1037
|
+
outputSchema: { report: z.unknown() }
|
|
643
1038
|
},
|
|
644
|
-
async (args) =>
|
|
1039
|
+
async (args) => {
|
|
1040
|
+
const data = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
1041
|
+
const res = jsonResult({ report: data });
|
|
1042
|
+
return {
|
|
1043
|
+
...res,
|
|
1044
|
+
resource_links: [
|
|
1045
|
+
{ uri: `project://reports/${args.reportId}`, title: `Report ${args.reportId.slice(0, 8)}\u2026` }
|
|
1046
|
+
]
|
|
1047
|
+
};
|
|
1048
|
+
}
|
|
645
1049
|
);
|
|
646
1050
|
server.registerTool(
|
|
647
1051
|
"get_report_timeline",
|
|
@@ -700,6 +1104,9 @@ function createMushiServer(config) {
|
|
|
700
1104
|
inputSchema: {
|
|
701
1105
|
query: z.string().describe("Component name, page path, or bug description"),
|
|
702
1106
|
limit: z.number().optional().describe("Max results (default 5, max 20)")
|
|
1107
|
+
},
|
|
1108
|
+
outputSchema: {
|
|
1109
|
+
results: z.array(z.unknown())
|
|
703
1110
|
}
|
|
704
1111
|
},
|
|
705
1112
|
async (args) => {
|
|
@@ -712,7 +1119,7 @@ function createMushiServer(config) {
|
|
|
712
1119
|
...projectId ? { projectId } : {}
|
|
713
1120
|
})
|
|
714
1121
|
});
|
|
715
|
-
return
|
|
1122
|
+
return jsonResult(data);
|
|
716
1123
|
}
|
|
717
1124
|
);
|
|
718
1125
|
server.registerTool(
|
|
@@ -721,11 +1128,18 @@ function createMushiServer(config) {
|
|
|
721
1128
|
title: titleOf("get_fix_context"),
|
|
722
1129
|
description: descOf("get_fix_context"),
|
|
723
1130
|
annotations: annotationsFor("get_fix_context"),
|
|
724
|
-
inputSchema: { reportId: z.string().describe("The report UUID to fix") }
|
|
1131
|
+
inputSchema: { reportId: z.string().describe("The report UUID to fix") },
|
|
1132
|
+
outputSchema: {
|
|
1133
|
+
report: z.unknown(),
|
|
1134
|
+
reproductionSteps: z.unknown(),
|
|
1135
|
+
component: z.unknown(),
|
|
1136
|
+
rootCause: z.unknown(),
|
|
1137
|
+
bugOntologyTags: z.unknown()
|
|
1138
|
+
}
|
|
725
1139
|
},
|
|
726
1140
|
async (args) => {
|
|
727
1141
|
const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
728
|
-
return
|
|
1142
|
+
return jsonResult({
|
|
729
1143
|
report,
|
|
730
1144
|
reproductionSteps: report.reproduction_steps ?? [],
|
|
731
1145
|
component: report.component,
|
|
@@ -963,13 +1377,115 @@ function createMushiServer(config) {
|
|
|
963
1377
|
});
|
|
964
1378
|
}
|
|
965
1379
|
);
|
|
1380
|
+
server.registerTool(
|
|
1381
|
+
"diagnose_setup",
|
|
1382
|
+
{
|
|
1383
|
+
title: titleOf("diagnose_setup"),
|
|
1384
|
+
description: descOf("diagnose_setup"),
|
|
1385
|
+
annotations: annotationsFor("diagnose_setup"),
|
|
1386
|
+
inputSchema: {
|
|
1387
|
+
mode: z.enum(["full", "ingest", "dispatch"]).optional().describe("full (default) = ingest + dispatch; ingest = SDK pipeline only; dispatch = fix preflight only."),
|
|
1388
|
+
project_id: z.string().optional().describe("Project UUID for dispatch checks (defaults to configured project)."),
|
|
1389
|
+
projectId: z.string().optional().describe("Alias for project_id.")
|
|
1390
|
+
},
|
|
1391
|
+
outputSchema: {
|
|
1392
|
+
mode: z.string(),
|
|
1393
|
+
ready: z.boolean(),
|
|
1394
|
+
summary: z.string(),
|
|
1395
|
+
nextAction: z.string().optional(),
|
|
1396
|
+
ingest: z.unknown().optional(),
|
|
1397
|
+
dispatch: z.unknown().optional()
|
|
1398
|
+
}
|
|
1399
|
+
},
|
|
1400
|
+
async (args) => {
|
|
1401
|
+
const mode = args.mode ?? "full";
|
|
1402
|
+
const resolvedId = args.project_id ?? args.projectId ?? projectId;
|
|
1403
|
+
if (mode === "ingest") {
|
|
1404
|
+
const data = await apiCall("/v1/sync/ingest-setup");
|
|
1405
|
+
const failed = data.steps.filter((s) => s.required && !s.complete);
|
|
1406
|
+
const summary2 = data.ready ? `Ingest setup complete (${data.required_complete}/${data.required_total}) for ${data.project_name}.` : `Ingest incomplete \u2014 still need: ${failed.map((s) => s.label).join(", ")}.`;
|
|
1407
|
+
return jsonResult({
|
|
1408
|
+
mode: "ingest",
|
|
1409
|
+
ready: data.ready,
|
|
1410
|
+
summary: summary2,
|
|
1411
|
+
nextAction: failed[0]?.hint,
|
|
1412
|
+
ingest: {
|
|
1413
|
+
projectId: data.project_id,
|
|
1414
|
+
projectName: data.project_name,
|
|
1415
|
+
requiredComplete: data.required_complete,
|
|
1416
|
+
requiredTotal: data.required_total,
|
|
1417
|
+
steps: data.steps,
|
|
1418
|
+
diagnostic: data.diagnostic ?? null
|
|
1419
|
+
}
|
|
1420
|
+
});
|
|
1421
|
+
}
|
|
1422
|
+
if (mode === "dispatch") {
|
|
1423
|
+
if (!resolvedId) {
|
|
1424
|
+
throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required for dispatch mode");
|
|
1425
|
+
}
|
|
1426
|
+
const data = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
1427
|
+
const failed = data.checks.filter((c) => !c.ready);
|
|
1428
|
+
const summary2 = data.ready ? `Project ${resolvedId} is ready to dispatch auto-fixes.` : `Dispatch blocked \u2014 ${failed.map((c) => c.label).join(", ")}.`;
|
|
1429
|
+
return jsonResult({
|
|
1430
|
+
mode: "dispatch",
|
|
1431
|
+
ready: data.ready,
|
|
1432
|
+
summary: summary2,
|
|
1433
|
+
nextAction: failed[0]?.hint,
|
|
1434
|
+
dispatch: { repoUrl: data.repoUrl ?? null, checks: data.checks }
|
|
1435
|
+
});
|
|
1436
|
+
}
|
|
1437
|
+
const ingest = await apiCall("/v1/sync/ingest-setup");
|
|
1438
|
+
let dispatchReady = true;
|
|
1439
|
+
let dispatchBlock;
|
|
1440
|
+
let dispatchChecks = null;
|
|
1441
|
+
if (resolvedId) {
|
|
1442
|
+
try {
|
|
1443
|
+
const preflight = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
1444
|
+
dispatchReady = preflight.ready;
|
|
1445
|
+
dispatchChecks = preflight.checks;
|
|
1446
|
+
if (!preflight.ready) {
|
|
1447
|
+
const failed = preflight.checks.filter((c) => !c.ready);
|
|
1448
|
+
dispatchBlock = failed[0]?.hint;
|
|
1449
|
+
}
|
|
1450
|
+
} catch {
|
|
1451
|
+
dispatchReady = false;
|
|
1452
|
+
dispatchBlock = "Could not run dispatch preflight \u2014 verify project_id and API key scope.";
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
const ingestFailed = ingest.steps.filter((s) => s.required && !s.complete);
|
|
1456
|
+
const ingestBlock = ingestFailed[0]?.hint;
|
|
1457
|
+
const ready = ingest.ready && dispatchReady;
|
|
1458
|
+
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.";
|
|
1459
|
+
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.";
|
|
1460
|
+
return jsonResult({
|
|
1461
|
+
mode: "full",
|
|
1462
|
+
ready,
|
|
1463
|
+
summary,
|
|
1464
|
+
nextAction,
|
|
1465
|
+
ingest: { ready: ingest.ready, steps: ingest.steps },
|
|
1466
|
+
dispatch: resolvedId ? { ready: dispatchReady, checks: dispatchChecks } : null
|
|
1467
|
+
});
|
|
1468
|
+
}
|
|
1469
|
+
);
|
|
966
1470
|
server.registerTool(
|
|
967
1471
|
"diagnose_connection",
|
|
968
1472
|
{
|
|
969
1473
|
title: titleOf("diagnose_connection"),
|
|
970
1474
|
description: descOf("diagnose_connection"),
|
|
971
1475
|
annotations: annotationsFor("diagnose_connection"),
|
|
972
|
-
inputSchema: {}
|
|
1476
|
+
inputSchema: {},
|
|
1477
|
+
outputSchema: {
|
|
1478
|
+
ready: z.boolean(),
|
|
1479
|
+
healthOk: z.boolean(),
|
|
1480
|
+
ingestReady: z.boolean(),
|
|
1481
|
+
dispatchReady: z.boolean(),
|
|
1482
|
+
endpoint: z.string().nullable(),
|
|
1483
|
+
projectId: z.string().nullable(),
|
|
1484
|
+
accessibleProjectCount: z.number().nullable(),
|
|
1485
|
+
issues: z.array(z.unknown()),
|
|
1486
|
+
nextAction: z.string(),
|
|
1487
|
+
summary: z.string()
|
|
1488
|
+
}
|
|
973
1489
|
},
|
|
974
1490
|
async () => {
|
|
975
1491
|
const issues = [];
|
|
@@ -980,13 +1496,6 @@ function createMushiServer(config) {
|
|
|
980
1496
|
fix: "Run `mushi connect` to write MUSHI_API_KEY into .cursor/mcp.json, then restart the MCP server."
|
|
981
1497
|
});
|
|
982
1498
|
}
|
|
983
|
-
if (!projectId) {
|
|
984
|
-
issues.push({
|
|
985
|
-
check: "mcp_project_id",
|
|
986
|
-
detail: "No projectId configured on the MCP server",
|
|
987
|
-
fix: "Add MUSHI_PROJECT_ID to .cursor/mcp.json (copy UUID from console \u2192 Projects)."
|
|
988
|
-
});
|
|
989
|
-
}
|
|
990
1499
|
if (!apiEndpoint) {
|
|
991
1500
|
issues.push({
|
|
992
1501
|
check: "mcp_endpoint",
|
|
@@ -1018,6 +1527,21 @@ function createMushiServer(config) {
|
|
|
1018
1527
|
}
|
|
1019
1528
|
let ingestReady = false;
|
|
1020
1529
|
let dispatchReady = false;
|
|
1530
|
+
let accessibleProjectCount = null;
|
|
1531
|
+
if (!projectId && healthOk) {
|
|
1532
|
+
try {
|
|
1533
|
+
const overview = await apiCall("/v1/admin/mcp/account-overview");
|
|
1534
|
+
accessibleProjectCount = overview.total;
|
|
1535
|
+
if (overview.total === 0) {
|
|
1536
|
+
issues.push({
|
|
1537
|
+
check: "no_accessible_projects",
|
|
1538
|
+
detail: "API key has no accessible projects",
|
|
1539
|
+
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."
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
} catch {
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1021
1545
|
try {
|
|
1022
1546
|
const ingest = await apiCall("/v1/sync/ingest-setup");
|
|
1023
1547
|
ingestReady = ingest.ready;
|
|
@@ -1053,30 +1577,99 @@ function createMushiServer(config) {
|
|
|
1053
1577
|
}
|
|
1054
1578
|
const ready = issues.length === 0 && healthOk && ingestReady;
|
|
1055
1579
|
const nextAction = issues[0]?.fix ?? (ready ? "Connection healthy \u2014 SDK ingest is working. Submit a report to confirm end-to-end." : "Run `mushi doctor` in your app repo for a full local checklist.");
|
|
1056
|
-
return
|
|
1580
|
+
return jsonResult({
|
|
1057
1581
|
ready,
|
|
1058
1582
|
healthOk,
|
|
1059
1583
|
ingestReady,
|
|
1060
1584
|
dispatchReady,
|
|
1061
1585
|
endpoint: apiEndpoint ?? null,
|
|
1062
1586
|
projectId: projectId ?? null,
|
|
1587
|
+
accessibleProjectCount,
|
|
1063
1588
|
issues,
|
|
1064
1589
|
nextAction,
|
|
1065
1590
|
summary: ready ? "MCP credentials valid; ingest pipeline ready." : `Connection issue \u2014 ${issues[0]?.check ?? "unknown"}: ${nextAction}`
|
|
1066
1591
|
});
|
|
1067
1592
|
}
|
|
1068
1593
|
);
|
|
1594
|
+
server.registerTool(
|
|
1595
|
+
"search_mushi_docs",
|
|
1596
|
+
{
|
|
1597
|
+
title: titleOf("search_mushi_docs"),
|
|
1598
|
+
description: descOf("search_mushi_docs"),
|
|
1599
|
+
annotations: annotationsFor("search_mushi_docs"),
|
|
1600
|
+
inputSchema: {
|
|
1601
|
+
query: z.string().describe("Keywords to search official Mushi docs (guides, MCP, inventory, QA)."),
|
|
1602
|
+
limit: z.number().int().min(1).max(20).optional().describe("Max results (default 8).")
|
|
1603
|
+
},
|
|
1604
|
+
outputSchema: {
|
|
1605
|
+
query: z.string(),
|
|
1606
|
+
results: z.array(
|
|
1607
|
+
z.object({
|
|
1608
|
+
title: z.string(),
|
|
1609
|
+
path: z.string(),
|
|
1610
|
+
excerpt: z.string(),
|
|
1611
|
+
score: z.number()
|
|
1612
|
+
})
|
|
1613
|
+
)
|
|
1614
|
+
}
|
|
1615
|
+
},
|
|
1616
|
+
async (args) => {
|
|
1617
|
+
const query = args.query ?? "";
|
|
1618
|
+
const hits = searchMushiDocs(query, args.limit ?? 8);
|
|
1619
|
+
const results = hits.map(({ title, path, excerpt, score }) => ({ title, path, excerpt, score }));
|
|
1620
|
+
return jsonResult({ query, results });
|
|
1621
|
+
}
|
|
1622
|
+
);
|
|
1069
1623
|
server.registerTool(
|
|
1070
1624
|
"list_projects",
|
|
1071
1625
|
{
|
|
1072
1626
|
title: titleOf("list_projects"),
|
|
1073
1627
|
description: descOf("list_projects"),
|
|
1074
1628
|
annotations: annotationsFor("list_projects"),
|
|
1075
|
-
inputSchema: {}
|
|
1629
|
+
inputSchema: {},
|
|
1630
|
+
outputSchema: {
|
|
1631
|
+
projects: z.array(z.unknown()),
|
|
1632
|
+
total: z.number().optional(),
|
|
1633
|
+
_multi_project_hint: z.string().optional()
|
|
1634
|
+
}
|
|
1076
1635
|
},
|
|
1077
1636
|
async () => {
|
|
1078
1637
|
const data = await apiCall("/v1/admin/mcp/projects");
|
|
1079
|
-
return
|
|
1638
|
+
return jsonResult({
|
|
1639
|
+
...data,
|
|
1640
|
+
_multi_project_hint: 'Each Mushi project uses a separate API key. To connect another project, open the Mushi console \u2192 MCP \u2192 Setup, pick the project from the switcher, and click "\u26A1 Add to Cursor". Each deeplink adds a uniquely-named server entry (mushi-{project-name}-{id-prefix}) so all your projects appear in your IDE simultaneously.'
|
|
1641
|
+
});
|
|
1642
|
+
}
|
|
1643
|
+
);
|
|
1644
|
+
server.registerTool(
|
|
1645
|
+
"get_account_overview",
|
|
1646
|
+
{
|
|
1647
|
+
title: titleOf("get_account_overview"),
|
|
1648
|
+
description: descOf("get_account_overview"),
|
|
1649
|
+
annotations: annotationsFor("get_account_overview"),
|
|
1650
|
+
inputSchema: {},
|
|
1651
|
+
outputSchema: {
|
|
1652
|
+
projects: z.array(z.unknown()),
|
|
1653
|
+
total: z.number(),
|
|
1654
|
+
active_project_id: z.string().nullable(),
|
|
1655
|
+
toolCount: z.number().optional(),
|
|
1656
|
+
resourceCount: z.number().optional(),
|
|
1657
|
+
promptCount: z.number().optional(),
|
|
1658
|
+
multi_project_hint: z.string()
|
|
1659
|
+
}
|
|
1660
|
+
},
|
|
1661
|
+
async () => {
|
|
1662
|
+
const data = await apiCall("/v1/admin/mcp/account-overview");
|
|
1663
|
+
const projects = Array.isArray(data?.projects) ? data.projects : [];
|
|
1664
|
+
return jsonResult({
|
|
1665
|
+
projects,
|
|
1666
|
+
total: data?.total ?? projects.length,
|
|
1667
|
+
active_project_id: projectId ?? null,
|
|
1668
|
+
toolCount: data?.toolCount,
|
|
1669
|
+
resourceCount: data?.resourceCount,
|
|
1670
|
+
promptCount: data?.promptCount,
|
|
1671
|
+
multi_project_hint: projects.length <= 1 ? 'You are currently connected to one project. To connect additional Mushi projects, open the Mushi console \u2192 switch to each project \u2192 MCP \u2192 Setup \u2192 "\u26A1 Add to Cursor". Each click adds a uniquely-named entry (mushi-{name}-{id}) to your global ~/.cursor/mcp.json so you can triage reports across all your apps from a single Cursor session.' : `You have access to ${projects.length} projects. To connect any unconnected project, open the Mushi console \u2192 switch to that project \u2192 MCP \u2192 Setup \u2192 "\u26A1 Add to Cursor".`
|
|
1672
|
+
});
|
|
1080
1673
|
}
|
|
1081
1674
|
);
|
|
1082
1675
|
server.registerTool(
|
|
@@ -1176,6 +1769,20 @@ function createMushiServer(config) {
|
|
|
1176
1769
|
report_id: z.string().describe("Report UUID to triage."),
|
|
1177
1770
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
|
|
1178
1771
|
include_logs: z.boolean().optional().describe("Include recent pipeline logs in triage packet (default: true).")
|
|
1772
|
+
},
|
|
1773
|
+
outputSchema: {
|
|
1774
|
+
report_id: z.string(),
|
|
1775
|
+
severity: z.unknown(),
|
|
1776
|
+
category: z.unknown(),
|
|
1777
|
+
status: z.unknown(),
|
|
1778
|
+
report: z.unknown(),
|
|
1779
|
+
reporter_thread: z.unknown().nullable(),
|
|
1780
|
+
similar_bugs: z.unknown().nullable(),
|
|
1781
|
+
fix_context: z.unknown().nullable(),
|
|
1782
|
+
blast_radius: z.unknown().nullable(),
|
|
1783
|
+
recent_logs: z.unknown().nullable(),
|
|
1784
|
+
recommended_actions: z.array(z.unknown()),
|
|
1785
|
+
triage_summary: z.string()
|
|
1179
1786
|
}
|
|
1180
1787
|
},
|
|
1181
1788
|
async (args) => {
|
|
@@ -1219,7 +1826,7 @@ function createMushiServer(config) {
|
|
|
1219
1826
|
args: { reportId: args.report_id }
|
|
1220
1827
|
});
|
|
1221
1828
|
}
|
|
1222
|
-
|
|
1829
|
+
const result = {
|
|
1223
1830
|
report_id: args.report_id,
|
|
1224
1831
|
severity,
|
|
1225
1832
|
category,
|
|
@@ -1232,7 +1839,15 @@ function createMushiServer(config) {
|
|
|
1232
1839
|
recent_logs: logsRes.status === "fulfilled" ? logsRes.value : null,
|
|
1233
1840
|
recommended_actions: actions,
|
|
1234
1841
|
triage_summary: `[${severity}] ${category} \u2014 status: ${status}. ${actions.length > 0 ? `Recommended: ${actions[0]?.action}.` : "No action required."}`
|
|
1235
|
-
}
|
|
1842
|
+
};
|
|
1843
|
+
const res = jsonResult(result);
|
|
1844
|
+
return {
|
|
1845
|
+
...res,
|
|
1846
|
+
resource_links: [
|
|
1847
|
+
{ uri: `project://reports/${args.report_id}`, title: `Report ${args.report_id.slice(0, 8)}\u2026` },
|
|
1848
|
+
...pid ? [{ uri: `project://dashboard`, title: "Project dashboard" }] : []
|
|
1849
|
+
]
|
|
1850
|
+
};
|
|
1236
1851
|
}
|
|
1237
1852
|
);
|
|
1238
1853
|
server.registerTool(
|
|
@@ -1613,7 +2228,7 @@ function createMushiServer(config) {
|
|
|
1613
2228
|
contents: [{
|
|
1614
2229
|
uri: "privacy://status",
|
|
1615
2230
|
mimeType: "application/json",
|
|
1616
|
-
text: JSON.stringify(await apiCall("/v1/admin/privacy
|
|
2231
|
+
text: JSON.stringify(await apiCall("/v1/admin/privacy-status"), null, 2)
|
|
1617
2232
|
}]
|
|
1618
2233
|
})
|
|
1619
2234
|
);
|
|
@@ -1627,7 +2242,11 @@ function createMushiServer(config) {
|
|
|
1627
2242
|
contents: [{
|
|
1628
2243
|
uri: "evolution://history",
|
|
1629
2244
|
mimeType: "application/json",
|
|
1630
|
-
text: JSON.stringify(
|
|
2245
|
+
text: projectId ? JSON.stringify(
|
|
2246
|
+
await apiCall(`/v1/admin/projects/${encodeURIComponent(projectId)}/evolution-history`),
|
|
2247
|
+
null,
|
|
2248
|
+
2
|
|
2249
|
+
) : JSON.stringify({ ok: false, error: "project_id required" })
|
|
1631
2250
|
}]
|
|
1632
2251
|
})
|
|
1633
2252
|
);
|
|
@@ -2206,12 +2825,192 @@ Be specific. No generic advice.`
|
|
|
2206
2825
|
// admin-authed timeline, which also carries fix / QA / status lanes.
|
|
2207
2826
|
async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}/timeline`))
|
|
2208
2827
|
);
|
|
2828
|
+
server.registerTool(
|
|
2829
|
+
"ask_codebase",
|
|
2830
|
+
{
|
|
2831
|
+
title: titleOf("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2832
|
+
description: descOf("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2833
|
+
annotations: annotationsFor("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2834
|
+
inputSchema: {
|
|
2835
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2836
|
+
question: z.string().describe("Plain-English question about the repo"),
|
|
2837
|
+
thread_id: z.string().optional().describe("Optional thread UUID to continue a conversation"),
|
|
2838
|
+
file_path: z.string().optional().describe("Optional file path focus"),
|
|
2839
|
+
symbol_name: z.string().optional().describe("Optional symbol name focus")
|
|
2840
|
+
}
|
|
2841
|
+
},
|
|
2842
|
+
async (args) => {
|
|
2843
|
+
const pid = args.project_id ?? projectId;
|
|
2844
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2845
|
+
const body = {
|
|
2846
|
+
messages: [{ role: "user", content: args.question }]
|
|
2847
|
+
};
|
|
2848
|
+
if (args.thread_id) body.threadId = args.thread_id;
|
|
2849
|
+
if (args.file_path) {
|
|
2850
|
+
body.fileFocus = {
|
|
2851
|
+
file_path: args.file_path,
|
|
2852
|
+
symbol_name: args.symbol_name ?? null
|
|
2853
|
+
};
|
|
2854
|
+
}
|
|
2855
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/chat`, {
|
|
2856
|
+
method: "POST",
|
|
2857
|
+
body: JSON.stringify(body)
|
|
2858
|
+
});
|
|
2859
|
+
return jsonText(data);
|
|
2860
|
+
}
|
|
2861
|
+
);
|
|
2862
|
+
server.registerTool(
|
|
2863
|
+
"get_file_summary",
|
|
2864
|
+
{
|
|
2865
|
+
title: titleOf("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2866
|
+
description: descOf("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2867
|
+
annotations: annotationsFor("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2868
|
+
inputSchema: {
|
|
2869
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2870
|
+
file_path: z.string().describe("Indexed file path"),
|
|
2871
|
+
symbol_name: z.string().optional().describe("Optional symbol name within the file"),
|
|
2872
|
+
force: z.boolean().optional().describe("Bypass cache and regenerate")
|
|
2873
|
+
}
|
|
2874
|
+
},
|
|
2875
|
+
async (args) => {
|
|
2876
|
+
const pid = args.project_id ?? projectId;
|
|
2877
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2878
|
+
const qs = new URLSearchParams({ file_path: args.file_path });
|
|
2879
|
+
if (args.symbol_name) qs.set("symbol_name", args.symbol_name);
|
|
2880
|
+
if (args.force) qs.set("force", "1");
|
|
2881
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/summary?${qs}`);
|
|
2882
|
+
return jsonText(data);
|
|
2883
|
+
}
|
|
2884
|
+
);
|
|
2885
|
+
server.registerTool(
|
|
2886
|
+
"get_codebase_tour",
|
|
2887
|
+
{
|
|
2888
|
+
title: titleOf("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2889
|
+
description: descOf("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2890
|
+
annotations: annotationsFor("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2891
|
+
inputSchema: {
|
|
2892
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2893
|
+
force: z.boolean().optional().describe("Bypass cache and regenerate")
|
|
2894
|
+
}
|
|
2895
|
+
},
|
|
2896
|
+
async (args) => {
|
|
2897
|
+
const pid = args.project_id ?? projectId;
|
|
2898
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2899
|
+
const qs = args.force ? "?force=1" : "";
|
|
2900
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/tour${qs}`);
|
|
2901
|
+
return jsonText(data);
|
|
2902
|
+
}
|
|
2903
|
+
);
|
|
2904
|
+
server.registerTool(
|
|
2905
|
+
"search_codebase",
|
|
2906
|
+
{
|
|
2907
|
+
title: titleOf("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2908
|
+
description: descOf("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2909
|
+
annotations: annotationsFor("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2910
|
+
inputSchema: {
|
|
2911
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2912
|
+
query: z.string().describe("Plain-English search query"),
|
|
2913
|
+
k: z.number().optional().describe("Number of results (default 8, max 20)"),
|
|
2914
|
+
scope_prefix: z.string().optional().describe("Optional subdirectory scope prefix")
|
|
2915
|
+
}
|
|
2916
|
+
},
|
|
2917
|
+
async (args) => {
|
|
2918
|
+
const pid = args.project_id ?? projectId;
|
|
2919
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2920
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/search`, {
|
|
2921
|
+
method: "POST",
|
|
2922
|
+
body: JSON.stringify({
|
|
2923
|
+
query: args.query,
|
|
2924
|
+
k: args.k ?? 8,
|
|
2925
|
+
scope_prefix: args.scope_prefix ?? void 0
|
|
2926
|
+
})
|
|
2927
|
+
});
|
|
2928
|
+
return jsonText(data);
|
|
2929
|
+
}
|
|
2930
|
+
);
|
|
2931
|
+
server.registerTool(
|
|
2932
|
+
"get_codebase_domains",
|
|
2933
|
+
{
|
|
2934
|
+
title: titleOf("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2935
|
+
description: descOf("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2936
|
+
annotations: annotationsFor("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2937
|
+
inputSchema: {
|
|
2938
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2939
|
+
force: z.boolean().optional().describe("Bypass cache and regenerate"),
|
|
2940
|
+
scope_prefix: z.string().optional().describe("Optional subdirectory scope prefix")
|
|
2941
|
+
}
|
|
2942
|
+
},
|
|
2943
|
+
async (args) => {
|
|
2944
|
+
const pid = args.project_id ?? projectId;
|
|
2945
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2946
|
+
const qs = new URLSearchParams();
|
|
2947
|
+
if (args.force) qs.set("force", "1");
|
|
2948
|
+
if (args.scope_prefix) qs.set("scope_prefix", args.scope_prefix);
|
|
2949
|
+
const suffix = qs.toString() ? `?${qs}` : "";
|
|
2950
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/domains${suffix}`);
|
|
2951
|
+
return jsonText(data);
|
|
2952
|
+
}
|
|
2953
|
+
);
|
|
2954
|
+
server.registerTool(
|
|
2955
|
+
"analyze_codebase_impact",
|
|
2956
|
+
{
|
|
2957
|
+
title: titleOf("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2958
|
+
description: descOf("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2959
|
+
annotations: annotationsFor("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2960
|
+
inputSchema: {
|
|
2961
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2962
|
+
paths: z.array(z.string()).optional().describe("Changed file paths"),
|
|
2963
|
+
source: z.enum(["last_push", "compare", "fix"]).optional().describe("Auto-resolve changed paths"),
|
|
2964
|
+
compare: z.string().optional().describe("GitHub compare range base...head"),
|
|
2965
|
+
fix_id: z.string().optional().describe("Fix attempt UUID for PR changed files")
|
|
2966
|
+
}
|
|
2967
|
+
},
|
|
2968
|
+
async (args) => {
|
|
2969
|
+
const pid = args.project_id ?? projectId;
|
|
2970
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2971
|
+
const qs = new URLSearchParams();
|
|
2972
|
+
if (args.paths?.length) qs.set("paths", args.paths.join(","));
|
|
2973
|
+
if (args.source === "last_push") qs.set("ref", "last_push");
|
|
2974
|
+
if (args.compare) qs.set("compare", args.compare);
|
|
2975
|
+
if (args.fix_id) qs.set("fix_id", args.fix_id);
|
|
2976
|
+
if (!qs.toString()) {
|
|
2977
|
+
throw new MushiApiError(400, "BAD_REQUEST", "Provide paths, source=last_push, compare, or fix_id");
|
|
2978
|
+
}
|
|
2979
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/impact?${qs}`);
|
|
2980
|
+
return jsonText(data);
|
|
2981
|
+
}
|
|
2982
|
+
);
|
|
2983
|
+
server.registerTool(
|
|
2984
|
+
"analyze_wiki_knowledge",
|
|
2985
|
+
{
|
|
2986
|
+
title: titleOf("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2987
|
+
description: descOf("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2988
|
+
annotations: annotationsFor("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2989
|
+
inputSchema: {
|
|
2990
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)")
|
|
2991
|
+
}
|
|
2992
|
+
},
|
|
2993
|
+
async (args) => {
|
|
2994
|
+
const pid = args.project_id ?? projectId;
|
|
2995
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2996
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/knowledge/graph`);
|
|
2997
|
+
return jsonText(data);
|
|
2998
|
+
}
|
|
2999
|
+
);
|
|
2209
3000
|
const grantedScopes = config.scopes;
|
|
2210
|
-
|
|
3001
|
+
const featureFilter = config.features ?? "all";
|
|
3002
|
+
if (grantedScopes !== void 0 || featureFilter !== "all") {
|
|
2211
3003
|
const toolRegistry = server._registeredTools;
|
|
2212
|
-
const allSpecs = [
|
|
3004
|
+
const allSpecs = [
|
|
3005
|
+
...TOOL_CATALOG,
|
|
3006
|
+
...TDD_TOOL_CATALOG,
|
|
3007
|
+
...CODEBASE_TOOL_CATALOG
|
|
3008
|
+
];
|
|
2213
3009
|
for (const spec of allSpecs) {
|
|
2214
|
-
if (!grantedScopes.includes(spec.scope)) {
|
|
3010
|
+
if (grantedScopes !== void 0 && !grantedScopes.includes(spec.scope)) {
|
|
3011
|
+
toolRegistry[spec.name]?.remove();
|
|
3012
|
+
}
|
|
3013
|
+
if (featureFilter !== "all" && !toolMatchesFeatures(spec.name, featureFilter)) {
|
|
2215
3014
|
toolRegistry[spec.name]?.remove();
|
|
2216
3015
|
}
|
|
2217
3016
|
}
|
|
@@ -2220,15 +3019,21 @@ Be specific. No generic advice.`
|
|
|
2220
3019
|
}
|
|
2221
3020
|
|
|
2222
3021
|
// src/index.ts
|
|
3022
|
+
var _writeStderr = (...args) => process.stderr.write(
|
|
3023
|
+
args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ") + "\n"
|
|
3024
|
+
);
|
|
3025
|
+
console.log = _writeStderr;
|
|
3026
|
+
console.warn = _writeStderr;
|
|
2223
3027
|
var require2 = createRequire(import.meta.url);
|
|
2224
3028
|
var VERSION = require2("../package.json").version;
|
|
2225
|
-
var log =
|
|
3029
|
+
var log = createLogger2({ scope: "mushi:mcp", level: "info", destination: "stderr" });
|
|
2226
3030
|
var API_ENDPOINT = process.env.MUSHI_API_ENDPOINT ?? "";
|
|
2227
3031
|
var API_KEY = process.env.MUSHI_API_KEY ?? "";
|
|
2228
3032
|
var PROJECT_ID = process.env.MUSHI_PROJECT_ID ?? "";
|
|
2229
3033
|
var SCOPES_RAW = process.env.MUSHI_SCOPES ?? "";
|
|
2230
3034
|
var parsedScopes = SCOPES_RAW ? SCOPES_RAW.split(",").map((s) => s.trim()).filter((s) => s === "mcp:read" || s === "mcp:write") : ALL_SCOPES;
|
|
2231
3035
|
var SCOPES = SCOPES_RAW && parsedScopes.length === 0 ? ALL_SCOPES : parsedScopes;
|
|
3036
|
+
var FEATURES = parseFeaturesCsv(process.env.MUSHI_FEATURES);
|
|
2232
3037
|
async function main() {
|
|
2233
3038
|
if (!API_KEY) {
|
|
2234
3039
|
log.fatal("MUSHI_API_KEY environment variable is required");
|
|
@@ -2239,13 +3044,15 @@ async function main() {
|
|
|
2239
3044
|
"[mushi-mcp] MUSHI_API_ENDPOINT is not set. All tool calls will fail.\nSet MUSHI_API_ENDPOINT to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api"
|
|
2240
3045
|
);
|
|
2241
3046
|
}
|
|
3047
|
+
const mode = PROJECT_ID ? "single-project" : "account";
|
|
2242
3048
|
if (!PROJECT_ID) {
|
|
2243
|
-
|
|
2244
|
-
"[mushi-mcp]
|
|
3049
|
+
log.info(
|
|
3050
|
+
"[mushi-mcp] Running in account mode (no MUSHI_PROJECT_ID set). Project-scoped tools accept an explicit projectId argument. Run `get_account_overview` to see accessible projects."
|
|
2245
3051
|
);
|
|
2246
3052
|
}
|
|
2247
3053
|
log.info("Starting Mushi MCP server", {
|
|
2248
3054
|
version: VERSION,
|
|
3055
|
+
mode,
|
|
2249
3056
|
endpoint: API_ENDPOINT || "(unset)",
|
|
2250
3057
|
hasProjectId: !!PROJECT_ID,
|
|
2251
3058
|
scopes: SCOPES.join(",")
|
|
@@ -2255,7 +3062,8 @@ async function main() {
|
|
|
2255
3062
|
apiEndpoint: API_ENDPOINT,
|
|
2256
3063
|
apiKey: API_KEY,
|
|
2257
3064
|
projectId: PROJECT_ID || void 0,
|
|
2258
|
-
scopes: SCOPES
|
|
3065
|
+
scopes: SCOPES,
|
|
3066
|
+
features: FEATURES
|
|
2259
3067
|
});
|
|
2260
3068
|
const transport = new StdioServerTransport();
|
|
2261
3069
|
await server.connect(transport);
|
|
@@ -2267,7 +3075,7 @@ async function main() {
|
|
|
2267
3075
|
const res = await fetch(`${API_ENDPOINT}/v1/admin/inventory/${PROJECT_ID}`, {
|
|
2268
3076
|
headers: {
|
|
2269
3077
|
"X-Mushi-Api-Key": API_KEY,
|
|
2270
|
-
"X-Mushi-Project": PROJECT_ID
|
|
3078
|
+
"X-Mushi-Project-Id": PROJECT_ID
|
|
2271
3079
|
},
|
|
2272
3080
|
signal: AbortSignal.timeout(1e4)
|
|
2273
3081
|
});
|