@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/server.js
CHANGED
|
@@ -1,11 +1,116 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CODEBASE_TOOL_CATALOG,
|
|
2
3
|
TDD_TOOL_CATALOG,
|
|
3
4
|
TOOL_CATALOG
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-FD6GVGQS.js";
|
|
6
|
+
import {
|
|
7
|
+
MUSHI_SERVER_METADATA
|
|
8
|
+
} from "./chunk-EHB2BGWX.js";
|
|
9
|
+
import {
|
|
10
|
+
toolMatchesFeatures
|
|
11
|
+
} from "./chunk-B2QEG35D.js";
|
|
5
12
|
|
|
6
13
|
// src/server.ts
|
|
7
14
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
15
|
import { z } from "zod";
|
|
16
|
+
import { createLogger } from "@mushi-mushi/core";
|
|
17
|
+
|
|
18
|
+
// src/docs-index.ts
|
|
19
|
+
var BASE = "https://kensaur.us/mushi-mushi/docs";
|
|
20
|
+
var MUSHI_DOCS_INDEX = [
|
|
21
|
+
{
|
|
22
|
+
title: "Architecture overview",
|
|
23
|
+
path: `${BASE}/concepts/architecture`,
|
|
24
|
+
keywords: ["architecture", "evolution loop", "pdca", "overview"],
|
|
25
|
+
excerpt: "How Mushi captures user-felt bugs, triages, dispatches fixes, and learns."
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
title: "MCP setup",
|
|
29
|
+
path: `${BASE}/guides/mcp-setup`,
|
|
30
|
+
keywords: ["mcp", "cursor", "api key", "stdio", "http"],
|
|
31
|
+
excerpt: "Wire Mushi MCP into Cursor, Claude, or any MCP client."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
title: "API key scopes",
|
|
35
|
+
path: `${BASE}/guides/api-keys`,
|
|
36
|
+
keywords: ["api key", "mcp:read", "mcp:write", "scopes", "security"],
|
|
37
|
+
excerpt: "Separate SDK ingest keys from MCP read/write keys."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
title: "Judge loop",
|
|
41
|
+
path: `${BASE}/concepts/judge-loop`,
|
|
42
|
+
keywords: ["judge", "sonnet", "quality", "fix scoring"],
|
|
43
|
+
excerpt: "Sonnet-as-Judge scores fix quality and feeds the lesson library."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
title: "Inventory v2",
|
|
47
|
+
path: `${BASE}/concepts/inventory`,
|
|
48
|
+
keywords: ["inventory", "user stories", "spec", "yaml", "actions"],
|
|
49
|
+
excerpt: "inventory.yaml maps pages, stories, and expected outcomes for spec traceability."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: "QA coverage stories",
|
|
53
|
+
path: `${BASE}/guides/qa-stories`,
|
|
54
|
+
keywords: ["qa", "playwright", "tdd", "cron", "browserbase", "firecrawl"],
|
|
55
|
+
excerpt: "Author, approve, and schedule user-story tests."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
title: "Skill pipelines",
|
|
59
|
+
path: `${BASE}/guides/skill-pipelines`,
|
|
60
|
+
keywords: ["skills", "pipeline", "cursor", "workflow"],
|
|
61
|
+
excerpt: "Attach agent skills to reports and run handoff or cloud pipelines."
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
title: "Dispatch fix",
|
|
65
|
+
path: `${BASE}/guides/dispatch-fix`,
|
|
66
|
+
keywords: ["dispatch", "fix", "pr", "cursor cloud", "autofix"],
|
|
67
|
+
excerpt: "Dispatch the Mushi fix agent or Cursor Cloud for a classified report."
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
title: "SDK installation",
|
|
71
|
+
path: `${BASE}/guides/sdk`,
|
|
72
|
+
keywords: ["sdk", "install", "react", "next", "ingest"],
|
|
73
|
+
excerpt: "Install @mushi-mushi/web and start capturing user reports."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
title: "Configuration reference",
|
|
77
|
+
path: `${BASE}/reference/config`,
|
|
78
|
+
keywords: ["env", "config", "settings", "byok"],
|
|
79
|
+
excerpt: "Environment variables and project settings reference."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
title: "Evolution loop",
|
|
83
|
+
path: `${BASE}/concepts/evolution`,
|
|
84
|
+
keywords: ["evolution", "lessons", "convergence", "prompt promotion"],
|
|
85
|
+
excerpt: "Closed-loop learning from bugs, fixes, and judge scores."
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
title: "Privacy & BYOK",
|
|
89
|
+
path: `${BASE}/guides/privacy`,
|
|
90
|
+
keywords: ["privacy", "byok", "data region", "llm"],
|
|
91
|
+
excerpt: "Data boundaries, BYOK LLM keys, and retention."
|
|
92
|
+
}
|
|
93
|
+
];
|
|
94
|
+
function searchMushiDocs(query, limit = 8) {
|
|
95
|
+
const q = query.trim().toLowerCase();
|
|
96
|
+
if (!q) {
|
|
97
|
+
return MUSHI_DOCS_INDEX.slice(0, limit).map((e) => ({ ...e, score: 0 }));
|
|
98
|
+
}
|
|
99
|
+
const terms = q.split(/\s+/).filter(Boolean);
|
|
100
|
+
const scored = MUSHI_DOCS_INDEX.map((entry) => {
|
|
101
|
+
const hay = `${entry.title} ${entry.keywords.join(" ")} ${entry.excerpt}`.toLowerCase();
|
|
102
|
+
let score = 0;
|
|
103
|
+
for (const term of terms) {
|
|
104
|
+
if (entry.title.toLowerCase().includes(term)) score += 4;
|
|
105
|
+
if (entry.keywords.some((k) => k.includes(term))) score += 3;
|
|
106
|
+
if (hay.includes(term)) score += 1;
|
|
107
|
+
}
|
|
108
|
+
return { ...entry, score };
|
|
109
|
+
}).filter((e) => e.score > 0).sort((a, b) => b.score - a.score);
|
|
110
|
+
return scored.slice(0, limit);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// src/server.ts
|
|
9
114
|
var MushiApiError = class extends Error {
|
|
10
115
|
constructor(status, code, message) {
|
|
11
116
|
super(`[${code}] ${message}`);
|
|
@@ -19,10 +124,22 @@ var MushiApiError = class extends Error {
|
|
|
19
124
|
function createMushiServer(config) {
|
|
20
125
|
const { version, apiEndpoint, apiKey, projectId } = config;
|
|
21
126
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
127
|
+
const apiLog = createLogger({
|
|
128
|
+
scope: "mushi:mcp:api",
|
|
129
|
+
level: process.env.MUSHI_LOG_LEVEL ?? "info"
|
|
130
|
+
});
|
|
22
131
|
if (config.scopes !== void 0 && config.scopes.length === 0) {
|
|
23
|
-
return new McpServer({
|
|
132
|
+
return new McpServer({
|
|
133
|
+
name: MUSHI_SERVER_METADATA.name,
|
|
134
|
+
version,
|
|
135
|
+
title: MUSHI_SERVER_METADATA.title,
|
|
136
|
+
websiteUrl: MUSHI_SERVER_METADATA.websiteUrl,
|
|
137
|
+
icons: [...MUSHI_SERVER_METADATA.icons]
|
|
138
|
+
});
|
|
24
139
|
}
|
|
25
140
|
async function apiCall(path, options) {
|
|
141
|
+
const requestId = crypto.randomUUID().slice(0, 12);
|
|
142
|
+
const started = Date.now();
|
|
26
143
|
const res = await doFetch(`${apiEndpoint}${path}`, {
|
|
27
144
|
...options,
|
|
28
145
|
headers: {
|
|
@@ -32,10 +149,12 @@ function createMushiServer(config) {
|
|
|
32
149
|
// and for transparent proxies that strip X-Mushi-* headers.
|
|
33
150
|
"Authorization": `Bearer ${apiKey}`,
|
|
34
151
|
"X-Mushi-Api-Key": apiKey,
|
|
35
|
-
|
|
152
|
+
"X-Request-Id": requestId,
|
|
153
|
+
...projectId ? { "X-Mushi-Project-Id": projectId } : {},
|
|
36
154
|
...options?.headers ?? {}
|
|
37
155
|
}
|
|
38
156
|
});
|
|
157
|
+
const durationMs = Date.now() - started;
|
|
39
158
|
const text = await res.text();
|
|
40
159
|
let body = null;
|
|
41
160
|
if (text) {
|
|
@@ -49,6 +168,7 @@ function createMushiServer(config) {
|
|
|
49
168
|
const envelope2 = body;
|
|
50
169
|
const code = envelope2?.error?.code ?? `HTTP_${res.status}`;
|
|
51
170
|
const message = envelope2?.error?.message ?? text.slice(0, 500) ?? `Request failed with ${res.status}`;
|
|
171
|
+
apiLog.warn("api.failed", { path, requestId, status: res.status, durationMs, code });
|
|
52
172
|
throw new MushiApiError(res.status, code, message);
|
|
53
173
|
}
|
|
54
174
|
const envelope = body;
|
|
@@ -56,10 +176,13 @@ function createMushiServer(config) {
|
|
|
56
176
|
if (!envelope.ok) {
|
|
57
177
|
const code = envelope.error?.code ?? "API_ERROR";
|
|
58
178
|
const message = envelope.error?.message ?? "API returned ok=false";
|
|
179
|
+
apiLog.warn("api.failed", { path, requestId, status: res.status, durationMs, code });
|
|
59
180
|
throw new MushiApiError(res.status, code, message);
|
|
60
181
|
}
|
|
182
|
+
apiLog.info("api.done", { path, requestId, status: res.status, durationMs });
|
|
61
183
|
return envelope.data ?? {};
|
|
62
184
|
}
|
|
185
|
+
apiLog.info("api.done", { path, requestId, status: res.status, durationMs });
|
|
63
186
|
return body;
|
|
64
187
|
}
|
|
65
188
|
function jsonText(value) {
|
|
@@ -74,10 +197,13 @@ function createMushiServer(config) {
|
|
|
74
197
|
};
|
|
75
198
|
}
|
|
76
199
|
const server = new McpServer({
|
|
77
|
-
name:
|
|
78
|
-
version
|
|
200
|
+
name: MUSHI_SERVER_METADATA.name,
|
|
201
|
+
version,
|
|
202
|
+
title: MUSHI_SERVER_METADATA.title,
|
|
203
|
+
websiteUrl: MUSHI_SERVER_METADATA.websiteUrl,
|
|
204
|
+
icons: [...MUSHI_SERVER_METADATA.icons]
|
|
79
205
|
});
|
|
80
|
-
const ALL_TOOL_CATALOG = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG];
|
|
206
|
+
const ALL_TOOL_CATALOG = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG, ...CODEBASE_TOOL_CATALOG];
|
|
81
207
|
function annotationsFor(name, catalog = ALL_TOOL_CATALOG) {
|
|
82
208
|
const spec = catalog.find((t) => t.name === name);
|
|
83
209
|
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
@@ -110,7 +236,10 @@ function createMushiServer(config) {
|
|
|
110
236
|
status: z.string().optional().describe("Filter by status: new, classified, grouped, fixing, fixed, dismissed"),
|
|
111
237
|
category: z.string().optional().describe("Filter by category: bug, slow, visual, confusing, other"),
|
|
112
238
|
severity: z.string().optional().describe("Filter by severity: critical, high, medium, low"),
|
|
113
|
-
limit: z.number().optional().describe("Max reports to return (default 20, max 100)")
|
|
239
|
+
limit: z.number().optional().describe("Max reports to return (default 20, max 100)"),
|
|
240
|
+
project_id: z.string().optional().describe(
|
|
241
|
+
"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."
|
|
242
|
+
)
|
|
114
243
|
},
|
|
115
244
|
outputSchema: {
|
|
116
245
|
reports: z.array(z.unknown()),
|
|
@@ -123,7 +252,11 @@ function createMushiServer(config) {
|
|
|
123
252
|
if (args.category) params.set("category", args.category);
|
|
124
253
|
if (args.severity) params.set("severity", args.severity);
|
|
125
254
|
params.set("limit", String(Math.min(args.limit ?? 20, 100)));
|
|
126
|
-
const
|
|
255
|
+
const pid = args.project_id ?? projectId;
|
|
256
|
+
const extraHeaders = pid && pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
|
|
257
|
+
const data = await apiCall(`/v1/admin/reports?${params}`, {
|
|
258
|
+
headers: extraHeaders
|
|
259
|
+
});
|
|
127
260
|
return jsonResult(data);
|
|
128
261
|
}
|
|
129
262
|
);
|
|
@@ -133,9 +266,19 @@ function createMushiServer(config) {
|
|
|
133
266
|
title: titleOf("get_report_detail"),
|
|
134
267
|
description: descOf("get_report_detail"),
|
|
135
268
|
annotations: annotationsFor("get_report_detail"),
|
|
136
|
-
inputSchema: { reportId: z.string().describe("The report UUID") }
|
|
269
|
+
inputSchema: { reportId: z.string().describe("The report UUID") },
|
|
270
|
+
outputSchema: { report: z.unknown() }
|
|
137
271
|
},
|
|
138
|
-
async (args) =>
|
|
272
|
+
async (args) => {
|
|
273
|
+
const data = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
274
|
+
const res = jsonResult({ report: data });
|
|
275
|
+
return {
|
|
276
|
+
...res,
|
|
277
|
+
resource_links: [
|
|
278
|
+
{ uri: `project://reports/${args.reportId}`, title: `Report ${args.reportId.slice(0, 8)}\u2026` }
|
|
279
|
+
]
|
|
280
|
+
};
|
|
281
|
+
}
|
|
139
282
|
);
|
|
140
283
|
server.registerTool(
|
|
141
284
|
"get_report_timeline",
|
|
@@ -194,6 +337,9 @@ function createMushiServer(config) {
|
|
|
194
337
|
inputSchema: {
|
|
195
338
|
query: z.string().describe("Component name, page path, or bug description"),
|
|
196
339
|
limit: z.number().optional().describe("Max results (default 5, max 20)")
|
|
340
|
+
},
|
|
341
|
+
outputSchema: {
|
|
342
|
+
results: z.array(z.unknown())
|
|
197
343
|
}
|
|
198
344
|
},
|
|
199
345
|
async (args) => {
|
|
@@ -206,7 +352,7 @@ function createMushiServer(config) {
|
|
|
206
352
|
...projectId ? { projectId } : {}
|
|
207
353
|
})
|
|
208
354
|
});
|
|
209
|
-
return
|
|
355
|
+
return jsonResult(data);
|
|
210
356
|
}
|
|
211
357
|
);
|
|
212
358
|
server.registerTool(
|
|
@@ -215,11 +361,18 @@ function createMushiServer(config) {
|
|
|
215
361
|
title: titleOf("get_fix_context"),
|
|
216
362
|
description: descOf("get_fix_context"),
|
|
217
363
|
annotations: annotationsFor("get_fix_context"),
|
|
218
|
-
inputSchema: { reportId: z.string().describe("The report UUID to fix") }
|
|
364
|
+
inputSchema: { reportId: z.string().describe("The report UUID to fix") },
|
|
365
|
+
outputSchema: {
|
|
366
|
+
report: z.unknown(),
|
|
367
|
+
reproductionSteps: z.unknown(),
|
|
368
|
+
component: z.unknown(),
|
|
369
|
+
rootCause: z.unknown(),
|
|
370
|
+
bugOntologyTags: z.unknown()
|
|
371
|
+
}
|
|
219
372
|
},
|
|
220
373
|
async (args) => {
|
|
221
374
|
const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
222
|
-
return
|
|
375
|
+
return jsonResult({
|
|
223
376
|
report,
|
|
224
377
|
reproductionSteps: report.reproduction_steps ?? [],
|
|
225
378
|
component: report.component,
|
|
@@ -457,13 +610,114 @@ function createMushiServer(config) {
|
|
|
457
610
|
});
|
|
458
611
|
}
|
|
459
612
|
);
|
|
613
|
+
server.registerTool(
|
|
614
|
+
"diagnose_setup",
|
|
615
|
+
{
|
|
616
|
+
title: titleOf("diagnose_setup"),
|
|
617
|
+
description: descOf("diagnose_setup"),
|
|
618
|
+
annotations: annotationsFor("diagnose_setup"),
|
|
619
|
+
inputSchema: {
|
|
620
|
+
mode: z.enum(["full", "ingest", "dispatch"]).optional().describe("full (default) = ingest + dispatch; ingest = SDK pipeline only; dispatch = fix preflight only."),
|
|
621
|
+
project_id: z.string().optional().describe("Project UUID for dispatch checks (defaults to configured project)."),
|
|
622
|
+
projectId: z.string().optional().describe("Alias for project_id.")
|
|
623
|
+
},
|
|
624
|
+
outputSchema: {
|
|
625
|
+
mode: z.string(),
|
|
626
|
+
ready: z.boolean(),
|
|
627
|
+
summary: z.string(),
|
|
628
|
+
nextAction: z.string().optional(),
|
|
629
|
+
ingest: z.unknown().optional(),
|
|
630
|
+
dispatch: z.unknown().optional()
|
|
631
|
+
}
|
|
632
|
+
},
|
|
633
|
+
async (args) => {
|
|
634
|
+
const mode = args.mode ?? "full";
|
|
635
|
+
const resolvedId = args.project_id ?? args.projectId ?? projectId;
|
|
636
|
+
if (mode === "ingest") {
|
|
637
|
+
const data = await apiCall("/v1/sync/ingest-setup");
|
|
638
|
+
const failed = data.steps.filter((s) => s.required && !s.complete);
|
|
639
|
+
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(", ")}.`;
|
|
640
|
+
return jsonResult({
|
|
641
|
+
mode: "ingest",
|
|
642
|
+
ready: data.ready,
|
|
643
|
+
summary: summary2,
|
|
644
|
+
nextAction: failed[0]?.hint,
|
|
645
|
+
ingest: {
|
|
646
|
+
projectId: data.project_id,
|
|
647
|
+
projectName: data.project_name,
|
|
648
|
+
requiredComplete: data.required_complete,
|
|
649
|
+
requiredTotal: data.required_total,
|
|
650
|
+
steps: data.steps,
|
|
651
|
+
diagnostic: data.diagnostic ?? null
|
|
652
|
+
}
|
|
653
|
+
});
|
|
654
|
+
}
|
|
655
|
+
if (mode === "dispatch") {
|
|
656
|
+
if (!resolvedId) {
|
|
657
|
+
throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required for dispatch mode");
|
|
658
|
+
}
|
|
659
|
+
const data = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
660
|
+
const failed = data.checks.filter((c) => !c.ready);
|
|
661
|
+
const summary2 = data.ready ? `Project ${resolvedId} is ready to dispatch auto-fixes.` : `Dispatch blocked \u2014 ${failed.map((c) => c.label).join(", ")}.`;
|
|
662
|
+
return jsonResult({
|
|
663
|
+
mode: "dispatch",
|
|
664
|
+
ready: data.ready,
|
|
665
|
+
summary: summary2,
|
|
666
|
+
nextAction: failed[0]?.hint,
|
|
667
|
+
dispatch: { repoUrl: data.repoUrl ?? null, checks: data.checks }
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
const ingest = await apiCall("/v1/sync/ingest-setup");
|
|
671
|
+
let dispatchReady = true;
|
|
672
|
+
let dispatchBlock;
|
|
673
|
+
let dispatchChecks = null;
|
|
674
|
+
if (resolvedId) {
|
|
675
|
+
try {
|
|
676
|
+
const preflight = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
677
|
+
dispatchReady = preflight.ready;
|
|
678
|
+
dispatchChecks = preflight.checks;
|
|
679
|
+
if (!preflight.ready) {
|
|
680
|
+
const failed = preflight.checks.filter((c) => !c.ready);
|
|
681
|
+
dispatchBlock = failed[0]?.hint;
|
|
682
|
+
}
|
|
683
|
+
} catch {
|
|
684
|
+
dispatchReady = false;
|
|
685
|
+
dispatchBlock = "Could not run dispatch preflight \u2014 verify project_id and API key scope.";
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
const ingestFailed = ingest.steps.filter((s) => s.required && !s.complete);
|
|
689
|
+
const ingestBlock = ingestFailed[0]?.hint;
|
|
690
|
+
const ready = ingest.ready && dispatchReady;
|
|
691
|
+
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.";
|
|
692
|
+
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.";
|
|
693
|
+
return jsonResult({
|
|
694
|
+
mode: "full",
|
|
695
|
+
ready,
|
|
696
|
+
summary,
|
|
697
|
+
nextAction,
|
|
698
|
+
ingest: { ready: ingest.ready, steps: ingest.steps },
|
|
699
|
+
dispatch: resolvedId ? { ready: dispatchReady, checks: dispatchChecks } : null
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
);
|
|
460
703
|
server.registerTool(
|
|
461
704
|
"diagnose_connection",
|
|
462
705
|
{
|
|
463
706
|
title: titleOf("diagnose_connection"),
|
|
464
707
|
description: descOf("diagnose_connection"),
|
|
465
708
|
annotations: annotationsFor("diagnose_connection"),
|
|
466
|
-
inputSchema: {}
|
|
709
|
+
inputSchema: {},
|
|
710
|
+
outputSchema: {
|
|
711
|
+
ready: z.boolean(),
|
|
712
|
+
healthOk: z.boolean(),
|
|
713
|
+
ingestReady: z.boolean(),
|
|
714
|
+
dispatchReady: z.boolean(),
|
|
715
|
+
endpoint: z.string().nullable(),
|
|
716
|
+
projectId: z.string().nullable(),
|
|
717
|
+
issues: z.array(z.unknown()),
|
|
718
|
+
nextAction: z.string(),
|
|
719
|
+
summary: z.string()
|
|
720
|
+
}
|
|
467
721
|
},
|
|
468
722
|
async () => {
|
|
469
723
|
const issues = [];
|
|
@@ -547,7 +801,7 @@ function createMushiServer(config) {
|
|
|
547
801
|
}
|
|
548
802
|
const ready = issues.length === 0 && healthOk && ingestReady;
|
|
549
803
|
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.");
|
|
550
|
-
return
|
|
804
|
+
return jsonResult({
|
|
551
805
|
ready,
|
|
552
806
|
healthOk,
|
|
553
807
|
ingestReady,
|
|
@@ -560,17 +814,77 @@ function createMushiServer(config) {
|
|
|
560
814
|
});
|
|
561
815
|
}
|
|
562
816
|
);
|
|
817
|
+
server.registerTool(
|
|
818
|
+
"search_mushi_docs",
|
|
819
|
+
{
|
|
820
|
+
title: titleOf("search_mushi_docs"),
|
|
821
|
+
description: descOf("search_mushi_docs"),
|
|
822
|
+
annotations: annotationsFor("search_mushi_docs"),
|
|
823
|
+
inputSchema: {
|
|
824
|
+
query: z.string().describe("Keywords to search official Mushi docs (guides, MCP, inventory, QA)."),
|
|
825
|
+
limit: z.number().int().min(1).max(20).optional().describe("Max results (default 8).")
|
|
826
|
+
},
|
|
827
|
+
outputSchema: {
|
|
828
|
+
query: z.string(),
|
|
829
|
+
results: z.array(
|
|
830
|
+
z.object({
|
|
831
|
+
title: z.string(),
|
|
832
|
+
path: z.string(),
|
|
833
|
+
excerpt: z.string(),
|
|
834
|
+
score: z.number()
|
|
835
|
+
})
|
|
836
|
+
)
|
|
837
|
+
}
|
|
838
|
+
},
|
|
839
|
+
async (args) => {
|
|
840
|
+
const query = args.query ?? "";
|
|
841
|
+
const hits = searchMushiDocs(query, args.limit ?? 8);
|
|
842
|
+
const results = hits.map(({ title, path, excerpt, score }) => ({ title, path, excerpt, score }));
|
|
843
|
+
return jsonResult({ query, results });
|
|
844
|
+
}
|
|
845
|
+
);
|
|
563
846
|
server.registerTool(
|
|
564
847
|
"list_projects",
|
|
565
848
|
{
|
|
566
849
|
title: titleOf("list_projects"),
|
|
567
850
|
description: descOf("list_projects"),
|
|
568
851
|
annotations: annotationsFor("list_projects"),
|
|
569
|
-
inputSchema: {}
|
|
852
|
+
inputSchema: {},
|
|
853
|
+
outputSchema: {
|
|
854
|
+
projects: z.array(z.unknown()),
|
|
855
|
+
total: z.number().optional(),
|
|
856
|
+
_multi_project_hint: z.string().optional()
|
|
857
|
+
}
|
|
570
858
|
},
|
|
571
859
|
async () => {
|
|
572
860
|
const data = await apiCall("/v1/admin/mcp/projects");
|
|
573
|
-
return
|
|
861
|
+
return jsonResult({
|
|
862
|
+
...data,
|
|
863
|
+
_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.'
|
|
864
|
+
});
|
|
865
|
+
}
|
|
866
|
+
);
|
|
867
|
+
server.registerTool(
|
|
868
|
+
"get_account_overview",
|
|
869
|
+
{
|
|
870
|
+
title: titleOf("get_account_overview"),
|
|
871
|
+
description: descOf("get_account_overview"),
|
|
872
|
+
annotations: annotationsFor("get_account_overview"),
|
|
873
|
+
inputSchema: {},
|
|
874
|
+
outputSchema: {
|
|
875
|
+
projects: z.array(z.unknown()),
|
|
876
|
+
active_project_id: z.string().nullable(),
|
|
877
|
+
multi_project_hint: z.string()
|
|
878
|
+
}
|
|
879
|
+
},
|
|
880
|
+
async () => {
|
|
881
|
+
const data = await apiCall("/v1/admin/mcp/projects");
|
|
882
|
+
const projects = Array.isArray(data?.projects) ? data.projects : [];
|
|
883
|
+
return jsonResult({
|
|
884
|
+
projects,
|
|
885
|
+
active_project_id: projectId ?? null,
|
|
886
|
+
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".`
|
|
887
|
+
});
|
|
574
888
|
}
|
|
575
889
|
);
|
|
576
890
|
server.registerTool(
|
|
@@ -605,7 +919,7 @@ function createMushiServer(config) {
|
|
|
605
919
|
annotations: annotationsFor("get_pipeline_logs"),
|
|
606
920
|
inputSchema: {
|
|
607
921
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
|
|
608
|
-
service: z.enum(["
|
|
922
|
+
service: z.enum(["fix-worker", "qa-story-runner", "pipeline", "all"]).optional().describe("Pipeline service to filter (default: all)."),
|
|
609
923
|
since: z.string().optional().describe("ISO-8601 timestamp \u2014 return only entries after this time."),
|
|
610
924
|
limit: z.number().optional().describe("Max entries to return (default 50, max 200)."),
|
|
611
925
|
level: z.enum(["info", "warn", "error", "fatal"]).optional().describe("Minimum severity level (default: warn).")
|
|
@@ -670,6 +984,20 @@ function createMushiServer(config) {
|
|
|
670
984
|
report_id: z.string().describe("Report UUID to triage."),
|
|
671
985
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
|
|
672
986
|
include_logs: z.boolean().optional().describe("Include recent pipeline logs in triage packet (default: true).")
|
|
987
|
+
},
|
|
988
|
+
outputSchema: {
|
|
989
|
+
report_id: z.string(),
|
|
990
|
+
severity: z.unknown(),
|
|
991
|
+
category: z.unknown(),
|
|
992
|
+
status: z.unknown(),
|
|
993
|
+
report: z.unknown(),
|
|
994
|
+
reporter_thread: z.unknown().nullable(),
|
|
995
|
+
similar_bugs: z.unknown().nullable(),
|
|
996
|
+
fix_context: z.unknown().nullable(),
|
|
997
|
+
blast_radius: z.unknown().nullable(),
|
|
998
|
+
recent_logs: z.unknown().nullable(),
|
|
999
|
+
recommended_actions: z.array(z.unknown()),
|
|
1000
|
+
triage_summary: z.string()
|
|
673
1001
|
}
|
|
674
1002
|
},
|
|
675
1003
|
async (args) => {
|
|
@@ -713,7 +1041,7 @@ function createMushiServer(config) {
|
|
|
713
1041
|
args: { reportId: args.report_id }
|
|
714
1042
|
});
|
|
715
1043
|
}
|
|
716
|
-
|
|
1044
|
+
const result = {
|
|
717
1045
|
report_id: args.report_id,
|
|
718
1046
|
severity,
|
|
719
1047
|
category,
|
|
@@ -726,7 +1054,15 @@ function createMushiServer(config) {
|
|
|
726
1054
|
recent_logs: logsRes.status === "fulfilled" ? logsRes.value : null,
|
|
727
1055
|
recommended_actions: actions,
|
|
728
1056
|
triage_summary: `[${severity}] ${category} \u2014 status: ${status}. ${actions.length > 0 ? `Recommended: ${actions[0]?.action}.` : "No action required."}`
|
|
729
|
-
}
|
|
1057
|
+
};
|
|
1058
|
+
const res = jsonResult(result);
|
|
1059
|
+
return {
|
|
1060
|
+
...res,
|
|
1061
|
+
resource_links: [
|
|
1062
|
+
{ uri: `project://reports/${args.report_id}`, title: `Report ${args.report_id.slice(0, 8)}\u2026` },
|
|
1063
|
+
...pid ? [{ uri: `project://dashboard`, title: "Project dashboard" }] : []
|
|
1064
|
+
]
|
|
1065
|
+
};
|
|
730
1066
|
}
|
|
731
1067
|
);
|
|
732
1068
|
server.registerTool(
|
|
@@ -963,12 +1299,15 @@ function createMushiServer(config) {
|
|
|
963
1299
|
},
|
|
964
1300
|
async (args) => {
|
|
965
1301
|
const pid = args.project_id ?? projectId;
|
|
966
|
-
const
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
1302
|
+
const data = await apiCall("/v1/admin/lessons/query", {
|
|
1303
|
+
method: "POST",
|
|
1304
|
+
body: JSON.stringify({
|
|
1305
|
+
diff_text: args.diff_text,
|
|
1306
|
+
...args.max_tokens !== void 0 ? { max_tokens: args.max_tokens } : {},
|
|
1307
|
+
...args.top_k !== void 0 ? { top_k: args.top_k } : {},
|
|
1308
|
+
...pid ? { project_id: pid } : {}
|
|
1309
|
+
})
|
|
1310
|
+
});
|
|
972
1311
|
return jsonText(data);
|
|
973
1312
|
}
|
|
974
1313
|
);
|
|
@@ -1104,7 +1443,7 @@ function createMushiServer(config) {
|
|
|
1104
1443
|
contents: [{
|
|
1105
1444
|
uri: "privacy://status",
|
|
1106
1445
|
mimeType: "application/json",
|
|
1107
|
-
text: JSON.stringify(await apiCall("/v1/admin/privacy
|
|
1446
|
+
text: JSON.stringify(await apiCall("/v1/admin/privacy-status"), null, 2)
|
|
1108
1447
|
}]
|
|
1109
1448
|
})
|
|
1110
1449
|
);
|
|
@@ -1118,7 +1457,11 @@ function createMushiServer(config) {
|
|
|
1118
1457
|
contents: [{
|
|
1119
1458
|
uri: "evolution://history",
|
|
1120
1459
|
mimeType: "application/json",
|
|
1121
|
-
text: JSON.stringify(
|
|
1460
|
+
text: projectId ? JSON.stringify(
|
|
1461
|
+
await apiCall(`/v1/admin/projects/${encodeURIComponent(projectId)}/evolution-history`),
|
|
1462
|
+
null,
|
|
1463
|
+
2
|
|
1464
|
+
) : JSON.stringify({ ok: false, error: "project_id required" })
|
|
1122
1465
|
}]
|
|
1123
1466
|
})
|
|
1124
1467
|
);
|
|
@@ -1697,12 +2040,192 @@ Be specific. No generic advice.`
|
|
|
1697
2040
|
// admin-authed timeline, which also carries fix / QA / status lanes.
|
|
1698
2041
|
async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}/timeline`))
|
|
1699
2042
|
);
|
|
2043
|
+
server.registerTool(
|
|
2044
|
+
"ask_codebase",
|
|
2045
|
+
{
|
|
2046
|
+
title: titleOf("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2047
|
+
description: descOf("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2048
|
+
annotations: annotationsFor("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2049
|
+
inputSchema: {
|
|
2050
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2051
|
+
question: z.string().describe("Plain-English question about the repo"),
|
|
2052
|
+
thread_id: z.string().optional().describe("Optional thread UUID to continue a conversation"),
|
|
2053
|
+
file_path: z.string().optional().describe("Optional file path focus"),
|
|
2054
|
+
symbol_name: z.string().optional().describe("Optional symbol name focus")
|
|
2055
|
+
}
|
|
2056
|
+
},
|
|
2057
|
+
async (args) => {
|
|
2058
|
+
const pid = args.project_id ?? projectId;
|
|
2059
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2060
|
+
const body = {
|
|
2061
|
+
messages: [{ role: "user", content: args.question }]
|
|
2062
|
+
};
|
|
2063
|
+
if (args.thread_id) body.threadId = args.thread_id;
|
|
2064
|
+
if (args.file_path) {
|
|
2065
|
+
body.fileFocus = {
|
|
2066
|
+
file_path: args.file_path,
|
|
2067
|
+
symbol_name: args.symbol_name ?? null
|
|
2068
|
+
};
|
|
2069
|
+
}
|
|
2070
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/chat`, {
|
|
2071
|
+
method: "POST",
|
|
2072
|
+
body: JSON.stringify(body)
|
|
2073
|
+
});
|
|
2074
|
+
return jsonText(data);
|
|
2075
|
+
}
|
|
2076
|
+
);
|
|
2077
|
+
server.registerTool(
|
|
2078
|
+
"get_file_summary",
|
|
2079
|
+
{
|
|
2080
|
+
title: titleOf("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2081
|
+
description: descOf("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2082
|
+
annotations: annotationsFor("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2083
|
+
inputSchema: {
|
|
2084
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2085
|
+
file_path: z.string().describe("Indexed file path"),
|
|
2086
|
+
symbol_name: z.string().optional().describe("Optional symbol name within the file"),
|
|
2087
|
+
force: z.boolean().optional().describe("Bypass cache and regenerate")
|
|
2088
|
+
}
|
|
2089
|
+
},
|
|
2090
|
+
async (args) => {
|
|
2091
|
+
const pid = args.project_id ?? projectId;
|
|
2092
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2093
|
+
const qs = new URLSearchParams({ file_path: args.file_path });
|
|
2094
|
+
if (args.symbol_name) qs.set("symbol_name", args.symbol_name);
|
|
2095
|
+
if (args.force) qs.set("force", "1");
|
|
2096
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/summary?${qs}`);
|
|
2097
|
+
return jsonText(data);
|
|
2098
|
+
}
|
|
2099
|
+
);
|
|
2100
|
+
server.registerTool(
|
|
2101
|
+
"get_codebase_tour",
|
|
2102
|
+
{
|
|
2103
|
+
title: titleOf("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2104
|
+
description: descOf("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2105
|
+
annotations: annotationsFor("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2106
|
+
inputSchema: {
|
|
2107
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2108
|
+
force: z.boolean().optional().describe("Bypass cache and regenerate")
|
|
2109
|
+
}
|
|
2110
|
+
},
|
|
2111
|
+
async (args) => {
|
|
2112
|
+
const pid = args.project_id ?? projectId;
|
|
2113
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2114
|
+
const qs = args.force ? "?force=1" : "";
|
|
2115
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/tour${qs}`);
|
|
2116
|
+
return jsonText(data);
|
|
2117
|
+
}
|
|
2118
|
+
);
|
|
2119
|
+
server.registerTool(
|
|
2120
|
+
"search_codebase",
|
|
2121
|
+
{
|
|
2122
|
+
title: titleOf("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2123
|
+
description: descOf("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2124
|
+
annotations: annotationsFor("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2125
|
+
inputSchema: {
|
|
2126
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2127
|
+
query: z.string().describe("Plain-English search query"),
|
|
2128
|
+
k: z.number().optional().describe("Number of results (default 8, max 20)"),
|
|
2129
|
+
scope_prefix: z.string().optional().describe("Optional subdirectory scope prefix")
|
|
2130
|
+
}
|
|
2131
|
+
},
|
|
2132
|
+
async (args) => {
|
|
2133
|
+
const pid = args.project_id ?? projectId;
|
|
2134
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2135
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/search`, {
|
|
2136
|
+
method: "POST",
|
|
2137
|
+
body: JSON.stringify({
|
|
2138
|
+
query: args.query,
|
|
2139
|
+
k: args.k ?? 8,
|
|
2140
|
+
scope_prefix: args.scope_prefix ?? void 0
|
|
2141
|
+
})
|
|
2142
|
+
});
|
|
2143
|
+
return jsonText(data);
|
|
2144
|
+
}
|
|
2145
|
+
);
|
|
2146
|
+
server.registerTool(
|
|
2147
|
+
"get_codebase_domains",
|
|
2148
|
+
{
|
|
2149
|
+
title: titleOf("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2150
|
+
description: descOf("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2151
|
+
annotations: annotationsFor("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2152
|
+
inputSchema: {
|
|
2153
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2154
|
+
force: z.boolean().optional().describe("Bypass cache and regenerate"),
|
|
2155
|
+
scope_prefix: z.string().optional().describe("Optional subdirectory scope prefix")
|
|
2156
|
+
}
|
|
2157
|
+
},
|
|
2158
|
+
async (args) => {
|
|
2159
|
+
const pid = args.project_id ?? projectId;
|
|
2160
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2161
|
+
const qs = new URLSearchParams();
|
|
2162
|
+
if (args.force) qs.set("force", "1");
|
|
2163
|
+
if (args.scope_prefix) qs.set("scope_prefix", args.scope_prefix);
|
|
2164
|
+
const suffix = qs.toString() ? `?${qs}` : "";
|
|
2165
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/domains${suffix}`);
|
|
2166
|
+
return jsonText(data);
|
|
2167
|
+
}
|
|
2168
|
+
);
|
|
2169
|
+
server.registerTool(
|
|
2170
|
+
"analyze_codebase_impact",
|
|
2171
|
+
{
|
|
2172
|
+
title: titleOf("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2173
|
+
description: descOf("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2174
|
+
annotations: annotationsFor("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2175
|
+
inputSchema: {
|
|
2176
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2177
|
+
paths: z.array(z.string()).optional().describe("Changed file paths"),
|
|
2178
|
+
source: z.enum(["last_push", "compare", "fix"]).optional().describe("Auto-resolve changed paths"),
|
|
2179
|
+
compare: z.string().optional().describe("GitHub compare range base...head"),
|
|
2180
|
+
fix_id: z.string().optional().describe("Fix attempt UUID for PR changed files")
|
|
2181
|
+
}
|
|
2182
|
+
},
|
|
2183
|
+
async (args) => {
|
|
2184
|
+
const pid = args.project_id ?? projectId;
|
|
2185
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2186
|
+
const qs = new URLSearchParams();
|
|
2187
|
+
if (args.paths?.length) qs.set("paths", args.paths.join(","));
|
|
2188
|
+
if (args.source === "last_push") qs.set("ref", "last_push");
|
|
2189
|
+
if (args.compare) qs.set("compare", args.compare);
|
|
2190
|
+
if (args.fix_id) qs.set("fix_id", args.fix_id);
|
|
2191
|
+
if (!qs.toString()) {
|
|
2192
|
+
throw new MushiApiError(400, "BAD_REQUEST", "Provide paths, source=last_push, compare, or fix_id");
|
|
2193
|
+
}
|
|
2194
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/impact?${qs}`);
|
|
2195
|
+
return jsonText(data);
|
|
2196
|
+
}
|
|
2197
|
+
);
|
|
2198
|
+
server.registerTool(
|
|
2199
|
+
"analyze_wiki_knowledge",
|
|
2200
|
+
{
|
|
2201
|
+
title: titleOf("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2202
|
+
description: descOf("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2203
|
+
annotations: annotationsFor("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2204
|
+
inputSchema: {
|
|
2205
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)")
|
|
2206
|
+
}
|
|
2207
|
+
},
|
|
2208
|
+
async (args) => {
|
|
2209
|
+
const pid = args.project_id ?? projectId;
|
|
2210
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2211
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/knowledge/graph`);
|
|
2212
|
+
return jsonText(data);
|
|
2213
|
+
}
|
|
2214
|
+
);
|
|
1700
2215
|
const grantedScopes = config.scopes;
|
|
1701
|
-
|
|
2216
|
+
const featureFilter = config.features ?? "all";
|
|
2217
|
+
if (grantedScopes !== void 0 || featureFilter !== "all") {
|
|
1702
2218
|
const toolRegistry = server._registeredTools;
|
|
1703
|
-
const allSpecs = [
|
|
2219
|
+
const allSpecs = [
|
|
2220
|
+
...TOOL_CATALOG,
|
|
2221
|
+
...TDD_TOOL_CATALOG,
|
|
2222
|
+
...CODEBASE_TOOL_CATALOG
|
|
2223
|
+
];
|
|
1704
2224
|
for (const spec of allSpecs) {
|
|
1705
|
-
if (!grantedScopes.includes(spec.scope)) {
|
|
2225
|
+
if (grantedScopes !== void 0 && !grantedScopes.includes(spec.scope)) {
|
|
2226
|
+
toolRegistry[spec.name]?.remove();
|
|
2227
|
+
}
|
|
2228
|
+
if (featureFilter !== "all" && !toolMatchesFeatures(spec.name, featureFilter)) {
|
|
1706
2229
|
toolRegistry[spec.name]?.remove();
|
|
1707
2230
|
}
|
|
1708
2231
|
}
|