@mushi-mushi/mcp 0.14.1 → 0.16.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +23 -1
- package/README.md +81 -10
- package/dist/branding.d.ts +31 -0
- package/dist/branding.js +16 -0
- package/dist/catalog.d.ts +2 -1
- package/dist/catalog.js +3 -1
- package/dist/chunk-B2QEG35D.js +158 -0
- package/dist/chunk-EHB2BGWX.js +28 -0
- package/dist/{chunk-FQ5WR3PS.js → chunk-PZEGN4XD.js} +90 -7
- package/dist/feature-groups.d.ts +23 -0
- package/dist/feature-groups.js +24 -0
- package/dist/index.js +850 -42
- package/dist/server.d.ts +6 -0
- package/dist/server.js +614 -31
- package/package.json +18 -8
package/dist/server.js
CHANGED
|
@@ -1,11 +1,128 @@
|
|
|
1
1
|
import {
|
|
2
|
+
CODEBASE_TOOL_CATALOG,
|
|
2
3
|
TDD_TOOL_CATALOG,
|
|
3
4
|
TOOL_CATALOG
|
|
4
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PZEGN4XD.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", "multi-project", "account mode", "install", "setup", "deeplink"],
|
|
31
|
+
excerpt: "Wire Mushi MCP into Cursor, Claude, or any MCP client. Supports per-project and account-level keys."
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
title: "API key scopes",
|
|
35
|
+
path: `${BASE}/guides/api-keys`,
|
|
36
|
+
keywords: ["api key", "mcp:read", "mcp:write", "scopes", "security", "account key", "org-scoped", "multi-project"],
|
|
37
|
+
excerpt: "Separate SDK ingest keys from MCP read/write keys. Mint an org-scoped account key for multi-project access."
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
title: "Account key (org-scoped)",
|
|
41
|
+
path: `${BASE}/guides/mcp-setup`,
|
|
42
|
+
keywords: ["account key", "org-scoped", "multi-project", "MUSHI_PROJECT_ID", "all projects", "mint-org-key", "get_account_overview"],
|
|
43
|
+
excerpt: "Mint one org-scoped key that covers all your projects; no MUSHI_PROJECT_ID needed. The agent calls get_account_overview to discover projects."
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
title: "Cursor MCP troubleshooting",
|
|
47
|
+
path: `${BASE}/guides/mcp-setup`,
|
|
48
|
+
keywords: ["cursor", "mcp.json", "red badge", "transport error", "stdout", "stderr", "json-rpc", "INVALID_TOKEN", "syntax helper", "diagnose_connection"],
|
|
49
|
+
excerpt: "Cursor stdio MCP requires stdout to contain only JSON-RPC. Use the console syntax helper, restart Cursor MCP, then run diagnose_connection."
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
title: "Judge loop",
|
|
53
|
+
path: `${BASE}/concepts/judge-loop`,
|
|
54
|
+
keywords: ["judge", "sonnet", "quality", "fix scoring"],
|
|
55
|
+
excerpt: "Sonnet-as-Judge scores fix quality and feeds the lesson library."
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
title: "Inventory v2",
|
|
59
|
+
path: `${BASE}/concepts/inventory`,
|
|
60
|
+
keywords: ["inventory", "user stories", "spec", "yaml", "actions"],
|
|
61
|
+
excerpt: "inventory.yaml maps pages, stories, and expected outcomes for spec traceability."
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
title: "QA coverage stories",
|
|
65
|
+
path: `${BASE}/guides/qa-stories`,
|
|
66
|
+
keywords: ["qa", "playwright", "tdd", "cron", "browserbase", "firecrawl"],
|
|
67
|
+
excerpt: "Author, approve, and schedule user-story tests."
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
title: "Skill pipelines",
|
|
71
|
+
path: `${BASE}/guides/skill-pipelines`,
|
|
72
|
+
keywords: ["skills", "pipeline", "cursor", "workflow"],
|
|
73
|
+
excerpt: "Attach agent skills to reports and run handoff or cloud pipelines."
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
title: "Dispatch fix",
|
|
77
|
+
path: `${BASE}/guides/dispatch-fix`,
|
|
78
|
+
keywords: ["dispatch", "fix", "pr", "cursor cloud", "autofix"],
|
|
79
|
+
excerpt: "Dispatch the Mushi fix agent or Cursor Cloud for a classified report."
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
title: "SDK installation",
|
|
83
|
+
path: `${BASE}/guides/sdk`,
|
|
84
|
+
keywords: ["sdk", "install", "react", "next", "ingest"],
|
|
85
|
+
excerpt: "Install @mushi-mushi/web and start capturing user reports."
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
title: "Configuration reference",
|
|
89
|
+
path: `${BASE}/reference/config`,
|
|
90
|
+
keywords: ["env", "config", "settings", "byok"],
|
|
91
|
+
excerpt: "Environment variables and project settings reference."
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
title: "Evolution loop",
|
|
95
|
+
path: `${BASE}/concepts/evolution`,
|
|
96
|
+
keywords: ["evolution", "lessons", "convergence", "prompt promotion"],
|
|
97
|
+
excerpt: "Closed-loop learning from bugs, fixes, and judge scores."
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
title: "Privacy & BYOK",
|
|
101
|
+
path: `${BASE}/guides/privacy`,
|
|
102
|
+
keywords: ["privacy", "byok", "data region", "llm"],
|
|
103
|
+
excerpt: "Data boundaries, BYOK LLM keys, and retention."
|
|
104
|
+
}
|
|
105
|
+
];
|
|
106
|
+
function searchMushiDocs(query, limit = 8) {
|
|
107
|
+
const q = query.trim().toLowerCase();
|
|
108
|
+
if (!q) {
|
|
109
|
+
return MUSHI_DOCS_INDEX.slice(0, limit).map((e) => ({ ...e, score: 0 }));
|
|
110
|
+
}
|
|
111
|
+
const terms = q.split(/\s+/).filter(Boolean);
|
|
112
|
+
const scored = MUSHI_DOCS_INDEX.map((entry) => {
|
|
113
|
+
const hay = `${entry.title} ${entry.keywords.join(" ")} ${entry.excerpt}`.toLowerCase();
|
|
114
|
+
let score = 0;
|
|
115
|
+
for (const term of terms) {
|
|
116
|
+
if (entry.title.toLowerCase().includes(term)) score += 4;
|
|
117
|
+
if (entry.keywords.some((k) => k.includes(term))) score += 3;
|
|
118
|
+
if (hay.includes(term)) score += 1;
|
|
119
|
+
}
|
|
120
|
+
return { ...entry, score };
|
|
121
|
+
}).filter((e) => e.score > 0).sort((a, b) => b.score - a.score);
|
|
122
|
+
return scored.slice(0, limit);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// src/server.ts
|
|
9
126
|
var MushiApiError = class extends Error {
|
|
10
127
|
constructor(status, code, message) {
|
|
11
128
|
super(`[${code}] ${message}`);
|
|
@@ -19,10 +136,23 @@ var MushiApiError = class extends Error {
|
|
|
19
136
|
function createMushiServer(config) {
|
|
20
137
|
const { version, apiEndpoint, apiKey, projectId } = config;
|
|
21
138
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
139
|
+
const apiLog = createLogger({
|
|
140
|
+
scope: "mushi:mcp:api",
|
|
141
|
+
level: process.env.MUSHI_LOG_LEVEL ?? "info",
|
|
142
|
+
destination: "stderr"
|
|
143
|
+
});
|
|
22
144
|
if (config.scopes !== void 0 && config.scopes.length === 0) {
|
|
23
|
-
return new McpServer({
|
|
145
|
+
return new McpServer({
|
|
146
|
+
name: MUSHI_SERVER_METADATA.name,
|
|
147
|
+
version,
|
|
148
|
+
title: MUSHI_SERVER_METADATA.title,
|
|
149
|
+
websiteUrl: MUSHI_SERVER_METADATA.websiteUrl,
|
|
150
|
+
icons: [...MUSHI_SERVER_METADATA.icons]
|
|
151
|
+
});
|
|
24
152
|
}
|
|
25
153
|
async function apiCall(path, options) {
|
|
154
|
+
const requestId = crypto.randomUUID().slice(0, 12);
|
|
155
|
+
const started = Date.now();
|
|
26
156
|
const res = await doFetch(`${apiEndpoint}${path}`, {
|
|
27
157
|
...options,
|
|
28
158
|
headers: {
|
|
@@ -32,10 +162,12 @@ function createMushiServer(config) {
|
|
|
32
162
|
// and for transparent proxies that strip X-Mushi-* headers.
|
|
33
163
|
"Authorization": `Bearer ${apiKey}`,
|
|
34
164
|
"X-Mushi-Api-Key": apiKey,
|
|
35
|
-
|
|
165
|
+
"X-Request-Id": requestId,
|
|
166
|
+
...projectId ? { "X-Mushi-Project-Id": projectId } : {},
|
|
36
167
|
...options?.headers ?? {}
|
|
37
168
|
}
|
|
38
169
|
});
|
|
170
|
+
const durationMs = Date.now() - started;
|
|
39
171
|
const text = await res.text();
|
|
40
172
|
let body = null;
|
|
41
173
|
if (text) {
|
|
@@ -49,6 +181,7 @@ function createMushiServer(config) {
|
|
|
49
181
|
const envelope2 = body;
|
|
50
182
|
const code = envelope2?.error?.code ?? `HTTP_${res.status}`;
|
|
51
183
|
const message = envelope2?.error?.message ?? text.slice(0, 500) ?? `Request failed with ${res.status}`;
|
|
184
|
+
apiLog.warn("api.failed", { path, requestId, status: res.status, durationMs, code });
|
|
52
185
|
throw new MushiApiError(res.status, code, message);
|
|
53
186
|
}
|
|
54
187
|
const envelope = body;
|
|
@@ -56,12 +189,47 @@ function createMushiServer(config) {
|
|
|
56
189
|
if (!envelope.ok) {
|
|
57
190
|
const code = envelope.error?.code ?? "API_ERROR";
|
|
58
191
|
const message = envelope.error?.message ?? "API returned ok=false";
|
|
192
|
+
apiLog.warn("api.failed", { path, requestId, status: res.status, durationMs, code });
|
|
59
193
|
throw new MushiApiError(res.status, code, message);
|
|
60
194
|
}
|
|
195
|
+
apiLog.info("api.done", { path, requestId, status: res.status, durationMs });
|
|
61
196
|
return envelope.data ?? {};
|
|
62
197
|
}
|
|
198
|
+
apiLog.info("api.done", { path, requestId, status: res.status, durationMs });
|
|
63
199
|
return body;
|
|
64
200
|
}
|
|
201
|
+
async function resolveProjectId(explicitId) {
|
|
202
|
+
if (explicitId) return explicitId;
|
|
203
|
+
if (projectId) return projectId;
|
|
204
|
+
try {
|
|
205
|
+
const data = await apiCall(
|
|
206
|
+
"/v1/admin/mcp/account-overview"
|
|
207
|
+
);
|
|
208
|
+
if (data.total === 1 && data.projects[0]) {
|
|
209
|
+
return data.projects[0].id;
|
|
210
|
+
}
|
|
211
|
+
if (data.total === 0) {
|
|
212
|
+
throw new MushiApiError(
|
|
213
|
+
400,
|
|
214
|
+
"NO_ACCESSIBLE_PROJECTS",
|
|
215
|
+
"This API key has no accessible projects. Mint an API key on a project in the admin console."
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
const projectList = data.projects.map((p) => `${p.name ?? "unnamed"} (${p.id})`).join(", ");
|
|
219
|
+
throw new MushiApiError(
|
|
220
|
+
400,
|
|
221
|
+
"PROJECT_ID_REQUIRED",
|
|
222
|
+
`Multiple projects are accessible with this key: ${projectList}. Pass projectId explicitly to target a specific project, or set MUSHI_PROJECT_ID in your MCP env config.`
|
|
223
|
+
);
|
|
224
|
+
} catch (err) {
|
|
225
|
+
if (err instanceof MushiApiError) throw err;
|
|
226
|
+
throw new MushiApiError(
|
|
227
|
+
400,
|
|
228
|
+
"MISSING_PROJECT_ID",
|
|
229
|
+
"projectId is required. Set MUSHI_PROJECT_ID in .cursor/mcp.json or pass it explicitly."
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
65
233
|
function jsonText(value) {
|
|
66
234
|
return {
|
|
67
235
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
@@ -74,10 +242,13 @@ function createMushiServer(config) {
|
|
|
74
242
|
};
|
|
75
243
|
}
|
|
76
244
|
const server = new McpServer({
|
|
77
|
-
name:
|
|
78
|
-
version
|
|
245
|
+
name: MUSHI_SERVER_METADATA.name,
|
|
246
|
+
version,
|
|
247
|
+
title: MUSHI_SERVER_METADATA.title,
|
|
248
|
+
websiteUrl: MUSHI_SERVER_METADATA.websiteUrl,
|
|
249
|
+
icons: [...MUSHI_SERVER_METADATA.icons]
|
|
79
250
|
});
|
|
80
|
-
const ALL_TOOL_CATALOG = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG];
|
|
251
|
+
const ALL_TOOL_CATALOG = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG, ...CODEBASE_TOOL_CATALOG];
|
|
81
252
|
function annotationsFor(name, catalog = ALL_TOOL_CATALOG) {
|
|
82
253
|
const spec = catalog.find((t) => t.name === name);
|
|
83
254
|
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
@@ -110,7 +281,10 @@ function createMushiServer(config) {
|
|
|
110
281
|
status: z.string().optional().describe("Filter by status: new, classified, grouped, fixing, fixed, dismissed"),
|
|
111
282
|
category: z.string().optional().describe("Filter by category: bug, slow, visual, confusing, other"),
|
|
112
283
|
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)")
|
|
284
|
+
limit: z.number().optional().describe("Max reports to return (default 20, max 100)"),
|
|
285
|
+
project_id: z.string().optional().describe(
|
|
286
|
+
"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."
|
|
287
|
+
)
|
|
114
288
|
},
|
|
115
289
|
outputSchema: {
|
|
116
290
|
reports: z.array(z.unknown()),
|
|
@@ -123,7 +297,11 @@ function createMushiServer(config) {
|
|
|
123
297
|
if (args.category) params.set("category", args.category);
|
|
124
298
|
if (args.severity) params.set("severity", args.severity);
|
|
125
299
|
params.set("limit", String(Math.min(args.limit ?? 20, 100)));
|
|
126
|
-
const
|
|
300
|
+
const pid = await resolveProjectId(args.project_id);
|
|
301
|
+
const extraHeaders = pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
|
|
302
|
+
const data = await apiCall(`/v1/admin/reports?${params}`, {
|
|
303
|
+
headers: extraHeaders
|
|
304
|
+
});
|
|
127
305
|
return jsonResult(data);
|
|
128
306
|
}
|
|
129
307
|
);
|
|
@@ -133,9 +311,19 @@ function createMushiServer(config) {
|
|
|
133
311
|
title: titleOf("get_report_detail"),
|
|
134
312
|
description: descOf("get_report_detail"),
|
|
135
313
|
annotations: annotationsFor("get_report_detail"),
|
|
136
|
-
inputSchema: { reportId: z.string().describe("The report UUID") }
|
|
314
|
+
inputSchema: { reportId: z.string().describe("The report UUID") },
|
|
315
|
+
outputSchema: { report: z.unknown() }
|
|
137
316
|
},
|
|
138
|
-
async (args) =>
|
|
317
|
+
async (args) => {
|
|
318
|
+
const data = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
319
|
+
const res = jsonResult({ report: data });
|
|
320
|
+
return {
|
|
321
|
+
...res,
|
|
322
|
+
resource_links: [
|
|
323
|
+
{ uri: `project://reports/${args.reportId}`, title: `Report ${args.reportId.slice(0, 8)}\u2026` }
|
|
324
|
+
]
|
|
325
|
+
};
|
|
326
|
+
}
|
|
139
327
|
);
|
|
140
328
|
server.registerTool(
|
|
141
329
|
"get_report_timeline",
|
|
@@ -194,6 +382,9 @@ function createMushiServer(config) {
|
|
|
194
382
|
inputSchema: {
|
|
195
383
|
query: z.string().describe("Component name, page path, or bug description"),
|
|
196
384
|
limit: z.number().optional().describe("Max results (default 5, max 20)")
|
|
385
|
+
},
|
|
386
|
+
outputSchema: {
|
|
387
|
+
results: z.array(z.unknown())
|
|
197
388
|
}
|
|
198
389
|
},
|
|
199
390
|
async (args) => {
|
|
@@ -206,7 +397,7 @@ function createMushiServer(config) {
|
|
|
206
397
|
...projectId ? { projectId } : {}
|
|
207
398
|
})
|
|
208
399
|
});
|
|
209
|
-
return
|
|
400
|
+
return jsonResult(data);
|
|
210
401
|
}
|
|
211
402
|
);
|
|
212
403
|
server.registerTool(
|
|
@@ -215,11 +406,18 @@ function createMushiServer(config) {
|
|
|
215
406
|
title: titleOf("get_fix_context"),
|
|
216
407
|
description: descOf("get_fix_context"),
|
|
217
408
|
annotations: annotationsFor("get_fix_context"),
|
|
218
|
-
inputSchema: { reportId: z.string().describe("The report UUID to fix") }
|
|
409
|
+
inputSchema: { reportId: z.string().describe("The report UUID to fix") },
|
|
410
|
+
outputSchema: {
|
|
411
|
+
report: z.unknown(),
|
|
412
|
+
reproductionSteps: z.unknown(),
|
|
413
|
+
component: z.unknown(),
|
|
414
|
+
rootCause: z.unknown(),
|
|
415
|
+
bugOntologyTags: z.unknown()
|
|
416
|
+
}
|
|
219
417
|
},
|
|
220
418
|
async (args) => {
|
|
221
419
|
const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
222
|
-
return
|
|
420
|
+
return jsonResult({
|
|
223
421
|
report,
|
|
224
422
|
reproductionSteps: report.reproduction_steps ?? [],
|
|
225
423
|
component: report.component,
|
|
@@ -457,13 +655,115 @@ function createMushiServer(config) {
|
|
|
457
655
|
});
|
|
458
656
|
}
|
|
459
657
|
);
|
|
658
|
+
server.registerTool(
|
|
659
|
+
"diagnose_setup",
|
|
660
|
+
{
|
|
661
|
+
title: titleOf("diagnose_setup"),
|
|
662
|
+
description: descOf("diagnose_setup"),
|
|
663
|
+
annotations: annotationsFor("diagnose_setup"),
|
|
664
|
+
inputSchema: {
|
|
665
|
+
mode: z.enum(["full", "ingest", "dispatch"]).optional().describe("full (default) = ingest + dispatch; ingest = SDK pipeline only; dispatch = fix preflight only."),
|
|
666
|
+
project_id: z.string().optional().describe("Project UUID for dispatch checks (defaults to configured project)."),
|
|
667
|
+
projectId: z.string().optional().describe("Alias for project_id.")
|
|
668
|
+
},
|
|
669
|
+
outputSchema: {
|
|
670
|
+
mode: z.string(),
|
|
671
|
+
ready: z.boolean(),
|
|
672
|
+
summary: z.string(),
|
|
673
|
+
nextAction: z.string().optional(),
|
|
674
|
+
ingest: z.unknown().optional(),
|
|
675
|
+
dispatch: z.unknown().optional()
|
|
676
|
+
}
|
|
677
|
+
},
|
|
678
|
+
async (args) => {
|
|
679
|
+
const mode = args.mode ?? "full";
|
|
680
|
+
const resolvedId = args.project_id ?? args.projectId ?? projectId;
|
|
681
|
+
if (mode === "ingest") {
|
|
682
|
+
const data = await apiCall("/v1/sync/ingest-setup");
|
|
683
|
+
const failed = data.steps.filter((s) => s.required && !s.complete);
|
|
684
|
+
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(", ")}.`;
|
|
685
|
+
return jsonResult({
|
|
686
|
+
mode: "ingest",
|
|
687
|
+
ready: data.ready,
|
|
688
|
+
summary: summary2,
|
|
689
|
+
nextAction: failed[0]?.hint,
|
|
690
|
+
ingest: {
|
|
691
|
+
projectId: data.project_id,
|
|
692
|
+
projectName: data.project_name,
|
|
693
|
+
requiredComplete: data.required_complete,
|
|
694
|
+
requiredTotal: data.required_total,
|
|
695
|
+
steps: data.steps,
|
|
696
|
+
diagnostic: data.diagnostic ?? null
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
if (mode === "dispatch") {
|
|
701
|
+
if (!resolvedId) {
|
|
702
|
+
throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required for dispatch mode");
|
|
703
|
+
}
|
|
704
|
+
const data = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
705
|
+
const failed = data.checks.filter((c) => !c.ready);
|
|
706
|
+
const summary2 = data.ready ? `Project ${resolvedId} is ready to dispatch auto-fixes.` : `Dispatch blocked \u2014 ${failed.map((c) => c.label).join(", ")}.`;
|
|
707
|
+
return jsonResult({
|
|
708
|
+
mode: "dispatch",
|
|
709
|
+
ready: data.ready,
|
|
710
|
+
summary: summary2,
|
|
711
|
+
nextAction: failed[0]?.hint,
|
|
712
|
+
dispatch: { repoUrl: data.repoUrl ?? null, checks: data.checks }
|
|
713
|
+
});
|
|
714
|
+
}
|
|
715
|
+
const ingest = await apiCall("/v1/sync/ingest-setup");
|
|
716
|
+
let dispatchReady = true;
|
|
717
|
+
let dispatchBlock;
|
|
718
|
+
let dispatchChecks = null;
|
|
719
|
+
if (resolvedId) {
|
|
720
|
+
try {
|
|
721
|
+
const preflight = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
722
|
+
dispatchReady = preflight.ready;
|
|
723
|
+
dispatchChecks = preflight.checks;
|
|
724
|
+
if (!preflight.ready) {
|
|
725
|
+
const failed = preflight.checks.filter((c) => !c.ready);
|
|
726
|
+
dispatchBlock = failed[0]?.hint;
|
|
727
|
+
}
|
|
728
|
+
} catch {
|
|
729
|
+
dispatchReady = false;
|
|
730
|
+
dispatchBlock = "Could not run dispatch preflight \u2014 verify project_id and API key scope.";
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
const ingestFailed = ingest.steps.filter((s) => s.required && !s.complete);
|
|
734
|
+
const ingestBlock = ingestFailed[0]?.hint;
|
|
735
|
+
const ready = ingest.ready && dispatchReady;
|
|
736
|
+
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.";
|
|
737
|
+
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.";
|
|
738
|
+
return jsonResult({
|
|
739
|
+
mode: "full",
|
|
740
|
+
ready,
|
|
741
|
+
summary,
|
|
742
|
+
nextAction,
|
|
743
|
+
ingest: { ready: ingest.ready, steps: ingest.steps },
|
|
744
|
+
dispatch: resolvedId ? { ready: dispatchReady, checks: dispatchChecks } : null
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
);
|
|
460
748
|
server.registerTool(
|
|
461
749
|
"diagnose_connection",
|
|
462
750
|
{
|
|
463
751
|
title: titleOf("diagnose_connection"),
|
|
464
752
|
description: descOf("diagnose_connection"),
|
|
465
753
|
annotations: annotationsFor("diagnose_connection"),
|
|
466
|
-
inputSchema: {}
|
|
754
|
+
inputSchema: {},
|
|
755
|
+
outputSchema: {
|
|
756
|
+
ready: z.boolean(),
|
|
757
|
+
healthOk: z.boolean(),
|
|
758
|
+
ingestReady: z.boolean(),
|
|
759
|
+
dispatchReady: z.boolean(),
|
|
760
|
+
endpoint: z.string().nullable(),
|
|
761
|
+
projectId: z.string().nullable(),
|
|
762
|
+
accessibleProjectCount: z.number().nullable(),
|
|
763
|
+
issues: z.array(z.unknown()),
|
|
764
|
+
nextAction: z.string(),
|
|
765
|
+
summary: z.string()
|
|
766
|
+
}
|
|
467
767
|
},
|
|
468
768
|
async () => {
|
|
469
769
|
const issues = [];
|
|
@@ -474,13 +774,6 @@ function createMushiServer(config) {
|
|
|
474
774
|
fix: "Run `mushi connect` to write MUSHI_API_KEY into .cursor/mcp.json, then restart the MCP server."
|
|
475
775
|
});
|
|
476
776
|
}
|
|
477
|
-
if (!projectId) {
|
|
478
|
-
issues.push({
|
|
479
|
-
check: "mcp_project_id",
|
|
480
|
-
detail: "No projectId configured on the MCP server",
|
|
481
|
-
fix: "Add MUSHI_PROJECT_ID to .cursor/mcp.json (copy UUID from console \u2192 Projects)."
|
|
482
|
-
});
|
|
483
|
-
}
|
|
484
777
|
if (!apiEndpoint) {
|
|
485
778
|
issues.push({
|
|
486
779
|
check: "mcp_endpoint",
|
|
@@ -512,6 +805,21 @@ function createMushiServer(config) {
|
|
|
512
805
|
}
|
|
513
806
|
let ingestReady = false;
|
|
514
807
|
let dispatchReady = false;
|
|
808
|
+
let accessibleProjectCount = null;
|
|
809
|
+
if (!projectId && healthOk) {
|
|
810
|
+
try {
|
|
811
|
+
const overview = await apiCall("/v1/admin/mcp/account-overview");
|
|
812
|
+
accessibleProjectCount = overview.total;
|
|
813
|
+
if (overview.total === 0) {
|
|
814
|
+
issues.push({
|
|
815
|
+
check: "no_accessible_projects",
|
|
816
|
+
detail: "API key has no accessible projects",
|
|
817
|
+
fix: "Mint an API key on a project (console \u2192 Projects \u2192 API Keys) or add MUSHI_PROJECT_ID to restrict to a single project."
|
|
818
|
+
});
|
|
819
|
+
}
|
|
820
|
+
} catch {
|
|
821
|
+
}
|
|
822
|
+
}
|
|
515
823
|
try {
|
|
516
824
|
const ingest = await apiCall("/v1/sync/ingest-setup");
|
|
517
825
|
ingestReady = ingest.ready;
|
|
@@ -547,30 +855,99 @@ function createMushiServer(config) {
|
|
|
547
855
|
}
|
|
548
856
|
const ready = issues.length === 0 && healthOk && ingestReady;
|
|
549
857
|
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
|
|
858
|
+
return jsonResult({
|
|
551
859
|
ready,
|
|
552
860
|
healthOk,
|
|
553
861
|
ingestReady,
|
|
554
862
|
dispatchReady,
|
|
555
863
|
endpoint: apiEndpoint ?? null,
|
|
556
864
|
projectId: projectId ?? null,
|
|
865
|
+
accessibleProjectCount,
|
|
557
866
|
issues,
|
|
558
867
|
nextAction,
|
|
559
868
|
summary: ready ? "MCP credentials valid; ingest pipeline ready." : `Connection issue \u2014 ${issues[0]?.check ?? "unknown"}: ${nextAction}`
|
|
560
869
|
});
|
|
561
870
|
}
|
|
562
871
|
);
|
|
872
|
+
server.registerTool(
|
|
873
|
+
"search_mushi_docs",
|
|
874
|
+
{
|
|
875
|
+
title: titleOf("search_mushi_docs"),
|
|
876
|
+
description: descOf("search_mushi_docs"),
|
|
877
|
+
annotations: annotationsFor("search_mushi_docs"),
|
|
878
|
+
inputSchema: {
|
|
879
|
+
query: z.string().describe("Keywords to search official Mushi docs (guides, MCP, inventory, QA)."),
|
|
880
|
+
limit: z.number().int().min(1).max(20).optional().describe("Max results (default 8).")
|
|
881
|
+
},
|
|
882
|
+
outputSchema: {
|
|
883
|
+
query: z.string(),
|
|
884
|
+
results: z.array(
|
|
885
|
+
z.object({
|
|
886
|
+
title: z.string(),
|
|
887
|
+
path: z.string(),
|
|
888
|
+
excerpt: z.string(),
|
|
889
|
+
score: z.number()
|
|
890
|
+
})
|
|
891
|
+
)
|
|
892
|
+
}
|
|
893
|
+
},
|
|
894
|
+
async (args) => {
|
|
895
|
+
const query = args.query ?? "";
|
|
896
|
+
const hits = searchMushiDocs(query, args.limit ?? 8);
|
|
897
|
+
const results = hits.map(({ title, path, excerpt, score }) => ({ title, path, excerpt, score }));
|
|
898
|
+
return jsonResult({ query, results });
|
|
899
|
+
}
|
|
900
|
+
);
|
|
563
901
|
server.registerTool(
|
|
564
902
|
"list_projects",
|
|
565
903
|
{
|
|
566
904
|
title: titleOf("list_projects"),
|
|
567
905
|
description: descOf("list_projects"),
|
|
568
906
|
annotations: annotationsFor("list_projects"),
|
|
569
|
-
inputSchema: {}
|
|
907
|
+
inputSchema: {},
|
|
908
|
+
outputSchema: {
|
|
909
|
+
projects: z.array(z.unknown()),
|
|
910
|
+
total: z.number().optional(),
|
|
911
|
+
_multi_project_hint: z.string().optional()
|
|
912
|
+
}
|
|
570
913
|
},
|
|
571
914
|
async () => {
|
|
572
915
|
const data = await apiCall("/v1/admin/mcp/projects");
|
|
573
|
-
return
|
|
916
|
+
return jsonResult({
|
|
917
|
+
...data,
|
|
918
|
+
_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.'
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
);
|
|
922
|
+
server.registerTool(
|
|
923
|
+
"get_account_overview",
|
|
924
|
+
{
|
|
925
|
+
title: titleOf("get_account_overview"),
|
|
926
|
+
description: descOf("get_account_overview"),
|
|
927
|
+
annotations: annotationsFor("get_account_overview"),
|
|
928
|
+
inputSchema: {},
|
|
929
|
+
outputSchema: {
|
|
930
|
+
projects: z.array(z.unknown()),
|
|
931
|
+
total: z.number(),
|
|
932
|
+
active_project_id: z.string().nullable(),
|
|
933
|
+
toolCount: z.number().optional(),
|
|
934
|
+
resourceCount: z.number().optional(),
|
|
935
|
+
promptCount: z.number().optional(),
|
|
936
|
+
multi_project_hint: z.string()
|
|
937
|
+
}
|
|
938
|
+
},
|
|
939
|
+
async () => {
|
|
940
|
+
const data = await apiCall("/v1/admin/mcp/account-overview");
|
|
941
|
+
const projects = Array.isArray(data?.projects) ? data.projects : [];
|
|
942
|
+
return jsonResult({
|
|
943
|
+
projects,
|
|
944
|
+
total: data?.total ?? projects.length,
|
|
945
|
+
active_project_id: projectId ?? null,
|
|
946
|
+
toolCount: data?.toolCount,
|
|
947
|
+
resourceCount: data?.resourceCount,
|
|
948
|
+
promptCount: data?.promptCount,
|
|
949
|
+
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".`
|
|
950
|
+
});
|
|
574
951
|
}
|
|
575
952
|
);
|
|
576
953
|
server.registerTool(
|
|
@@ -670,6 +1047,20 @@ function createMushiServer(config) {
|
|
|
670
1047
|
report_id: z.string().describe("Report UUID to triage."),
|
|
671
1048
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
|
|
672
1049
|
include_logs: z.boolean().optional().describe("Include recent pipeline logs in triage packet (default: true).")
|
|
1050
|
+
},
|
|
1051
|
+
outputSchema: {
|
|
1052
|
+
report_id: z.string(),
|
|
1053
|
+
severity: z.unknown(),
|
|
1054
|
+
category: z.unknown(),
|
|
1055
|
+
status: z.unknown(),
|
|
1056
|
+
report: z.unknown(),
|
|
1057
|
+
reporter_thread: z.unknown().nullable(),
|
|
1058
|
+
similar_bugs: z.unknown().nullable(),
|
|
1059
|
+
fix_context: z.unknown().nullable(),
|
|
1060
|
+
blast_radius: z.unknown().nullable(),
|
|
1061
|
+
recent_logs: z.unknown().nullable(),
|
|
1062
|
+
recommended_actions: z.array(z.unknown()),
|
|
1063
|
+
triage_summary: z.string()
|
|
673
1064
|
}
|
|
674
1065
|
},
|
|
675
1066
|
async (args) => {
|
|
@@ -713,7 +1104,7 @@ function createMushiServer(config) {
|
|
|
713
1104
|
args: { reportId: args.report_id }
|
|
714
1105
|
});
|
|
715
1106
|
}
|
|
716
|
-
|
|
1107
|
+
const result = {
|
|
717
1108
|
report_id: args.report_id,
|
|
718
1109
|
severity,
|
|
719
1110
|
category,
|
|
@@ -726,7 +1117,15 @@ function createMushiServer(config) {
|
|
|
726
1117
|
recent_logs: logsRes.status === "fulfilled" ? logsRes.value : null,
|
|
727
1118
|
recommended_actions: actions,
|
|
728
1119
|
triage_summary: `[${severity}] ${category} \u2014 status: ${status}. ${actions.length > 0 ? `Recommended: ${actions[0]?.action}.` : "No action required."}`
|
|
729
|
-
}
|
|
1120
|
+
};
|
|
1121
|
+
const res = jsonResult(result);
|
|
1122
|
+
return {
|
|
1123
|
+
...res,
|
|
1124
|
+
resource_links: [
|
|
1125
|
+
{ uri: `project://reports/${args.report_id}`, title: `Report ${args.report_id.slice(0, 8)}\u2026` },
|
|
1126
|
+
...pid ? [{ uri: `project://dashboard`, title: "Project dashboard" }] : []
|
|
1127
|
+
]
|
|
1128
|
+
};
|
|
730
1129
|
}
|
|
731
1130
|
);
|
|
732
1131
|
server.registerTool(
|
|
@@ -1107,7 +1506,7 @@ function createMushiServer(config) {
|
|
|
1107
1506
|
contents: [{
|
|
1108
1507
|
uri: "privacy://status",
|
|
1109
1508
|
mimeType: "application/json",
|
|
1110
|
-
text: JSON.stringify(await apiCall("/v1/admin/privacy
|
|
1509
|
+
text: JSON.stringify(await apiCall("/v1/admin/privacy-status"), null, 2)
|
|
1111
1510
|
}]
|
|
1112
1511
|
})
|
|
1113
1512
|
);
|
|
@@ -1121,7 +1520,11 @@ function createMushiServer(config) {
|
|
|
1121
1520
|
contents: [{
|
|
1122
1521
|
uri: "evolution://history",
|
|
1123
1522
|
mimeType: "application/json",
|
|
1124
|
-
text: JSON.stringify(
|
|
1523
|
+
text: projectId ? JSON.stringify(
|
|
1524
|
+
await apiCall(`/v1/admin/projects/${encodeURIComponent(projectId)}/evolution-history`),
|
|
1525
|
+
null,
|
|
1526
|
+
2
|
|
1527
|
+
) : JSON.stringify({ ok: false, error: "project_id required" })
|
|
1125
1528
|
}]
|
|
1126
1529
|
})
|
|
1127
1530
|
);
|
|
@@ -1700,12 +2103,192 @@ Be specific. No generic advice.`
|
|
|
1700
2103
|
// admin-authed timeline, which also carries fix / QA / status lanes.
|
|
1701
2104
|
async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}/timeline`))
|
|
1702
2105
|
);
|
|
2106
|
+
server.registerTool(
|
|
2107
|
+
"ask_codebase",
|
|
2108
|
+
{
|
|
2109
|
+
title: titleOf("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2110
|
+
description: descOf("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2111
|
+
annotations: annotationsFor("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2112
|
+
inputSchema: {
|
|
2113
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2114
|
+
question: z.string().describe("Plain-English question about the repo"),
|
|
2115
|
+
thread_id: z.string().optional().describe("Optional thread UUID to continue a conversation"),
|
|
2116
|
+
file_path: z.string().optional().describe("Optional file path focus"),
|
|
2117
|
+
symbol_name: z.string().optional().describe("Optional symbol name focus")
|
|
2118
|
+
}
|
|
2119
|
+
},
|
|
2120
|
+
async (args) => {
|
|
2121
|
+
const pid = args.project_id ?? projectId;
|
|
2122
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2123
|
+
const body = {
|
|
2124
|
+
messages: [{ role: "user", content: args.question }]
|
|
2125
|
+
};
|
|
2126
|
+
if (args.thread_id) body.threadId = args.thread_id;
|
|
2127
|
+
if (args.file_path) {
|
|
2128
|
+
body.fileFocus = {
|
|
2129
|
+
file_path: args.file_path,
|
|
2130
|
+
symbol_name: args.symbol_name ?? null
|
|
2131
|
+
};
|
|
2132
|
+
}
|
|
2133
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/chat`, {
|
|
2134
|
+
method: "POST",
|
|
2135
|
+
body: JSON.stringify(body)
|
|
2136
|
+
});
|
|
2137
|
+
return jsonText(data);
|
|
2138
|
+
}
|
|
2139
|
+
);
|
|
2140
|
+
server.registerTool(
|
|
2141
|
+
"get_file_summary",
|
|
2142
|
+
{
|
|
2143
|
+
title: titleOf("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2144
|
+
description: descOf("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2145
|
+
annotations: annotationsFor("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2146
|
+
inputSchema: {
|
|
2147
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2148
|
+
file_path: z.string().describe("Indexed file path"),
|
|
2149
|
+
symbol_name: z.string().optional().describe("Optional symbol name within the file"),
|
|
2150
|
+
force: z.boolean().optional().describe("Bypass cache and regenerate")
|
|
2151
|
+
}
|
|
2152
|
+
},
|
|
2153
|
+
async (args) => {
|
|
2154
|
+
const pid = args.project_id ?? projectId;
|
|
2155
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2156
|
+
const qs = new URLSearchParams({ file_path: args.file_path });
|
|
2157
|
+
if (args.symbol_name) qs.set("symbol_name", args.symbol_name);
|
|
2158
|
+
if (args.force) qs.set("force", "1");
|
|
2159
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/summary?${qs}`);
|
|
2160
|
+
return jsonText(data);
|
|
2161
|
+
}
|
|
2162
|
+
);
|
|
2163
|
+
server.registerTool(
|
|
2164
|
+
"get_codebase_tour",
|
|
2165
|
+
{
|
|
2166
|
+
title: titleOf("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2167
|
+
description: descOf("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2168
|
+
annotations: annotationsFor("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2169
|
+
inputSchema: {
|
|
2170
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2171
|
+
force: z.boolean().optional().describe("Bypass cache and regenerate")
|
|
2172
|
+
}
|
|
2173
|
+
},
|
|
2174
|
+
async (args) => {
|
|
2175
|
+
const pid = args.project_id ?? projectId;
|
|
2176
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2177
|
+
const qs = args.force ? "?force=1" : "";
|
|
2178
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/tour${qs}`);
|
|
2179
|
+
return jsonText(data);
|
|
2180
|
+
}
|
|
2181
|
+
);
|
|
2182
|
+
server.registerTool(
|
|
2183
|
+
"search_codebase",
|
|
2184
|
+
{
|
|
2185
|
+
title: titleOf("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2186
|
+
description: descOf("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2187
|
+
annotations: annotationsFor("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2188
|
+
inputSchema: {
|
|
2189
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2190
|
+
query: z.string().describe("Plain-English search query"),
|
|
2191
|
+
k: z.number().optional().describe("Number of results (default 8, max 20)"),
|
|
2192
|
+
scope_prefix: z.string().optional().describe("Optional subdirectory scope prefix")
|
|
2193
|
+
}
|
|
2194
|
+
},
|
|
2195
|
+
async (args) => {
|
|
2196
|
+
const pid = args.project_id ?? projectId;
|
|
2197
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2198
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/search`, {
|
|
2199
|
+
method: "POST",
|
|
2200
|
+
body: JSON.stringify({
|
|
2201
|
+
query: args.query,
|
|
2202
|
+
k: args.k ?? 8,
|
|
2203
|
+
scope_prefix: args.scope_prefix ?? void 0
|
|
2204
|
+
})
|
|
2205
|
+
});
|
|
2206
|
+
return jsonText(data);
|
|
2207
|
+
}
|
|
2208
|
+
);
|
|
2209
|
+
server.registerTool(
|
|
2210
|
+
"get_codebase_domains",
|
|
2211
|
+
{
|
|
2212
|
+
title: titleOf("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2213
|
+
description: descOf("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2214
|
+
annotations: annotationsFor("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2215
|
+
inputSchema: {
|
|
2216
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2217
|
+
force: z.boolean().optional().describe("Bypass cache and regenerate"),
|
|
2218
|
+
scope_prefix: z.string().optional().describe("Optional subdirectory scope prefix")
|
|
2219
|
+
}
|
|
2220
|
+
},
|
|
2221
|
+
async (args) => {
|
|
2222
|
+
const pid = args.project_id ?? projectId;
|
|
2223
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2224
|
+
const qs = new URLSearchParams();
|
|
2225
|
+
if (args.force) qs.set("force", "1");
|
|
2226
|
+
if (args.scope_prefix) qs.set("scope_prefix", args.scope_prefix);
|
|
2227
|
+
const suffix = qs.toString() ? `?${qs}` : "";
|
|
2228
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/domains${suffix}`);
|
|
2229
|
+
return jsonText(data);
|
|
2230
|
+
}
|
|
2231
|
+
);
|
|
2232
|
+
server.registerTool(
|
|
2233
|
+
"analyze_codebase_impact",
|
|
2234
|
+
{
|
|
2235
|
+
title: titleOf("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2236
|
+
description: descOf("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2237
|
+
annotations: annotationsFor("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2238
|
+
inputSchema: {
|
|
2239
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2240
|
+
paths: z.array(z.string()).optional().describe("Changed file paths"),
|
|
2241
|
+
source: z.enum(["last_push", "compare", "fix"]).optional().describe("Auto-resolve changed paths"),
|
|
2242
|
+
compare: z.string().optional().describe("GitHub compare range base...head"),
|
|
2243
|
+
fix_id: z.string().optional().describe("Fix attempt UUID for PR changed files")
|
|
2244
|
+
}
|
|
2245
|
+
},
|
|
2246
|
+
async (args) => {
|
|
2247
|
+
const pid = args.project_id ?? projectId;
|
|
2248
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2249
|
+
const qs = new URLSearchParams();
|
|
2250
|
+
if (args.paths?.length) qs.set("paths", args.paths.join(","));
|
|
2251
|
+
if (args.source === "last_push") qs.set("ref", "last_push");
|
|
2252
|
+
if (args.compare) qs.set("compare", args.compare);
|
|
2253
|
+
if (args.fix_id) qs.set("fix_id", args.fix_id);
|
|
2254
|
+
if (!qs.toString()) {
|
|
2255
|
+
throw new MushiApiError(400, "BAD_REQUEST", "Provide paths, source=last_push, compare, or fix_id");
|
|
2256
|
+
}
|
|
2257
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/impact?${qs}`);
|
|
2258
|
+
return jsonText(data);
|
|
2259
|
+
}
|
|
2260
|
+
);
|
|
2261
|
+
server.registerTool(
|
|
2262
|
+
"analyze_wiki_knowledge",
|
|
2263
|
+
{
|
|
2264
|
+
title: titleOf("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2265
|
+
description: descOf("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2266
|
+
annotations: annotationsFor("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2267
|
+
inputSchema: {
|
|
2268
|
+
project_id: z.string().optional().describe("Project UUID (defaults to configured project)")
|
|
2269
|
+
}
|
|
2270
|
+
},
|
|
2271
|
+
async (args) => {
|
|
2272
|
+
const pid = args.project_id ?? projectId;
|
|
2273
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "project_id is required");
|
|
2274
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/knowledge/graph`);
|
|
2275
|
+
return jsonText(data);
|
|
2276
|
+
}
|
|
2277
|
+
);
|
|
1703
2278
|
const grantedScopes = config.scopes;
|
|
1704
|
-
|
|
2279
|
+
const featureFilter = config.features ?? "all";
|
|
2280
|
+
if (grantedScopes !== void 0 || featureFilter !== "all") {
|
|
1705
2281
|
const toolRegistry = server._registeredTools;
|
|
1706
|
-
const allSpecs = [
|
|
2282
|
+
const allSpecs = [
|
|
2283
|
+
...TOOL_CATALOG,
|
|
2284
|
+
...TDD_TOOL_CATALOG,
|
|
2285
|
+
...CODEBASE_TOOL_CATALOG
|
|
2286
|
+
];
|
|
1707
2287
|
for (const spec of allSpecs) {
|
|
1708
|
-
if (!grantedScopes.includes(spec.scope)) {
|
|
2288
|
+
if (grantedScopes !== void 0 && !grantedScopes.includes(spec.scope)) {
|
|
2289
|
+
toolRegistry[spec.name]?.remove();
|
|
2290
|
+
}
|
|
2291
|
+
if (featureFilter !== "all" && !toolMatchesFeatures(spec.name, featureFilter)) {
|
|
1709
2292
|
toolRegistry[spec.name]?.remove();
|
|
1710
2293
|
}
|
|
1711
2294
|
}
|