@mushi-mushi/mcp 0.14.0 → 0.15.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/README.md +70 -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-QWZEQYCN.js → chunk-FD6GVGQS.js} +91 -8
- package/dist/feature-groups.d.ts +23 -0
- package/dist/feature-groups.js +24 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +782 -41
- package/dist/server.d.ts +6 -0
- package/dist/server.js +554 -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:
|
|
284
|
+
description: `List the Mushi projects accessible to this API key. For project-scoped keys (the typical case), returns a single-item list with the bound project's id, name, created_at. Use this when MUSHI_PROJECT_ID is not configured \u2014 call list_projects first, then pass the id to other tools. Multi-project note: each Mushi project uses its own API key. To connect multiple projects, run "Add to Cursor" in the Mushi console for each project \u2014 each deeplink creates a uniquely-named MCP server entry (mushi-{slug}-{id}). You can then call list_projects on each named server to confirm which project it serves.`,
|
|
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 a summary of every Mushi project accessible to this API key, including report counts, SDK heartbeat status, and the last ingest timestamp for each. For project-scoped keys this is a one-item list; for future org-level keys it lists every project. Also includes a hint explaining how to connect additional projects to your IDE. 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",
|
|
@@ -281,7 +305,7 @@ var TOOL_CATALOG = [
|
|
|
281
305
|
{
|
|
282
306
|
name: "get_pipeline_logs",
|
|
283
307
|
title: "Recent pipeline logs",
|
|
284
|
-
description: "Pull recent log entries from the Mushi pipeline services:
|
|
308
|
+
description: "Pull recent log entries from the Mushi pipeline services: fix-worker, qa-story-runner, pipeline, or all. Accepts project_id, service, since (ISO-8601), limit (max 200), and level (info | warn | error | fatal) filters. Returns structured log rows with timestamp, level, service, message, and a trace_id/report_id when available. Use this when a fix failed, a QA story keeps erroring, or an ingest pipeline went silent.",
|
|
285
309
|
scope: "mcp:read",
|
|
286
310
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
287
311
|
useCase: "Why did the last fix attempt fail? Show me recent pipeline errors."
|
|
@@ -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,307 @@ 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"],
|
|
753
|
+
excerpt: "Wire Mushi MCP into Cursor, Claude, or any MCP client."
|
|
754
|
+
},
|
|
755
|
+
{
|
|
756
|
+
title: "API key scopes",
|
|
757
|
+
path: `${BASE}/guides/api-keys`,
|
|
758
|
+
keywords: ["api key", "mcp:read", "mcp:write", "scopes", "security"],
|
|
759
|
+
excerpt: "Separate SDK ingest keys from MCP read/write keys."
|
|
760
|
+
},
|
|
761
|
+
{
|
|
762
|
+
title: "Judge loop",
|
|
763
|
+
path: `${BASE}/concepts/judge-loop`,
|
|
764
|
+
keywords: ["judge", "sonnet", "quality", "fix scoring"],
|
|
765
|
+
excerpt: "Sonnet-as-Judge scores fix quality and feeds the lesson library."
|
|
766
|
+
},
|
|
767
|
+
{
|
|
768
|
+
title: "Inventory v2",
|
|
769
|
+
path: `${BASE}/concepts/inventory`,
|
|
770
|
+
keywords: ["inventory", "user stories", "spec", "yaml", "actions"],
|
|
771
|
+
excerpt: "inventory.yaml maps pages, stories, and expected outcomes for spec traceability."
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
title: "QA coverage stories",
|
|
775
|
+
path: `${BASE}/guides/qa-stories`,
|
|
776
|
+
keywords: ["qa", "playwright", "tdd", "cron", "browserbase", "firecrawl"],
|
|
777
|
+
excerpt: "Author, approve, and schedule user-story tests."
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
title: "Skill pipelines",
|
|
781
|
+
path: `${BASE}/guides/skill-pipelines`,
|
|
782
|
+
keywords: ["skills", "pipeline", "cursor", "workflow"],
|
|
783
|
+
excerpt: "Attach agent skills to reports and run handoff or cloud pipelines."
|
|
784
|
+
},
|
|
785
|
+
{
|
|
786
|
+
title: "Dispatch fix",
|
|
787
|
+
path: `${BASE}/guides/dispatch-fix`,
|
|
788
|
+
keywords: ["dispatch", "fix", "pr", "cursor cloud", "autofix"],
|
|
789
|
+
excerpt: "Dispatch the Mushi fix agent or Cursor Cloud for a classified report."
|
|
790
|
+
},
|
|
791
|
+
{
|
|
792
|
+
title: "SDK installation",
|
|
793
|
+
path: `${BASE}/guides/sdk`,
|
|
794
|
+
keywords: ["sdk", "install", "react", "next", "ingest"],
|
|
795
|
+
excerpt: "Install @mushi-mushi/web and start capturing user reports."
|
|
796
|
+
},
|
|
797
|
+
{
|
|
798
|
+
title: "Configuration reference",
|
|
799
|
+
path: `${BASE}/reference/config`,
|
|
800
|
+
keywords: ["env", "config", "settings", "byok"],
|
|
801
|
+
excerpt: "Environment variables and project settings reference."
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
title: "Evolution loop",
|
|
805
|
+
path: `${BASE}/concepts/evolution`,
|
|
806
|
+
keywords: ["evolution", "lessons", "convergence", "prompt promotion"],
|
|
807
|
+
excerpt: "Closed-loop learning from bugs, fixes, and judge scores."
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
title: "Privacy & BYOK",
|
|
811
|
+
path: `${BASE}/guides/privacy`,
|
|
812
|
+
keywords: ["privacy", "byok", "data region", "llm"],
|
|
813
|
+
excerpt: "Data boundaries, BYOK LLM keys, and retention."
|
|
814
|
+
}
|
|
815
|
+
];
|
|
816
|
+
function searchMushiDocs(query, limit = 8) {
|
|
817
|
+
const q = query.trim().toLowerCase();
|
|
818
|
+
if (!q) {
|
|
819
|
+
return MUSHI_DOCS_INDEX.slice(0, limit).map((e) => ({ ...e, score: 0 }));
|
|
820
|
+
}
|
|
821
|
+
const terms = q.split(/\s+/).filter(Boolean);
|
|
822
|
+
const scored = MUSHI_DOCS_INDEX.map((entry) => {
|
|
823
|
+
const hay = `${entry.title} ${entry.keywords.join(" ")} ${entry.excerpt}`.toLowerCase();
|
|
824
|
+
let score = 0;
|
|
825
|
+
for (const term of terms) {
|
|
826
|
+
if (entry.title.toLowerCase().includes(term)) score += 4;
|
|
827
|
+
if (entry.keywords.some((k) => k.includes(term))) score += 3;
|
|
828
|
+
if (hay.includes(term)) score += 1;
|
|
829
|
+
}
|
|
830
|
+
return { ...entry, score };
|
|
831
|
+
}).filter((e) => e.score > 0).sort((a, b) => b.score - a.score);
|
|
832
|
+
return scored.slice(0, limit);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// src/server.ts
|
|
515
836
|
var MushiApiError = class extends Error {
|
|
516
837
|
constructor(status, code, message) {
|
|
517
838
|
super(`[${code}] ${message}`);
|
|
@@ -525,10 +846,22 @@ var MushiApiError = class extends Error {
|
|
|
525
846
|
function createMushiServer(config) {
|
|
526
847
|
const { version, apiEndpoint, apiKey, projectId } = config;
|
|
527
848
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
849
|
+
const apiLog = createLogger({
|
|
850
|
+
scope: "mushi:mcp:api",
|
|
851
|
+
level: process.env.MUSHI_LOG_LEVEL ?? "info"
|
|
852
|
+
});
|
|
528
853
|
if (config.scopes !== void 0 && config.scopes.length === 0) {
|
|
529
|
-
return new McpServer({
|
|
854
|
+
return new McpServer({
|
|
855
|
+
name: MUSHI_SERVER_METADATA.name,
|
|
856
|
+
version,
|
|
857
|
+
title: MUSHI_SERVER_METADATA.title,
|
|
858
|
+
websiteUrl: MUSHI_SERVER_METADATA.websiteUrl,
|
|
859
|
+
icons: [...MUSHI_SERVER_METADATA.icons]
|
|
860
|
+
});
|
|
530
861
|
}
|
|
531
862
|
async function apiCall(path, options) {
|
|
863
|
+
const requestId = crypto.randomUUID().slice(0, 12);
|
|
864
|
+
const started = Date.now();
|
|
532
865
|
const res = await doFetch(`${apiEndpoint}${path}`, {
|
|
533
866
|
...options,
|
|
534
867
|
headers: {
|
|
@@ -538,10 +871,12 @@ function createMushiServer(config) {
|
|
|
538
871
|
// and for transparent proxies that strip X-Mushi-* headers.
|
|
539
872
|
"Authorization": `Bearer ${apiKey}`,
|
|
540
873
|
"X-Mushi-Api-Key": apiKey,
|
|
541
|
-
|
|
874
|
+
"X-Request-Id": requestId,
|
|
875
|
+
...projectId ? { "X-Mushi-Project-Id": projectId } : {},
|
|
542
876
|
...options?.headers ?? {}
|
|
543
877
|
}
|
|
544
878
|
});
|
|
879
|
+
const durationMs = Date.now() - started;
|
|
545
880
|
const text = await res.text();
|
|
546
881
|
let body = null;
|
|
547
882
|
if (text) {
|
|
@@ -555,6 +890,7 @@ function createMushiServer(config) {
|
|
|
555
890
|
const envelope2 = body;
|
|
556
891
|
const code = envelope2?.error?.code ?? `HTTP_${res.status}`;
|
|
557
892
|
const message = envelope2?.error?.message ?? text.slice(0, 500) ?? `Request failed with ${res.status}`;
|
|
893
|
+
apiLog.warn("api.failed", { path, requestId, status: res.status, durationMs, code });
|
|
558
894
|
throw new MushiApiError(res.status, code, message);
|
|
559
895
|
}
|
|
560
896
|
const envelope = body;
|
|
@@ -562,10 +898,13 @@ function createMushiServer(config) {
|
|
|
562
898
|
if (!envelope.ok) {
|
|
563
899
|
const code = envelope.error?.code ?? "API_ERROR";
|
|
564
900
|
const message = envelope.error?.message ?? "API returned ok=false";
|
|
901
|
+
apiLog.warn("api.failed", { path, requestId, status: res.status, durationMs, code });
|
|
565
902
|
throw new MushiApiError(res.status, code, message);
|
|
566
903
|
}
|
|
904
|
+
apiLog.info("api.done", { path, requestId, status: res.status, durationMs });
|
|
567
905
|
return envelope.data ?? {};
|
|
568
906
|
}
|
|
907
|
+
apiLog.info("api.done", { path, requestId, status: res.status, durationMs });
|
|
569
908
|
return body;
|
|
570
909
|
}
|
|
571
910
|
function jsonText(value) {
|
|
@@ -580,10 +919,13 @@ function createMushiServer(config) {
|
|
|
580
919
|
};
|
|
581
920
|
}
|
|
582
921
|
const server = new McpServer({
|
|
583
|
-
name:
|
|
584
|
-
version
|
|
922
|
+
name: MUSHI_SERVER_METADATA.name,
|
|
923
|
+
version,
|
|
924
|
+
title: MUSHI_SERVER_METADATA.title,
|
|
925
|
+
websiteUrl: MUSHI_SERVER_METADATA.websiteUrl,
|
|
926
|
+
icons: [...MUSHI_SERVER_METADATA.icons]
|
|
585
927
|
});
|
|
586
|
-
const ALL_TOOL_CATALOG = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG];
|
|
928
|
+
const ALL_TOOL_CATALOG = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG, ...CODEBASE_TOOL_CATALOG];
|
|
587
929
|
function annotationsFor(name, catalog = ALL_TOOL_CATALOG) {
|
|
588
930
|
const spec = catalog.find((t) => t.name === name);
|
|
589
931
|
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
@@ -616,7 +958,10 @@ function createMushiServer(config) {
|
|
|
616
958
|
status: z.string().optional().describe("Filter by status: new, classified, grouped, fixing, fixed, dismissed"),
|
|
617
959
|
category: z.string().optional().describe("Filter by category: bug, slow, visual, confusing, other"),
|
|
618
960
|
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)")
|
|
961
|
+
limit: z.number().optional().describe("Max reports to return (default 20, max 100)"),
|
|
962
|
+
project_id: z.string().optional().describe(
|
|
963
|
+
"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."
|
|
964
|
+
)
|
|
620
965
|
},
|
|
621
966
|
outputSchema: {
|
|
622
967
|
reports: z.array(z.unknown()),
|
|
@@ -629,7 +974,11 @@ function createMushiServer(config) {
|
|
|
629
974
|
if (args.category) params.set("category", args.category);
|
|
630
975
|
if (args.severity) params.set("severity", args.severity);
|
|
631
976
|
params.set("limit", String(Math.min(args.limit ?? 20, 100)));
|
|
632
|
-
const
|
|
977
|
+
const pid = args.project_id ?? projectId;
|
|
978
|
+
const extraHeaders = pid && pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
|
|
979
|
+
const data = await apiCall(`/v1/admin/reports?${params}`, {
|
|
980
|
+
headers: extraHeaders
|
|
981
|
+
});
|
|
633
982
|
return jsonResult(data);
|
|
634
983
|
}
|
|
635
984
|
);
|
|
@@ -639,9 +988,19 @@ function createMushiServer(config) {
|
|
|
639
988
|
title: titleOf("get_report_detail"),
|
|
640
989
|
description: descOf("get_report_detail"),
|
|
641
990
|
annotations: annotationsFor("get_report_detail"),
|
|
642
|
-
inputSchema: { reportId: z.string().describe("The report UUID") }
|
|
991
|
+
inputSchema: { reportId: z.string().describe("The report UUID") },
|
|
992
|
+
outputSchema: { report: z.unknown() }
|
|
643
993
|
},
|
|
644
|
-
async (args) =>
|
|
994
|
+
async (args) => {
|
|
995
|
+
const data = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
996
|
+
const res = jsonResult({ report: data });
|
|
997
|
+
return {
|
|
998
|
+
...res,
|
|
999
|
+
resource_links: [
|
|
1000
|
+
{ uri: `project://reports/${args.reportId}`, title: `Report ${args.reportId.slice(0, 8)}\u2026` }
|
|
1001
|
+
]
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
645
1004
|
);
|
|
646
1005
|
server.registerTool(
|
|
647
1006
|
"get_report_timeline",
|
|
@@ -700,6 +1059,9 @@ function createMushiServer(config) {
|
|
|
700
1059
|
inputSchema: {
|
|
701
1060
|
query: z.string().describe("Component name, page path, or bug description"),
|
|
702
1061
|
limit: z.number().optional().describe("Max results (default 5, max 20)")
|
|
1062
|
+
},
|
|
1063
|
+
outputSchema: {
|
|
1064
|
+
results: z.array(z.unknown())
|
|
703
1065
|
}
|
|
704
1066
|
},
|
|
705
1067
|
async (args) => {
|
|
@@ -712,7 +1074,7 @@ function createMushiServer(config) {
|
|
|
712
1074
|
...projectId ? { projectId } : {}
|
|
713
1075
|
})
|
|
714
1076
|
});
|
|
715
|
-
return
|
|
1077
|
+
return jsonResult(data);
|
|
716
1078
|
}
|
|
717
1079
|
);
|
|
718
1080
|
server.registerTool(
|
|
@@ -721,11 +1083,18 @@ function createMushiServer(config) {
|
|
|
721
1083
|
title: titleOf("get_fix_context"),
|
|
722
1084
|
description: descOf("get_fix_context"),
|
|
723
1085
|
annotations: annotationsFor("get_fix_context"),
|
|
724
|
-
inputSchema: { reportId: z.string().describe("The report UUID to fix") }
|
|
1086
|
+
inputSchema: { reportId: z.string().describe("The report UUID to fix") },
|
|
1087
|
+
outputSchema: {
|
|
1088
|
+
report: z.unknown(),
|
|
1089
|
+
reproductionSteps: z.unknown(),
|
|
1090
|
+
component: z.unknown(),
|
|
1091
|
+
rootCause: z.unknown(),
|
|
1092
|
+
bugOntologyTags: z.unknown()
|
|
1093
|
+
}
|
|
725
1094
|
},
|
|
726
1095
|
async (args) => {
|
|
727
1096
|
const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
728
|
-
return
|
|
1097
|
+
return jsonResult({
|
|
729
1098
|
report,
|
|
730
1099
|
reproductionSteps: report.reproduction_steps ?? [],
|
|
731
1100
|
component: report.component,
|
|
@@ -963,13 +1332,114 @@ function createMushiServer(config) {
|
|
|
963
1332
|
});
|
|
964
1333
|
}
|
|
965
1334
|
);
|
|
1335
|
+
server.registerTool(
|
|
1336
|
+
"diagnose_setup",
|
|
1337
|
+
{
|
|
1338
|
+
title: titleOf("diagnose_setup"),
|
|
1339
|
+
description: descOf("diagnose_setup"),
|
|
1340
|
+
annotations: annotationsFor("diagnose_setup"),
|
|
1341
|
+
inputSchema: {
|
|
1342
|
+
mode: z.enum(["full", "ingest", "dispatch"]).optional().describe("full (default) = ingest + dispatch; ingest = SDK pipeline only; dispatch = fix preflight only."),
|
|
1343
|
+
project_id: z.string().optional().describe("Project UUID for dispatch checks (defaults to configured project)."),
|
|
1344
|
+
projectId: z.string().optional().describe("Alias for project_id.")
|
|
1345
|
+
},
|
|
1346
|
+
outputSchema: {
|
|
1347
|
+
mode: z.string(),
|
|
1348
|
+
ready: z.boolean(),
|
|
1349
|
+
summary: z.string(),
|
|
1350
|
+
nextAction: z.string().optional(),
|
|
1351
|
+
ingest: z.unknown().optional(),
|
|
1352
|
+
dispatch: z.unknown().optional()
|
|
1353
|
+
}
|
|
1354
|
+
},
|
|
1355
|
+
async (args) => {
|
|
1356
|
+
const mode = args.mode ?? "full";
|
|
1357
|
+
const resolvedId = args.project_id ?? args.projectId ?? projectId;
|
|
1358
|
+
if (mode === "ingest") {
|
|
1359
|
+
const data = await apiCall("/v1/sync/ingest-setup");
|
|
1360
|
+
const failed = data.steps.filter((s) => s.required && !s.complete);
|
|
1361
|
+
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(", ")}.`;
|
|
1362
|
+
return jsonResult({
|
|
1363
|
+
mode: "ingest",
|
|
1364
|
+
ready: data.ready,
|
|
1365
|
+
summary: summary2,
|
|
1366
|
+
nextAction: failed[0]?.hint,
|
|
1367
|
+
ingest: {
|
|
1368
|
+
projectId: data.project_id,
|
|
1369
|
+
projectName: data.project_name,
|
|
1370
|
+
requiredComplete: data.required_complete,
|
|
1371
|
+
requiredTotal: data.required_total,
|
|
1372
|
+
steps: data.steps,
|
|
1373
|
+
diagnostic: data.diagnostic ?? null
|
|
1374
|
+
}
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
if (mode === "dispatch") {
|
|
1378
|
+
if (!resolvedId) {
|
|
1379
|
+
throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required for dispatch mode");
|
|
1380
|
+
}
|
|
1381
|
+
const data = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
1382
|
+
const failed = data.checks.filter((c) => !c.ready);
|
|
1383
|
+
const summary2 = data.ready ? `Project ${resolvedId} is ready to dispatch auto-fixes.` : `Dispatch blocked \u2014 ${failed.map((c) => c.label).join(", ")}.`;
|
|
1384
|
+
return jsonResult({
|
|
1385
|
+
mode: "dispatch",
|
|
1386
|
+
ready: data.ready,
|
|
1387
|
+
summary: summary2,
|
|
1388
|
+
nextAction: failed[0]?.hint,
|
|
1389
|
+
dispatch: { repoUrl: data.repoUrl ?? null, checks: data.checks }
|
|
1390
|
+
});
|
|
1391
|
+
}
|
|
1392
|
+
const ingest = await apiCall("/v1/sync/ingest-setup");
|
|
1393
|
+
let dispatchReady = true;
|
|
1394
|
+
let dispatchBlock;
|
|
1395
|
+
let dispatchChecks = null;
|
|
1396
|
+
if (resolvedId) {
|
|
1397
|
+
try {
|
|
1398
|
+
const preflight = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
1399
|
+
dispatchReady = preflight.ready;
|
|
1400
|
+
dispatchChecks = preflight.checks;
|
|
1401
|
+
if (!preflight.ready) {
|
|
1402
|
+
const failed = preflight.checks.filter((c) => !c.ready);
|
|
1403
|
+
dispatchBlock = failed[0]?.hint;
|
|
1404
|
+
}
|
|
1405
|
+
} catch {
|
|
1406
|
+
dispatchReady = false;
|
|
1407
|
+
dispatchBlock = "Could not run dispatch preflight \u2014 verify project_id and API key scope.";
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
const ingestFailed = ingest.steps.filter((s) => s.required && !s.complete);
|
|
1411
|
+
const ingestBlock = ingestFailed[0]?.hint;
|
|
1412
|
+
const ready = ingest.ready && dispatchReady;
|
|
1413
|
+
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.";
|
|
1414
|
+
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.";
|
|
1415
|
+
return jsonResult({
|
|
1416
|
+
mode: "full",
|
|
1417
|
+
ready,
|
|
1418
|
+
summary,
|
|
1419
|
+
nextAction,
|
|
1420
|
+
ingest: { ready: ingest.ready, steps: ingest.steps },
|
|
1421
|
+
dispatch: resolvedId ? { ready: dispatchReady, checks: dispatchChecks } : null
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
);
|
|
966
1425
|
server.registerTool(
|
|
967
1426
|
"diagnose_connection",
|
|
968
1427
|
{
|
|
969
1428
|
title: titleOf("diagnose_connection"),
|
|
970
1429
|
description: descOf("diagnose_connection"),
|
|
971
1430
|
annotations: annotationsFor("diagnose_connection"),
|
|
972
|
-
inputSchema: {}
|
|
1431
|
+
inputSchema: {},
|
|
1432
|
+
outputSchema: {
|
|
1433
|
+
ready: z.boolean(),
|
|
1434
|
+
healthOk: z.boolean(),
|
|
1435
|
+
ingestReady: z.boolean(),
|
|
1436
|
+
dispatchReady: z.boolean(),
|
|
1437
|
+
endpoint: z.string().nullable(),
|
|
1438
|
+
projectId: z.string().nullable(),
|
|
1439
|
+
issues: z.array(z.unknown()),
|
|
1440
|
+
nextAction: z.string(),
|
|
1441
|
+
summary: z.string()
|
|
1442
|
+
}
|
|
973
1443
|
},
|
|
974
1444
|
async () => {
|
|
975
1445
|
const issues = [];
|
|
@@ -1053,7 +1523,7 @@ function createMushiServer(config) {
|
|
|
1053
1523
|
}
|
|
1054
1524
|
const ready = issues.length === 0 && healthOk && ingestReady;
|
|
1055
1525
|
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
|
|
1526
|
+
return jsonResult({
|
|
1057
1527
|
ready,
|
|
1058
1528
|
healthOk,
|
|
1059
1529
|
ingestReady,
|
|
@@ -1066,17 +1536,77 @@ function createMushiServer(config) {
|
|
|
1066
1536
|
});
|
|
1067
1537
|
}
|
|
1068
1538
|
);
|
|
1539
|
+
server.registerTool(
|
|
1540
|
+
"search_mushi_docs",
|
|
1541
|
+
{
|
|
1542
|
+
title: titleOf("search_mushi_docs"),
|
|
1543
|
+
description: descOf("search_mushi_docs"),
|
|
1544
|
+
annotations: annotationsFor("search_mushi_docs"),
|
|
1545
|
+
inputSchema: {
|
|
1546
|
+
query: z.string().describe("Keywords to search official Mushi docs (guides, MCP, inventory, QA)."),
|
|
1547
|
+
limit: z.number().int().min(1).max(20).optional().describe("Max results (default 8).")
|
|
1548
|
+
},
|
|
1549
|
+
outputSchema: {
|
|
1550
|
+
query: z.string(),
|
|
1551
|
+
results: z.array(
|
|
1552
|
+
z.object({
|
|
1553
|
+
title: z.string(),
|
|
1554
|
+
path: z.string(),
|
|
1555
|
+
excerpt: z.string(),
|
|
1556
|
+
score: z.number()
|
|
1557
|
+
})
|
|
1558
|
+
)
|
|
1559
|
+
}
|
|
1560
|
+
},
|
|
1561
|
+
async (args) => {
|
|
1562
|
+
const query = args.query ?? "";
|
|
1563
|
+
const hits = searchMushiDocs(query, args.limit ?? 8);
|
|
1564
|
+
const results = hits.map(({ title, path, excerpt, score }) => ({ title, path, excerpt, score }));
|
|
1565
|
+
return jsonResult({ query, results });
|
|
1566
|
+
}
|
|
1567
|
+
);
|
|
1069
1568
|
server.registerTool(
|
|
1070
1569
|
"list_projects",
|
|
1071
1570
|
{
|
|
1072
1571
|
title: titleOf("list_projects"),
|
|
1073
1572
|
description: descOf("list_projects"),
|
|
1074
1573
|
annotations: annotationsFor("list_projects"),
|
|
1075
|
-
inputSchema: {}
|
|
1574
|
+
inputSchema: {},
|
|
1575
|
+
outputSchema: {
|
|
1576
|
+
projects: z.array(z.unknown()),
|
|
1577
|
+
total: z.number().optional(),
|
|
1578
|
+
_multi_project_hint: z.string().optional()
|
|
1579
|
+
}
|
|
1076
1580
|
},
|
|
1077
1581
|
async () => {
|
|
1078
1582
|
const data = await apiCall("/v1/admin/mcp/projects");
|
|
1079
|
-
return
|
|
1583
|
+
return jsonResult({
|
|
1584
|
+
...data,
|
|
1585
|
+
_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.'
|
|
1586
|
+
});
|
|
1587
|
+
}
|
|
1588
|
+
);
|
|
1589
|
+
server.registerTool(
|
|
1590
|
+
"get_account_overview",
|
|
1591
|
+
{
|
|
1592
|
+
title: titleOf("get_account_overview"),
|
|
1593
|
+
description: descOf("get_account_overview"),
|
|
1594
|
+
annotations: annotationsFor("get_account_overview"),
|
|
1595
|
+
inputSchema: {},
|
|
1596
|
+
outputSchema: {
|
|
1597
|
+
projects: z.array(z.unknown()),
|
|
1598
|
+
active_project_id: z.string().nullable(),
|
|
1599
|
+
multi_project_hint: z.string()
|
|
1600
|
+
}
|
|
1601
|
+
},
|
|
1602
|
+
async () => {
|
|
1603
|
+
const data = await apiCall("/v1/admin/mcp/projects");
|
|
1604
|
+
const projects = Array.isArray(data?.projects) ? data.projects : [];
|
|
1605
|
+
return jsonResult({
|
|
1606
|
+
projects,
|
|
1607
|
+
active_project_id: projectId ?? null,
|
|
1608
|
+
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".`
|
|
1609
|
+
});
|
|
1080
1610
|
}
|
|
1081
1611
|
);
|
|
1082
1612
|
server.registerTool(
|
|
@@ -1111,7 +1641,7 @@ function createMushiServer(config) {
|
|
|
1111
1641
|
annotations: annotationsFor("get_pipeline_logs"),
|
|
1112
1642
|
inputSchema: {
|
|
1113
1643
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
|
|
1114
|
-
service: z.enum(["
|
|
1644
|
+
service: z.enum(["fix-worker", "qa-story-runner", "pipeline", "all"]).optional().describe("Pipeline service to filter (default: all)."),
|
|
1115
1645
|
since: z.string().optional().describe("ISO-8601 timestamp \u2014 return only entries after this time."),
|
|
1116
1646
|
limit: z.number().optional().describe("Max entries to return (default 50, max 200)."),
|
|
1117
1647
|
level: z.enum(["info", "warn", "error", "fatal"]).optional().describe("Minimum severity level (default: warn).")
|
|
@@ -1176,6 +1706,20 @@ function createMushiServer(config) {
|
|
|
1176
1706
|
report_id: z.string().describe("Report UUID to triage."),
|
|
1177
1707
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
|
|
1178
1708
|
include_logs: z.boolean().optional().describe("Include recent pipeline logs in triage packet (default: true).")
|
|
1709
|
+
},
|
|
1710
|
+
outputSchema: {
|
|
1711
|
+
report_id: z.string(),
|
|
1712
|
+
severity: z.unknown(),
|
|
1713
|
+
category: z.unknown(),
|
|
1714
|
+
status: z.unknown(),
|
|
1715
|
+
report: z.unknown(),
|
|
1716
|
+
reporter_thread: z.unknown().nullable(),
|
|
1717
|
+
similar_bugs: z.unknown().nullable(),
|
|
1718
|
+
fix_context: z.unknown().nullable(),
|
|
1719
|
+
blast_radius: z.unknown().nullable(),
|
|
1720
|
+
recent_logs: z.unknown().nullable(),
|
|
1721
|
+
recommended_actions: z.array(z.unknown()),
|
|
1722
|
+
triage_summary: z.string()
|
|
1179
1723
|
}
|
|
1180
1724
|
},
|
|
1181
1725
|
async (args) => {
|
|
@@ -1219,7 +1763,7 @@ function createMushiServer(config) {
|
|
|
1219
1763
|
args: { reportId: args.report_id }
|
|
1220
1764
|
});
|
|
1221
1765
|
}
|
|
1222
|
-
|
|
1766
|
+
const result = {
|
|
1223
1767
|
report_id: args.report_id,
|
|
1224
1768
|
severity,
|
|
1225
1769
|
category,
|
|
@@ -1232,7 +1776,15 @@ function createMushiServer(config) {
|
|
|
1232
1776
|
recent_logs: logsRes.status === "fulfilled" ? logsRes.value : null,
|
|
1233
1777
|
recommended_actions: actions,
|
|
1234
1778
|
triage_summary: `[${severity}] ${category} \u2014 status: ${status}. ${actions.length > 0 ? `Recommended: ${actions[0]?.action}.` : "No action required."}`
|
|
1235
|
-
}
|
|
1779
|
+
};
|
|
1780
|
+
const res = jsonResult(result);
|
|
1781
|
+
return {
|
|
1782
|
+
...res,
|
|
1783
|
+
resource_links: [
|
|
1784
|
+
{ uri: `project://reports/${args.report_id}`, title: `Report ${args.report_id.slice(0, 8)}\u2026` },
|
|
1785
|
+
...pid ? [{ uri: `project://dashboard`, title: "Project dashboard" }] : []
|
|
1786
|
+
]
|
|
1787
|
+
};
|
|
1236
1788
|
}
|
|
1237
1789
|
);
|
|
1238
1790
|
server.registerTool(
|
|
@@ -1469,12 +2021,15 @@ function createMushiServer(config) {
|
|
|
1469
2021
|
},
|
|
1470
2022
|
async (args) => {
|
|
1471
2023
|
const pid = args.project_id ?? projectId;
|
|
1472
|
-
const
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
2024
|
+
const data = await apiCall("/v1/admin/lessons/query", {
|
|
2025
|
+
method: "POST",
|
|
2026
|
+
body: JSON.stringify({
|
|
2027
|
+
diff_text: args.diff_text,
|
|
2028
|
+
...args.max_tokens !== void 0 ? { max_tokens: args.max_tokens } : {},
|
|
2029
|
+
...args.top_k !== void 0 ? { top_k: args.top_k } : {},
|
|
2030
|
+
...pid ? { project_id: pid } : {}
|
|
2031
|
+
})
|
|
2032
|
+
});
|
|
1478
2033
|
return jsonText(data);
|
|
1479
2034
|
}
|
|
1480
2035
|
);
|
|
@@ -1610,7 +2165,7 @@ function createMushiServer(config) {
|
|
|
1610
2165
|
contents: [{
|
|
1611
2166
|
uri: "privacy://status",
|
|
1612
2167
|
mimeType: "application/json",
|
|
1613
|
-
text: JSON.stringify(await apiCall("/v1/admin/privacy
|
|
2168
|
+
text: JSON.stringify(await apiCall("/v1/admin/privacy-status"), null, 2)
|
|
1614
2169
|
}]
|
|
1615
2170
|
})
|
|
1616
2171
|
);
|
|
@@ -1624,7 +2179,11 @@ function createMushiServer(config) {
|
|
|
1624
2179
|
contents: [{
|
|
1625
2180
|
uri: "evolution://history",
|
|
1626
2181
|
mimeType: "application/json",
|
|
1627
|
-
text: JSON.stringify(
|
|
2182
|
+
text: projectId ? JSON.stringify(
|
|
2183
|
+
await apiCall(`/v1/admin/projects/${encodeURIComponent(projectId)}/evolution-history`),
|
|
2184
|
+
null,
|
|
2185
|
+
2
|
|
2186
|
+
) : JSON.stringify({ ok: false, error: "project_id required" })
|
|
1628
2187
|
}]
|
|
1629
2188
|
})
|
|
1630
2189
|
);
|
|
@@ -2203,12 +2762,192 @@ Be specific. No generic advice.`
|
|
|
2203
2762
|
// admin-authed timeline, which also carries fix / QA / status lanes.
|
|
2204
2763
|
async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}/timeline`))
|
|
2205
2764
|
);
|
|
2765
|
+
server.registerTool(
|
|
2766
|
+
"ask_codebase",
|
|
2767
|
+
{
|
|
2768
|
+
title: titleOf("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2769
|
+
description: descOf("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2770
|
+
annotations: annotationsFor("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2771
|
+
inputSchema: {
|
|
2772
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2773
|
+
question: z.string().describe("Plain-English question about the repo"),
|
|
2774
|
+
thread_id: z.string().optional().describe("Optional thread UUID to continue a conversation"),
|
|
2775
|
+
file_path: z.string().optional().describe("Optional file path focus"),
|
|
2776
|
+
symbol_name: z.string().optional().describe("Optional symbol name focus")
|
|
2777
|
+
}
|
|
2778
|
+
},
|
|
2779
|
+
async (args) => {
|
|
2780
|
+
const pid = args.project_id ?? projectId;
|
|
2781
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2782
|
+
const body = {
|
|
2783
|
+
messages: [{ role: "user", content: args.question }]
|
|
2784
|
+
};
|
|
2785
|
+
if (args.thread_id) body.threadId = args.thread_id;
|
|
2786
|
+
if (args.file_path) {
|
|
2787
|
+
body.fileFocus = {
|
|
2788
|
+
file_path: args.file_path,
|
|
2789
|
+
symbol_name: args.symbol_name ?? null
|
|
2790
|
+
};
|
|
2791
|
+
}
|
|
2792
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/chat`, {
|
|
2793
|
+
method: "POST",
|
|
2794
|
+
body: JSON.stringify(body)
|
|
2795
|
+
});
|
|
2796
|
+
return jsonText(data);
|
|
2797
|
+
}
|
|
2798
|
+
);
|
|
2799
|
+
server.registerTool(
|
|
2800
|
+
"get_file_summary",
|
|
2801
|
+
{
|
|
2802
|
+
title: titleOf("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2803
|
+
description: descOf("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2804
|
+
annotations: annotationsFor("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2805
|
+
inputSchema: {
|
|
2806
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2807
|
+
file_path: z.string().describe("Indexed file path"),
|
|
2808
|
+
symbol_name: z.string().optional().describe("Optional symbol name within the file"),
|
|
2809
|
+
force: z.boolean().optional().describe("Bypass cache and regenerate")
|
|
2810
|
+
}
|
|
2811
|
+
},
|
|
2812
|
+
async (args) => {
|
|
2813
|
+
const pid = args.project_id ?? projectId;
|
|
2814
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2815
|
+
const qs = new URLSearchParams({ file_path: args.file_path });
|
|
2816
|
+
if (args.symbol_name) qs.set("symbol_name", args.symbol_name);
|
|
2817
|
+
if (args.force) qs.set("force", "1");
|
|
2818
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/summary?${qs}`);
|
|
2819
|
+
return jsonText(data);
|
|
2820
|
+
}
|
|
2821
|
+
);
|
|
2822
|
+
server.registerTool(
|
|
2823
|
+
"get_codebase_tour",
|
|
2824
|
+
{
|
|
2825
|
+
title: titleOf("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2826
|
+
description: descOf("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2827
|
+
annotations: annotationsFor("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2828
|
+
inputSchema: {
|
|
2829
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2830
|
+
force: z.boolean().optional().describe("Bypass cache and regenerate")
|
|
2831
|
+
}
|
|
2832
|
+
},
|
|
2833
|
+
async (args) => {
|
|
2834
|
+
const pid = args.project_id ?? projectId;
|
|
2835
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2836
|
+
const qs = args.force ? "?force=1" : "";
|
|
2837
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/tour${qs}`);
|
|
2838
|
+
return jsonText(data);
|
|
2839
|
+
}
|
|
2840
|
+
);
|
|
2841
|
+
server.registerTool(
|
|
2842
|
+
"search_codebase",
|
|
2843
|
+
{
|
|
2844
|
+
title: titleOf("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2845
|
+
description: descOf("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2846
|
+
annotations: annotationsFor("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2847
|
+
inputSchema: {
|
|
2848
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2849
|
+
query: z.string().describe("Plain-English search query"),
|
|
2850
|
+
k: z.number().optional().describe("Number of results (default 8, max 20)"),
|
|
2851
|
+
scope_prefix: z.string().optional().describe("Optional subdirectory scope prefix")
|
|
2852
|
+
}
|
|
2853
|
+
},
|
|
2854
|
+
async (args) => {
|
|
2855
|
+
const pid = args.project_id ?? projectId;
|
|
2856
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2857
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/search`, {
|
|
2858
|
+
method: "POST",
|
|
2859
|
+
body: JSON.stringify({
|
|
2860
|
+
query: args.query,
|
|
2861
|
+
k: args.k ?? 8,
|
|
2862
|
+
scope_prefix: args.scope_prefix ?? void 0
|
|
2863
|
+
})
|
|
2864
|
+
});
|
|
2865
|
+
return jsonText(data);
|
|
2866
|
+
}
|
|
2867
|
+
);
|
|
2868
|
+
server.registerTool(
|
|
2869
|
+
"get_codebase_domains",
|
|
2870
|
+
{
|
|
2871
|
+
title: titleOf("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2872
|
+
description: descOf("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2873
|
+
annotations: annotationsFor("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2874
|
+
inputSchema: {
|
|
2875
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2876
|
+
force: z.boolean().optional().describe("Bypass cache and regenerate"),
|
|
2877
|
+
scope_prefix: z.string().optional().describe("Optional subdirectory scope prefix")
|
|
2878
|
+
}
|
|
2879
|
+
},
|
|
2880
|
+
async (args) => {
|
|
2881
|
+
const pid = args.project_id ?? projectId;
|
|
2882
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2883
|
+
const qs = new URLSearchParams();
|
|
2884
|
+
if (args.force) qs.set("force", "1");
|
|
2885
|
+
if (args.scope_prefix) qs.set("scope_prefix", args.scope_prefix);
|
|
2886
|
+
const suffix = qs.toString() ? `?${qs}` : "";
|
|
2887
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/domains${suffix}`);
|
|
2888
|
+
return jsonText(data);
|
|
2889
|
+
}
|
|
2890
|
+
);
|
|
2891
|
+
server.registerTool(
|
|
2892
|
+
"analyze_codebase_impact",
|
|
2893
|
+
{
|
|
2894
|
+
title: titleOf("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2895
|
+
description: descOf("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2896
|
+
annotations: annotationsFor("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2897
|
+
inputSchema: {
|
|
2898
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2899
|
+
paths: z.array(z.string()).optional().describe("Changed file paths"),
|
|
2900
|
+
source: z.enum(["last_push", "compare", "fix"]).optional().describe("Auto-resolve changed paths"),
|
|
2901
|
+
compare: z.string().optional().describe("GitHub compare range base...head"),
|
|
2902
|
+
fix_id: z.string().optional().describe("Fix attempt UUID for PR changed files")
|
|
2903
|
+
}
|
|
2904
|
+
},
|
|
2905
|
+
async (args) => {
|
|
2906
|
+
const pid = args.project_id ?? projectId;
|
|
2907
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2908
|
+
const qs = new URLSearchParams();
|
|
2909
|
+
if (args.paths?.length) qs.set("paths", args.paths.join(","));
|
|
2910
|
+
if (args.source === "last_push") qs.set("ref", "last_push");
|
|
2911
|
+
if (args.compare) qs.set("compare", args.compare);
|
|
2912
|
+
if (args.fix_id) qs.set("fix_id", args.fix_id);
|
|
2913
|
+
if (!qs.toString()) {
|
|
2914
|
+
throw new MushiApiError(400, "BAD_REQUEST", "Provide paths, source=last_push, compare, or fix_id");
|
|
2915
|
+
}
|
|
2916
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/impact?${qs}`);
|
|
2917
|
+
return jsonText(data);
|
|
2918
|
+
}
|
|
2919
|
+
);
|
|
2920
|
+
server.registerTool(
|
|
2921
|
+
"analyze_wiki_knowledge",
|
|
2922
|
+
{
|
|
2923
|
+
title: titleOf("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2924
|
+
description: descOf("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2925
|
+
annotations: annotationsFor("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2926
|
+
inputSchema: {
|
|
2927
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)")
|
|
2928
|
+
}
|
|
2929
|
+
},
|
|
2930
|
+
async (args) => {
|
|
2931
|
+
const pid = args.project_id ?? projectId;
|
|
2932
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2933
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/knowledge/graph`);
|
|
2934
|
+
return jsonText(data);
|
|
2935
|
+
}
|
|
2936
|
+
);
|
|
2206
2937
|
const grantedScopes = config.scopes;
|
|
2207
|
-
|
|
2938
|
+
const featureFilter = config.features ?? "all";
|
|
2939
|
+
if (grantedScopes !== void 0 || featureFilter !== "all") {
|
|
2208
2940
|
const toolRegistry = server._registeredTools;
|
|
2209
|
-
const allSpecs = [
|
|
2941
|
+
const allSpecs = [
|
|
2942
|
+
...TOOL_CATALOG,
|
|
2943
|
+
...TDD_TOOL_CATALOG,
|
|
2944
|
+
...CODEBASE_TOOL_CATALOG
|
|
2945
|
+
];
|
|
2210
2946
|
for (const spec of allSpecs) {
|
|
2211
|
-
if (!grantedScopes.includes(spec.scope)) {
|
|
2947
|
+
if (grantedScopes !== void 0 && !grantedScopes.includes(spec.scope)) {
|
|
2948
|
+
toolRegistry[spec.name]?.remove();
|
|
2949
|
+
}
|
|
2950
|
+
if (featureFilter !== "all" && !toolMatchesFeatures(spec.name, featureFilter)) {
|
|
2212
2951
|
toolRegistry[spec.name]?.remove();
|
|
2213
2952
|
}
|
|
2214
2953
|
}
|
|
@@ -2219,13 +2958,14 @@ Be specific. No generic advice.`
|
|
|
2219
2958
|
// src/index.ts
|
|
2220
2959
|
var require2 = createRequire(import.meta.url);
|
|
2221
2960
|
var VERSION = require2("../package.json").version;
|
|
2222
|
-
var log =
|
|
2961
|
+
var log = createLogger2({ scope: "mushi:mcp", level: "info" });
|
|
2223
2962
|
var API_ENDPOINT = process.env.MUSHI_API_ENDPOINT ?? "";
|
|
2224
2963
|
var API_KEY = process.env.MUSHI_API_KEY ?? "";
|
|
2225
2964
|
var PROJECT_ID = process.env.MUSHI_PROJECT_ID ?? "";
|
|
2226
2965
|
var SCOPES_RAW = process.env.MUSHI_SCOPES ?? "";
|
|
2227
2966
|
var parsedScopes = SCOPES_RAW ? SCOPES_RAW.split(",").map((s) => s.trim()).filter((s) => s === "mcp:read" || s === "mcp:write") : ALL_SCOPES;
|
|
2228
2967
|
var SCOPES = SCOPES_RAW && parsedScopes.length === 0 ? ALL_SCOPES : parsedScopes;
|
|
2968
|
+
var FEATURES = parseFeaturesCsv(process.env.MUSHI_FEATURES);
|
|
2229
2969
|
async function main() {
|
|
2230
2970
|
if (!API_KEY) {
|
|
2231
2971
|
log.fatal("MUSHI_API_KEY environment variable is required");
|
|
@@ -2252,7 +2992,8 @@ async function main() {
|
|
|
2252
2992
|
apiEndpoint: API_ENDPOINT,
|
|
2253
2993
|
apiKey: API_KEY,
|
|
2254
2994
|
projectId: PROJECT_ID || void 0,
|
|
2255
|
-
scopes: SCOPES
|
|
2995
|
+
scopes: SCOPES,
|
|
2996
|
+
features: FEATURES
|
|
2256
2997
|
});
|
|
2257
2998
|
const transport = new StdioServerTransport();
|
|
2258
2999
|
await server.connect(transport);
|
|
@@ -2264,7 +3005,7 @@ async function main() {
|
|
|
2264
3005
|
const res = await fetch(`${API_ENDPOINT}/v1/admin/inventory/${PROJECT_ID}`, {
|
|
2265
3006
|
headers: {
|
|
2266
3007
|
"X-Mushi-Api-Key": API_KEY,
|
|
2267
|
-
"X-Mushi-Project": PROJECT_ID
|
|
3008
|
+
"X-Mushi-Project-Id": PROJECT_ID
|
|
2268
3009
|
},
|
|
2269
3010
|
signal: AbortSignal.timeout(1e4)
|
|
2270
3011
|
});
|