@mushi-mushi/mcp 0.20.1 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +21 -4
- package/dist/catalog.js +1 -1
- package/dist/{chunk-TP2QQWDJ.js → chunk-FNDGCFSJ.js} +31 -10
- package/dist/{chunk-ZSVRLSW5.js → chunk-XSL2FPLV.js} +1 -0
- package/dist/clients.d.ts +1 -1
- package/dist/clients.js +2 -2
- package/dist/feature-groups.js +1 -1
- package/dist/index.js +759 -284
- package/dist/server.d.ts +10 -2
- package/dist/server.js +612 -250
- package/package.json +5 -3
package/dist/server.js
CHANGED
|
@@ -3,17 +3,17 @@ import {
|
|
|
3
3
|
TDD_TOOL_CATALOG,
|
|
4
4
|
TOOL_CATALOG,
|
|
5
5
|
USE_MUSHI_INTENTS
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-FNDGCFSJ.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-XSL2FPLV.js";
|
|
14
14
|
|
|
15
15
|
// src/server.ts
|
|
16
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
16
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
17
17
|
import { z } from "zod";
|
|
18
18
|
import { createLogger } from "@mushi-mushi/core";
|
|
19
19
|
|
|
@@ -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
|
|
147
|
-
|
|
213
|
+
installStdoutGuard();
|
|
214
|
+
var NO_ARG_INPUT = z.object({});
|
|
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,10 +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
382
|
const data = await apiCall("/v1/admin/mcp/account-overview");
|
|
237
383
|
if (data.total === 1 && data.projects[0]) {
|
|
238
|
-
|
|
384
|
+
resolvedAccountProjectId = data.projects[0].id;
|
|
385
|
+
return resolvedAccountProjectId;
|
|
239
386
|
}
|
|
240
387
|
if (data.total === 0) {
|
|
241
388
|
throw new MushiApiError(
|
|
@@ -251,11 +398,27 @@ function createMushiServer(config) {
|
|
|
251
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.`
|
|
252
399
|
);
|
|
253
400
|
} catch (err) {
|
|
254
|
-
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
|
+
}
|
|
255
418
|
throw new MushiApiError(
|
|
256
419
|
400,
|
|
257
420
|
"MISSING_PROJECT_ID",
|
|
258
|
-
|
|
421
|
+
`projectId is required. Set MUSHI_PROJECT_ID in .cursor/mcp.json or pass it explicitly. (Project lookup failed with: ${bareMessage(err)})`
|
|
259
422
|
);
|
|
260
423
|
}
|
|
261
424
|
}
|
|
@@ -322,7 +485,7 @@ function createMushiServer(config) {
|
|
|
322
485
|
title: titleOf("get_recent_reports"),
|
|
323
486
|
description: descOf("get_recent_reports"),
|
|
324
487
|
annotations: annotationsFor("get_recent_reports"),
|
|
325
|
-
inputSchema: {
|
|
488
|
+
inputSchema: z.object({
|
|
326
489
|
status: z.string().optional().describe("Filter by status: new, classified, grouped, fixing, fixed, dismissed"),
|
|
327
490
|
category: z.string().optional().describe("Filter by category: bug, slow, visual, confusing, other"),
|
|
328
491
|
severity: z.string().optional().describe("Filter by severity: critical, high, medium, low"),
|
|
@@ -330,11 +493,11 @@ function createMushiServer(config) {
|
|
|
330
493
|
project_id: z.string().optional().describe(
|
|
331
494
|
"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."
|
|
332
495
|
)
|
|
333
|
-
},
|
|
334
|
-
outputSchema: {
|
|
496
|
+
}),
|
|
497
|
+
outputSchema: z.object({
|
|
335
498
|
reports: z.array(z.unknown()),
|
|
336
499
|
total: z.number()
|
|
337
|
-
}
|
|
500
|
+
})
|
|
338
501
|
},
|
|
339
502
|
async (args) => {
|
|
340
503
|
const params = new URLSearchParams();
|
|
@@ -359,11 +522,11 @@ function createMushiServer(config) {
|
|
|
359
522
|
title: titleOf("get_report_detail"),
|
|
360
523
|
description: descOf("get_report_detail"),
|
|
361
524
|
annotations: annotationsFor("get_report_detail"),
|
|
362
|
-
inputSchema: {
|
|
525
|
+
inputSchema: z.object({
|
|
363
526
|
reportId: z.string().describe("The report UUID"),
|
|
364
527
|
project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
|
|
365
|
-
},
|
|
366
|
-
outputSchema: { report: z.unknown() }
|
|
528
|
+
}),
|
|
529
|
+
outputSchema: z.object({ report: z.unknown() })
|
|
367
530
|
},
|
|
368
531
|
async (args) => {
|
|
369
532
|
const { headers } = await projectScopeHeaders(args.project_id);
|
|
@@ -386,7 +549,7 @@ function createMushiServer(config) {
|
|
|
386
549
|
title: titleOf("get_report_timeline"),
|
|
387
550
|
description: descOf("get_report_timeline"),
|
|
388
551
|
annotations: annotationsFor("get_report_timeline"),
|
|
389
|
-
inputSchema: { reportId: z.string().describe("The report UUID") }
|
|
552
|
+
inputSchema: z.object({ reportId: z.string().describe("The report UUID") })
|
|
390
553
|
},
|
|
391
554
|
async (args) => jsonText(await apiCall(`/v1/sync/reports/${args.reportId}/timeline`))
|
|
392
555
|
);
|
|
@@ -406,14 +569,14 @@ function createMushiServer(config) {
|
|
|
406
569
|
title: titleOf("search_reports"),
|
|
407
570
|
description: descOf("search_reports"),
|
|
408
571
|
annotations: annotationsFor("search_reports"),
|
|
409
|
-
inputSchema: {
|
|
572
|
+
inputSchema: z.object({
|
|
410
573
|
query: z.string().describe("Natural-language search text or component path"),
|
|
411
574
|
limit: z.number().optional().describe("Max results (default 10, max 50)"),
|
|
412
575
|
threshold: z.number().optional().describe("Similarity threshold 0..1, default 0.2")
|
|
413
|
-
},
|
|
414
|
-
outputSchema: {
|
|
576
|
+
}),
|
|
577
|
+
outputSchema: z.object({
|
|
415
578
|
results: z.array(z.unknown())
|
|
416
|
-
}
|
|
579
|
+
})
|
|
417
580
|
},
|
|
418
581
|
async (args) => {
|
|
419
582
|
const data = await apiCall("/v1/admin/reports/similarity", {
|
|
@@ -434,13 +597,13 @@ function createMushiServer(config) {
|
|
|
434
597
|
title: titleOf("get_similar_bugs"),
|
|
435
598
|
description: descOf("get_similar_bugs"),
|
|
436
599
|
annotations: annotationsFor("get_similar_bugs"),
|
|
437
|
-
inputSchema: {
|
|
600
|
+
inputSchema: z.object({
|
|
438
601
|
query: z.string().describe("Component name, page path, or bug description"),
|
|
439
602
|
limit: z.number().optional().describe("Max results (default 5, max 20)")
|
|
440
|
-
},
|
|
441
|
-
outputSchema: {
|
|
603
|
+
}),
|
|
604
|
+
outputSchema: z.object({
|
|
442
605
|
results: z.array(z.unknown())
|
|
443
|
-
}
|
|
606
|
+
})
|
|
444
607
|
},
|
|
445
608
|
async (args) => {
|
|
446
609
|
const data = await apiCall("/v1/admin/reports/similarity", {
|
|
@@ -461,18 +624,18 @@ function createMushiServer(config) {
|
|
|
461
624
|
title: titleOf("get_fix_context"),
|
|
462
625
|
description: descOf("get_fix_context"),
|
|
463
626
|
annotations: annotationsFor("get_fix_context"),
|
|
464
|
-
inputSchema: {
|
|
627
|
+
inputSchema: z.object({
|
|
465
628
|
reportId: z.string().describe("The report UUID to fix"),
|
|
466
629
|
project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
|
|
467
|
-
},
|
|
468
|
-
outputSchema: {
|
|
630
|
+
}),
|
|
631
|
+
outputSchema: z.object({
|
|
469
632
|
report: z.unknown(),
|
|
470
633
|
fixPrompt: z.unknown(),
|
|
471
634
|
reproductionSteps: z.unknown(),
|
|
472
635
|
component: z.unknown(),
|
|
473
636
|
rootCause: z.unknown(),
|
|
474
637
|
bugOntologyTags: z.unknown()
|
|
475
|
-
}
|
|
638
|
+
})
|
|
476
639
|
},
|
|
477
640
|
async (args) => {
|
|
478
641
|
const { headers } = await projectScopeHeaders(args.project_id);
|
|
@@ -501,10 +664,10 @@ function createMushiServer(config) {
|
|
|
501
664
|
title: titleOf("get_fix_timeline"),
|
|
502
665
|
description: descOf("get_fix_timeline"),
|
|
503
666
|
annotations: annotationsFor("get_fix_timeline"),
|
|
504
|
-
inputSchema: {
|
|
667
|
+
inputSchema: z.object({
|
|
505
668
|
fixId: z.string().describe("fix_attempt UUID"),
|
|
506
669
|
project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
|
|
507
|
-
}
|
|
670
|
+
})
|
|
508
671
|
},
|
|
509
672
|
async (args) => {
|
|
510
673
|
const { headers } = await projectScopeHeaders(args.project_id);
|
|
@@ -517,7 +680,7 @@ function createMushiServer(config) {
|
|
|
517
680
|
title: titleOf("get_blast_radius"),
|
|
518
681
|
description: descOf("get_blast_radius"),
|
|
519
682
|
annotations: annotationsFor("get_blast_radius"),
|
|
520
|
-
inputSchema: { nodeId: z.string().describe("Graph node UUID") }
|
|
683
|
+
inputSchema: z.object({ nodeId: z.string().describe("Graph node UUID") })
|
|
521
684
|
},
|
|
522
685
|
async (args) => jsonText(await apiCall(`/v1/admin/graph/blast-radius/${args.nodeId}`))
|
|
523
686
|
);
|
|
@@ -527,14 +690,14 @@ function createMushiServer(config) {
|
|
|
527
690
|
title: titleOf("get_knowledge_graph"),
|
|
528
691
|
description: descOf("get_knowledge_graph"),
|
|
529
692
|
annotations: annotationsFor("get_knowledge_graph"),
|
|
530
|
-
inputSchema: {
|
|
693
|
+
inputSchema: z.object({
|
|
531
694
|
seed: z.string().describe("Starting graph node id or human-readable label to traverse from"),
|
|
532
695
|
depth: z.number().optional().describe("BFS hops outward from seed \u2014 clamped to 4 in the handler (default 2)")
|
|
533
|
-
},
|
|
534
|
-
outputSchema: {
|
|
696
|
+
}),
|
|
697
|
+
outputSchema: z.object({
|
|
535
698
|
nodes: z.array(z.unknown()).describe("Graph nodes within the depth budget"),
|
|
536
699
|
edges: z.array(z.unknown()).describe("Edges connecting the returned nodes")
|
|
537
|
-
}
|
|
700
|
+
})
|
|
538
701
|
},
|
|
539
702
|
async (args) => {
|
|
540
703
|
const params = new URLSearchParams({
|
|
@@ -553,16 +716,16 @@ function createMushiServer(config) {
|
|
|
553
716
|
title: titleOf("get_graph_neighborhood"),
|
|
554
717
|
description: descOf("get_graph_neighborhood"),
|
|
555
718
|
annotations: annotationsFor("get_graph_neighborhood"),
|
|
556
|
-
inputSchema: {
|
|
719
|
+
inputSchema: z.object({
|
|
557
720
|
seed: z.string().describe(
|
|
558
721
|
"Starting graph node id or label to expand around (e.g. an inventory Action id or component name)"
|
|
559
722
|
),
|
|
560
723
|
depth: z.number().int().min(1).max(4).optional().describe("BFS hops to traverse outward. Default 2; clamped to a max of 4.")
|
|
561
|
-
},
|
|
562
|
-
outputSchema: {
|
|
724
|
+
}),
|
|
725
|
+
outputSchema: z.object({
|
|
563
726
|
nodes: z.array(z.unknown()).describe("Graph nodes within the depth budget"),
|
|
564
727
|
edges: z.array(z.unknown()).describe("Edges connecting the returned nodes")
|
|
565
|
-
}
|
|
728
|
+
})
|
|
566
729
|
},
|
|
567
730
|
async (args) => {
|
|
568
731
|
const params = new URLSearchParams({
|
|
@@ -581,12 +744,12 @@ function createMushiServer(config) {
|
|
|
581
744
|
title: titleOf("get_graph_node"),
|
|
582
745
|
description: descOf("get_graph_node"),
|
|
583
746
|
annotations: annotationsFor("get_graph_node"),
|
|
584
|
-
inputSchema: {
|
|
747
|
+
inputSchema: z.object({
|
|
585
748
|
nodeId: z.string().describe("The graph_nodes.id of the node to fetch (UUID).")
|
|
586
|
-
},
|
|
587
|
-
outputSchema: {
|
|
749
|
+
}),
|
|
750
|
+
outputSchema: z.object({
|
|
588
751
|
node: z.unknown().describe("Single graph_nodes row including metadata (Action nodes carry v2 status)")
|
|
589
|
-
}
|
|
752
|
+
})
|
|
590
753
|
},
|
|
591
754
|
async (args) => {
|
|
592
755
|
const data = await apiCall(`/v1/admin/graph/node/${args.nodeId}`);
|
|
@@ -599,9 +762,9 @@ function createMushiServer(config) {
|
|
|
599
762
|
title: titleOf("get_inventory"),
|
|
600
763
|
description: descOf("get_inventory"),
|
|
601
764
|
annotations: annotationsFor("get_inventory"),
|
|
602
|
-
inputSchema: {
|
|
765
|
+
inputSchema: z.object({
|
|
603
766
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted")
|
|
604
|
-
}
|
|
767
|
+
})
|
|
605
768
|
},
|
|
606
769
|
async (args) => {
|
|
607
770
|
const pid = args.projectId ?? projectId;
|
|
@@ -616,11 +779,11 @@ function createMushiServer(config) {
|
|
|
616
779
|
title: titleOf("diff_inventory"),
|
|
617
780
|
description: descOf("diff_inventory"),
|
|
618
781
|
annotations: annotationsFor("diff_inventory"),
|
|
619
|
-
inputSchema: {
|
|
782
|
+
inputSchema: z.object({
|
|
620
783
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project"),
|
|
621
784
|
fromSha: z.string().describe("Older commit SHA (the baseline to diff from)"),
|
|
622
785
|
toSha: z.string().describe("Newer commit SHA (the candidate to diff to)")
|
|
623
|
-
}
|
|
786
|
+
})
|
|
624
787
|
},
|
|
625
788
|
async (args) => {
|
|
626
789
|
const pid = args.projectId ?? projectId;
|
|
@@ -636,15 +799,15 @@ function createMushiServer(config) {
|
|
|
636
799
|
title: titleOf("list_gate_findings"),
|
|
637
800
|
description: descOf("list_gate_findings"),
|
|
638
801
|
annotations: annotationsFor("list_gate_findings"),
|
|
639
|
-
inputSchema: {
|
|
802
|
+
inputSchema: z.object({
|
|
640
803
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project"),
|
|
641
804
|
gate: z.string().optional().describe("Filter to one gate id: dead-handler | mock-leak | crawl | status-claim"),
|
|
642
805
|
severity: z.string().optional().describe("Minimum severity to include: low | medium | high | critical")
|
|
643
|
-
},
|
|
644
|
-
outputSchema: {
|
|
806
|
+
}),
|
|
807
|
+
outputSchema: z.object({
|
|
645
808
|
runs: z.array(z.unknown()).describe("Recent gate_runs rows, newest first"),
|
|
646
809
|
findings: z.array(z.unknown()).describe("gate_findings rows for those runs")
|
|
647
|
-
}
|
|
810
|
+
})
|
|
648
811
|
},
|
|
649
812
|
async (args) => {
|
|
650
813
|
const pid = args.projectId ?? projectId;
|
|
@@ -670,21 +833,35 @@ function createMushiServer(config) {
|
|
|
670
833
|
title: titleOf("suggest_fix"),
|
|
671
834
|
description: descOf("suggest_fix"),
|
|
672
835
|
annotations: annotationsFor("suggest_fix"),
|
|
673
|
-
inputSchema: {
|
|
836
|
+
inputSchema: z.object({
|
|
674
837
|
reportId: z.string().describe("Report UUID to read the Stage-2 suggested-fix slice for")
|
|
675
|
-
},
|
|
676
|
-
outputSchema: {
|
|
838
|
+
}),
|
|
839
|
+
outputSchema: z.object({
|
|
677
840
|
reportId: z.string().describe("The report this slice was read from"),
|
|
678
841
|
rootCause: z.unknown().describe("Stage-2 root-cause hint, or null if not yet classified"),
|
|
679
842
|
suggestedFix: z.unknown().describe("Stage-2 suggested fix, or null if not yet classified"),
|
|
680
843
|
reproductionSteps: z.unknown().describe("Reproduction steps recorded on the report (array; [] if none)"),
|
|
681
844
|
summary: z.unknown().describe("One-line report summary, or null"),
|
|
682
|
-
component: z.unknown().describe("Component/page the bug was attributed to, or null")
|
|
683
|
-
|
|
845
|
+
component: z.unknown().describe("Component/page the bug was attributed to, or null"),
|
|
846
|
+
note: z.string().optional().describe(
|
|
847
|
+
"Present when no Stage-2 analysis exists \u2014 explains why and what to call instead"
|
|
848
|
+
)
|
|
849
|
+
})
|
|
684
850
|
},
|
|
685
851
|
async (args) => {
|
|
686
852
|
const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
687
853
|
const s2 = report.stage2_analysis;
|
|
854
|
+
if (!s2 || s2.rootCause == null && s2.suggestedFix == null) {
|
|
855
|
+
return jsonResult({
|
|
856
|
+
reportId: args.reportId,
|
|
857
|
+
rootCause: null,
|
|
858
|
+
suggestedFix: null,
|
|
859
|
+
reproductionSteps: report.reproduction_steps ?? [],
|
|
860
|
+
summary: report.summary ?? null,
|
|
861
|
+
component: report.component ?? null,
|
|
862
|
+
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."
|
|
863
|
+
});
|
|
864
|
+
}
|
|
688
865
|
return jsonResult({
|
|
689
866
|
reportId: args.reportId,
|
|
690
867
|
rootCause: s2?.rootCause ?? null,
|
|
@@ -701,11 +878,11 @@ function createMushiServer(config) {
|
|
|
701
878
|
title: titleOf("run_nl_query"),
|
|
702
879
|
description: descOf("run_nl_query"),
|
|
703
880
|
annotations: annotationsFor("run_nl_query"),
|
|
704
|
-
inputSchema: {
|
|
881
|
+
inputSchema: z.object({
|
|
705
882
|
question: z.string().describe(
|
|
706
883
|
'Question in plain English, e.g. "Which components had the most critical bugs this week?"'
|
|
707
884
|
)
|
|
708
|
-
}
|
|
885
|
+
})
|
|
709
886
|
},
|
|
710
887
|
async (args) => {
|
|
711
888
|
const data = await apiCall("/v1/admin/query", {
|
|
@@ -721,14 +898,14 @@ function createMushiServer(config) {
|
|
|
721
898
|
title: titleOf("diagnose_setup"),
|
|
722
899
|
description: descOf("diagnose_setup"),
|
|
723
900
|
annotations: annotationsFor("diagnose_setup"),
|
|
724
|
-
inputSchema: {
|
|
901
|
+
inputSchema: z.object({
|
|
725
902
|
mode: z.enum(["full", "ingest", "dispatch"]).optional().describe(
|
|
726
903
|
"full (default) = ingest + dispatch; ingest = SDK pipeline only; dispatch = fix preflight only."
|
|
727
904
|
),
|
|
728
905
|
project_id: z.string().optional().describe("Project UUID for dispatch checks (defaults to configured project)."),
|
|
729
906
|
projectId: z.string().optional().describe("Alias for project_id.")
|
|
730
|
-
},
|
|
731
|
-
outputSchema: {
|
|
907
|
+
}),
|
|
908
|
+
outputSchema: z.object({
|
|
732
909
|
mode: z.string(),
|
|
733
910
|
ready: z.boolean(),
|
|
734
911
|
summary: z.string(),
|
|
@@ -736,7 +913,7 @@ function createMushiServer(config) {
|
|
|
736
913
|
ingest: z.unknown().optional(),
|
|
737
914
|
dispatch: z.unknown().optional(),
|
|
738
915
|
connection: z.unknown().optional()
|
|
739
|
-
}
|
|
916
|
+
})
|
|
740
917
|
},
|
|
741
918
|
async (args) => {
|
|
742
919
|
const mode = args.mode ?? "full";
|
|
@@ -875,17 +1052,71 @@ function createMushiServer(config) {
|
|
|
875
1052
|
});
|
|
876
1053
|
}
|
|
877
1054
|
);
|
|
1055
|
+
server.registerTool(
|
|
1056
|
+
"check_sdk_version",
|
|
1057
|
+
{
|
|
1058
|
+
title: titleOf("check_sdk_version"),
|
|
1059
|
+
description: descOf("check_sdk_version"),
|
|
1060
|
+
annotations: annotationsFor("check_sdk_version"),
|
|
1061
|
+
inputSchema: z.object({
|
|
1062
|
+
package: z.string().optional().describe(
|
|
1063
|
+
"npm package name (default @mushi-mushi/web). Examples: @mushi-mushi/web, @mushi-mushi/node, @mushi-mushi/core."
|
|
1064
|
+
),
|
|
1065
|
+
current: z.string().optional().describe("Installed version from package.json, if known (e.g. 1.27.0).")
|
|
1066
|
+
}),
|
|
1067
|
+
outputSchema: z.object({
|
|
1068
|
+
package: z.string(),
|
|
1069
|
+
latest: z.string().optional(),
|
|
1070
|
+
current: z.string().optional(),
|
|
1071
|
+
outdated: z.boolean().optional(),
|
|
1072
|
+
suggestedActions: z.array(
|
|
1073
|
+
z.object({
|
|
1074
|
+
type: z.literal("tool_call"),
|
|
1075
|
+
toolName: z.string(),
|
|
1076
|
+
arguments: z.record(z.string(), z.unknown()),
|
|
1077
|
+
reason: z.string()
|
|
1078
|
+
})
|
|
1079
|
+
).optional()
|
|
1080
|
+
})
|
|
1081
|
+
},
|
|
1082
|
+
async (args) => {
|
|
1083
|
+
const pkg = args.package?.trim() || "@mushi-mushi/web";
|
|
1084
|
+
const query = new URLSearchParams({ package: pkg }).toString();
|
|
1085
|
+
const data = await apiCall(
|
|
1086
|
+
`/v1/sdk/latest-version?${query}`
|
|
1087
|
+
);
|
|
1088
|
+
const latest = data.latest ?? data.version;
|
|
1089
|
+
const current = args.current;
|
|
1090
|
+
const outdated = current && latest ? current !== latest : void 0;
|
|
1091
|
+
return jsonResult({
|
|
1092
|
+
package: data.package ?? pkg,
|
|
1093
|
+
latest,
|
|
1094
|
+
current,
|
|
1095
|
+
outdated,
|
|
1096
|
+
...outdated ? {
|
|
1097
|
+
suggestedActions: [
|
|
1098
|
+
{
|
|
1099
|
+
type: "tool_call",
|
|
1100
|
+
toolName: "search_mushi_docs",
|
|
1101
|
+
arguments: { query: `${pkg} upgrade mushi-sdk-upgrade` },
|
|
1102
|
+
reason: "Read current upgrade notes, then apply the mushi-sdk-upgrade skill. This tool does not bump the pin."
|
|
1103
|
+
}
|
|
1104
|
+
]
|
|
1105
|
+
} : {}
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
);
|
|
878
1109
|
server.registerTool(
|
|
879
1110
|
"search_mushi_docs",
|
|
880
1111
|
{
|
|
881
1112
|
title: titleOf("search_mushi_docs"),
|
|
882
1113
|
description: descOf("search_mushi_docs"),
|
|
883
1114
|
annotations: annotationsFor("search_mushi_docs"),
|
|
884
|
-
inputSchema: {
|
|
1115
|
+
inputSchema: z.object({
|
|
885
1116
|
query: z.string().describe("Keywords to search official Mushi docs (guides, MCP, inventory, QA)."),
|
|
886
1117
|
limit: z.number().int().min(1).max(20).optional().describe("Max results (default 8).")
|
|
887
|
-
},
|
|
888
|
-
outputSchema: {
|
|
1118
|
+
}),
|
|
1119
|
+
outputSchema: z.object({
|
|
889
1120
|
query: z.string(),
|
|
890
1121
|
results: z.array(
|
|
891
1122
|
z.object({
|
|
@@ -895,7 +1126,7 @@ function createMushiServer(config) {
|
|
|
895
1126
|
score: z.number()
|
|
896
1127
|
})
|
|
897
1128
|
)
|
|
898
|
-
}
|
|
1129
|
+
})
|
|
899
1130
|
},
|
|
900
1131
|
async (args) => {
|
|
901
1132
|
const query = args.query ?? "";
|
|
@@ -916,11 +1147,11 @@ function createMushiServer(config) {
|
|
|
916
1147
|
description: descOf("list_projects"),
|
|
917
1148
|
annotations: annotationsFor("list_projects"),
|
|
918
1149
|
inputSchema: NO_ARG_INPUT,
|
|
919
|
-
outputSchema: {
|
|
1150
|
+
outputSchema: z.object({
|
|
920
1151
|
projects: z.array(z.unknown()),
|
|
921
1152
|
total: z.number().optional(),
|
|
922
1153
|
_multi_project_hint: z.string().optional()
|
|
923
|
-
}
|
|
1154
|
+
})
|
|
924
1155
|
},
|
|
925
1156
|
async () => {
|
|
926
1157
|
const data = await apiCall("/v1/admin/mcp/projects");
|
|
@@ -937,7 +1168,7 @@ function createMushiServer(config) {
|
|
|
937
1168
|
description: descOf("get_account_overview"),
|
|
938
1169
|
annotations: annotationsFor("get_account_overview"),
|
|
939
1170
|
inputSchema: NO_ARG_INPUT,
|
|
940
|
-
outputSchema: {
|
|
1171
|
+
outputSchema: z.object({
|
|
941
1172
|
projects: z.array(z.unknown()),
|
|
942
1173
|
total: z.number(),
|
|
943
1174
|
active_project_id: z.string().nullable(),
|
|
@@ -945,7 +1176,7 @@ function createMushiServer(config) {
|
|
|
945
1176
|
resourceCount: z.number().optional(),
|
|
946
1177
|
promptCount: z.number().optional(),
|
|
947
1178
|
multi_project_hint: z.string()
|
|
948
|
-
}
|
|
1179
|
+
})
|
|
949
1180
|
},
|
|
950
1181
|
async () => {
|
|
951
1182
|
const data = await apiCall("/v1/admin/mcp/account-overview");
|
|
@@ -967,9 +1198,9 @@ function createMushiServer(config) {
|
|
|
967
1198
|
title: titleOf("get_project_context"),
|
|
968
1199
|
description: descOf("get_project_context"),
|
|
969
1200
|
annotations: annotationsFor("get_project_context"),
|
|
970
|
-
inputSchema: {
|
|
1201
|
+
inputSchema: z.object({
|
|
971
1202
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project.")
|
|
972
|
-
}
|
|
1203
|
+
})
|
|
973
1204
|
},
|
|
974
1205
|
async (args) => {
|
|
975
1206
|
const pid = args.project_id ?? projectId;
|
|
@@ -991,13 +1222,13 @@ function createMushiServer(config) {
|
|
|
991
1222
|
title: titleOf("get_pipeline_logs"),
|
|
992
1223
|
description: descOf("get_pipeline_logs"),
|
|
993
1224
|
annotations: annotationsFor("get_pipeline_logs"),
|
|
994
|
-
inputSchema: {
|
|
1225
|
+
inputSchema: z.object({
|
|
995
1226
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
|
|
996
1227
|
service: z.enum(["fix-worker", "qa-story-runner", "pipeline", "all"]).optional().describe("Pipeline service to filter (default: all)."),
|
|
997
1228
|
since: z.string().optional().describe("ISO-8601 timestamp \u2014 return only entries after this time."),
|
|
998
1229
|
limit: z.number().optional().describe("Max entries to return (default 50, max 200)."),
|
|
999
1230
|
level: z.enum(["info", "warn", "error", "fatal"]).optional().describe("Minimum severity level (default: warn).")
|
|
1000
|
-
}
|
|
1231
|
+
})
|
|
1001
1232
|
},
|
|
1002
1233
|
async (args) => {
|
|
1003
1234
|
const pid = args.project_id ?? projectId;
|
|
@@ -1017,9 +1248,9 @@ function createMushiServer(config) {
|
|
|
1017
1248
|
title: titleOf("get_report_evidence"),
|
|
1018
1249
|
description: descOf("get_report_evidence"),
|
|
1019
1250
|
annotations: annotationsFor("get_report_evidence"),
|
|
1020
|
-
inputSchema: {
|
|
1251
|
+
inputSchema: z.object({
|
|
1021
1252
|
report_id: z.string().describe("Report UUID.")
|
|
1022
|
-
}
|
|
1253
|
+
})
|
|
1023
1254
|
},
|
|
1024
1255
|
async (args) => {
|
|
1025
1256
|
const [reportRes, timelineRes] = await Promise.allSettled([
|
|
@@ -1067,12 +1298,12 @@ function createMushiServer(config) {
|
|
|
1067
1298
|
title: titleOf("triage_issue"),
|
|
1068
1299
|
description: descOf("triage_issue"),
|
|
1069
1300
|
annotations: annotationsFor("triage_issue"),
|
|
1070
|
-
inputSchema: {
|
|
1301
|
+
inputSchema: z.object({
|
|
1071
1302
|
report_id: z.string().uuid().describe("Report UUID to triage."),
|
|
1072
1303
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
|
|
1073
1304
|
include_logs: z.boolean().optional().describe("Include recent pipeline logs in triage packet (default: true).")
|
|
1074
|
-
},
|
|
1075
|
-
outputSchema: {
|
|
1305
|
+
}),
|
|
1306
|
+
outputSchema: z.object({
|
|
1076
1307
|
report_id: z.string(),
|
|
1077
1308
|
severity: z.unknown(),
|
|
1078
1309
|
category: z.unknown(),
|
|
@@ -1085,7 +1316,7 @@ function createMushiServer(config) {
|
|
|
1085
1316
|
recent_logs: z.unknown().nullable(),
|
|
1086
1317
|
recommended_actions: z.array(z.unknown()),
|
|
1087
1318
|
triage_summary: z.string()
|
|
1088
|
-
}
|
|
1319
|
+
})
|
|
1089
1320
|
},
|
|
1090
1321
|
async (args) => {
|
|
1091
1322
|
const { projectId: pid, headers } = await projectScopeHeaders(args.project_id);
|
|
@@ -1115,12 +1346,31 @@ function createMushiServer(config) {
|
|
|
1115
1346
|
const category = report ? report.category : "unknown";
|
|
1116
1347
|
const status = report ? report.status : "unknown";
|
|
1117
1348
|
const actions = [];
|
|
1118
|
-
|
|
1349
|
+
const processingError = report ? report.processing_error : null;
|
|
1350
|
+
const attempts = report ? report.fix_attempts ?? [] : [];
|
|
1351
|
+
const lastAttempt = attempts.length > 0 ? attempts[attempts.length - 1] : null;
|
|
1352
|
+
const lastAttemptStatus = lastAttempt ? String(lastAttempt.status ?? "") : "";
|
|
1353
|
+
const autofixBlocked = typeof processingError === "string" && processingError.startsWith("autofix_blocked:") || lastAttemptStatus.startsWith("skipped") || lastAttemptStatus === "failed";
|
|
1354
|
+
if (autofixBlocked && status !== "fixing" && status !== "fixed") {
|
|
1355
|
+
const blockReason = typeof processingError === "string" && processingError || String(lastAttempt?.error ?? "previous fix attempt was skipped or failed");
|
|
1356
|
+
actions.push({
|
|
1357
|
+
action: "unblock_autofix",
|
|
1358
|
+
reason: `Auto-fix is blocked: ${blockReason}. Resolve the blocker (settings/integration/budget), then re-dispatch.`,
|
|
1359
|
+
tool: "diagnose_setup",
|
|
1360
|
+
args: { mode: "dispatch" }
|
|
1361
|
+
});
|
|
1362
|
+
actions.push({
|
|
1363
|
+
action: "redispatch_after_unblock",
|
|
1364
|
+
reason: "Once the blocker above is resolved, dispatch a fresh fix attempt.",
|
|
1365
|
+
tool: "dispatch_fix",
|
|
1366
|
+
args: { reportId: args.report_id }
|
|
1367
|
+
});
|
|
1368
|
+
} else if (status === "new" || status === "classified") {
|
|
1119
1369
|
actions.push({
|
|
1120
1370
|
action: "dispatch_fix",
|
|
1121
|
-
reason: "Report is classified
|
|
1371
|
+
reason: "Report is classified and no fix has been attempted",
|
|
1122
1372
|
tool: "dispatch_fix",
|
|
1123
|
-
args: { reportId: args.report_id
|
|
1373
|
+
args: { reportId: args.report_id }
|
|
1124
1374
|
});
|
|
1125
1375
|
} else if (status === "fixing") {
|
|
1126
1376
|
actions.push({
|
|
@@ -1190,7 +1440,7 @@ function createMushiServer(config) {
|
|
|
1190
1440
|
title: titleOf("submit_fix_result"),
|
|
1191
1441
|
description: descOf("submit_fix_result"),
|
|
1192
1442
|
annotations: annotationsFor("submit_fix_result"),
|
|
1193
|
-
inputSchema: {
|
|
1443
|
+
inputSchema: z.object({
|
|
1194
1444
|
reportId: z.string().describe("Report UUID to attach this external fix attempt to"),
|
|
1195
1445
|
branch: z.string().describe("Git branch name where the fix was implemented"),
|
|
1196
1446
|
prUrl: z.string().optional().describe("GitHub pull request URL, if a PR was opened"),
|
|
@@ -1198,43 +1448,136 @@ function createMushiServer(config) {
|
|
|
1198
1448
|
linesChanged: z.number().describe("Total lines added + removed across filesChanged"),
|
|
1199
1449
|
summary: z.string().describe("One-line human summary of what the fix changed"),
|
|
1200
1450
|
idempotencyKey: z.string().uuid().optional().describe(
|
|
1201
|
-
"Optional UUID \u2014 resend the same key to safely retry without creating duplicate fix rows"
|
|
1451
|
+
"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."
|
|
1202
1452
|
)
|
|
1203
|
-
},
|
|
1204
|
-
outputSchema: {
|
|
1453
|
+
}),
|
|
1454
|
+
outputSchema: z.object({
|
|
1205
1455
|
ok: z.boolean().describe("True when the fix_attempt row was created and marked completed"),
|
|
1206
1456
|
fixId: z.string().describe("UUID of the new fix_attempt row")
|
|
1207
|
-
}
|
|
1457
|
+
})
|
|
1208
1458
|
},
|
|
1209
1459
|
async (args) => {
|
|
1210
|
-
const idemKey = args.idempotencyKey ??
|
|
1460
|
+
const idemKey = args.idempotencyKey ?? await stableUuidFrom(IDEMPOTENCY_NAMESPACE, args.reportId, args.branch, args.prUrl ?? "");
|
|
1211
1461
|
const created = await apiCall("/v1/admin/fixes", {
|
|
1212
1462
|
method: "POST",
|
|
1213
1463
|
headers: { "Idempotency-Key": idemKey },
|
|
1214
1464
|
body: JSON.stringify({ reportId: args.reportId, agent: "mcp" })
|
|
1215
1465
|
});
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1466
|
+
try {
|
|
1467
|
+
await apiCall(`/v1/admin/fixes/${created.fixId}`, {
|
|
1468
|
+
method: "PATCH",
|
|
1469
|
+
body: JSON.stringify({
|
|
1470
|
+
status: "completed",
|
|
1471
|
+
branch: args.branch,
|
|
1472
|
+
pr_url: args.prUrl,
|
|
1473
|
+
files_changed: args.filesChanged,
|
|
1474
|
+
lines_changed: args.linesChanged,
|
|
1475
|
+
summary: args.summary,
|
|
1476
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
1477
|
+
})
|
|
1478
|
+
});
|
|
1479
|
+
} catch (err) {
|
|
1480
|
+
const status = err instanceof MushiApiError ? err.status : 500;
|
|
1481
|
+
throw new MushiApiError(
|
|
1482
|
+
status,
|
|
1483
|
+
"FIX_PATCH_FAILED",
|
|
1484
|
+
`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.`
|
|
1485
|
+
);
|
|
1486
|
+
}
|
|
1228
1487
|
return jsonResult({ ok: true, fixId: created.fixId });
|
|
1229
1488
|
}
|
|
1230
1489
|
);
|
|
1490
|
+
server.registerTool(
|
|
1491
|
+
"triage_next_steps",
|
|
1492
|
+
{
|
|
1493
|
+
title: titleOf("triage_next_steps"),
|
|
1494
|
+
description: descOf("triage_next_steps"),
|
|
1495
|
+
annotations: annotationsFor("triage_next_steps"),
|
|
1496
|
+
inputSchema: z.object({
|
|
1497
|
+
project_id: z.string().uuid().optional().describe("Project UUID (defaults to the configured project).")
|
|
1498
|
+
}),
|
|
1499
|
+
outputSchema: z.object({
|
|
1500
|
+
steps: z.array(
|
|
1501
|
+
z.object({
|
|
1502
|
+
priority: z.number(),
|
|
1503
|
+
action: z.string(),
|
|
1504
|
+
reason: z.string(),
|
|
1505
|
+
tool: z.string().optional(),
|
|
1506
|
+
args: z.record(z.string(), z.unknown()).optional()
|
|
1507
|
+
})
|
|
1508
|
+
),
|
|
1509
|
+
summary: z.string()
|
|
1510
|
+
})
|
|
1511
|
+
},
|
|
1512
|
+
async (args) => {
|
|
1513
|
+
const pid = await resolveProjectId(args.project_id);
|
|
1514
|
+
const extraHeaders = pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
|
|
1515
|
+
const data = await apiCall(
|
|
1516
|
+
"/v1/admin/reports?limit=100",
|
|
1517
|
+
{ headers: extraHeaders }
|
|
1518
|
+
);
|
|
1519
|
+
const reports = data.reports ?? [];
|
|
1520
|
+
const isCron = (r) => typeof r.reporter_token_hash === "string" && r.reporter_token_hash.startsWith("cron:");
|
|
1521
|
+
const isBlocked = (r) => typeof r.processing_error === "string" && r.processing_error.startsWith("autofix_blocked:");
|
|
1522
|
+
const label = (r) => String(r.title ?? r.summary ?? r.description ?? "").slice(0, 90);
|
|
1523
|
+
const steps = [];
|
|
1524
|
+
let priority = 1;
|
|
1525
|
+
const blocked = reports.filter(isBlocked);
|
|
1526
|
+
if (blocked.length > 0) {
|
|
1527
|
+
steps.push({
|
|
1528
|
+
priority: priority++,
|
|
1529
|
+
action: `Unblock auto-fix (${blocked.length} report${blocked.length === 1 ? "" : "s"} blocked)`,
|
|
1530
|
+
reason: String(blocked[0].processing_error).slice(0, 200),
|
|
1531
|
+
tool: "diagnose_setup",
|
|
1532
|
+
args: { mode: "dispatch" }
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
const fixing = reports.filter((r) => r.status === "fixing");
|
|
1536
|
+
for (const r of fixing.slice(0, 2)) {
|
|
1537
|
+
steps.push({
|
|
1538
|
+
priority: priority++,
|
|
1539
|
+
action: `Shepherd in-flight fix: ${label(r)}`,
|
|
1540
|
+
reason: "A fix branch/PR is open \u2014 check CI and merge when green.",
|
|
1541
|
+
tool: "get_report_timeline",
|
|
1542
|
+
args: { report_id: r.id }
|
|
1543
|
+
});
|
|
1544
|
+
}
|
|
1545
|
+
const openUserReports = reports.filter(
|
|
1546
|
+
(r) => (r.status === "new" || r.status === "classified") && !isCron(r) && !isBlocked(r)
|
|
1547
|
+
).sort((a, b) => {
|
|
1548
|
+
const rank = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
1549
|
+
return (rank[String(a.severity)] ?? 4) - (rank[String(b.severity)] ?? 4);
|
|
1550
|
+
});
|
|
1551
|
+
for (const r of openUserReports.slice(0, Math.max(0, 4 - steps.length))) {
|
|
1552
|
+
steps.push({
|
|
1553
|
+
priority: priority++,
|
|
1554
|
+
action: `Triage [${r.severity}] ${label(r)}`,
|
|
1555
|
+
reason: "User-felt report awaiting triage \u2014 review the packet, then dispatch or dismiss.",
|
|
1556
|
+
tool: "triage_issue",
|
|
1557
|
+
args: { report_id: r.id }
|
|
1558
|
+
});
|
|
1559
|
+
}
|
|
1560
|
+
const chores = reports.filter((r) => r.status === "classified" && isCron(r) && !isBlocked(r));
|
|
1561
|
+
if (chores.length > 0 && steps.length < 5) {
|
|
1562
|
+
steps.push({
|
|
1563
|
+
priority: priority++,
|
|
1564
|
+
action: `Batch ${chores.length} maintenance chore${chores.length === 1 ? "" : "s"} (dependency bumps)`,
|
|
1565
|
+
reason: "Robot-filed modernization reports \u2014 batch-dispatch or dismiss in one sitting; do not let them crowd out user bugs.",
|
|
1566
|
+
tool: "get_recent_reports",
|
|
1567
|
+
args: { status: "classified" }
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
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 ");
|
|
1571
|
+
return jsonResult({ steps, summary });
|
|
1572
|
+
}
|
|
1573
|
+
);
|
|
1231
1574
|
server.registerTool(
|
|
1232
1575
|
"dispatch_fix",
|
|
1233
1576
|
{
|
|
1234
1577
|
title: titleOf("dispatch_fix"),
|
|
1235
1578
|
description: descOf("dispatch_fix"),
|
|
1236
1579
|
annotations: annotationsFor("dispatch_fix"),
|
|
1237
|
-
inputSchema: {
|
|
1580
|
+
inputSchema: z.object({
|
|
1238
1581
|
reportId: z.string().uuid().describe("Report UUID to fix"),
|
|
1239
1582
|
agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
|
|
1240
1583
|
idempotencyKey: z.string().uuid().optional().describe(
|
|
@@ -1243,19 +1586,19 @@ function createMushiServer(config) {
|
|
|
1243
1586
|
inventoryActionNodeId: z.string().uuid().optional().describe(
|
|
1244
1587
|
"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."
|
|
1245
1588
|
)
|
|
1246
|
-
},
|
|
1247
|
-
outputSchema: {
|
|
1589
|
+
}),
|
|
1590
|
+
outputSchema: z.object({
|
|
1248
1591
|
fixId: z.string(),
|
|
1249
1592
|
status: z.string()
|
|
1250
|
-
}
|
|
1593
|
+
})
|
|
1251
1594
|
},
|
|
1252
|
-
async (args,
|
|
1253
|
-
if (
|
|
1595
|
+
async (args, ctx) => {
|
|
1596
|
+
if (ctx?.mcpReq.notify && ctx?.mcpReq._meta?.progressToken) {
|
|
1254
1597
|
try {
|
|
1255
|
-
await
|
|
1598
|
+
await ctx.mcpReq.notify({
|
|
1256
1599
|
method: "notifications/progress",
|
|
1257
1600
|
params: {
|
|
1258
|
-
progressToken:
|
|
1601
|
+
progressToken: ctx.mcpReq._meta.progressToken,
|
|
1259
1602
|
progress: 0,
|
|
1260
1603
|
total: 100,
|
|
1261
1604
|
message: "Dispatching Mushi fix orchestrator\u2026"
|
|
@@ -1264,7 +1607,7 @@ function createMushiServer(config) {
|
|
|
1264
1607
|
} catch {
|
|
1265
1608
|
}
|
|
1266
1609
|
}
|
|
1267
|
-
const
|
|
1610
|
+
const dispatch = await apiCall("/v1/admin/fixes/dispatch", {
|
|
1268
1611
|
method: "POST",
|
|
1269
1612
|
headers: args.idempotencyKey ? { "Idempotency-Key": args.idempotencyKey } : void 0,
|
|
1270
1613
|
body: JSON.stringify({
|
|
@@ -1274,7 +1617,12 @@ function createMushiServer(config) {
|
|
|
1274
1617
|
...projectId ? { projectId } : {}
|
|
1275
1618
|
})
|
|
1276
1619
|
});
|
|
1277
|
-
|
|
1620
|
+
if (!dispatch?.dispatchId) {
|
|
1621
|
+
throw new Error(
|
|
1622
|
+
`Dispatch response missing dispatchId \u2014 got: ${JSON.stringify(dispatch).slice(0, 300)}`
|
|
1623
|
+
);
|
|
1624
|
+
}
|
|
1625
|
+
return jsonResult({ fixId: dispatch.dispatchId, status: dispatch.status ?? "queued" });
|
|
1278
1626
|
}
|
|
1279
1627
|
);
|
|
1280
1628
|
server.registerTool(
|
|
@@ -1283,13 +1631,13 @@ function createMushiServer(config) {
|
|
|
1283
1631
|
title: titleOf("trigger_judge"),
|
|
1284
1632
|
description: descOf("trigger_judge"),
|
|
1285
1633
|
annotations: annotationsFor("trigger_judge"),
|
|
1286
|
-
inputSchema: {
|
|
1634
|
+
inputSchema: z.object({
|
|
1287
1635
|
limit: z.number().optional().describe("Max reports to judge in this batch (default 25, max 100)"),
|
|
1288
1636
|
projectId: z.string().optional().describe("Restrict to one project when the API key owns multiple")
|
|
1289
|
-
},
|
|
1290
|
-
outputSchema: {
|
|
1637
|
+
}),
|
|
1638
|
+
outputSchema: z.object({
|
|
1291
1639
|
dispatched: z.number().describe("Number of judge-batch jobs dispatched (one per accessible project)")
|
|
1292
|
-
}
|
|
1640
|
+
})
|
|
1293
1641
|
},
|
|
1294
1642
|
async (args) => {
|
|
1295
1643
|
const data = await apiCall("/v1/admin/judge/run", {
|
|
@@ -1308,10 +1656,10 @@ function createMushiServer(config) {
|
|
|
1308
1656
|
title: titleOf("test_gen_from_report"),
|
|
1309
1657
|
description: descOf("test_gen_from_report"),
|
|
1310
1658
|
annotations: annotationsFor("test_gen_from_report"),
|
|
1311
|
-
inputSchema: {
|
|
1659
|
+
inputSchema: z.object({
|
|
1312
1660
|
reportId: z.string().describe("Report UUID to turn into a Playwright PR"),
|
|
1313
1661
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project")
|
|
1314
|
-
}
|
|
1662
|
+
})
|
|
1315
1663
|
},
|
|
1316
1664
|
async (args) => {
|
|
1317
1665
|
const pid = args.projectId ?? projectId;
|
|
@@ -1337,16 +1685,16 @@ function createMushiServer(config) {
|
|
|
1337
1685
|
title: titleOf("merge_fix"),
|
|
1338
1686
|
description: descOf("merge_fix"),
|
|
1339
1687
|
annotations: annotationsFor("merge_fix"),
|
|
1340
|
-
inputSchema: {
|
|
1688
|
+
inputSchema: z.object({
|
|
1341
1689
|
fixId: z.string().describe("Fix attempt UUID whose GitHub PR should be squash-merged"),
|
|
1342
1690
|
mergeMethod: z.enum(["squash", "merge", "rebase"]).optional().describe("GitHub merge method (default squash)")
|
|
1343
|
-
},
|
|
1344
|
-
outputSchema: {
|
|
1691
|
+
}),
|
|
1692
|
+
outputSchema: z.object({
|
|
1345
1693
|
merged: z.boolean().optional().describe("True when GitHub accepted the merge in this call"),
|
|
1346
1694
|
alreadyMerged: z.boolean().optional().describe("True when the PR was already merged (idempotent no-op)"),
|
|
1347
1695
|
reportId: z.string().describe("Report UUID linked to this fix attempt"),
|
|
1348
1696
|
reportStatus: z.string().describe("Report workflow status after merge bookkeeping")
|
|
1349
|
-
}
|
|
1697
|
+
})
|
|
1350
1698
|
},
|
|
1351
1699
|
async (args) => {
|
|
1352
1700
|
const data = await apiCall(`/v1/admin/fixes/${args.fixId}/merge`, {
|
|
@@ -1362,14 +1710,14 @@ function createMushiServer(config) {
|
|
|
1362
1710
|
title: titleOf("refresh_ci"),
|
|
1363
1711
|
description: descOf("refresh_ci"),
|
|
1364
1712
|
annotations: annotationsFor("refresh_ci"),
|
|
1365
|
-
inputSchema: {
|
|
1713
|
+
inputSchema: z.object({
|
|
1366
1714
|
fixId: z.string().describe("Fix attempt UUID whose PR check-runs should be re-polled")
|
|
1367
|
-
},
|
|
1368
|
-
outputSchema: {
|
|
1715
|
+
}),
|
|
1716
|
+
outputSchema: z.object({
|
|
1369
1717
|
check_run_status: z.string().nullable().describe("GitHub check run status after refresh"),
|
|
1370
1718
|
check_run_conclusion: z.string().nullable().describe("success | failure | neutral | null while pending"),
|
|
1371
1719
|
check_run_updated_at: z.string().nullable().describe("ISO timestamp when CI status was last persisted")
|
|
1372
|
-
}
|
|
1720
|
+
})
|
|
1373
1721
|
},
|
|
1374
1722
|
async (args) => {
|
|
1375
1723
|
const data = await apiCall(`/v1/admin/fixes/${args.fixId}/refresh-ci`, { method: "POST" });
|
|
@@ -1382,13 +1730,13 @@ function createMushiServer(config) {
|
|
|
1382
1730
|
title: titleOf("reopen_report"),
|
|
1383
1731
|
description: descOf("reopen_report"),
|
|
1384
1732
|
annotations: annotationsFor("reopen_report"),
|
|
1385
|
-
inputSchema: {
|
|
1733
|
+
inputSchema: z.object({
|
|
1386
1734
|
reportId: z.string().describe("Report UUID to move back to reopened status"),
|
|
1387
1735
|
note: z.string().optional().describe("Operator note recorded on the reopen transition")
|
|
1388
|
-
},
|
|
1389
|
-
outputSchema: {
|
|
1736
|
+
}),
|
|
1737
|
+
outputSchema: z.object({
|
|
1390
1738
|
report: z.unknown().describe("Updated report row with status=reopened")
|
|
1391
|
-
}
|
|
1739
|
+
})
|
|
1392
1740
|
},
|
|
1393
1741
|
async (args) => {
|
|
1394
1742
|
const report = await apiCall(`/v1/sync/reports/${args.reportId}`, {
|
|
@@ -1404,11 +1752,16 @@ function createMushiServer(config) {
|
|
|
1404
1752
|
title: titleOf("transition_status"),
|
|
1405
1753
|
description: descOf("transition_status"),
|
|
1406
1754
|
annotations: annotationsFor("transition_status"),
|
|
1407
|
-
inputSchema: {
|
|
1755
|
+
inputSchema: z.object({
|
|
1408
1756
|
reportId: z.string().describe("Report UUID"),
|
|
1409
1757
|
status: z.enum([
|
|
1410
1758
|
"pending",
|
|
1411
1759
|
"classified",
|
|
1760
|
+
// Workflow states added 20260520600000 but missing from this
|
|
1761
|
+
// enum ever since (2026-08-16 audit P2-2) — agents could not
|
|
1762
|
+
// mark a report as being looked at.
|
|
1763
|
+
"triaged",
|
|
1764
|
+
"in_progress",
|
|
1412
1765
|
"grouped",
|
|
1413
1766
|
"fixing",
|
|
1414
1767
|
"fixed",
|
|
@@ -1416,9 +1769,9 @@ function createMushiServer(config) {
|
|
|
1416
1769
|
"verified",
|
|
1417
1770
|
"reopened",
|
|
1418
1771
|
"dismissed"
|
|
1419
|
-
]).describe("Target status"),
|
|
1772
|
+
]).describe("Target status (resolved is stored as fixed)"),
|
|
1420
1773
|
reason: z.string().optional().describe("Reason for the transition (audit trail)")
|
|
1421
|
-
}
|
|
1774
|
+
})
|
|
1422
1775
|
},
|
|
1423
1776
|
async (args) => {
|
|
1424
1777
|
const data = await apiCall(`/v1/admin/reports/${args.reportId}`, {
|
|
@@ -1434,9 +1787,9 @@ function createMushiServer(config) {
|
|
|
1434
1787
|
title: titleOf("setup_repo_for_mushi"),
|
|
1435
1788
|
description: descOf("setup_repo_for_mushi"),
|
|
1436
1789
|
annotations: annotationsFor("setup_repo_for_mushi"),
|
|
1437
|
-
inputSchema: {
|
|
1790
|
+
inputSchema: z.object({
|
|
1438
1791
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project")
|
|
1439
|
-
}
|
|
1792
|
+
})
|
|
1440
1793
|
},
|
|
1441
1794
|
async (args) => {
|
|
1442
1795
|
const pid = args.projectId ?? projectId;
|
|
@@ -1459,12 +1812,12 @@ function createMushiServer(config) {
|
|
|
1459
1812
|
title: titleOf("query_lessons"),
|
|
1460
1813
|
description: descOf("query_lessons"),
|
|
1461
1814
|
annotations: annotationsFor("query_lessons"),
|
|
1462
|
-
inputSchema: {
|
|
1815
|
+
inputSchema: z.object({
|
|
1463
1816
|
diff_text: z.string().describe("The PR diff, code snippet, or description of the change being made."),
|
|
1464
1817
|
max_tokens: z.number().optional().describe("Maximum tokens for returned lessons context (default 3000, max 8000)."),
|
|
1465
1818
|
top_k: z.number().optional().describe("Max number of lessons to return (default 15, max 50)."),
|
|
1466
1819
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project.")
|
|
1467
|
-
}
|
|
1820
|
+
})
|
|
1468
1821
|
},
|
|
1469
1822
|
async (args) => {
|
|
1470
1823
|
const pid = args.project_id ?? projectId;
|
|
@@ -1486,14 +1839,14 @@ function createMushiServer(config) {
|
|
|
1486
1839
|
title: titleOf("list_lessons"),
|
|
1487
1840
|
description: descOf("list_lessons"),
|
|
1488
1841
|
annotations: annotationsFor("list_lessons"),
|
|
1489
|
-
inputSchema: {
|
|
1842
|
+
inputSchema: z.object({
|
|
1490
1843
|
severity: z.enum(["info", "warn", "critical"]).optional().describe("Filter to one severity level"),
|
|
1491
1844
|
limit: z.number().optional().describe("Max lessons to return (default 50, max 200)"),
|
|
1492
1845
|
project_id: z.string().optional().describe("Project UUID \u2014 defaults to configured project")
|
|
1493
|
-
},
|
|
1494
|
-
outputSchema: {
|
|
1846
|
+
}),
|
|
1847
|
+
outputSchema: z.object({
|
|
1495
1848
|
lessons: z.array(z.unknown()).describe("Promoted lesson rows ordered by frequency")
|
|
1496
|
-
}
|
|
1849
|
+
})
|
|
1497
1850
|
},
|
|
1498
1851
|
async (args) => {
|
|
1499
1852
|
const pid = args.project_id ?? projectId;
|
|
@@ -1505,7 +1858,7 @@ function createMushiServer(config) {
|
|
|
1505
1858
|
return wrappedJsonResult({ lessons }, "lesson text");
|
|
1506
1859
|
}
|
|
1507
1860
|
);
|
|
1508
|
-
server.
|
|
1861
|
+
server.registerResource(
|
|
1509
1862
|
"project_stats",
|
|
1510
1863
|
"project://stats",
|
|
1511
1864
|
{ description: "Report counts, category breakdown, severity distribution" },
|
|
@@ -1519,7 +1872,7 @@ function createMushiServer(config) {
|
|
|
1519
1872
|
]
|
|
1520
1873
|
})
|
|
1521
1874
|
);
|
|
1522
|
-
server.
|
|
1875
|
+
server.registerResource(
|
|
1523
1876
|
"project_settings",
|
|
1524
1877
|
"project://settings",
|
|
1525
1878
|
{
|
|
@@ -1535,7 +1888,7 @@ function createMushiServer(config) {
|
|
|
1535
1888
|
]
|
|
1536
1889
|
})
|
|
1537
1890
|
);
|
|
1538
|
-
server.
|
|
1891
|
+
server.registerResource(
|
|
1539
1892
|
"project_dashboard",
|
|
1540
1893
|
"project://dashboard",
|
|
1541
1894
|
{
|
|
@@ -1557,10 +1910,10 @@ function createMushiServer(config) {
|
|
|
1557
1910
|
title: titleOf("list_top_contributors"),
|
|
1558
1911
|
description: descOf("list_top_contributors"),
|
|
1559
1912
|
annotations: annotationsFor("list_top_contributors"),
|
|
1560
|
-
inputSchema: {
|
|
1913
|
+
inputSchema: z.object({
|
|
1561
1914
|
limit: z.number().int().min(1).max(100).optional().default(10).describe("Max rows to return (default 10, max 100)"),
|
|
1562
1915
|
range: z.enum(["30d", "90d", "all"]).optional().default("30d").describe("Time window for points calculation")
|
|
1563
|
-
}
|
|
1916
|
+
})
|
|
1564
1917
|
},
|
|
1565
1918
|
async ({ limit, range }) => ({
|
|
1566
1919
|
content: [
|
|
@@ -1581,11 +1934,11 @@ function createMushiServer(config) {
|
|
|
1581
1934
|
title: titleOf("award_bonus_points"),
|
|
1582
1935
|
description: descOf("award_bonus_points"),
|
|
1583
1936
|
annotations: annotationsFor("award_bonus_points"),
|
|
1584
|
-
inputSchema: {
|
|
1937
|
+
inputSchema: z.object({
|
|
1585
1938
|
external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
|
|
1586
1939
|
points: z.number().int().min(1).max(5e4).describe("Bonus points to award (max 50,000 per call)"),
|
|
1587
1940
|
reason: z.string().max(200).describe("Human-readable reason, logged to end_user_activity")
|
|
1588
|
-
}
|
|
1941
|
+
})
|
|
1589
1942
|
},
|
|
1590
1943
|
async ({ external_user_id, points, reason }) => ({
|
|
1591
1944
|
content: [
|
|
@@ -1610,11 +1963,11 @@ function createMushiServer(config) {
|
|
|
1610
1963
|
title: titleOf("set_tier"),
|
|
1611
1964
|
description: descOf("set_tier"),
|
|
1612
1965
|
annotations: annotationsFor("set_tier"),
|
|
1613
|
-
inputSchema: {
|
|
1966
|
+
inputSchema: z.object({
|
|
1614
1967
|
external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
|
|
1615
1968
|
tier_slug: z.string().describe('Tier slug to assign, e.g. "champion", "contributor", "explorer"'),
|
|
1616
1969
|
reason: z.string().max(200).optional().describe("Optional reason for manual override")
|
|
1617
|
-
}
|
|
1970
|
+
})
|
|
1618
1971
|
},
|
|
1619
1972
|
async ({ external_user_id, tier_slug, reason }) => ({
|
|
1620
1973
|
content: [
|
|
@@ -1633,7 +1986,7 @@ function createMushiServer(config) {
|
|
|
1633
1986
|
]
|
|
1634
1987
|
})
|
|
1635
1988
|
);
|
|
1636
|
-
server.
|
|
1989
|
+
server.registerResource(
|
|
1637
1990
|
"privacy_status",
|
|
1638
1991
|
"privacy://status",
|
|
1639
1992
|
{
|
|
@@ -1649,7 +2002,7 @@ function createMushiServer(config) {
|
|
|
1649
2002
|
]
|
|
1650
2003
|
})
|
|
1651
2004
|
);
|
|
1652
|
-
server.
|
|
2005
|
+
server.registerResource(
|
|
1653
2006
|
"evolution_history",
|
|
1654
2007
|
"evolution://history",
|
|
1655
2008
|
{
|
|
@@ -1671,7 +2024,7 @@ function createMushiServer(config) {
|
|
|
1671
2024
|
]
|
|
1672
2025
|
})
|
|
1673
2026
|
);
|
|
1674
|
-
server.
|
|
2027
|
+
server.registerResource(
|
|
1675
2028
|
"project_integration_health",
|
|
1676
2029
|
"project://integration-health",
|
|
1677
2030
|
{
|
|
@@ -1687,7 +2040,7 @@ function createMushiServer(config) {
|
|
|
1687
2040
|
]
|
|
1688
2041
|
})
|
|
1689
2042
|
);
|
|
1690
|
-
server.
|
|
2043
|
+
server.registerResource(
|
|
1691
2044
|
"inventory_current",
|
|
1692
2045
|
"inventory://current",
|
|
1693
2046
|
{
|
|
@@ -1712,9 +2065,9 @@ function createMushiServer(config) {
|
|
|
1712
2065
|
title: titleOf("activation_status"),
|
|
1713
2066
|
description: descOf("activation_status"),
|
|
1714
2067
|
annotations: annotationsFor("activation_status"),
|
|
1715
|
-
inputSchema: {
|
|
2068
|
+
inputSchema: z.object({
|
|
1716
2069
|
project_id: z.string().optional().describe("Project UUID (defaults to the configured project).")
|
|
1717
|
-
}
|
|
2070
|
+
})
|
|
1718
2071
|
},
|
|
1719
2072
|
async (args) => {
|
|
1720
2073
|
const pid = args.project_id ?? projectId;
|
|
@@ -1722,7 +2075,7 @@ function createMushiServer(config) {
|
|
|
1722
2075
|
return jsonResult(await apiCall(`/v1/admin/activation${qs}`));
|
|
1723
2076
|
}
|
|
1724
2077
|
);
|
|
1725
|
-
server.
|
|
2078
|
+
server.registerResource(
|
|
1726
2079
|
"activation_status",
|
|
1727
2080
|
"mushi://activation",
|
|
1728
2081
|
{
|
|
@@ -1741,10 +2094,12 @@ function createMushiServer(config) {
|
|
|
1741
2094
|
};
|
|
1742
2095
|
}
|
|
1743
2096
|
);
|
|
1744
|
-
server.
|
|
2097
|
+
server.registerPrompt(
|
|
1745
2098
|
"summarize_report_for_fix",
|
|
1746
|
-
|
|
1747
|
-
|
|
2099
|
+
{
|
|
2100
|
+
description: "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.",
|
|
2101
|
+
argsSchema: z.object({ reportId: z.string().describe("The report UUID to summarize") })
|
|
2102
|
+
},
|
|
1748
2103
|
({ reportId }) => ({
|
|
1749
2104
|
messages: [
|
|
1750
2105
|
{
|
|
@@ -1769,10 +2124,12 @@ Lead with the fix. Skip the preamble.`
|
|
|
1769
2124
|
]
|
|
1770
2125
|
})
|
|
1771
2126
|
);
|
|
1772
|
-
server.
|
|
2127
|
+
server.registerPrompt(
|
|
1773
2128
|
"explain_judge_result",
|
|
1774
|
-
|
|
1775
|
-
|
|
2129
|
+
{
|
|
2130
|
+
description: "Turn raw Sonnet-as-Judge scores into ship / iterate / dismiss guidance. Use after a fix attempt has been judged.",
|
|
2131
|
+
argsSchema: z.object({ fixId: z.string().describe("The fix_attempt UUID to explain") })
|
|
2132
|
+
},
|
|
1776
2133
|
({ fixId }) => ({
|
|
1777
2134
|
messages: [
|
|
1778
2135
|
{
|
|
@@ -1793,10 +2150,12 @@ No preamble, no score-by-score recap \u2014 the numbers are in the tool output.`
|
|
|
1793
2150
|
]
|
|
1794
2151
|
})
|
|
1795
2152
|
);
|
|
1796
|
-
server.
|
|
2153
|
+
server.registerPrompt(
|
|
1797
2154
|
"triage_next_steps",
|
|
1798
|
-
|
|
1799
|
-
|
|
2155
|
+
{
|
|
2156
|
+
description: 'Answer "what should I focus on right now?" \u2014 five-item markdown list drawn from the dashboard + recent classified queue.',
|
|
2157
|
+
argsSchema: z.object({})
|
|
2158
|
+
},
|
|
1800
2159
|
() => ({
|
|
1801
2160
|
messages: [
|
|
1802
2161
|
{
|
|
@@ -1816,10 +2175,12 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
|
|
|
1816
2175
|
]
|
|
1817
2176
|
})
|
|
1818
2177
|
);
|
|
1819
|
-
server.
|
|
2178
|
+
server.registerPrompt(
|
|
1820
2179
|
"mushi_setup",
|
|
1821
|
-
|
|
1822
|
-
|
|
2180
|
+
{
|
|
2181
|
+
description: "Diagnose why Mushi setup is stuck and return the single next command or console step to unblock it.",
|
|
2182
|
+
argsSchema: z.object({})
|
|
2183
|
+
},
|
|
1823
2184
|
() => ({
|
|
1824
2185
|
messages: [
|
|
1825
2186
|
{
|
|
@@ -1849,13 +2210,13 @@ Be specific. No generic advice.`
|
|
|
1849
2210
|
title: titleOf("map_user_stories", TDD_TOOL_CATALOG),
|
|
1850
2211
|
description: descOf("map_user_stories", TDD_TOOL_CATALOG),
|
|
1851
2212
|
annotations: annotationsFor("map_user_stories", TDD_TOOL_CATALOG),
|
|
1852
|
-
inputSchema: {
|
|
2213
|
+
inputSchema: z.object({
|
|
1853
2214
|
projectId: z.string().describe("Project id to map stories for"),
|
|
1854
2215
|
baseUrl: z.string().url().describe("Live app URL to crawl"),
|
|
1855
2216
|
maxPages: z.number().int().min(1).max(50).optional().describe("Max pages to crawl (default 20)"),
|
|
1856
2217
|
provider: z.enum(["firecrawl", "browserbase"]).optional().describe("Crawl provider (default: firecrawl)"),
|
|
1857
2218
|
cursorCloudRefine: z.boolean().optional().describe("Dispatch Cursor Cloud agent to refine and open a PR")
|
|
1858
|
-
}
|
|
2219
|
+
})
|
|
1859
2220
|
},
|
|
1860
2221
|
async ({ projectId: projectId2, baseUrl, maxPages, provider, cursorCloudRefine }) => {
|
|
1861
2222
|
if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
|
|
@@ -1880,9 +2241,9 @@ Be specific. No generic advice.`
|
|
|
1880
2241
|
title: titleOf("get_map_run_status", TDD_TOOL_CATALOG),
|
|
1881
2242
|
description: descOf("get_map_run_status", TDD_TOOL_CATALOG),
|
|
1882
2243
|
annotations: annotationsFor("get_map_run_status", TDD_TOOL_CATALOG),
|
|
1883
|
-
inputSchema: {
|
|
2244
|
+
inputSchema: z.object({
|
|
1884
2245
|
projectId: z.string().describe("Project id")
|
|
1885
|
-
}
|
|
2246
|
+
})
|
|
1886
2247
|
},
|
|
1887
2248
|
async ({ projectId: projectId2 }) => {
|
|
1888
2249
|
const data = await apiCall(`/v1/admin/inventory/${projectId2}/map-runs`);
|
|
@@ -1895,7 +2256,7 @@ Be specific. No generic advice.`
|
|
|
1895
2256
|
title: titleOf("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
1896
2257
|
description: descOf("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
1897
2258
|
annotations: annotationsFor("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
1898
|
-
inputSchema: {
|
|
2259
|
+
inputSchema: z.object({
|
|
1899
2260
|
projectId: z.string().describe("Project UUID that owns the inventory story"),
|
|
1900
2261
|
storyNodeId: z.string().describe(
|
|
1901
2262
|
"User story node id/slug from accepted inventory (from map_user_stories or inventory.yaml)"
|
|
@@ -1903,13 +2264,13 @@ Be specific. No generic advice.`
|
|
|
1903
2264
|
automationMode: z.enum(["auto", "review", "approve"]).optional().describe("Approval gate for the generated qa_story (default review \u2192 pending_review)"),
|
|
1904
2265
|
baseUrl: z.string().url().optional().describe("Override the live app URL the Playwright test should target"),
|
|
1905
2266
|
openPr: z.boolean().optional().describe("Open a draft GitHub PR with the generated spec (default true)")
|
|
1906
|
-
},
|
|
1907
|
-
outputSchema: {
|
|
2267
|
+
}),
|
|
2268
|
+
outputSchema: z.object({
|
|
1908
2269
|
qaStoryId: z.string().describe("UUID of the inserted qa_stories row"),
|
|
1909
2270
|
prUrl: z.string().nullable().describe("GitHub PR URL when openPr=true and GitHub is connected"),
|
|
1910
2271
|
approvalStatus: z.string().describe("pending_review | approved \u2014 follows automationMode and project defaults"),
|
|
1911
2272
|
needsHumanReview: z.boolean().describe("True when the story requires operator approval before scheduled runs")
|
|
1912
|
-
}
|
|
2273
|
+
})
|
|
1913
2274
|
},
|
|
1914
2275
|
async ({ projectId: projectId2, storyNodeId, automationMode, baseUrl, openPr }) => {
|
|
1915
2276
|
if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
|
|
@@ -1930,9 +2291,9 @@ Be specific. No generic advice.`
|
|
|
1930
2291
|
title: titleOf("improve_qa_story", TDD_TOOL_CATALOG),
|
|
1931
2292
|
description: descOf("improve_qa_story", TDD_TOOL_CATALOG),
|
|
1932
2293
|
annotations: annotationsFor("improve_qa_story", TDD_TOOL_CATALOG),
|
|
1933
|
-
inputSchema: {
|
|
2294
|
+
inputSchema: z.object({
|
|
1934
2295
|
projectId: z.string().optional().describe("Project id (omit to run across all projects)")
|
|
1935
|
-
}
|
|
2296
|
+
})
|
|
1936
2297
|
},
|
|
1937
2298
|
async ({ projectId: projectId2 }) => {
|
|
1938
2299
|
const data = await apiCall("/v1/admin/pdca/improve-qa-stories", {
|
|
@@ -1948,10 +2309,10 @@ Be specific. No generic advice.`
|
|
|
1948
2309
|
title: titleOf("run_qa_story", TDD_TOOL_CATALOG),
|
|
1949
2310
|
description: descOf("run_qa_story", TDD_TOOL_CATALOG),
|
|
1950
2311
|
annotations: annotationsFor("run_qa_story", TDD_TOOL_CATALOG),
|
|
1951
|
-
inputSchema: {
|
|
2312
|
+
inputSchema: z.object({
|
|
1952
2313
|
projectId: z.string().describe("Project id"),
|
|
1953
2314
|
qaStoryId: z.string().describe("qa_story id to run")
|
|
1954
|
-
}
|
|
2315
|
+
})
|
|
1955
2316
|
},
|
|
1956
2317
|
async ({ projectId: projectId2, qaStoryId }) => {
|
|
1957
2318
|
const data = await apiCall(
|
|
@@ -1967,9 +2328,9 @@ Be specific. No generic advice.`
|
|
|
1967
2328
|
title: titleOf("list_byok_keys", TDD_TOOL_CATALOG),
|
|
1968
2329
|
description: descOf("list_byok_keys", TDD_TOOL_CATALOG),
|
|
1969
2330
|
annotations: annotationsFor("list_byok_keys", TDD_TOOL_CATALOG),
|
|
1970
|
-
inputSchema: {
|
|
2331
|
+
inputSchema: z.object({
|
|
1971
2332
|
projectId: z.string().describe("Project id")
|
|
1972
|
-
}
|
|
2333
|
+
})
|
|
1973
2334
|
},
|
|
1974
2335
|
async ({ projectId: projectId2 }) => {
|
|
1975
2336
|
const data = await apiCall(
|
|
@@ -1984,14 +2345,14 @@ Be specific. No generic advice.`
|
|
|
1984
2345
|
title: titleOf("add_byok_key", TDD_TOOL_CATALOG),
|
|
1985
2346
|
description: descOf("add_byok_key", TDD_TOOL_CATALOG),
|
|
1986
2347
|
annotations: annotationsFor("add_byok_key", TDD_TOOL_CATALOG),
|
|
1987
|
-
inputSchema: {
|
|
2348
|
+
inputSchema: z.object({
|
|
1988
2349
|
projectId: z.string().describe("Project id"),
|
|
1989
2350
|
provider: z.enum(["anthropic", "openai", "firecrawl", "browserbase", "cursor"]).describe("Provider slug"),
|
|
1990
2351
|
key: z.string().min(8).max(4096).describe("The API key value to add"),
|
|
1991
2352
|
label: z.string().optional().describe("Human-readable label for this key"),
|
|
1992
2353
|
priority: z.number().int().min(0).max(1e4).optional().describe("Priority for ordering (lower = higher priority)"),
|
|
1993
2354
|
baseUrl: z.string().url().max(2048).optional().describe("Allow-listed OpenAI-compatible HTTPS base URL")
|
|
1994
|
-
}
|
|
2355
|
+
})
|
|
1995
2356
|
},
|
|
1996
2357
|
async ({ projectId: projectId2, provider, key, label, priority, baseUrl }) => {
|
|
1997
2358
|
const data = await apiCall("/v1/admin/byok/keys", {
|
|
@@ -2014,10 +2375,10 @@ Be specific. No generic advice.`
|
|
|
2014
2375
|
title: titleOf("test_byok_key", TDD_TOOL_CATALOG),
|
|
2015
2376
|
description: descOf("test_byok_key", TDD_TOOL_CATALOG),
|
|
2016
2377
|
annotations: annotationsFor("test_byok_key", TDD_TOOL_CATALOG),
|
|
2017
|
-
inputSchema: {
|
|
2378
|
+
inputSchema: z.object({
|
|
2018
2379
|
projectId: z.string().describe("Project id"),
|
|
2019
2380
|
keyId: z.string().uuid().describe("Pooled BYOK key id")
|
|
2020
|
-
}
|
|
2381
|
+
})
|
|
2021
2382
|
},
|
|
2022
2383
|
async ({ keyId }) => {
|
|
2023
2384
|
const data = await apiCall(`/v1/admin/byok/keys/${encodeURIComponent(keyId)}/test`, {
|
|
@@ -2032,10 +2393,10 @@ Be specific. No generic advice.`
|
|
|
2032
2393
|
title: titleOf("remove_byok_key", TDD_TOOL_CATALOG),
|
|
2033
2394
|
description: descOf("remove_byok_key", TDD_TOOL_CATALOG),
|
|
2034
2395
|
annotations: annotationsFor("remove_byok_key", TDD_TOOL_CATALOG),
|
|
2035
|
-
inputSchema: {
|
|
2396
|
+
inputSchema: z.object({
|
|
2036
2397
|
projectId: z.string().describe("Project id"),
|
|
2037
2398
|
keyId: z.string().uuid().describe("Pooled BYOK key UUID from list_byok_keys")
|
|
2038
|
-
}
|
|
2399
|
+
})
|
|
2039
2400
|
},
|
|
2040
2401
|
async ({ keyId }) => {
|
|
2041
2402
|
const data = await apiCall(`/v1/admin/byok/keys/${encodeURIComponent(keyId)}`, {
|
|
@@ -2050,9 +2411,9 @@ Be specific. No generic advice.`
|
|
|
2050
2411
|
title: titleOf("list_pending_review_stories", TDD_TOOL_CATALOG),
|
|
2051
2412
|
description: descOf("list_pending_review_stories", TDD_TOOL_CATALOG),
|
|
2052
2413
|
annotations: annotationsFor("list_pending_review_stories", TDD_TOOL_CATALOG),
|
|
2053
|
-
inputSchema: {
|
|
2414
|
+
inputSchema: z.object({
|
|
2054
2415
|
projectId: z.string().describe("Project id")
|
|
2055
|
-
}
|
|
2416
|
+
})
|
|
2056
2417
|
},
|
|
2057
2418
|
async ({ projectId: projectId2 }) => {
|
|
2058
2419
|
const data = await apiCall(
|
|
@@ -2067,11 +2428,11 @@ Be specific. No generic advice.`
|
|
|
2067
2428
|
title: titleOf("approve_qa_story", TDD_TOOL_CATALOG),
|
|
2068
2429
|
description: descOf("approve_qa_story", TDD_TOOL_CATALOG),
|
|
2069
2430
|
annotations: annotationsFor("approve_qa_story", TDD_TOOL_CATALOG),
|
|
2070
|
-
inputSchema: {
|
|
2431
|
+
inputSchema: z.object({
|
|
2071
2432
|
projectId: z.string().describe("Project id"),
|
|
2072
2433
|
qaStoryId: z.string().describe("QA story id to approve or reject"),
|
|
2073
2434
|
status: z.enum(["approved", "rejected"]).describe("New approval status")
|
|
2074
|
-
}
|
|
2435
|
+
})
|
|
2075
2436
|
},
|
|
2076
2437
|
async ({ projectId: projectId2, qaStoryId, status }) => {
|
|
2077
2438
|
const data = await apiCall(
|
|
@@ -2087,11 +2448,11 @@ Be specific. No generic advice.`
|
|
|
2087
2448
|
title: titleOf("reply_to_reporter", TDD_TOOL_CATALOG),
|
|
2088
2449
|
description: descOf("reply_to_reporter", TDD_TOOL_CATALOG),
|
|
2089
2450
|
annotations: annotationsFor("reply_to_reporter", TDD_TOOL_CATALOG),
|
|
2090
|
-
inputSchema: {
|
|
2451
|
+
inputSchema: z.object({
|
|
2091
2452
|
reportId: z.string().describe("Report id to reply to"),
|
|
2092
2453
|
message: z.string().min(1).max(1e4).describe("Message text to send to the reporter"),
|
|
2093
2454
|
authorName: z.string().optional().describe('Display name for the admin sender (default: "Mushi Admin")')
|
|
2094
|
-
}
|
|
2455
|
+
})
|
|
2095
2456
|
},
|
|
2096
2457
|
async ({ reportId, message, authorName }) => {
|
|
2097
2458
|
const data = await apiCall(`/v1/sync/reports/${reportId}/reply`, {
|
|
@@ -2107,10 +2468,10 @@ Be specific. No generic advice.`
|
|
|
2107
2468
|
title: titleOf("list_qa_story_runs", TDD_TOOL_CATALOG),
|
|
2108
2469
|
description: descOf("list_qa_story_runs", TDD_TOOL_CATALOG),
|
|
2109
2470
|
annotations: annotationsFor("list_qa_story_runs", TDD_TOOL_CATALOG),
|
|
2110
|
-
inputSchema: {
|
|
2471
|
+
inputSchema: z.object({
|
|
2111
2472
|
storyId: z.string().describe("QA story id (uuid)"),
|
|
2112
2473
|
limit: z.number().int().min(1).max(50).optional().default(10).describe("Max runs to return (default 10)")
|
|
2113
|
-
}
|
|
2474
|
+
})
|
|
2114
2475
|
},
|
|
2115
2476
|
async ({ storyId, limit }) => {
|
|
2116
2477
|
const data = await apiCall(
|
|
@@ -2125,10 +2486,10 @@ Be specific. No generic advice.`
|
|
|
2125
2486
|
title: titleOf("get_qa_story_run", TDD_TOOL_CATALOG),
|
|
2126
2487
|
description: descOf("get_qa_story_run", TDD_TOOL_CATALOG),
|
|
2127
2488
|
annotations: annotationsFor("get_qa_story_run", TDD_TOOL_CATALOG),
|
|
2128
|
-
inputSchema: {
|
|
2489
|
+
inputSchema: z.object({
|
|
2129
2490
|
storyId: z.string().describe("QA story id (uuid)"),
|
|
2130
2491
|
runId: z.string().describe("Run id (uuid) to fetch detail for")
|
|
2131
|
-
}
|
|
2492
|
+
})
|
|
2132
2493
|
},
|
|
2133
2494
|
async ({ storyId, runId }) => {
|
|
2134
2495
|
const data = await apiCall(
|
|
@@ -2151,9 +2512,9 @@ Be specific. No generic advice.`
|
|
|
2151
2512
|
title: titleOf("test_notification_channel", TDD_TOOL_CATALOG),
|
|
2152
2513
|
description: descOf("test_notification_channel", TDD_TOOL_CATALOG),
|
|
2153
2514
|
annotations: annotationsFor("test_notification_channel", TDD_TOOL_CATALOG),
|
|
2154
|
-
inputSchema: {
|
|
2515
|
+
inputSchema: z.object({
|
|
2155
2516
|
kind: z.enum(["slack", "discord"]).describe("Notification channel kind to test")
|
|
2156
|
-
}
|
|
2517
|
+
})
|
|
2157
2518
|
},
|
|
2158
2519
|
async ({ kind }) => {
|
|
2159
2520
|
const data = await apiCall(
|
|
@@ -2169,9 +2530,9 @@ Be specific. No generic advice.`
|
|
|
2169
2530
|
title: titleOf("run_fullstack_audit"),
|
|
2170
2531
|
description: descOf("run_fullstack_audit"),
|
|
2171
2532
|
annotations: annotationsFor("run_fullstack_audit"),
|
|
2172
|
-
inputSchema: {
|
|
2533
|
+
inputSchema: z.object({
|
|
2173
2534
|
project_id: z.string().optional().describe("Project ID to audit. Defaults to the configured project.")
|
|
2174
|
-
}
|
|
2535
|
+
})
|
|
2175
2536
|
},
|
|
2176
2537
|
async ({ project_id }) => {
|
|
2177
2538
|
const pid = project_id ?? config.projectId;
|
|
@@ -2189,10 +2550,10 @@ Be specific. No generic advice.`
|
|
|
2189
2550
|
title: titleOf("get_backend_health"),
|
|
2190
2551
|
description: descOf("get_backend_health"),
|
|
2191
2552
|
annotations: annotationsFor("get_backend_health"),
|
|
2192
|
-
inputSchema: {
|
|
2553
|
+
inputSchema: z.object({
|
|
2193
2554
|
project_id: z.string().optional().describe("Project ID. Defaults to the configured project."),
|
|
2194
2555
|
include_logs: z.boolean().optional().describe("Whether to include recent backend error logs (default: true).")
|
|
2195
|
-
}
|
|
2556
|
+
})
|
|
2196
2557
|
},
|
|
2197
2558
|
async ({ project_id, include_logs = true }) => {
|
|
2198
2559
|
const pid = project_id ?? config.projectId;
|
|
@@ -2216,9 +2577,9 @@ Be specific. No generic advice.`
|
|
|
2216
2577
|
title: titleOf("get_usage"),
|
|
2217
2578
|
description: descOf("get_usage"),
|
|
2218
2579
|
annotations: annotationsFor("get_usage"),
|
|
2219
|
-
inputSchema: {
|
|
2580
|
+
inputSchema: z.object({
|
|
2220
2581
|
project_id: z.string().optional().describe("Project ID. Defaults to the configured project.")
|
|
2221
|
-
}
|
|
2582
|
+
})
|
|
2222
2583
|
},
|
|
2223
2584
|
async ({ project_id }) => {
|
|
2224
2585
|
const pid = project_id ?? config.projectId;
|
|
@@ -2233,12 +2594,12 @@ Be specific. No generic advice.`
|
|
|
2233
2594
|
title: titleOf("list_skills"),
|
|
2234
2595
|
description: descOf("list_skills"),
|
|
2235
2596
|
annotations: annotationsFor("list_skills"),
|
|
2236
|
-
inputSchema: {
|
|
2597
|
+
inputSchema: z.object({
|
|
2237
2598
|
category: z.string().optional().describe("Filter by category: workflow, debug, test, audit, enhance, \u2026"),
|
|
2238
2599
|
search: z.string().optional().describe("Free-text search across slug, title, description"),
|
|
2239
2600
|
page: z.number().optional().describe("Page number (default 1)"),
|
|
2240
2601
|
limit: z.number().optional().describe("Max results per page (default 200, max 200)")
|
|
2241
|
-
}
|
|
2602
|
+
})
|
|
2242
2603
|
},
|
|
2243
2604
|
async (args) => {
|
|
2244
2605
|
const qs = new URLSearchParams();
|
|
@@ -2257,9 +2618,9 @@ Be specific. No generic advice.`
|
|
|
2257
2618
|
title: titleOf("get_skill"),
|
|
2258
2619
|
description: descOf("get_skill"),
|
|
2259
2620
|
annotations: annotationsFor("get_skill"),
|
|
2260
|
-
inputSchema: {
|
|
2621
|
+
inputSchema: z.object({
|
|
2261
2622
|
slug: z.string().describe('Skill slug, e.g. "workflow-fix-and-ship"')
|
|
2262
|
-
}
|
|
2623
|
+
})
|
|
2263
2624
|
},
|
|
2264
2625
|
async (args) => {
|
|
2265
2626
|
const data = await apiCall(`/v1/admin/skills/${args.slug}`);
|
|
@@ -2272,14 +2633,14 @@ Be specific. No generic advice.`
|
|
|
2272
2633
|
title: titleOf("start_skill_pipeline"),
|
|
2273
2634
|
description: descOf("start_skill_pipeline"),
|
|
2274
2635
|
annotations: annotationsFor("start_skill_pipeline"),
|
|
2275
|
-
inputSchema: {
|
|
2636
|
+
inputSchema: z.object({
|
|
2276
2637
|
root_skill_slug: z.string().describe('Root skill slug to run, e.g. "workflow-fix-and-ship"'),
|
|
2277
2638
|
report_id: z.string().optional().describe("Report UUID to attach the pipeline to"),
|
|
2278
2639
|
mode: z.enum(["handoff", "cloud"]).optional().describe(
|
|
2279
2640
|
"handoff (default): get context packet for local agent. cloud: auto-dispatch via Cursor Cloud."
|
|
2280
2641
|
),
|
|
2281
2642
|
project_id: z.string().optional().describe("Project UUID. Falls back to the configured project.")
|
|
2282
|
-
}
|
|
2643
|
+
})
|
|
2283
2644
|
},
|
|
2284
2645
|
async (args) => {
|
|
2285
2646
|
const resolvedProjectId = args.project_id ?? projectId;
|
|
@@ -2297,9 +2658,9 @@ Be specific. No generic advice.`
|
|
|
2297
2658
|
title: titleOf("get_pipeline_run"),
|
|
2298
2659
|
description: descOf("get_pipeline_run"),
|
|
2299
2660
|
annotations: annotationsFor("get_pipeline_run"),
|
|
2300
|
-
inputSchema: {
|
|
2661
|
+
inputSchema: z.object({
|
|
2301
2662
|
run_id: z.string().describe("Pipeline run UUID")
|
|
2302
|
-
}
|
|
2663
|
+
})
|
|
2303
2664
|
},
|
|
2304
2665
|
async (args) => {
|
|
2305
2666
|
const data = await apiCall(`/v1/admin/skills/pipelines/${args.run_id}`);
|
|
@@ -2312,14 +2673,14 @@ Be specific. No generic advice.`
|
|
|
2312
2673
|
title: titleOf("checkin_pipeline_step"),
|
|
2313
2674
|
description: descOf("checkin_pipeline_step"),
|
|
2314
2675
|
annotations: annotationsFor("checkin_pipeline_step"),
|
|
2315
|
-
inputSchema: {
|
|
2676
|
+
inputSchema: z.object({
|
|
2316
2677
|
run_id: z.string().describe("Pipeline run UUID"),
|
|
2317
2678
|
step_index: z.number().describe("Step index (0-based)"),
|
|
2318
2679
|
status: z.enum(["running", "passed", "failed", "skipped"]).describe("Step status"),
|
|
2319
2680
|
notes: z.string().optional().describe("Optional notes or output summary"),
|
|
2320
2681
|
pr_url: z.string().optional().describe("PR URL opened during this step"),
|
|
2321
2682
|
agent_ref: z.string().optional().describe("Cursor agentId or external agent reference")
|
|
2322
|
-
}
|
|
2683
|
+
})
|
|
2323
2684
|
},
|
|
2324
2685
|
async (args) => {
|
|
2325
2686
|
const { run_id, step_index, ...body } = args;
|
|
@@ -2336,13 +2697,13 @@ Be specific. No generic advice.`
|
|
|
2336
2697
|
title: titleOf("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2337
2698
|
description: descOf("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2338
2699
|
annotations: annotationsFor("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
2339
|
-
inputSchema: {
|
|
2700
|
+
inputSchema: z.object({
|
|
2340
2701
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2341
2702
|
question: z.string().describe("Plain-English question about the repo"),
|
|
2342
2703
|
thread_id: z.string().optional().describe("Optional thread UUID to continue a conversation"),
|
|
2343
2704
|
file_path: z.string().optional().describe("Optional file path focus"),
|
|
2344
2705
|
symbol_name: z.string().optional().describe("Optional symbol name focus")
|
|
2345
|
-
}
|
|
2706
|
+
})
|
|
2346
2707
|
},
|
|
2347
2708
|
async (args) => {
|
|
2348
2709
|
const pid = args.project_id ?? projectId;
|
|
@@ -2370,12 +2731,12 @@ Be specific. No generic advice.`
|
|
|
2370
2731
|
title: titleOf("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2371
2732
|
description: descOf("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2372
2733
|
annotations: annotationsFor("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
2373
|
-
inputSchema: {
|
|
2734
|
+
inputSchema: z.object({
|
|
2374
2735
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2375
2736
|
file_path: z.string().describe("Indexed file path"),
|
|
2376
2737
|
symbol_name: z.string().optional().describe("Optional symbol name within the file"),
|
|
2377
2738
|
force: z.boolean().optional().describe("Bypass cache and regenerate")
|
|
2378
|
-
}
|
|
2739
|
+
})
|
|
2379
2740
|
},
|
|
2380
2741
|
async (args) => {
|
|
2381
2742
|
const pid = args.project_id ?? projectId;
|
|
@@ -2393,10 +2754,10 @@ Be specific. No generic advice.`
|
|
|
2393
2754
|
title: titleOf("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2394
2755
|
description: descOf("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2395
2756
|
annotations: annotationsFor("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
2396
|
-
inputSchema: {
|
|
2757
|
+
inputSchema: z.object({
|
|
2397
2758
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2398
2759
|
force: z.boolean().optional().describe("Bypass cache and regenerate")
|
|
2399
|
-
}
|
|
2760
|
+
})
|
|
2400
2761
|
},
|
|
2401
2762
|
async (args) => {
|
|
2402
2763
|
const pid = args.project_id ?? projectId;
|
|
@@ -2412,17 +2773,17 @@ Be specific. No generic advice.`
|
|
|
2412
2773
|
title: titleOf("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2413
2774
|
description: descOf("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2414
2775
|
annotations: annotationsFor("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
2415
|
-
inputSchema: {
|
|
2776
|
+
inputSchema: z.object({
|
|
2416
2777
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2417
2778
|
query: z.string().describe("Plain-English search query against indexed source files"),
|
|
2418
2779
|
k: z.number().int().min(1).max(20).optional().describe("Max hits to return (default 8, max 20)"),
|
|
2419
2780
|
scope_prefix: z.string().optional().describe("Optional repo subdirectory prefix to limit search scope")
|
|
2420
|
-
},
|
|
2421
|
-
outputSchema: {
|
|
2781
|
+
}),
|
|
2782
|
+
outputSchema: z.object({
|
|
2422
2783
|
results: z.array(z.unknown()).describe("Ranked file/symbol hits with paths, line ranges, and similarity"),
|
|
2423
2784
|
query: z.string().describe("Normalized query string that was searched"),
|
|
2424
2785
|
mode: z.string().optional().describe("semantic (embeddings) or name (path/symbol match)")
|
|
2425
|
-
}
|
|
2786
|
+
})
|
|
2426
2787
|
},
|
|
2427
2788
|
async (args) => {
|
|
2428
2789
|
const pid = args.project_id ?? projectId;
|
|
@@ -2447,11 +2808,11 @@ Be specific. No generic advice.`
|
|
|
2447
2808
|
title: titleOf("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2448
2809
|
description: descOf("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2449
2810
|
annotations: annotationsFor("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
2450
|
-
inputSchema: {
|
|
2811
|
+
inputSchema: z.object({
|
|
2451
2812
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2452
2813
|
force: z.boolean().optional().describe("Bypass cache and regenerate"),
|
|
2453
2814
|
scope_prefix: z.string().optional().describe("Optional subdirectory scope prefix")
|
|
2454
|
-
}
|
|
2815
|
+
})
|
|
2455
2816
|
},
|
|
2456
2817
|
async (args) => {
|
|
2457
2818
|
const pid = args.project_id ?? projectId;
|
|
@@ -2470,20 +2831,20 @@ Be specific. No generic advice.`
|
|
|
2470
2831
|
title: titleOf("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2471
2832
|
description: descOf("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2472
2833
|
annotations: annotationsFor("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
2473
|
-
inputSchema: {
|
|
2834
|
+
inputSchema: z.object({
|
|
2474
2835
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
2475
2836
|
paths: z.array(z.string()).optional().describe("Changed repo-relative file paths to analyze"),
|
|
2476
2837
|
source: z.enum(["last_push", "compare", "fix"]).optional().describe("Auto-resolve changed paths instead of manual paths"),
|
|
2477
2838
|
compare: z.string().optional().describe("GitHub compare range base...head when source=compare"),
|
|
2478
2839
|
fix_id: z.string().optional().describe("Fix attempt UUID \u2014 uses that PR's changed files when source=fix")
|
|
2479
|
-
},
|
|
2480
|
-
outputSchema: {
|
|
2840
|
+
}),
|
|
2841
|
+
outputSchema: z.object({
|
|
2481
2842
|
changed_paths: z.array(z.string()).describe("Resolved set of changed paths analyzed"),
|
|
2482
2843
|
source: z.string().describe("How changed_paths were resolved (manual, last_push, compare, fix, \u2026)"),
|
|
2483
2844
|
affected_file_paths: z.array(z.string()).describe("Indexed files that import or depend on changed paths"),
|
|
2484
2845
|
affected_node_ids: z.array(z.string()).describe("Codebase graph node ids impacted by the change"),
|
|
2485
2846
|
meta: z.unknown().nullable().optional().describe("Extra resolution metadata when auto-sourced")
|
|
2486
|
-
}
|
|
2847
|
+
})
|
|
2487
2848
|
},
|
|
2488
2849
|
async (args) => {
|
|
2489
2850
|
const pid = args.project_id ?? projectId;
|
|
@@ -2510,9 +2871,9 @@ Be specific. No generic advice.`
|
|
|
2510
2871
|
title: titleOf("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2511
2872
|
description: descOf("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2512
2873
|
annotations: annotationsFor("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
2513
|
-
inputSchema: {
|
|
2874
|
+
inputSchema: z.object({
|
|
2514
2875
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)")
|
|
2515
|
-
}
|
|
2876
|
+
})
|
|
2516
2877
|
},
|
|
2517
2878
|
async (args) => {
|
|
2518
2879
|
const pid = args.project_id ?? projectId;
|
|
@@ -2526,11 +2887,11 @@ Be specific. No generic advice.`
|
|
|
2526
2887
|
{
|
|
2527
2888
|
title: titleOf("use_mushi"),
|
|
2528
2889
|
description: descOf("use_mushi"),
|
|
2529
|
-
inputSchema: {
|
|
2890
|
+
inputSchema: z.object({
|
|
2530
2891
|
intent: z.string().optional().describe(
|
|
2531
2892
|
'What you are trying to accomplish with Mushi, e.g. "fix the top bug", "check project health", "run QA tests", "set up Mushi". Leave blank for a general orientation.'
|
|
2532
2893
|
)
|
|
2533
|
-
},
|
|
2894
|
+
}),
|
|
2534
2895
|
annotations: annotationsFor("use_mushi")
|
|
2535
2896
|
},
|
|
2536
2897
|
async (args) => {
|
|
@@ -2632,5 +2993,6 @@ Be specific. No generic advice.`
|
|
|
2632
2993
|
}
|
|
2633
2994
|
export {
|
|
2634
2995
|
MushiApiError,
|
|
2635
|
-
createMushiServer
|
|
2996
|
+
createMushiServer,
|
|
2997
|
+
resolveTimeoutMsFromEnv
|
|
2636
2998
|
};
|