@mushi-mushi/mcp 0.20.0 → 0.20.2
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 +3 -3
- package/dist/catalog.js +1 -1
- package/dist/{chunk-OZJEY3QR.js → chunk-RVZIKP4X.js} +43 -18
- package/dist/{chunk-VBXXUK5G.js → chunk-ZSVRLSW5.js} +4 -2
- package/dist/clients.d.ts +1 -1
- package/dist/clients.js +2 -2
- package/dist/feature-groups.js +1 -1
- package/dist/index.js +856 -253
- package/dist/server.d.ts +9 -1
- package/dist/server.js +715 -222
- package/package.json +2 -2
package/dist/server.js
CHANGED
|
@@ -3,14 +3,14 @@ import {
|
|
|
3
3
|
TDD_TOOL_CATALOG,
|
|
4
4
|
TOOL_CATALOG,
|
|
5
5
|
USE_MUSHI_INTENTS
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-RVZIKP4X.js";
|
|
7
7
|
import {
|
|
8
8
|
MUSHI_SERVER_METADATA
|
|
9
9
|
} from "./chunk-EANW5YTI.js";
|
|
10
10
|
import {
|
|
11
11
|
DEPRECATED_TOOL_ALIASES,
|
|
12
12
|
toolMatchesFeatures
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-ZSVRLSW5.js";
|
|
14
14
|
|
|
15
15
|
// src/server.ts
|
|
16
16
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -143,8 +143,107 @@ function wrapUntrustedJson(value, role) {
|
|
|
143
143
|
return wrapUntrusted(serialised, role);
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
// src/stdout-guard.ts
|
|
147
|
+
import { Console } from "console";
|
|
148
|
+
var GUARDED_CONSOLE_METHODS = [
|
|
149
|
+
"log",
|
|
150
|
+
"info",
|
|
151
|
+
"debug",
|
|
152
|
+
"dir",
|
|
153
|
+
"dirxml",
|
|
154
|
+
"table",
|
|
155
|
+
"group",
|
|
156
|
+
"groupCollapsed",
|
|
157
|
+
"groupEnd",
|
|
158
|
+
"count",
|
|
159
|
+
"countReset",
|
|
160
|
+
"time",
|
|
161
|
+
"timeEnd",
|
|
162
|
+
"timeLog",
|
|
163
|
+
"trace",
|
|
164
|
+
"assert",
|
|
165
|
+
"warn",
|
|
166
|
+
"error"
|
|
167
|
+
];
|
|
168
|
+
var installed = false;
|
|
169
|
+
function installStdoutGuard() {
|
|
170
|
+
if (installed) return false;
|
|
171
|
+
if (typeof process !== "undefined" && process.env?.MUSHI_MCP_STDOUT_GUARD === "off") {
|
|
172
|
+
return false;
|
|
173
|
+
}
|
|
174
|
+
if (typeof process === "undefined" || typeof process.stderr?.write !== "function" || typeof console === "undefined") {
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
try {
|
|
178
|
+
const stderrConsole = new Console({
|
|
179
|
+
stdout: process.stderr,
|
|
180
|
+
stderr: process.stderr,
|
|
181
|
+
// Colour codes would land in the client's log pane as escape noise.
|
|
182
|
+
colorMode: false
|
|
183
|
+
});
|
|
184
|
+
const target = console;
|
|
185
|
+
const source = stderrConsole;
|
|
186
|
+
for (const method of GUARDED_CONSOLE_METHODS) {
|
|
187
|
+
const fn = source[method];
|
|
188
|
+
if (typeof fn === "function") {
|
|
189
|
+
target[method] = fn.bind(stderrConsole);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch {
|
|
193
|
+
const write = (...args) => process.stderr.write(
|
|
194
|
+
args.map((a) => typeof a === "string" ? a : safeStringify(a)).join(" ") + "\n"
|
|
195
|
+
);
|
|
196
|
+
const target = console;
|
|
197
|
+
for (const method of GUARDED_CONSOLE_METHODS) {
|
|
198
|
+
target[method] = write;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
installed = true;
|
|
202
|
+
return true;
|
|
203
|
+
}
|
|
204
|
+
function safeStringify(value) {
|
|
205
|
+
try {
|
|
206
|
+
return JSON.stringify(value) ?? String(value);
|
|
207
|
+
} catch {
|
|
208
|
+
return String(value);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
146
212
|
// src/server.ts
|
|
213
|
+
installStdoutGuard();
|
|
147
214
|
var NO_ARG_INPUT = {};
|
|
215
|
+
var DEFAULT_API_TIMEOUT_MS = 15e3;
|
|
216
|
+
var MIN_API_TIMEOUT_MS = 250;
|
|
217
|
+
var MAX_API_TIMEOUT_MS = 3e5;
|
|
218
|
+
function resolveTimeoutMsFromEnv() {
|
|
219
|
+
const raw = process.env?.MUSHI_MCP_TIMEOUT_MS?.trim();
|
|
220
|
+
if (!raw) return DEFAULT_API_TIMEOUT_MS;
|
|
221
|
+
const parsed = Number(raw);
|
|
222
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_API_TIMEOUT_MS;
|
|
223
|
+
return Math.min(Math.max(parsed, MIN_API_TIMEOUT_MS), MAX_API_TIMEOUT_MS);
|
|
224
|
+
}
|
|
225
|
+
function bareMessage(err) {
|
|
226
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
227
|
+
return raw.replace(/^\[[A-Z0-9_]+\]\s*/, "");
|
|
228
|
+
}
|
|
229
|
+
var IDEMPOTENCY_NAMESPACE = "mushi.mcp.submit_fix_result.v1";
|
|
230
|
+
async function stableUuidFrom(...parts) {
|
|
231
|
+
const input = parts.join("");
|
|
232
|
+
const digest = new Uint8Array(
|
|
233
|
+
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input))
|
|
234
|
+
);
|
|
235
|
+
const bytes = digest.slice(0, 16);
|
|
236
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
237
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
238
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
239
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
240
|
+
}
|
|
241
|
+
function withTimeoutSignal(callerSignal, timeoutSignal) {
|
|
242
|
+
if (!callerSignal) return timeoutSignal;
|
|
243
|
+
const anyFn = AbortSignal.any;
|
|
244
|
+
if (typeof anyFn === "function") return anyFn.call(AbortSignal, [callerSignal, timeoutSignal]);
|
|
245
|
+
return timeoutSignal;
|
|
246
|
+
}
|
|
148
247
|
var MushiApiError = class extends Error {
|
|
149
248
|
constructor(status, code, message) {
|
|
150
249
|
super(`[${code}] ${message}`);
|
|
@@ -158,6 +257,7 @@ var MushiApiError = class extends Error {
|
|
|
158
257
|
function createMushiServer(config) {
|
|
159
258
|
const { version, apiEndpoint, apiKey, projectId } = config;
|
|
160
259
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
260
|
+
const timeoutMs = typeof config.timeoutMs === "number" && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? config.timeoutMs : resolveTimeoutMsFromEnv();
|
|
161
261
|
const apiLog = createLogger({
|
|
162
262
|
scope: "mushi:mcp:api",
|
|
163
263
|
level: process.env.MUSHI_LOG_LEVEL ?? "info",
|
|
@@ -175,22 +275,66 @@ function createMushiServer(config) {
|
|
|
175
275
|
async function apiCall(path, options) {
|
|
176
276
|
const requestId = crypto.randomUUID().slice(0, 12);
|
|
177
277
|
const started = Date.now();
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
278
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
279
|
+
let res;
|
|
280
|
+
try {
|
|
281
|
+
res = await doFetch(`${apiEndpoint}${path}`, {
|
|
282
|
+
...options,
|
|
283
|
+
headers: {
|
|
284
|
+
"Content-Type": "application/json",
|
|
285
|
+
// Authorization is accepted by `adminOrApiKey` only as a JWT, but we
|
|
286
|
+
// still send it for endpoints still behind plain `jwtAuth` (legacy)
|
|
287
|
+
// and for transparent proxies that strip X-Mushi-* headers.
|
|
288
|
+
Authorization: `Bearer ${apiKey}`,
|
|
289
|
+
"X-Mushi-Api-Key": apiKey,
|
|
290
|
+
"X-Request-Id": requestId,
|
|
291
|
+
...projectId ? { "X-Mushi-Project-Id": projectId } : {},
|
|
292
|
+
...options?.headers ?? {}
|
|
293
|
+
},
|
|
294
|
+
// After the spread so a caller-supplied `signal` is merged, not lost.
|
|
295
|
+
signal: withTimeoutSignal(options?.signal, timeoutSignal)
|
|
296
|
+
});
|
|
297
|
+
} catch (err) {
|
|
298
|
+
throw classifyTransportError(err);
|
|
299
|
+
}
|
|
300
|
+
let text;
|
|
301
|
+
try {
|
|
302
|
+
text = await res.text();
|
|
303
|
+
} catch (err) {
|
|
304
|
+
throw classifyTransportError(err);
|
|
305
|
+
}
|
|
306
|
+
function classifyTransportError(err) {
|
|
307
|
+
const durationMs2 = Date.now() - started;
|
|
308
|
+
if (timeoutSignal.aborted) {
|
|
309
|
+
apiLog.warn("api.timeout", { path, requestId, durationMs: durationMs2, timeoutMs });
|
|
310
|
+
return new MushiApiError(
|
|
311
|
+
504,
|
|
312
|
+
"MUSHI_TIMEOUT",
|
|
313
|
+
`Timed out after ${timeoutMs}ms waiting for ${path}. The Mushi API accepted the connection but did not answer in time \u2014 retry, or raise the budget with MUSHI_MCP_TIMEOUT_MS (ms) in your MCP env block if you self-host and cold starts are slow. Endpoint: ${apiEndpoint}`
|
|
314
|
+
);
|
|
190
315
|
}
|
|
191
|
-
|
|
316
|
+
const name = err instanceof Error ? err.name : "";
|
|
317
|
+
if (name === "AbortError") {
|
|
318
|
+
apiLog.warn("api.aborted", { path, requestId, durationMs: durationMs2 });
|
|
319
|
+
return new MushiApiError(
|
|
320
|
+
499,
|
|
321
|
+
"REQUEST_ABORTED",
|
|
322
|
+
`The request to ${path} was aborted by the caller after ${durationMs2}ms.`
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
apiLog.warn("api.unreachable", {
|
|
326
|
+
path,
|
|
327
|
+
requestId,
|
|
328
|
+
durationMs: durationMs2,
|
|
329
|
+
err: bareMessage(err)
|
|
330
|
+
});
|
|
331
|
+
return new MushiApiError(
|
|
332
|
+
503,
|
|
333
|
+
"NETWORK_ERROR",
|
|
334
|
+
`Could not reach the Mushi API at ${apiEndpoint}${path} \u2014 ${bareMessage(err)}. This is a connectivity failure, not an API rejection: check that you are online, that MUSHI_API_ENDPOINT points at your \`.../functions/v1/api\` URL, and that no proxy/VPN is blocking it.`
|
|
335
|
+
);
|
|
336
|
+
}
|
|
192
337
|
const durationMs = Date.now() - started;
|
|
193
|
-
const text = await res.text();
|
|
194
338
|
let body = null;
|
|
195
339
|
if (text) {
|
|
196
340
|
try {
|
|
@@ -220,6 +364,7 @@ function createMushiServer(config) {
|
|
|
220
364
|
apiLog.info("api.done", { path, requestId, status: res.status, durationMs });
|
|
221
365
|
return body;
|
|
222
366
|
}
|
|
367
|
+
let resolvedAccountProjectId = null;
|
|
223
368
|
async function resolveProjectId(explicitId) {
|
|
224
369
|
if (explicitId) {
|
|
225
370
|
if (projectId && explicitId !== projectId) {
|
|
@@ -232,12 +377,12 @@ function createMushiServer(config) {
|
|
|
232
377
|
return explicitId;
|
|
233
378
|
}
|
|
234
379
|
if (projectId) return projectId;
|
|
380
|
+
if (resolvedAccountProjectId) return resolvedAccountProjectId;
|
|
235
381
|
try {
|
|
236
|
-
const data = await apiCall(
|
|
237
|
-
"/v1/admin/mcp/account-overview"
|
|
238
|
-
);
|
|
382
|
+
const data = await apiCall("/v1/admin/mcp/account-overview");
|
|
239
383
|
if (data.total === 1 && data.projects[0]) {
|
|
240
|
-
|
|
384
|
+
resolvedAccountProjectId = data.projects[0].id;
|
|
385
|
+
return resolvedAccountProjectId;
|
|
241
386
|
}
|
|
242
387
|
if (data.total === 0) {
|
|
243
388
|
throw new MushiApiError(
|
|
@@ -253,11 +398,27 @@ function createMushiServer(config) {
|
|
|
253
398
|
`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.`
|
|
254
399
|
);
|
|
255
400
|
} catch (err) {
|
|
256
|
-
if (err instanceof MushiApiError)
|
|
401
|
+
if (err instanceof MushiApiError) {
|
|
402
|
+
if (err.code === "MUSHI_TIMEOUT" || err.code === "NETWORK_ERROR") {
|
|
403
|
+
throw new MushiApiError(
|
|
404
|
+
err.status,
|
|
405
|
+
err.code,
|
|
406
|
+
`Could not resolve which project to use because the Mushi API is unreachable: ${bareMessage(err)} This is a connectivity problem \u2014 setting MUSHI_PROJECT_ID would only skip the lookup, not fix the tool call that follows it.`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
if (err.status === 401 || err.status === 403) {
|
|
410
|
+
throw new MushiApiError(
|
|
411
|
+
err.status,
|
|
412
|
+
err.code,
|
|
413
|
+
`The API key was rejected while resolving the project: ${bareMessage(err)} Mint a fresh key in the console (Settings \u2192 API keys) and update MUSHI_API_KEY, or re-run \`mushi login\`. This is an authentication problem, not a missing project id.`
|
|
414
|
+
);
|
|
415
|
+
}
|
|
416
|
+
throw err;
|
|
417
|
+
}
|
|
257
418
|
throw new MushiApiError(
|
|
258
419
|
400,
|
|
259
420
|
"MISSING_PROJECT_ID",
|
|
260
|
-
|
|
421
|
+
`projectId is required. Set MUSHI_PROJECT_ID in .cursor/mcp.json or pass it explicitly. (Project lookup failed with: ${bareMessage(err)})`
|
|
261
422
|
);
|
|
262
423
|
}
|
|
263
424
|
}
|
|
@@ -346,9 +507,12 @@ function createMushiServer(config) {
|
|
|
346
507
|
params.set("limit", String(Math.min(args.limit ?? 20, 100)));
|
|
347
508
|
const pid = await resolveProjectId(args.project_id);
|
|
348
509
|
const extraHeaders = pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
|
|
349
|
-
const data = await apiCall(
|
|
350
|
-
|
|
351
|
-
|
|
510
|
+
const data = await apiCall(
|
|
511
|
+
`/v1/admin/reports?${params}`,
|
|
512
|
+
{
|
|
513
|
+
headers: extraHeaders
|
|
514
|
+
}
|
|
515
|
+
);
|
|
352
516
|
return jsonResult(data);
|
|
353
517
|
}
|
|
354
518
|
);
|
|
@@ -371,7 +535,10 @@ function createMushiServer(config) {
|
|
|
371
535
|
return {
|
|
372
536
|
...res,
|
|
373
537
|
resource_links: [
|
|
374
|
-
{
|
|
538
|
+
{
|
|
539
|
+
uri: `project://reports/${args.reportId}`,
|
|
540
|
+
title: `Report ${args.reportId.slice(0, 8)}\u2026`
|
|
541
|
+
}
|
|
375
542
|
]
|
|
376
543
|
};
|
|
377
544
|
}
|
|
@@ -475,17 +642,20 @@ function createMushiServer(config) {
|
|
|
475
642
|
const report = await apiCall(`/v1/admin/reports/${args.reportId}`, {
|
|
476
643
|
headers
|
|
477
644
|
});
|
|
478
|
-
return wrappedJsonResult(
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
645
|
+
return wrappedJsonResult(
|
|
646
|
+
{
|
|
647
|
+
report,
|
|
648
|
+
// Paste-ready fix prompt composed server-side by composeFixPacket()
|
|
649
|
+
// (diagnosis + repro + suggested fix + relevant code + blast radius).
|
|
650
|
+
// Hand this straight to the editing agent — no second LLM key needed.
|
|
651
|
+
fixPrompt: report.fix_packet ?? null,
|
|
652
|
+
reproductionSteps: report.reproduction_steps ?? [],
|
|
653
|
+
component: report.component,
|
|
654
|
+
rootCause: report.stage2_analysis?.rootCause,
|
|
655
|
+
bugOntologyTags: report.bug_ontology_tags
|
|
656
|
+
},
|
|
657
|
+
"fix context"
|
|
658
|
+
);
|
|
489
659
|
}
|
|
490
660
|
);
|
|
491
661
|
server.registerTool(
|
|
@@ -534,7 +704,9 @@ function createMushiServer(config) {
|
|
|
534
704
|
seed: args.seed,
|
|
535
705
|
depth: String(Math.min(args.depth ?? 2, 4))
|
|
536
706
|
});
|
|
537
|
-
const data = await apiCall(
|
|
707
|
+
const data = await apiCall(
|
|
708
|
+
`/v1/admin/graph/traverse?${params}`
|
|
709
|
+
);
|
|
538
710
|
return jsonResult(data);
|
|
539
711
|
}
|
|
540
712
|
);
|
|
@@ -545,7 +717,9 @@ function createMushiServer(config) {
|
|
|
545
717
|
description: descOf("get_graph_neighborhood"),
|
|
546
718
|
annotations: annotationsFor("get_graph_neighborhood"),
|
|
547
719
|
inputSchema: {
|
|
548
|
-
seed: z.string().describe(
|
|
720
|
+
seed: z.string().describe(
|
|
721
|
+
"Starting graph node id or label to expand around (e.g. an inventory Action id or component name)"
|
|
722
|
+
),
|
|
549
723
|
depth: z.number().int().min(1).max(4).optional().describe("BFS hops to traverse outward. Default 2; clamped to a max of 4.")
|
|
550
724
|
},
|
|
551
725
|
outputSchema: {
|
|
@@ -558,7 +732,9 @@ function createMushiServer(config) {
|
|
|
558
732
|
seed: args.seed,
|
|
559
733
|
depth: String(Math.min(args.depth ?? 2, 4))
|
|
560
734
|
});
|
|
561
|
-
const data = await apiCall(
|
|
735
|
+
const data = await apiCall(
|
|
736
|
+
`/v1/admin/graph/traverse?${params}`
|
|
737
|
+
);
|
|
562
738
|
return jsonResult(data);
|
|
563
739
|
}
|
|
564
740
|
);
|
|
@@ -568,7 +744,9 @@ function createMushiServer(config) {
|
|
|
568
744
|
title: titleOf("get_graph_node"),
|
|
569
745
|
description: descOf("get_graph_node"),
|
|
570
746
|
annotations: annotationsFor("get_graph_node"),
|
|
571
|
-
inputSchema: {
|
|
747
|
+
inputSchema: {
|
|
748
|
+
nodeId: z.string().describe("The graph_nodes.id of the node to fetch (UUID).")
|
|
749
|
+
},
|
|
572
750
|
outputSchema: {
|
|
573
751
|
node: z.unknown().describe("Single graph_nodes row including metadata (Action nodes carry v2 status)")
|
|
574
752
|
}
|
|
@@ -590,7 +768,8 @@ function createMushiServer(config) {
|
|
|
590
768
|
},
|
|
591
769
|
async (args) => {
|
|
592
770
|
const pid = args.projectId ?? projectId;
|
|
593
|
-
if (!pid)
|
|
771
|
+
if (!pid)
|
|
772
|
+
throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for get_inventory");
|
|
594
773
|
return jsonText(await apiCall(`/v1/admin/inventory/${pid}`));
|
|
595
774
|
}
|
|
596
775
|
);
|
|
@@ -608,7 +787,8 @@ function createMushiServer(config) {
|
|
|
608
787
|
},
|
|
609
788
|
async (args) => {
|
|
610
789
|
const pid = args.projectId ?? projectId;
|
|
611
|
-
if (!pid)
|
|
790
|
+
if (!pid)
|
|
791
|
+
throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for diff_inventory");
|
|
612
792
|
const q = new URLSearchParams({ from: args.fromSha, to: args.toSha });
|
|
613
793
|
return jsonText(await apiCall(`/v1/admin/inventory/${pid}/diff?${q}`));
|
|
614
794
|
}
|
|
@@ -631,12 +811,19 @@ function createMushiServer(config) {
|
|
|
631
811
|
},
|
|
632
812
|
async (args) => {
|
|
633
813
|
const pid = args.projectId ?? projectId;
|
|
634
|
-
if (!pid)
|
|
814
|
+
if (!pid)
|
|
815
|
+
throw new MushiApiError(
|
|
816
|
+
400,
|
|
817
|
+
"MISSING_PROJECT",
|
|
818
|
+
"projectId is required for list_gate_findings"
|
|
819
|
+
);
|
|
635
820
|
const q = new URLSearchParams();
|
|
636
821
|
if (args.gate) q.set("gate", args.gate);
|
|
637
822
|
if (args.severity) q.set("severity", args.severity);
|
|
638
823
|
const suffix = q.toString() ? `?${q}` : "";
|
|
639
|
-
const data = await apiCall(
|
|
824
|
+
const data = await apiCall(
|
|
825
|
+
`/v1/admin/inventory/${pid}/findings${suffix}`
|
|
826
|
+
);
|
|
640
827
|
return jsonResult(data);
|
|
641
828
|
}
|
|
642
829
|
);
|
|
@@ -646,19 +833,33 @@ function createMushiServer(config) {
|
|
|
646
833
|
title: titleOf("suggest_fix"),
|
|
647
834
|
description: descOf("suggest_fix"),
|
|
648
835
|
annotations: annotationsFor("suggest_fix"),
|
|
649
|
-
inputSchema: {
|
|
836
|
+
inputSchema: {
|
|
837
|
+
reportId: z.string().describe("Report UUID to read the Stage-2 suggested-fix slice for")
|
|
838
|
+
},
|
|
650
839
|
outputSchema: {
|
|
651
840
|
reportId: z.string().describe("The report this slice was read from"),
|
|
652
841
|
rootCause: z.unknown().describe("Stage-2 root-cause hint, or null if not yet classified"),
|
|
653
842
|
suggestedFix: z.unknown().describe("Stage-2 suggested fix, or null if not yet classified"),
|
|
654
843
|
reproductionSteps: z.unknown().describe("Reproduction steps recorded on the report (array; [] if none)"),
|
|
655
844
|
summary: z.unknown().describe("One-line report summary, or null"),
|
|
656
|
-
component: z.unknown().describe("Component/page the bug was attributed to, or null")
|
|
845
|
+
component: z.unknown().describe("Component/page the bug was attributed to, or null"),
|
|
846
|
+
note: z.string().optional().describe("Present when no Stage-2 analysis exists \u2014 explains why and what to call instead")
|
|
657
847
|
}
|
|
658
848
|
},
|
|
659
849
|
async (args) => {
|
|
660
850
|
const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
661
851
|
const s2 = report.stage2_analysis;
|
|
852
|
+
if (!s2 || s2.rootCause == null && s2.suggestedFix == null) {
|
|
853
|
+
return jsonResult({
|
|
854
|
+
reportId: args.reportId,
|
|
855
|
+
rootCause: null,
|
|
856
|
+
suggestedFix: null,
|
|
857
|
+
reproductionSteps: report.reproduction_steps ?? [],
|
|
858
|
+
summary: report.summary ?? null,
|
|
859
|
+
component: report.component ?? null,
|
|
860
|
+
note: "No Stage-2 analysis exists for this report (it was classified by the fast Stage-1 path, which does not produce root-cause/fix hints). Use triage_issue for the full review packet, or dispatch_fix \u2014 the fix worker does its own grounded analysis."
|
|
861
|
+
});
|
|
862
|
+
}
|
|
662
863
|
return jsonResult({
|
|
663
864
|
reportId: args.reportId,
|
|
664
865
|
rootCause: s2?.rootCause ?? null,
|
|
@@ -675,7 +876,11 @@ function createMushiServer(config) {
|
|
|
675
876
|
title: titleOf("run_nl_query"),
|
|
676
877
|
description: descOf("run_nl_query"),
|
|
677
878
|
annotations: annotationsFor("run_nl_query"),
|
|
678
|
-
inputSchema: {
|
|
879
|
+
inputSchema: {
|
|
880
|
+
question: z.string().describe(
|
|
881
|
+
'Question in plain English, e.g. "Which components had the most critical bugs this week?"'
|
|
882
|
+
)
|
|
883
|
+
}
|
|
679
884
|
},
|
|
680
885
|
async (args) => {
|
|
681
886
|
const data = await apiCall("/v1/admin/query", {
|
|
@@ -692,7 +897,9 @@ function createMushiServer(config) {
|
|
|
692
897
|
description: descOf("diagnose_setup"),
|
|
693
898
|
annotations: annotationsFor("diagnose_setup"),
|
|
694
899
|
inputSchema: {
|
|
695
|
-
mode: z.enum(["full", "ingest", "dispatch"]).optional().describe(
|
|
900
|
+
mode: z.enum(["full", "ingest", "dispatch"]).optional().describe(
|
|
901
|
+
"full (default) = ingest + dispatch; ingest = SDK pipeline only; dispatch = fix preflight only."
|
|
902
|
+
),
|
|
696
903
|
project_id: z.string().optional().describe("Project UUID for dispatch checks (defaults to configured project)."),
|
|
697
904
|
projectId: z.string().optional().describe("Alias for project_id.")
|
|
698
905
|
},
|
|
@@ -730,7 +937,11 @@ function createMushiServer(config) {
|
|
|
730
937
|
}
|
|
731
938
|
if (mode === "dispatch") {
|
|
732
939
|
if (!resolvedId) {
|
|
733
|
-
throw new MushiApiError(
|
|
940
|
+
throw new MushiApiError(
|
|
941
|
+
400,
|
|
942
|
+
"MISSING_PROJECT",
|
|
943
|
+
"project_id is required for dispatch mode"
|
|
944
|
+
);
|
|
734
945
|
}
|
|
735
946
|
const data = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
736
947
|
const failed = data.checks.filter((c) => !c.ready);
|
|
@@ -864,7 +1075,12 @@ function createMushiServer(config) {
|
|
|
864
1075
|
async (args) => {
|
|
865
1076
|
const query = args.query ?? "";
|
|
866
1077
|
const hits = searchMushiDocs(query, args.limit ?? 8);
|
|
867
|
-
const results = hits.map(({ title, path, excerpt, score }) => ({
|
|
1078
|
+
const results = hits.map(({ title, path, excerpt, score }) => ({
|
|
1079
|
+
title,
|
|
1080
|
+
path,
|
|
1081
|
+
excerpt,
|
|
1082
|
+
score
|
|
1083
|
+
}));
|
|
868
1084
|
return jsonResult({ query, results });
|
|
869
1085
|
}
|
|
870
1086
|
);
|
|
@@ -1014,7 +1230,9 @@ function createMushiServer(config) {
|
|
|
1014
1230
|
} : { error: String(reportRes.reason) };
|
|
1015
1231
|
return jsonText({
|
|
1016
1232
|
evidence,
|
|
1017
|
-
reporter_thread: timeline ?? {
|
|
1233
|
+
reporter_thread: timeline ?? {
|
|
1234
|
+
error: String(timelineRes.reason)
|
|
1235
|
+
}
|
|
1018
1236
|
});
|
|
1019
1237
|
}
|
|
1020
1238
|
);
|
|
@@ -1049,27 +1267,54 @@ function createMushiServer(config) {
|
|
|
1049
1267
|
const includeLogs = args.include_logs !== false;
|
|
1050
1268
|
const [reportRes, evidenceRes, similarRes, fixCtxRes, blastRes, logsRes] = await Promise.allSettled([
|
|
1051
1269
|
apiCall(`/v1/admin/reports/${args.report_id}`, { headers }),
|
|
1052
|
-
apiCall(`/v1/admin/reports/${args.report_id}/timeline`, {
|
|
1270
|
+
apiCall(`/v1/admin/reports/${args.report_id}/timeline`, {
|
|
1271
|
+
headers
|
|
1272
|
+
}),
|
|
1053
1273
|
apiCall(`/v1/admin/reports/similarity`, {
|
|
1054
1274
|
method: "POST",
|
|
1055
1275
|
headers,
|
|
1056
1276
|
body: JSON.stringify({ report_id: args.report_id })
|
|
1057
1277
|
}).catch(() => null),
|
|
1058
|
-
apiCall(`/v1/admin/reports/${args.report_id}/fix-context`, { headers }).catch(
|
|
1059
|
-
|
|
1060
|
-
|
|
1278
|
+
apiCall(`/v1/admin/reports/${args.report_id}/fix-context`, { headers }).catch(
|
|
1279
|
+
() => null
|
|
1280
|
+
),
|
|
1281
|
+
apiCall(`/v1/admin/reports/${args.report_id}/blast-radius`, { headers }).catch(
|
|
1282
|
+
() => null
|
|
1283
|
+
),
|
|
1284
|
+
includeLogs ? apiCall(`/v1/admin/mcp/logs/${pid}?limit=20&level=warn`, { headers }).catch(
|
|
1285
|
+
() => null
|
|
1286
|
+
) : Promise.resolve(null)
|
|
1061
1287
|
]);
|
|
1062
1288
|
const report = reportRes.status === "fulfilled" ? reportRes.value : null;
|
|
1063
1289
|
const severity = report ? report.severity : "unknown";
|
|
1064
1290
|
const category = report ? report.category : "unknown";
|
|
1065
1291
|
const status = report ? report.status : "unknown";
|
|
1066
1292
|
const actions = [];
|
|
1067
|
-
|
|
1293
|
+
const processingError = report ? report.processing_error : null;
|
|
1294
|
+
const attempts = report ? report.fix_attempts ?? [] : [];
|
|
1295
|
+
const lastAttempt = attempts.length > 0 ? attempts[attempts.length - 1] : null;
|
|
1296
|
+
const lastAttemptStatus = lastAttempt ? String(lastAttempt.status ?? "") : "";
|
|
1297
|
+
const autofixBlocked = typeof processingError === "string" && processingError.startsWith("autofix_blocked:") || lastAttemptStatus.startsWith("skipped") || lastAttemptStatus === "failed";
|
|
1298
|
+
if (autofixBlocked && status !== "fixing" && status !== "fixed") {
|
|
1299
|
+
const blockReason = typeof processingError === "string" && processingError || String(lastAttempt?.error ?? "previous fix attempt was skipped or failed");
|
|
1300
|
+
actions.push({
|
|
1301
|
+
action: "unblock_autofix",
|
|
1302
|
+
reason: `Auto-fix is blocked: ${blockReason}. Resolve the blocker (settings/integration/budget), then re-dispatch.`,
|
|
1303
|
+
tool: "diagnose_setup",
|
|
1304
|
+
args: { mode: "dispatch" }
|
|
1305
|
+
});
|
|
1306
|
+
actions.push({
|
|
1307
|
+
action: "redispatch_after_unblock",
|
|
1308
|
+
reason: "Once the blocker above is resolved, dispatch a fresh fix attempt.",
|
|
1309
|
+
tool: "dispatch_fix",
|
|
1310
|
+
args: { reportId: args.report_id }
|
|
1311
|
+
});
|
|
1312
|
+
} else if (status === "new" || status === "classified") {
|
|
1068
1313
|
actions.push({
|
|
1069
1314
|
action: "dispatch_fix",
|
|
1070
|
-
reason: "Report is classified
|
|
1315
|
+
reason: "Report is classified and no fix has been attempted",
|
|
1071
1316
|
tool: "dispatch_fix",
|
|
1072
|
-
args: { reportId: args.report_id
|
|
1317
|
+
args: { reportId: args.report_id }
|
|
1073
1318
|
});
|
|
1074
1319
|
} else if (status === "fixing") {
|
|
1075
1320
|
actions.push({
|
|
@@ -1124,7 +1369,10 @@ function createMushiServer(config) {
|
|
|
1124
1369
|
return {
|
|
1125
1370
|
...res,
|
|
1126
1371
|
resource_links: [
|
|
1127
|
-
{
|
|
1372
|
+
{
|
|
1373
|
+
uri: `project://reports/${args.report_id}`,
|
|
1374
|
+
title: `Report ${args.report_id.slice(0, 8)}\u2026`
|
|
1375
|
+
},
|
|
1128
1376
|
...pid ? [{ uri: `project://dashboard`, title: "Project dashboard" }] : []
|
|
1129
1377
|
]
|
|
1130
1378
|
};
|
|
@@ -1143,7 +1391,9 @@ function createMushiServer(config) {
|
|
|
1143
1391
|
filesChanged: z.array(z.string()).describe("Repo-relative paths modified by the fix"),
|
|
1144
1392
|
linesChanged: z.number().describe("Total lines added + removed across filesChanged"),
|
|
1145
1393
|
summary: z.string().describe("One-line human summary of what the fix changed"),
|
|
1146
|
-
idempotencyKey: z.string().uuid().optional().describe(
|
|
1394
|
+
idempotencyKey: z.string().uuid().optional().describe(
|
|
1395
|
+
"Optional UUID \u2014 resend the same key to safely retry without creating duplicate fix rows. Omit it and a stable key is derived from reportId + branch + prUrl, so a retried submission carries the identical key instead of a fresh random one."
|
|
1396
|
+
)
|
|
1147
1397
|
},
|
|
1148
1398
|
outputSchema: {
|
|
1149
1399
|
ok: z.boolean().describe("True when the fix_attempt row was created and marked completed"),
|
|
@@ -1151,27 +1401,123 @@ function createMushiServer(config) {
|
|
|
1151
1401
|
}
|
|
1152
1402
|
},
|
|
1153
1403
|
async (args) => {
|
|
1154
|
-
const idemKey = args.idempotencyKey ??
|
|
1404
|
+
const idemKey = args.idempotencyKey ?? await stableUuidFrom(
|
|
1405
|
+
IDEMPOTENCY_NAMESPACE,
|
|
1406
|
+
args.reportId,
|
|
1407
|
+
args.branch,
|
|
1408
|
+
args.prUrl ?? ""
|
|
1409
|
+
);
|
|
1155
1410
|
const created = await apiCall("/v1/admin/fixes", {
|
|
1156
1411
|
method: "POST",
|
|
1157
1412
|
headers: { "Idempotency-Key": idemKey },
|
|
1158
1413
|
body: JSON.stringify({ reportId: args.reportId, agent: "mcp" })
|
|
1159
1414
|
});
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1415
|
+
try {
|
|
1416
|
+
await apiCall(`/v1/admin/fixes/${created.fixId}`, {
|
|
1417
|
+
method: "PATCH",
|
|
1418
|
+
body: JSON.stringify({
|
|
1419
|
+
status: "completed",
|
|
1420
|
+
branch: args.branch,
|
|
1421
|
+
pr_url: args.prUrl,
|
|
1422
|
+
files_changed: args.filesChanged,
|
|
1423
|
+
lines_changed: args.linesChanged,
|
|
1424
|
+
summary: args.summary,
|
|
1425
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1426
|
+
})
|
|
1427
|
+
});
|
|
1428
|
+
} catch (err) {
|
|
1429
|
+
const status = err instanceof MushiApiError ? err.status : 500;
|
|
1430
|
+
throw new MushiApiError(
|
|
1431
|
+
status,
|
|
1432
|
+
"FIX_PATCH_FAILED",
|
|
1433
|
+
`Fix row ${created.fixId} was created for report ${args.reportId}, but marking it completed failed: ${bareMessage(err)} \u2014 the fix row exists and is still in its initial (pending) state, so no work is lost. Preferred repair: PATCH /v1/admin/fixes/${created.fixId} with {"status":"completed"} to finish this exact row. Re-running submit_fix_result re-sends the same derived Idempotency-Key (reportId + branch + prUrl) rather than a fresh one, so it dedupes wherever the API honours that header \u2014 retry only with identical arguments.`
|
|
1434
|
+
);
|
|
1435
|
+
}
|
|
1172
1436
|
return jsonResult({ ok: true, fixId: created.fixId });
|
|
1173
1437
|
}
|
|
1174
1438
|
);
|
|
1439
|
+
server.registerTool(
|
|
1440
|
+
"triage_next_steps",
|
|
1441
|
+
{
|
|
1442
|
+
title: titleOf("triage_next_steps"),
|
|
1443
|
+
description: descOf("triage_next_steps"),
|
|
1444
|
+
annotations: annotationsFor("triage_next_steps"),
|
|
1445
|
+
inputSchema: {
|
|
1446
|
+
project_id: z.string().uuid().optional().describe("Project UUID (defaults to the configured project).")
|
|
1447
|
+
},
|
|
1448
|
+
outputSchema: {
|
|
1449
|
+
steps: z.array(
|
|
1450
|
+
z.object({
|
|
1451
|
+
priority: z.number(),
|
|
1452
|
+
action: z.string(),
|
|
1453
|
+
reason: z.string(),
|
|
1454
|
+
tool: z.string().optional(),
|
|
1455
|
+
args: z.record(z.string(), z.unknown()).optional()
|
|
1456
|
+
})
|
|
1457
|
+
),
|
|
1458
|
+
summary: z.string()
|
|
1459
|
+
}
|
|
1460
|
+
},
|
|
1461
|
+
async (args) => {
|
|
1462
|
+
const pid = await resolveProjectId(args.project_id);
|
|
1463
|
+
const extraHeaders = pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
|
|
1464
|
+
const data = await apiCall(
|
|
1465
|
+
"/v1/admin/reports?limit=100",
|
|
1466
|
+
{ headers: extraHeaders }
|
|
1467
|
+
);
|
|
1468
|
+
const reports = data.reports ?? [];
|
|
1469
|
+
const isCron = (r) => typeof r.reporter_token_hash === "string" && r.reporter_token_hash.startsWith("cron:");
|
|
1470
|
+
const isBlocked = (r) => typeof r.processing_error === "string" && r.processing_error.startsWith("autofix_blocked:");
|
|
1471
|
+
const label = (r) => String(r.title ?? r.summary ?? r.description ?? "").slice(0, 90);
|
|
1472
|
+
const steps = [];
|
|
1473
|
+
let priority = 1;
|
|
1474
|
+
const blocked = reports.filter(isBlocked);
|
|
1475
|
+
if (blocked.length > 0) {
|
|
1476
|
+
steps.push({
|
|
1477
|
+
priority: priority++,
|
|
1478
|
+
action: `Unblock auto-fix (${blocked.length} report${blocked.length === 1 ? "" : "s"} blocked)`,
|
|
1479
|
+
reason: String(blocked[0].processing_error).slice(0, 200),
|
|
1480
|
+
tool: "diagnose_setup",
|
|
1481
|
+
args: { mode: "dispatch" }
|
|
1482
|
+
});
|
|
1483
|
+
}
|
|
1484
|
+
const fixing = reports.filter((r) => r.status === "fixing");
|
|
1485
|
+
for (const r of fixing.slice(0, 2)) {
|
|
1486
|
+
steps.push({
|
|
1487
|
+
priority: priority++,
|
|
1488
|
+
action: `Shepherd in-flight fix: ${label(r)}`,
|
|
1489
|
+
reason: "A fix branch/PR is open \u2014 check CI and merge when green.",
|
|
1490
|
+
tool: "get_report_timeline",
|
|
1491
|
+
args: { report_id: r.id }
|
|
1492
|
+
});
|
|
1493
|
+
}
|
|
1494
|
+
const openUserReports = reports.filter((r) => (r.status === "new" || r.status === "classified") && !isCron(r) && !isBlocked(r)).sort((a, b) => {
|
|
1495
|
+
const rank = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
1496
|
+
return (rank[String(a.severity)] ?? 4) - (rank[String(b.severity)] ?? 4);
|
|
1497
|
+
});
|
|
1498
|
+
for (const r of openUserReports.slice(0, Math.max(0, 4 - steps.length))) {
|
|
1499
|
+
steps.push({
|
|
1500
|
+
priority: priority++,
|
|
1501
|
+
action: `Triage [${r.severity}] ${label(r)}`,
|
|
1502
|
+
reason: "User-felt report awaiting triage \u2014 review the packet, then dispatch or dismiss.",
|
|
1503
|
+
tool: "triage_issue",
|
|
1504
|
+
args: { report_id: r.id }
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
const chores = reports.filter((r) => r.status === "classified" && isCron(r) && !isBlocked(r));
|
|
1508
|
+
if (chores.length > 0 && steps.length < 5) {
|
|
1509
|
+
steps.push({
|
|
1510
|
+
priority: priority++,
|
|
1511
|
+
action: `Batch ${chores.length} maintenance chore${chores.length === 1 ? "" : "s"} (dependency bumps)`,
|
|
1512
|
+
reason: "Robot-filed modernization reports \u2014 batch-dispatch or dismiss in one sitting; do not let them crowd out user bugs.",
|
|
1513
|
+
tool: "get_recent_reports",
|
|
1514
|
+
args: { status: "classified" }
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
const summary = steps.length === 0 ? "Inbox is clear \u2014 no blocked fixes, no open user reports, no chores. Nothing needs your attention." : `${steps.length} prioritised step${steps.length === 1 ? "" : "s"}: ` + steps.map((s) => s.action).join(" \u2192 ");
|
|
1518
|
+
return jsonResult({ steps, summary });
|
|
1519
|
+
}
|
|
1520
|
+
);
|
|
1175
1521
|
server.registerTool(
|
|
1176
1522
|
"dispatch_fix",
|
|
1177
1523
|
{
|
|
@@ -1181,8 +1527,12 @@ function createMushiServer(config) {
|
|
|
1181
1527
|
inputSchema: {
|
|
1182
1528
|
reportId: z.string().uuid().describe("Report UUID to fix"),
|
|
1183
1529
|
agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
|
|
1184
|
-
idempotencyKey: z.string().uuid().optional().describe(
|
|
1185
|
-
|
|
1530
|
+
idempotencyKey: z.string().uuid().optional().describe(
|
|
1531
|
+
"Optional RFC 4122 UUID. Resend the same key to safely retry without dispatching a duplicate fix job (Idempotency-Key IETF draft)."
|
|
1532
|
+
),
|
|
1533
|
+
inventoryActionNodeId: z.string().uuid().optional().describe(
|
|
1534
|
+
"Optional inventory Action node UUID for spec-traceability (\xA72.10). When provided, the fix-worker embeds the expected_outcome contract in the LLM prompt and runs validateAgainstSpec before opening the PR."
|
|
1535
|
+
)
|
|
1186
1536
|
},
|
|
1187
1537
|
outputSchema: {
|
|
1188
1538
|
fixId: z.string(),
|
|
@@ -1204,7 +1554,7 @@ function createMushiServer(config) {
|
|
|
1204
1554
|
} catch {
|
|
1205
1555
|
}
|
|
1206
1556
|
}
|
|
1207
|
-
const
|
|
1557
|
+
const dispatch = await apiCall("/v1/admin/fixes/dispatch", {
|
|
1208
1558
|
method: "POST",
|
|
1209
1559
|
headers: args.idempotencyKey ? { "Idempotency-Key": args.idempotencyKey } : void 0,
|
|
1210
1560
|
body: JSON.stringify({
|
|
@@ -1214,7 +1564,12 @@ function createMushiServer(config) {
|
|
|
1214
1564
|
...projectId ? { projectId } : {}
|
|
1215
1565
|
})
|
|
1216
1566
|
});
|
|
1217
|
-
|
|
1567
|
+
if (!dispatch?.dispatchId) {
|
|
1568
|
+
throw new Error(
|
|
1569
|
+
`Dispatch response missing dispatchId \u2014 got: ${JSON.stringify(dispatch).slice(0, 300)}`
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
return jsonResult({ fixId: dispatch.dispatchId, status: dispatch.status ?? "queued" });
|
|
1218
1573
|
}
|
|
1219
1574
|
);
|
|
1220
1575
|
server.registerTool(
|
|
@@ -1255,11 +1610,19 @@ function createMushiServer(config) {
|
|
|
1255
1610
|
},
|
|
1256
1611
|
async (args) => {
|
|
1257
1612
|
const pid = args.projectId ?? projectId;
|
|
1258
|
-
if (!pid)
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1613
|
+
if (!pid)
|
|
1614
|
+
throw new MushiApiError(
|
|
1615
|
+
400,
|
|
1616
|
+
"MISSING_PROJECT",
|
|
1617
|
+
"projectId is required for test_gen_from_report"
|
|
1618
|
+
);
|
|
1619
|
+
const data = await apiCall(
|
|
1620
|
+
`/v1/admin/inventory/${pid}/test-gen/from-report/${args.reportId}`,
|
|
1621
|
+
{
|
|
1622
|
+
method: "POST",
|
|
1623
|
+
body: JSON.stringify({})
|
|
1624
|
+
}
|
|
1625
|
+
);
|
|
1263
1626
|
return jsonText(data);
|
|
1264
1627
|
}
|
|
1265
1628
|
);
|
|
@@ -1338,7 +1701,22 @@ function createMushiServer(config) {
|
|
|
1338
1701
|
annotations: annotationsFor("transition_status"),
|
|
1339
1702
|
inputSchema: {
|
|
1340
1703
|
reportId: z.string().describe("Report UUID"),
|
|
1341
|
-
status: z.enum([
|
|
1704
|
+
status: z.enum([
|
|
1705
|
+
"pending",
|
|
1706
|
+
"classified",
|
|
1707
|
+
// Workflow states added 20260520600000 but missing from this
|
|
1708
|
+
// enum ever since (2026-08-16 audit P2-2) — agents could not
|
|
1709
|
+
// mark a report as being looked at.
|
|
1710
|
+
"triaged",
|
|
1711
|
+
"in_progress",
|
|
1712
|
+
"grouped",
|
|
1713
|
+
"fixing",
|
|
1714
|
+
"fixed",
|
|
1715
|
+
"resolved",
|
|
1716
|
+
"verified",
|
|
1717
|
+
"reopened",
|
|
1718
|
+
"dismissed"
|
|
1719
|
+
]).describe("Target status (resolved is stored as fixed)"),
|
|
1342
1720
|
reason: z.string().optional().describe("Reason for the transition (audit trail)")
|
|
1343
1721
|
}
|
|
1344
1722
|
},
|
|
@@ -1362,7 +1740,12 @@ function createMushiServer(config) {
|
|
|
1362
1740
|
},
|
|
1363
1741
|
async (args) => {
|
|
1364
1742
|
const pid = args.projectId ?? projectId;
|
|
1365
|
-
if (!pid)
|
|
1743
|
+
if (!pid)
|
|
1744
|
+
throw new MushiApiError(
|
|
1745
|
+
400,
|
|
1746
|
+
"MISSING_PROJECT",
|
|
1747
|
+
"projectId is required for setup_repo_for_mushi"
|
|
1748
|
+
);
|
|
1366
1749
|
const data = await apiCall(`/v1/admin/projects/${pid}/repo/bootstrap`, {
|
|
1367
1750
|
method: "POST",
|
|
1368
1751
|
body: JSON.stringify({})
|
|
@@ -1427,23 +1810,45 @@ function createMushiServer(config) {
|
|
|
1427
1810
|
"project://stats",
|
|
1428
1811
|
{ description: "Report counts, category breakdown, severity distribution" },
|
|
1429
1812
|
async () => ({
|
|
1430
|
-
contents: [
|
|
1813
|
+
contents: [
|
|
1814
|
+
{
|
|
1815
|
+
uri: "project://stats",
|
|
1816
|
+
mimeType: "application/json",
|
|
1817
|
+
text: JSON.stringify(await apiCall("/v1/admin/stats"), null, 2)
|
|
1818
|
+
}
|
|
1819
|
+
]
|
|
1431
1820
|
})
|
|
1432
1821
|
);
|
|
1433
1822
|
server.resource(
|
|
1434
1823
|
"project_settings",
|
|
1435
1824
|
"project://settings",
|
|
1436
|
-
{
|
|
1825
|
+
{
|
|
1826
|
+
description: "Project configuration \u2014 autofix agent, plugins enabled, ontology, LLM budgets"
|
|
1827
|
+
},
|
|
1437
1828
|
async () => ({
|
|
1438
|
-
contents: [
|
|
1829
|
+
contents: [
|
|
1830
|
+
{
|
|
1831
|
+
uri: "project://settings",
|
|
1832
|
+
mimeType: "application/json",
|
|
1833
|
+
text: JSON.stringify(await apiCall("/v1/admin/settings"), null, 2)
|
|
1834
|
+
}
|
|
1835
|
+
]
|
|
1439
1836
|
})
|
|
1440
1837
|
);
|
|
1441
1838
|
server.resource(
|
|
1442
1839
|
"project_dashboard",
|
|
1443
1840
|
"project://dashboard",
|
|
1444
|
-
{
|
|
1841
|
+
{
|
|
1842
|
+
description: "PDCA health snapshot \u2014 stage counts, bottleneck, recent activity (same payload the admin console polls)"
|
|
1843
|
+
},
|
|
1445
1844
|
async () => ({
|
|
1446
|
-
contents: [
|
|
1845
|
+
contents: [
|
|
1846
|
+
{
|
|
1847
|
+
uri: "project://dashboard",
|
|
1848
|
+
mimeType: "application/json",
|
|
1849
|
+
text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2)
|
|
1850
|
+
}
|
|
1851
|
+
]
|
|
1447
1852
|
})
|
|
1448
1853
|
);
|
|
1449
1854
|
server.registerTool(
|
|
@@ -1458,14 +1863,16 @@ function createMushiServer(config) {
|
|
|
1458
1863
|
}
|
|
1459
1864
|
},
|
|
1460
1865
|
async ({ limit, range }) => ({
|
|
1461
|
-
content: [
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1866
|
+
content: [
|
|
1867
|
+
{
|
|
1868
|
+
type: "text",
|
|
1869
|
+
text: JSON.stringify(
|
|
1870
|
+
await apiCall(`/v1/admin/rewards/leaderboard?range=${range}&limit=${limit}`),
|
|
1871
|
+
null,
|
|
1872
|
+
2
|
|
1873
|
+
)
|
|
1874
|
+
}
|
|
1875
|
+
]
|
|
1469
1876
|
})
|
|
1470
1877
|
);
|
|
1471
1878
|
server.registerTool(
|
|
@@ -1481,18 +1888,20 @@ function createMushiServer(config) {
|
|
|
1481
1888
|
}
|
|
1482
1889
|
},
|
|
1483
1890
|
async ({ external_user_id, points, reason }) => ({
|
|
1484
|
-
content: [
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1891
|
+
content: [
|
|
1892
|
+
{
|
|
1893
|
+
type: "text",
|
|
1894
|
+
text: JSON.stringify(
|
|
1895
|
+
await apiCall("/v1/admin/rewards/bonus-points", {
|
|
1896
|
+
method: "POST",
|
|
1897
|
+
headers: { "Content-Type": "application/json" },
|
|
1898
|
+
body: JSON.stringify({ external_user_id, points, reason })
|
|
1899
|
+
}),
|
|
1900
|
+
null,
|
|
1901
|
+
2
|
|
1902
|
+
)
|
|
1903
|
+
}
|
|
1904
|
+
]
|
|
1496
1905
|
})
|
|
1497
1906
|
);
|
|
1498
1907
|
server.registerTool(
|
|
@@ -1508,18 +1917,20 @@ function createMushiServer(config) {
|
|
|
1508
1917
|
}
|
|
1509
1918
|
},
|
|
1510
1919
|
async ({ external_user_id, tier_slug, reason }) => ({
|
|
1511
|
-
content: [
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1920
|
+
content: [
|
|
1921
|
+
{
|
|
1922
|
+
type: "text",
|
|
1923
|
+
text: JSON.stringify(
|
|
1924
|
+
await apiCall("/v1/admin/rewards/set-tier", {
|
|
1925
|
+
method: "POST",
|
|
1926
|
+
headers: { "Content-Type": "application/json" },
|
|
1927
|
+
body: JSON.stringify({ external_user_id, tier_slug, reason })
|
|
1928
|
+
}),
|
|
1929
|
+
null,
|
|
1930
|
+
2
|
|
1931
|
+
)
|
|
1932
|
+
}
|
|
1933
|
+
]
|
|
1523
1934
|
})
|
|
1524
1935
|
);
|
|
1525
1936
|
server.resource(
|
|
@@ -1529,11 +1940,13 @@ function createMushiServer(config) {
|
|
|
1529
1940
|
description: "Returns the privacy posture for this project: storage region, LLM provider, whether BYOK is configured, data retention window, and last audit timestamp."
|
|
1530
1941
|
},
|
|
1531
1942
|
async () => ({
|
|
1532
|
-
contents: [
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1943
|
+
contents: [
|
|
1944
|
+
{
|
|
1945
|
+
uri: "privacy://status",
|
|
1946
|
+
mimeType: "application/json",
|
|
1947
|
+
text: JSON.stringify(await apiCall("/v1/admin/privacy-status"), null, 2)
|
|
1948
|
+
}
|
|
1949
|
+
]
|
|
1537
1950
|
})
|
|
1538
1951
|
);
|
|
1539
1952
|
server.resource(
|
|
@@ -1543,15 +1956,19 @@ function createMushiServer(config) {
|
|
|
1543
1956
|
description: "Returns the project's last 30 days of judge scores, prompt promotions, fixed-bug count, and lesson inductions. Agents can read this to see whether the loop is converging (rising judge scores, falling recurrence) or stalling."
|
|
1544
1957
|
},
|
|
1545
1958
|
async () => ({
|
|
1546
|
-
contents: [
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1959
|
+
contents: [
|
|
1960
|
+
{
|
|
1961
|
+
uri: "evolution://history",
|
|
1962
|
+
mimeType: "application/json",
|
|
1963
|
+
text: projectId ? JSON.stringify(
|
|
1964
|
+
await apiCall(
|
|
1965
|
+
`/v1/admin/projects/${encodeURIComponent(projectId)}/evolution-history`
|
|
1966
|
+
),
|
|
1967
|
+
null,
|
|
1968
|
+
2
|
|
1969
|
+
) : JSON.stringify({ ok: false, error: "project_id required" })
|
|
1970
|
+
}
|
|
1971
|
+
]
|
|
1555
1972
|
})
|
|
1556
1973
|
);
|
|
1557
1974
|
server.resource(
|
|
@@ -1561,11 +1978,13 @@ function createMushiServer(config) {
|
|
|
1561
1978
|
description: "Live health status of every configured BYOK channel (Sentry, GitHub, LangFuse, PagerDuty, \u2026). Orchestrators should check this before dispatching a fix to fail-fast on broken channels rather than burning LLM budget and discovering the failure mid-run."
|
|
1562
1979
|
},
|
|
1563
1980
|
async () => ({
|
|
1564
|
-
contents: [
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1981
|
+
contents: [
|
|
1982
|
+
{
|
|
1983
|
+
uri: "project://integration-health",
|
|
1984
|
+
mimeType: "application/json",
|
|
1985
|
+
text: JSON.stringify(await apiCall("/v1/admin/integrations/health"), null, 2)
|
|
1986
|
+
}
|
|
1987
|
+
]
|
|
1569
1988
|
})
|
|
1570
1989
|
);
|
|
1571
1990
|
server.resource(
|
|
@@ -1577,11 +1996,13 @@ function createMushiServer(config) {
|
|
|
1577
1996
|
async () => {
|
|
1578
1997
|
const path = projectId ? `/v1/admin/inventory/${projectId}` : "/v1/admin/inventory";
|
|
1579
1998
|
return {
|
|
1580
|
-
contents: [
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1999
|
+
contents: [
|
|
2000
|
+
{
|
|
2001
|
+
uri: "inventory://current",
|
|
2002
|
+
mimeType: "application/json",
|
|
2003
|
+
text: JSON.stringify(await apiCall(path), null, 2)
|
|
2004
|
+
}
|
|
2005
|
+
]
|
|
1585
2006
|
};
|
|
1586
2007
|
}
|
|
1587
2008
|
);
|
|
@@ -1610,11 +2031,13 @@ function createMushiServer(config) {
|
|
|
1610
2031
|
async () => {
|
|
1611
2032
|
const qs = projectId ? `?project_id=${encodeURIComponent(projectId)}` : "";
|
|
1612
2033
|
return {
|
|
1613
|
-
contents: [
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
2034
|
+
contents: [
|
|
2035
|
+
{
|
|
2036
|
+
uri: "mushi://activation",
|
|
2037
|
+
mimeType: "application/json",
|
|
2038
|
+
text: JSON.stringify(await apiCall(`/v1/admin/activation${qs}`), null, 2)
|
|
2039
|
+
}
|
|
2040
|
+
]
|
|
1618
2041
|
};
|
|
1619
2042
|
}
|
|
1620
2043
|
);
|
|
@@ -1623,11 +2046,12 @@ function createMushiServer(config) {
|
|
|
1623
2046
|
"Turn a Mushi report into a one-line root cause, smallest file set, repro steps, and blast-radius warnings. Use before asking an agent to write the patch.",
|
|
1624
2047
|
{ reportId: z.string().describe("The report UUID to summarize") },
|
|
1625
2048
|
({ reportId }) => ({
|
|
1626
|
-
messages: [
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
2049
|
+
messages: [
|
|
2050
|
+
{
|
|
2051
|
+
role: "user",
|
|
2052
|
+
content: {
|
|
2053
|
+
type: "text",
|
|
2054
|
+
text: `You are a senior engineer preparing a fix plan. Use the Mushi MCP tools to:
|
|
1631
2055
|
1. Call get_fix_context for reportId "${reportId}".
|
|
1632
2056
|
2. Call get_blast_radius if the report has a component node id.
|
|
1633
2057
|
3. Call get_similar_bugs with the component or summary as the query.
|
|
@@ -1640,8 +2064,9 @@ Then produce a markdown fix plan with exactly these sections:
|
|
|
1640
2064
|
- Confidence (low/medium/high) with a one-line justification
|
|
1641
2065
|
|
|
1642
2066
|
Lead with the fix. Skip the preamble.`
|
|
2067
|
+
}
|
|
1643
2068
|
}
|
|
1644
|
-
|
|
2069
|
+
]
|
|
1645
2070
|
})
|
|
1646
2071
|
);
|
|
1647
2072
|
server.prompt(
|
|
@@ -1649,11 +2074,12 @@ Lead with the fix. Skip the preamble.`
|
|
|
1649
2074
|
"Turn raw Sonnet-as-Judge scores into ship / iterate / dismiss guidance. Use after a fix attempt has been judged.",
|
|
1650
2075
|
{ fixId: z.string().describe("The fix_attempt UUID to explain") },
|
|
1651
2076
|
({ fixId }) => ({
|
|
1652
|
-
messages: [
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
2077
|
+
messages: [
|
|
2078
|
+
{
|
|
2079
|
+
role: "user",
|
|
2080
|
+
content: {
|
|
2081
|
+
type: "text",
|
|
2082
|
+
text: `You are a release engineer. Use the Mushi MCP tools:
|
|
1657
2083
|
1. Call get_fix_timeline for fixId "${fixId}" to see the full PDCA journey.
|
|
1658
2084
|
|
|
1659
2085
|
Then write a short verdict in this format:
|
|
@@ -1662,8 +2088,9 @@ Then write a short verdict in this format:
|
|
|
1662
2088
|
- **If iterate:** bullet list of the smallest next patch
|
|
1663
2089
|
|
|
1664
2090
|
No preamble, no score-by-score recap \u2014 the numbers are in the tool output.`
|
|
2091
|
+
}
|
|
1665
2092
|
}
|
|
1666
|
-
|
|
2093
|
+
]
|
|
1667
2094
|
})
|
|
1668
2095
|
);
|
|
1669
2096
|
server.prompt(
|
|
@@ -1671,11 +2098,12 @@ No preamble, no score-by-score recap \u2014 the numbers are in the tool output.`
|
|
|
1671
2098
|
'Answer "what should I focus on right now?" \u2014 five-item markdown list drawn from the dashboard + recent classified queue.',
|
|
1672
2099
|
{},
|
|
1673
2100
|
() => ({
|
|
1674
|
-
messages: [
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
2101
|
+
messages: [
|
|
2102
|
+
{
|
|
2103
|
+
role: "user",
|
|
2104
|
+
content: {
|
|
2105
|
+
type: "text",
|
|
2106
|
+
text: `You are a tech lead looking at the Mushi cockpit. Use the MCP tools:
|
|
1679
2107
|
1. Read the project://dashboard resource for the PDCA snapshot.
|
|
1680
2108
|
2. Call get_recent_reports with status="classified", limit=10.
|
|
1681
2109
|
|
|
@@ -1683,8 +2111,9 @@ Then output exactly 5 bullets, in priority order, each formatted as:
|
|
|
1683
2111
|
\`**Action** \u2014 why it matters \u2014 suggested tool call\`
|
|
1684
2112
|
|
|
1685
2113
|
Prefer items that are bottlenecks or critical severity. Skip filler.`
|
|
2114
|
+
}
|
|
1686
2115
|
}
|
|
1687
|
-
|
|
2116
|
+
]
|
|
1688
2117
|
})
|
|
1689
2118
|
);
|
|
1690
2119
|
server.prompt(
|
|
@@ -1692,11 +2121,12 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
|
|
|
1692
2121
|
"Diagnose why Mushi setup is stuck and return the single next command or console step to unblock it.",
|
|
1693
2122
|
{},
|
|
1694
2123
|
() => ({
|
|
1695
|
-
messages: [
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
2124
|
+
messages: [
|
|
2125
|
+
{
|
|
2126
|
+
role: "user",
|
|
2127
|
+
content: {
|
|
2128
|
+
type: "text",
|
|
2129
|
+
text: `You are a Mushi onboarding copilot. Use the MCP tools:
|
|
1700
2130
|
1. Read mushi://activation for unified setup posture.
|
|
1701
2131
|
2. Read project://integration-health if GitHub or Sentry looks blocked.
|
|
1702
2132
|
3. Call diagnose_setup if the resource is unavailable.
|
|
@@ -1708,8 +2138,9 @@ Then output:
|
|
|
1708
2138
|
- **If still stuck:** one diagnostic command (e.g. mushi doctor --fix)
|
|
1709
2139
|
|
|
1710
2140
|
Be specific. No generic advice.`
|
|
2141
|
+
}
|
|
1711
2142
|
}
|
|
1712
|
-
|
|
2143
|
+
]
|
|
1713
2144
|
})
|
|
1714
2145
|
);
|
|
1715
2146
|
server.registerTool(
|
|
@@ -1730,7 +2161,15 @@ Be specific. No generic advice.`
|
|
|
1730
2161
|
if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
|
|
1731
2162
|
const data = await apiCall(
|
|
1732
2163
|
`/v1/admin/inventory/${projectId2}/map-from-live`,
|
|
1733
|
-
{
|
|
2164
|
+
{
|
|
2165
|
+
method: "POST",
|
|
2166
|
+
body: JSON.stringify({
|
|
2167
|
+
base_url: baseUrl,
|
|
2168
|
+
max_pages: maxPages,
|
|
2169
|
+
provider,
|
|
2170
|
+
cursor_cloud_refine: cursorCloudRefine
|
|
2171
|
+
})
|
|
2172
|
+
}
|
|
1734
2173
|
);
|
|
1735
2174
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1736
2175
|
}
|
|
@@ -1758,7 +2197,9 @@ Be specific. No generic advice.`
|
|
|
1758
2197
|
annotations: annotationsFor("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
1759
2198
|
inputSchema: {
|
|
1760
2199
|
projectId: z.string().describe("Project UUID that owns the inventory story"),
|
|
1761
|
-
storyNodeId: z.string().describe(
|
|
2200
|
+
storyNodeId: z.string().describe(
|
|
2201
|
+
"User story node id/slug from accepted inventory (from map_user_stories or inventory.yaml)"
|
|
2202
|
+
),
|
|
1762
2203
|
automationMode: z.enum(["auto", "review", "approve"]).optional().describe("Approval gate for the generated qa_story (default review \u2192 pending_review)"),
|
|
1763
2204
|
baseUrl: z.string().url().optional().describe("Override the live app URL the Playwright test should target"),
|
|
1764
2205
|
openPr: z.boolean().optional().describe("Open a draft GitHub PR with the generated spec (default true)")
|
|
@@ -1772,10 +2213,14 @@ Be specific. No generic advice.`
|
|
|
1772
2213
|
},
|
|
1773
2214
|
async ({ projectId: projectId2, storyNodeId, automationMode, baseUrl, openPr }) => {
|
|
1774
2215
|
if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
|
|
1775
|
-
const data = await apiCall(
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
2216
|
+
const data = await apiCall(`/v1/admin/inventory/${projectId2}/stories/${storyNodeId}/generate-test`, {
|
|
2217
|
+
method: "POST",
|
|
2218
|
+
body: JSON.stringify({
|
|
2219
|
+
automation_mode: automationMode,
|
|
2220
|
+
base_url: baseUrl,
|
|
2221
|
+
open_pr: openPr
|
|
2222
|
+
})
|
|
2223
|
+
});
|
|
1779
2224
|
return jsonResult(data);
|
|
1780
2225
|
}
|
|
1781
2226
|
);
|
|
@@ -1790,10 +2235,10 @@ Be specific. No generic advice.`
|
|
|
1790
2235
|
}
|
|
1791
2236
|
},
|
|
1792
2237
|
async ({ projectId: projectId2 }) => {
|
|
1793
|
-
const data = await apiCall(
|
|
1794
|
-
"
|
|
1795
|
-
|
|
1796
|
-
);
|
|
2238
|
+
const data = await apiCall("/v1/admin/pdca/improve-qa-stories", {
|
|
2239
|
+
method: "POST",
|
|
2240
|
+
body: JSON.stringify({ project_id: projectId2 })
|
|
2241
|
+
});
|
|
1797
2242
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1798
2243
|
}
|
|
1799
2244
|
);
|
|
@@ -1827,7 +2272,9 @@ Be specific. No generic advice.`
|
|
|
1827
2272
|
}
|
|
1828
2273
|
},
|
|
1829
2274
|
async ({ projectId: projectId2 }) => {
|
|
1830
|
-
const data = await apiCall(
|
|
2275
|
+
const data = await apiCall(
|
|
2276
|
+
`/v1/admin/byok/keys?project_id=${encodeURIComponent(projectId2)}`
|
|
2277
|
+
);
|
|
1831
2278
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1832
2279
|
}
|
|
1833
2280
|
);
|
|
@@ -1840,16 +2287,60 @@ Be specific. No generic advice.`
|
|
|
1840
2287
|
inputSchema: {
|
|
1841
2288
|
projectId: z.string().describe("Project id"),
|
|
1842
2289
|
provider: z.enum(["anthropic", "openai", "firecrawl", "browserbase", "cursor"]).describe("Provider slug"),
|
|
1843
|
-
key: z.string().min(
|
|
2290
|
+
key: z.string().min(8).max(4096).describe("The API key value to add"),
|
|
1844
2291
|
label: z.string().optional().describe("Human-readable label for this key"),
|
|
1845
|
-
priority: z.number().int().min(
|
|
2292
|
+
priority: z.number().int().min(0).max(1e4).optional().describe("Priority for ordering (lower = higher priority)"),
|
|
2293
|
+
baseUrl: z.string().url().max(2048).optional().describe("Allow-listed OpenAI-compatible HTTPS base URL")
|
|
1846
2294
|
}
|
|
1847
2295
|
},
|
|
1848
|
-
async ({ projectId: projectId2, provider, key, label, priority }) => {
|
|
1849
|
-
const data = await apiCall(
|
|
1850
|
-
"
|
|
1851
|
-
|
|
1852
|
-
|
|
2296
|
+
async ({ projectId: projectId2, provider, key, label, priority, baseUrl }) => {
|
|
2297
|
+
const data = await apiCall("/v1/admin/byok/keys", {
|
|
2298
|
+
method: "POST",
|
|
2299
|
+
body: JSON.stringify({
|
|
2300
|
+
project_id: projectId2,
|
|
2301
|
+
provider_slug: provider,
|
|
2302
|
+
key,
|
|
2303
|
+
label,
|
|
2304
|
+
priority,
|
|
2305
|
+
base_url: baseUrl
|
|
2306
|
+
})
|
|
2307
|
+
});
|
|
2308
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
2309
|
+
}
|
|
2310
|
+
);
|
|
2311
|
+
server.registerTool(
|
|
2312
|
+
"test_byok_key",
|
|
2313
|
+
{
|
|
2314
|
+
title: titleOf("test_byok_key", TDD_TOOL_CATALOG),
|
|
2315
|
+
description: descOf("test_byok_key", TDD_TOOL_CATALOG),
|
|
2316
|
+
annotations: annotationsFor("test_byok_key", TDD_TOOL_CATALOG),
|
|
2317
|
+
inputSchema: {
|
|
2318
|
+
projectId: z.string().describe("Project id"),
|
|
2319
|
+
keyId: z.string().uuid().describe("Pooled BYOK key id")
|
|
2320
|
+
}
|
|
2321
|
+
},
|
|
2322
|
+
async ({ keyId }) => {
|
|
2323
|
+
const data = await apiCall(`/v1/admin/byok/keys/${encodeURIComponent(keyId)}/test`, {
|
|
2324
|
+
method: "POST"
|
|
2325
|
+
});
|
|
2326
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
2327
|
+
}
|
|
2328
|
+
);
|
|
2329
|
+
server.registerTool(
|
|
2330
|
+
"remove_byok_key",
|
|
2331
|
+
{
|
|
2332
|
+
title: titleOf("remove_byok_key", TDD_TOOL_CATALOG),
|
|
2333
|
+
description: descOf("remove_byok_key", TDD_TOOL_CATALOG),
|
|
2334
|
+
annotations: annotationsFor("remove_byok_key", TDD_TOOL_CATALOG),
|
|
2335
|
+
inputSchema: {
|
|
2336
|
+
projectId: z.string().describe("Project id"),
|
|
2337
|
+
keyId: z.string().uuid().describe("Pooled BYOK key UUID from list_byok_keys")
|
|
2338
|
+
}
|
|
2339
|
+
},
|
|
2340
|
+
async ({ keyId }) => {
|
|
2341
|
+
const data = await apiCall(`/v1/admin/byok/keys/${encodeURIComponent(keyId)}`, {
|
|
2342
|
+
method: "DELETE"
|
|
2343
|
+
});
|
|
1853
2344
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1854
2345
|
}
|
|
1855
2346
|
);
|
|
@@ -1864,7 +2355,9 @@ Be specific. No generic advice.`
|
|
|
1864
2355
|
}
|
|
1865
2356
|
},
|
|
1866
2357
|
async ({ projectId: projectId2 }) => {
|
|
1867
|
-
const data = await apiCall(
|
|
2358
|
+
const data = await apiCall(
|
|
2359
|
+
`/v1/admin/inventory/${projectId2}/stories/pending-review`
|
|
2360
|
+
);
|
|
1868
2361
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1869
2362
|
}
|
|
1870
2363
|
);
|
|
@@ -1901,13 +2394,10 @@ Be specific. No generic advice.`
|
|
|
1901
2394
|
}
|
|
1902
2395
|
},
|
|
1903
2396
|
async ({ reportId, message, authorName }) => {
|
|
1904
|
-
const data = await apiCall(
|
|
1905
|
-
|
|
1906
|
-
{
|
|
1907
|
-
|
|
1908
|
-
body: JSON.stringify({ message, author_name: authorName })
|
|
1909
|
-
}
|
|
1910
|
-
);
|
|
2397
|
+
const data = await apiCall(`/v1/sync/reports/${reportId}/reply`, {
|
|
2398
|
+
method: "POST",
|
|
2399
|
+
body: JSON.stringify({ message, author_name: authorName })
|
|
2400
|
+
});
|
|
1911
2401
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1912
2402
|
}
|
|
1913
2403
|
);
|
|
@@ -1986,10 +2476,10 @@ Be specific. No generic advice.`
|
|
|
1986
2476
|
async ({ project_id }) => {
|
|
1987
2477
|
const pid = project_id ?? config.projectId;
|
|
1988
2478
|
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT_ID", "project_id is required");
|
|
1989
|
-
const data = await apiCall(
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
);
|
|
2479
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/audit`, {
|
|
2480
|
+
method: "POST",
|
|
2481
|
+
body: "{}"
|
|
2482
|
+
});
|
|
1993
2483
|
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1994
2484
|
}
|
|
1995
2485
|
);
|
|
@@ -2085,17 +2575,19 @@ Be specific. No generic advice.`
|
|
|
2085
2575
|
inputSchema: {
|
|
2086
2576
|
root_skill_slug: z.string().describe('Root skill slug to run, e.g. "workflow-fix-and-ship"'),
|
|
2087
2577
|
report_id: z.string().optional().describe("Report UUID to attach the pipeline to"),
|
|
2088
|
-
mode: z.enum(["handoff", "cloud"]).optional().describe(
|
|
2578
|
+
mode: z.enum(["handoff", "cloud"]).optional().describe(
|
|
2579
|
+
"handoff (default): get context packet for local agent. cloud: auto-dispatch via Cursor Cloud."
|
|
2580
|
+
),
|
|
2089
2581
|
project_id: z.string().optional().describe("Project UUID. Falls back to the configured project.")
|
|
2090
2582
|
}
|
|
2091
2583
|
},
|
|
2092
2584
|
async (args) => {
|
|
2093
2585
|
const resolvedProjectId = args.project_id ?? projectId;
|
|
2094
2586
|
if (!resolvedProjectId) return jsonText({ error: "No project_id provided or configured." });
|
|
2095
|
-
const data = await apiCall(
|
|
2096
|
-
"
|
|
2097
|
-
|
|
2098
|
-
);
|
|
2587
|
+
const data = await apiCall("/v1/admin/skills/pipelines", {
|
|
2588
|
+
method: "POST",
|
|
2589
|
+
body: JSON.stringify({ ...args, project_id: resolvedProjectId })
|
|
2590
|
+
});
|
|
2099
2591
|
return jsonText(data);
|
|
2100
2592
|
}
|
|
2101
2593
|
);
|
|
@@ -2131,10 +2623,10 @@ Be specific. No generic advice.`
|
|
|
2131
2623
|
},
|
|
2132
2624
|
async (args) => {
|
|
2133
2625
|
const { run_id, step_index, ...body } = args;
|
|
2134
|
-
await apiCall(
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
);
|
|
2626
|
+
await apiCall(`/v1/admin/skills/pipelines/${run_id}/steps/${step_index}/checkin`, {
|
|
2627
|
+
method: "POST",
|
|
2628
|
+
body: JSON.stringify(body)
|
|
2629
|
+
});
|
|
2138
2630
|
return jsonText({ ok: true, message: `Step ${step_index} \u2192 ${args.status}` });
|
|
2139
2631
|
}
|
|
2140
2632
|
);
|
|
@@ -2302,7 +2794,11 @@ Be specific. No generic advice.`
|
|
|
2302
2794
|
if (args.compare) qs.set("compare", args.compare);
|
|
2303
2795
|
if (args.fix_id) qs.set("fix_id", args.fix_id);
|
|
2304
2796
|
if (!qs.toString()) {
|
|
2305
|
-
throw new MushiApiError(
|
|
2797
|
+
throw new MushiApiError(
|
|
2798
|
+
400,
|
|
2799
|
+
"BAD_REQUEST",
|
|
2800
|
+
"Provide paths, source=last_push, compare, or fix_id"
|
|
2801
|
+
);
|
|
2306
2802
|
}
|
|
2307
2803
|
const data = await apiCall(`/v1/admin/projects/${pid}/codebase/impact?${qs}`);
|
|
2308
2804
|
return jsonResult(data);
|
|
@@ -2354,7 +2850,7 @@ Be specific. No generic advice.`
|
|
|
2354
2850
|
"### First step",
|
|
2355
2851
|
`Call \`${cluster.tools[0]}\` to get started.`,
|
|
2356
2852
|
"",
|
|
2357
|
-
"> Tip: call any tool by name \u2014 `use_mushi` is a read-only helper that never calls other tools itself. All
|
|
2853
|
+
"> Tip: call any tool by name \u2014 `use_mushi` is a read-only helper that never calls other tools itself. All 71 tools remain available."
|
|
2358
2854
|
].join("\n");
|
|
2359
2855
|
return { content: [{ type: "text", text: orientation }] };
|
|
2360
2856
|
}
|
|
@@ -2363,11 +2859,7 @@ Be specific. No generic advice.`
|
|
|
2363
2859
|
const featureFilter = config.features ?? "all";
|
|
2364
2860
|
if (grantedScopes !== void 0 || featureFilter !== "all") {
|
|
2365
2861
|
const toolRegistry = server._registeredTools;
|
|
2366
|
-
const allSpecs = [
|
|
2367
|
-
...TOOL_CATALOG,
|
|
2368
|
-
...TDD_TOOL_CATALOG,
|
|
2369
|
-
...CODEBASE_TOOL_CATALOG
|
|
2370
|
-
];
|
|
2862
|
+
const allSpecs = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG, ...CODEBASE_TOOL_CATALOG];
|
|
2371
2863
|
for (const spec of allSpecs) {
|
|
2372
2864
|
if (grantedScopes !== void 0 && !grantedScopes.includes(spec.scope)) {
|
|
2373
2865
|
toolRegistry[spec.name]?.remove();
|
|
@@ -2440,5 +2932,6 @@ Be specific. No generic advice.`
|
|
|
2440
2932
|
}
|
|
2441
2933
|
export {
|
|
2442
2934
|
MushiApiError,
|
|
2443
|
-
createMushiServer
|
|
2935
|
+
createMushiServer,
|
|
2936
|
+
resolveTimeoutMsFromEnv
|
|
2444
2937
|
};
|