@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/index.js
CHANGED
|
@@ -1,13 +1,79 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import {
|
|
4
|
+
import { serveStdio } from "@modelcontextprotocol/server/stdio";
|
|
5
5
|
import { createRequire } from "module";
|
|
6
6
|
import { readFileSync } from "fs";
|
|
7
7
|
import { join } from "path";
|
|
8
8
|
import { homedir } from "os";
|
|
9
9
|
import { createLogger as createLogger2, DEFAULT_API_ENDPOINT } from "@mushi-mushi/core";
|
|
10
10
|
|
|
11
|
+
// src/stdout-guard.ts
|
|
12
|
+
import { Console } from "console";
|
|
13
|
+
var GUARDED_CONSOLE_METHODS = [
|
|
14
|
+
"log",
|
|
15
|
+
"info",
|
|
16
|
+
"debug",
|
|
17
|
+
"dir",
|
|
18
|
+
"dirxml",
|
|
19
|
+
"table",
|
|
20
|
+
"group",
|
|
21
|
+
"groupCollapsed",
|
|
22
|
+
"groupEnd",
|
|
23
|
+
"count",
|
|
24
|
+
"countReset",
|
|
25
|
+
"time",
|
|
26
|
+
"timeEnd",
|
|
27
|
+
"timeLog",
|
|
28
|
+
"trace",
|
|
29
|
+
"assert",
|
|
30
|
+
"warn",
|
|
31
|
+
"error"
|
|
32
|
+
];
|
|
33
|
+
var installed = false;
|
|
34
|
+
function installStdoutGuard() {
|
|
35
|
+
if (installed) return false;
|
|
36
|
+
if (typeof process !== "undefined" && process.env?.MUSHI_MCP_STDOUT_GUARD === "off") {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (typeof process === "undefined" || typeof process.stderr?.write !== "function" || typeof console === "undefined") {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const stderrConsole = new Console({
|
|
44
|
+
stdout: process.stderr,
|
|
45
|
+
stderr: process.stderr,
|
|
46
|
+
// Colour codes would land in the client's log pane as escape noise.
|
|
47
|
+
colorMode: false
|
|
48
|
+
});
|
|
49
|
+
const target = console;
|
|
50
|
+
const source = stderrConsole;
|
|
51
|
+
for (const method of GUARDED_CONSOLE_METHODS) {
|
|
52
|
+
const fn = source[method];
|
|
53
|
+
if (typeof fn === "function") {
|
|
54
|
+
target[method] = fn.bind(stderrConsole);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
} catch {
|
|
58
|
+
const write = (...args) => process.stderr.write(
|
|
59
|
+
args.map((a) => typeof a === "string" ? a : safeStringify(a)).join(" ") + "\n"
|
|
60
|
+
);
|
|
61
|
+
const target = console;
|
|
62
|
+
for (const method of GUARDED_CONSOLE_METHODS) {
|
|
63
|
+
target[method] = write;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
installed = true;
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
69
|
+
function safeStringify(value) {
|
|
70
|
+
try {
|
|
71
|
+
return JSON.stringify(value) ?? String(value);
|
|
72
|
+
} catch {
|
|
73
|
+
return String(value);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
11
77
|
// src/catalog.ts
|
|
12
78
|
var ALL_SCOPES = ["mcp:read", "mcp:write"];
|
|
13
79
|
var TOOL_CATALOG = [
|
|
@@ -149,6 +215,14 @@ var TOOL_CATALOG = [
|
|
|
149
215
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
150
216
|
useCase: "Why is Mushi setup broken \u2014 one call, clear next step."
|
|
151
217
|
},
|
|
218
|
+
{
|
|
219
|
+
name: "check_sdk_version",
|
|
220
|
+
title: "Check SDK freshness",
|
|
221
|
+
description: "Compare a published @mushi-mushi/* package version against the catalog (GET /v1/sdk/latest-version). Returns { package, current, latest, outdated } and, when outdated, suggestedActions (Sentry-style, max 1) pointing at search_mushi_docs plus the mushi-sdk-upgrade skill. Read-only. Use when Dependabot or mushi upgrade --check reports a drift, or before dispatching a fix that assumes a current SDK. Does not bump the pin \u2014 that stays a human/Dependabot change.",
|
|
222
|
+
scope: "mcp:read",
|
|
223
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
224
|
+
useCase: "Is this project on the current Mushi SDK?"
|
|
225
|
+
},
|
|
152
226
|
{
|
|
153
227
|
name: "search_mushi_docs",
|
|
154
228
|
title: "Search Mushi documentation",
|
|
@@ -170,7 +244,7 @@ var TOOL_CATALOG = [
|
|
|
170
244
|
{
|
|
171
245
|
name: "dispatch_fix",
|
|
172
246
|
title: "Dispatch Mushi fix agent",
|
|
173
|
-
description: 'Start a Mushi fix agent for a classified report; it writes a branch and opens a signed draft PR. Set agent="cursor_cloud" to dispatch a Cursor Cloud Agent (default uses the in-repo worker). Requires GitHub connected + an LLM key (run diagnose_setup mode=dispatch first). Returns {
|
|
247
|
+
description: 'Start a Mushi fix agent for a classified report; it writes a branch and opens a signed draft PR. Set agent="cursor_cloud" to dispatch a Cursor Cloud Agent (default uses the in-repo worker). Requires GitHub connected + an LLM key (run diagnose_setup mode=dispatch first). Returns { fixId, status } (fixId is the dispatch id; get_fix_timeline accepts it immediately); poll get_fix_timeline for progress and merge_fix when CI is green. Write; pass the same idempotencyKey to retry safely \u2014 without it each call starts a new attempt. Report must be classified \u2014 run triage_issue if not.',
|
|
174
248
|
scope: "mcp:write",
|
|
175
249
|
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
176
250
|
useCase: "Let the in-repo agent attempt this fix for me (or: dispatch a Cursor Cloud Agent)."
|
|
@@ -305,6 +379,14 @@ var TOOL_CATALOG = [
|
|
|
305
379
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
306
380
|
useCase: "Analyze this bug report end-to-end and tell me what to do."
|
|
307
381
|
},
|
|
382
|
+
{
|
|
383
|
+
name: "triage_next_steps",
|
|
384
|
+
title: "What should I work on now?",
|
|
385
|
+
description: 'Prioritised "do this next" list for the project: blocked auto-fixes first (with the unblock action), then in-flight fixes to shepherd to merge, then user-felt classified reports by severity, with robot/cron chores (dependency bumps) last. Returns { steps: [{ priority, action, reason, tool, args }], summary }. Read-only. Call this first when the user asks "what needs my attention / what should I triage or fix".',
|
|
386
|
+
scope: "mcp:read",
|
|
387
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
388
|
+
useCase: "What should I triage or fix right now?"
|
|
389
|
+
},
|
|
308
390
|
// ── Lessons / evolution loop ─────────────────────────────────────────────
|
|
309
391
|
{
|
|
310
392
|
name: "query_lessons",
|
|
@@ -546,24 +628,29 @@ var TDD_TOOL_CATALOG = [
|
|
|
546
628
|
}
|
|
547
629
|
];
|
|
548
630
|
var USE_MUSHI_INTENTS = {
|
|
631
|
+
// NOTE: every name below MUST be a registered TOOL (scopes.test.ts guards
|
|
632
|
+
// this). 2026-08-16 audit P0-3 found phantom names (get_report,
|
|
633
|
+
// get_dashboard, list_qa_stories) and prompts listed as tools
|
|
634
|
+
// (summarize_report_for_fix, mushi_setup) — agents routed here then
|
|
635
|
+
// failed the call and gave up.
|
|
549
636
|
fix: {
|
|
550
637
|
label: "Fix a bug",
|
|
551
638
|
tools: [
|
|
552
639
|
"get_recent_reports",
|
|
553
|
-
"
|
|
554
|
-
"
|
|
640
|
+
"get_report_detail",
|
|
641
|
+
"triage_issue",
|
|
555
642
|
"dispatch_fix",
|
|
556
643
|
"start_skill_pipeline",
|
|
557
644
|
"checkin_pipeline_step",
|
|
558
645
|
"get_pipeline_run"
|
|
559
646
|
],
|
|
560
|
-
hint: "Call get_recent_reports to find the top unresolved bug, then
|
|
647
|
+
hint: "Call get_recent_reports to find the top unresolved bug, then triage_issue before dispatching."
|
|
561
648
|
},
|
|
562
649
|
status: {
|
|
563
650
|
label: "Check project status",
|
|
564
651
|
tools: [
|
|
565
|
-
"get_dashboard",
|
|
566
652
|
"triage_next_steps",
|
|
653
|
+
"get_account_overview",
|
|
567
654
|
"get_usage",
|
|
568
655
|
"get_backend_health",
|
|
569
656
|
"activation_status"
|
|
@@ -573,7 +660,8 @@ var USE_MUSHI_INTENTS = {
|
|
|
573
660
|
setup: {
|
|
574
661
|
label: "Set up Mushi",
|
|
575
662
|
tools: [
|
|
576
|
-
"
|
|
663
|
+
"diagnose_setup",
|
|
664
|
+
"check_sdk_version",
|
|
577
665
|
"activation_status",
|
|
578
666
|
"get_backend_health",
|
|
579
667
|
"list_byok_keys",
|
|
@@ -581,12 +669,11 @@ var USE_MUSHI_INTENTS = {
|
|
|
581
669
|
"test_byok_key",
|
|
582
670
|
"remove_byok_key"
|
|
583
671
|
],
|
|
584
|
-
hint: "Call
|
|
672
|
+
hint: "Call diagnose_setup first \u2014 it diagnoses setup gaps and returns the next action to take."
|
|
585
673
|
},
|
|
586
674
|
qa: {
|
|
587
675
|
label: "Run / review QA tests",
|
|
588
676
|
tools: [
|
|
589
|
-
"list_qa_stories",
|
|
590
677
|
"run_qa_story",
|
|
591
678
|
"list_qa_story_runs",
|
|
592
679
|
"get_qa_story_run",
|
|
@@ -594,7 +681,7 @@ var USE_MUSHI_INTENTS = {
|
|
|
594
681
|
"approve_qa_story",
|
|
595
682
|
"improve_qa_story"
|
|
596
683
|
],
|
|
597
|
-
hint: "Call
|
|
684
|
+
hint: "Call list_qa_story_runs to see recent coverage and outcomes; run_qa_story to trigger a run."
|
|
598
685
|
},
|
|
599
686
|
pipeline: {
|
|
600
687
|
label: "Run an agent pipeline / skill",
|
|
@@ -609,7 +696,7 @@ var USE_MUSHI_INTENTS = {
|
|
|
609
696
|
},
|
|
610
697
|
audit: {
|
|
611
698
|
label: "Audit / health check",
|
|
612
|
-
tools: ["run_fullstack_audit", "get_backend_health", "
|
|
699
|
+
tools: ["run_fullstack_audit", "get_backend_health", "get_account_overview", "get_usage"],
|
|
613
700
|
hint: "Call run_fullstack_audit for a full-stack health scorecard."
|
|
614
701
|
}
|
|
615
702
|
};
|
|
@@ -725,6 +812,7 @@ var TOOL_FEATURE_MAP = {
|
|
|
725
812
|
inventory_current: "admin",
|
|
726
813
|
// setup
|
|
727
814
|
diagnose_setup: "setup",
|
|
815
|
+
check_sdk_version: "setup",
|
|
728
816
|
activation_status: "setup",
|
|
729
817
|
project_integration_health: "setup",
|
|
730
818
|
setup_repo_for_mushi: "setup",
|
|
@@ -834,7 +922,7 @@ function toolMatchesFeatures(toolName, filter) {
|
|
|
834
922
|
}
|
|
835
923
|
|
|
836
924
|
// src/server.ts
|
|
837
|
-
import { McpServer } from "@modelcontextprotocol/
|
|
925
|
+
import { McpServer } from "@modelcontextprotocol/server";
|
|
838
926
|
import { z } from "zod";
|
|
839
927
|
import { createLogger } from "@mushi-mushi/core";
|
|
840
928
|
|
|
@@ -981,7 +1069,40 @@ function wrapUntrustedJson(value, role) {
|
|
|
981
1069
|
}
|
|
982
1070
|
|
|
983
1071
|
// src/server.ts
|
|
984
|
-
|
|
1072
|
+
installStdoutGuard();
|
|
1073
|
+
var NO_ARG_INPUT = z.object({});
|
|
1074
|
+
var DEFAULT_API_TIMEOUT_MS = 15e3;
|
|
1075
|
+
var MIN_API_TIMEOUT_MS = 250;
|
|
1076
|
+
var MAX_API_TIMEOUT_MS = 3e5;
|
|
1077
|
+
function resolveTimeoutMsFromEnv() {
|
|
1078
|
+
const raw = process.env?.MUSHI_MCP_TIMEOUT_MS?.trim();
|
|
1079
|
+
if (!raw) return DEFAULT_API_TIMEOUT_MS;
|
|
1080
|
+
const parsed = Number(raw);
|
|
1081
|
+
if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_API_TIMEOUT_MS;
|
|
1082
|
+
return Math.min(Math.max(parsed, MIN_API_TIMEOUT_MS), MAX_API_TIMEOUT_MS);
|
|
1083
|
+
}
|
|
1084
|
+
function bareMessage(err) {
|
|
1085
|
+
const raw = err instanceof Error ? err.message : String(err);
|
|
1086
|
+
return raw.replace(/^\[[A-Z0-9_]+\]\s*/, "");
|
|
1087
|
+
}
|
|
1088
|
+
var IDEMPOTENCY_NAMESPACE = "mushi.mcp.submit_fix_result.v1";
|
|
1089
|
+
async function stableUuidFrom(...parts) {
|
|
1090
|
+
const input = parts.join("");
|
|
1091
|
+
const digest = new Uint8Array(
|
|
1092
|
+
await crypto.subtle.digest("SHA-256", new TextEncoder().encode(input))
|
|
1093
|
+
);
|
|
1094
|
+
const bytes = digest.slice(0, 16);
|
|
1095
|
+
bytes[6] = bytes[6] & 15 | 80;
|
|
1096
|
+
bytes[8] = bytes[8] & 63 | 128;
|
|
1097
|
+
const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
|
|
1098
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20, 32)}`;
|
|
1099
|
+
}
|
|
1100
|
+
function withTimeoutSignal(callerSignal, timeoutSignal) {
|
|
1101
|
+
if (!callerSignal) return timeoutSignal;
|
|
1102
|
+
const anyFn = AbortSignal.any;
|
|
1103
|
+
if (typeof anyFn === "function") return anyFn.call(AbortSignal, [callerSignal, timeoutSignal]);
|
|
1104
|
+
return timeoutSignal;
|
|
1105
|
+
}
|
|
985
1106
|
var MushiApiError = class extends Error {
|
|
986
1107
|
constructor(status, code, message) {
|
|
987
1108
|
super(`[${code}] ${message}`);
|
|
@@ -995,6 +1116,7 @@ var MushiApiError = class extends Error {
|
|
|
995
1116
|
function createMushiServer(config) {
|
|
996
1117
|
const { version, apiEndpoint, apiKey, projectId } = config;
|
|
997
1118
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
1119
|
+
const timeoutMs = typeof config.timeoutMs === "number" && Number.isFinite(config.timeoutMs) && config.timeoutMs > 0 ? config.timeoutMs : resolveTimeoutMsFromEnv();
|
|
998
1120
|
const apiLog = createLogger({
|
|
999
1121
|
scope: "mushi:mcp:api",
|
|
1000
1122
|
level: process.env.MUSHI_LOG_LEVEL ?? "info",
|
|
@@ -1012,22 +1134,66 @@ function createMushiServer(config) {
|
|
|
1012
1134
|
async function apiCall(path, options) {
|
|
1013
1135
|
const requestId = crypto.randomUUID().slice(0, 12);
|
|
1014
1136
|
const started = Date.now();
|
|
1015
|
-
const
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1137
|
+
const timeoutSignal = AbortSignal.timeout(timeoutMs);
|
|
1138
|
+
let res;
|
|
1139
|
+
try {
|
|
1140
|
+
res = await doFetch(`${apiEndpoint}${path}`, {
|
|
1141
|
+
...options,
|
|
1142
|
+
headers: {
|
|
1143
|
+
"Content-Type": "application/json",
|
|
1144
|
+
// Authorization is accepted by `adminOrApiKey` only as a JWT, but we
|
|
1145
|
+
// still send it for endpoints still behind plain `jwtAuth` (legacy)
|
|
1146
|
+
// and for transparent proxies that strip X-Mushi-* headers.
|
|
1147
|
+
Authorization: `Bearer ${apiKey}`,
|
|
1148
|
+
"X-Mushi-Api-Key": apiKey,
|
|
1149
|
+
"X-Request-Id": requestId,
|
|
1150
|
+
...projectId ? { "X-Mushi-Project-Id": projectId } : {},
|
|
1151
|
+
...options?.headers ?? {}
|
|
1152
|
+
},
|
|
1153
|
+
// After the spread so a caller-supplied `signal` is merged, not lost.
|
|
1154
|
+
signal: withTimeoutSignal(options?.signal, timeoutSignal)
|
|
1155
|
+
});
|
|
1156
|
+
} catch (err) {
|
|
1157
|
+
throw classifyTransportError(err);
|
|
1158
|
+
}
|
|
1159
|
+
let text;
|
|
1160
|
+
try {
|
|
1161
|
+
text = await res.text();
|
|
1162
|
+
} catch (err) {
|
|
1163
|
+
throw classifyTransportError(err);
|
|
1164
|
+
}
|
|
1165
|
+
function classifyTransportError(err) {
|
|
1166
|
+
const durationMs2 = Date.now() - started;
|
|
1167
|
+
if (timeoutSignal.aborted) {
|
|
1168
|
+
apiLog.warn("api.timeout", { path, requestId, durationMs: durationMs2, timeoutMs });
|
|
1169
|
+
return new MushiApiError(
|
|
1170
|
+
504,
|
|
1171
|
+
"MUSHI_TIMEOUT",
|
|
1172
|
+
`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}`
|
|
1173
|
+
);
|
|
1027
1174
|
}
|
|
1028
|
-
|
|
1175
|
+
const name = err instanceof Error ? err.name : "";
|
|
1176
|
+
if (name === "AbortError") {
|
|
1177
|
+
apiLog.warn("api.aborted", { path, requestId, durationMs: durationMs2 });
|
|
1178
|
+
return new MushiApiError(
|
|
1179
|
+
499,
|
|
1180
|
+
"REQUEST_ABORTED",
|
|
1181
|
+
`The request to ${path} was aborted by the caller after ${durationMs2}ms.`
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1184
|
+
apiLog.warn("api.unreachable", {
|
|
1185
|
+
path,
|
|
1186
|
+
requestId,
|
|
1187
|
+
durationMs: durationMs2,
|
|
1188
|
+
err: bareMessage(err)
|
|
1189
|
+
});
|
|
1190
|
+
return new MushiApiError(
|
|
1191
|
+
503,
|
|
1192
|
+
"NETWORK_ERROR",
|
|
1193
|
+
`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.`
|
|
1194
|
+
);
|
|
1195
|
+
}
|
|
1029
1196
|
const durationMs = Date.now() - started;
|
|
1030
|
-
const text = await res.text();
|
|
1031
1197
|
let body = null;
|
|
1032
1198
|
if (text) {
|
|
1033
1199
|
try {
|
|
@@ -1057,6 +1223,7 @@ function createMushiServer(config) {
|
|
|
1057
1223
|
apiLog.info("api.done", { path, requestId, status: res.status, durationMs });
|
|
1058
1224
|
return body;
|
|
1059
1225
|
}
|
|
1226
|
+
let resolvedAccountProjectId = null;
|
|
1060
1227
|
async function resolveProjectId(explicitId) {
|
|
1061
1228
|
if (explicitId) {
|
|
1062
1229
|
if (projectId && explicitId !== projectId) {
|
|
@@ -1069,10 +1236,12 @@ function createMushiServer(config) {
|
|
|
1069
1236
|
return explicitId;
|
|
1070
1237
|
}
|
|
1071
1238
|
if (projectId) return projectId;
|
|
1239
|
+
if (resolvedAccountProjectId) return resolvedAccountProjectId;
|
|
1072
1240
|
try {
|
|
1073
1241
|
const data = await apiCall("/v1/admin/mcp/account-overview");
|
|
1074
1242
|
if (data.total === 1 && data.projects[0]) {
|
|
1075
|
-
|
|
1243
|
+
resolvedAccountProjectId = data.projects[0].id;
|
|
1244
|
+
return resolvedAccountProjectId;
|
|
1076
1245
|
}
|
|
1077
1246
|
if (data.total === 0) {
|
|
1078
1247
|
throw new MushiApiError(
|
|
@@ -1088,11 +1257,27 @@ function createMushiServer(config) {
|
|
|
1088
1257
|
`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.`
|
|
1089
1258
|
);
|
|
1090
1259
|
} catch (err) {
|
|
1091
|
-
if (err instanceof MushiApiError)
|
|
1260
|
+
if (err instanceof MushiApiError) {
|
|
1261
|
+
if (err.code === "MUSHI_TIMEOUT" || err.code === "NETWORK_ERROR") {
|
|
1262
|
+
throw new MushiApiError(
|
|
1263
|
+
err.status,
|
|
1264
|
+
err.code,
|
|
1265
|
+
`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.`
|
|
1266
|
+
);
|
|
1267
|
+
}
|
|
1268
|
+
if (err.status === 401 || err.status === 403) {
|
|
1269
|
+
throw new MushiApiError(
|
|
1270
|
+
err.status,
|
|
1271
|
+
err.code,
|
|
1272
|
+
`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.`
|
|
1273
|
+
);
|
|
1274
|
+
}
|
|
1275
|
+
throw err;
|
|
1276
|
+
}
|
|
1092
1277
|
throw new MushiApiError(
|
|
1093
1278
|
400,
|
|
1094
1279
|
"MISSING_PROJECT_ID",
|
|
1095
|
-
|
|
1280
|
+
`projectId is required. Set MUSHI_PROJECT_ID in .cursor/mcp.json or pass it explicitly. (Project lookup failed with: ${bareMessage(err)})`
|
|
1096
1281
|
);
|
|
1097
1282
|
}
|
|
1098
1283
|
}
|
|
@@ -1159,7 +1344,7 @@ function createMushiServer(config) {
|
|
|
1159
1344
|
title: titleOf("get_recent_reports"),
|
|
1160
1345
|
description: descOf("get_recent_reports"),
|
|
1161
1346
|
annotations: annotationsFor("get_recent_reports"),
|
|
1162
|
-
inputSchema: {
|
|
1347
|
+
inputSchema: z.object({
|
|
1163
1348
|
status: z.string().optional().describe("Filter by status: new, classified, grouped, fixing, fixed, dismissed"),
|
|
1164
1349
|
category: z.string().optional().describe("Filter by category: bug, slow, visual, confusing, other"),
|
|
1165
1350
|
severity: z.string().optional().describe("Filter by severity: critical, high, medium, low"),
|
|
@@ -1167,11 +1352,11 @@ function createMushiServer(config) {
|
|
|
1167
1352
|
project_id: z.string().optional().describe(
|
|
1168
1353
|
"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."
|
|
1169
1354
|
)
|
|
1170
|
-
},
|
|
1171
|
-
outputSchema: {
|
|
1355
|
+
}),
|
|
1356
|
+
outputSchema: z.object({
|
|
1172
1357
|
reports: z.array(z.unknown()),
|
|
1173
1358
|
total: z.number()
|
|
1174
|
-
}
|
|
1359
|
+
})
|
|
1175
1360
|
},
|
|
1176
1361
|
async (args) => {
|
|
1177
1362
|
const params = new URLSearchParams();
|
|
@@ -1196,11 +1381,11 @@ function createMushiServer(config) {
|
|
|
1196
1381
|
title: titleOf("get_report_detail"),
|
|
1197
1382
|
description: descOf("get_report_detail"),
|
|
1198
1383
|
annotations: annotationsFor("get_report_detail"),
|
|
1199
|
-
inputSchema: {
|
|
1384
|
+
inputSchema: z.object({
|
|
1200
1385
|
reportId: z.string().describe("The report UUID"),
|
|
1201
1386
|
project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
|
|
1202
|
-
},
|
|
1203
|
-
outputSchema: { report: z.unknown() }
|
|
1387
|
+
}),
|
|
1388
|
+
outputSchema: z.object({ report: z.unknown() })
|
|
1204
1389
|
},
|
|
1205
1390
|
async (args) => {
|
|
1206
1391
|
const { headers } = await projectScopeHeaders(args.project_id);
|
|
@@ -1223,7 +1408,7 @@ function createMushiServer(config) {
|
|
|
1223
1408
|
title: titleOf("get_report_timeline"),
|
|
1224
1409
|
description: descOf("get_report_timeline"),
|
|
1225
1410
|
annotations: annotationsFor("get_report_timeline"),
|
|
1226
|
-
inputSchema: { reportId: z.string().describe("The report UUID") }
|
|
1411
|
+
inputSchema: z.object({ reportId: z.string().describe("The report UUID") })
|
|
1227
1412
|
},
|
|
1228
1413
|
async (args) => jsonText(await apiCall(`/v1/sync/reports/${args.reportId}/timeline`))
|
|
1229
1414
|
);
|
|
@@ -1243,14 +1428,14 @@ function createMushiServer(config) {
|
|
|
1243
1428
|
title: titleOf("search_reports"),
|
|
1244
1429
|
description: descOf("search_reports"),
|
|
1245
1430
|
annotations: annotationsFor("search_reports"),
|
|
1246
|
-
inputSchema: {
|
|
1431
|
+
inputSchema: z.object({
|
|
1247
1432
|
query: z.string().describe("Natural-language search text or component path"),
|
|
1248
1433
|
limit: z.number().optional().describe("Max results (default 10, max 50)"),
|
|
1249
1434
|
threshold: z.number().optional().describe("Similarity threshold 0..1, default 0.2")
|
|
1250
|
-
},
|
|
1251
|
-
outputSchema: {
|
|
1435
|
+
}),
|
|
1436
|
+
outputSchema: z.object({
|
|
1252
1437
|
results: z.array(z.unknown())
|
|
1253
|
-
}
|
|
1438
|
+
})
|
|
1254
1439
|
},
|
|
1255
1440
|
async (args) => {
|
|
1256
1441
|
const data = await apiCall("/v1/admin/reports/similarity", {
|
|
@@ -1271,13 +1456,13 @@ function createMushiServer(config) {
|
|
|
1271
1456
|
title: titleOf("get_similar_bugs"),
|
|
1272
1457
|
description: descOf("get_similar_bugs"),
|
|
1273
1458
|
annotations: annotationsFor("get_similar_bugs"),
|
|
1274
|
-
inputSchema: {
|
|
1459
|
+
inputSchema: z.object({
|
|
1275
1460
|
query: z.string().describe("Component name, page path, or bug description"),
|
|
1276
1461
|
limit: z.number().optional().describe("Max results (default 5, max 20)")
|
|
1277
|
-
},
|
|
1278
|
-
outputSchema: {
|
|
1462
|
+
}),
|
|
1463
|
+
outputSchema: z.object({
|
|
1279
1464
|
results: z.array(z.unknown())
|
|
1280
|
-
}
|
|
1465
|
+
})
|
|
1281
1466
|
},
|
|
1282
1467
|
async (args) => {
|
|
1283
1468
|
const data = await apiCall("/v1/admin/reports/similarity", {
|
|
@@ -1298,18 +1483,18 @@ function createMushiServer(config) {
|
|
|
1298
1483
|
title: titleOf("get_fix_context"),
|
|
1299
1484
|
description: descOf("get_fix_context"),
|
|
1300
1485
|
annotations: annotationsFor("get_fix_context"),
|
|
1301
|
-
inputSchema: {
|
|
1486
|
+
inputSchema: z.object({
|
|
1302
1487
|
reportId: z.string().describe("The report UUID to fix"),
|
|
1303
1488
|
project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
|
|
1304
|
-
},
|
|
1305
|
-
outputSchema: {
|
|
1489
|
+
}),
|
|
1490
|
+
outputSchema: z.object({
|
|
1306
1491
|
report: z.unknown(),
|
|
1307
1492
|
fixPrompt: z.unknown(),
|
|
1308
1493
|
reproductionSteps: z.unknown(),
|
|
1309
1494
|
component: z.unknown(),
|
|
1310
1495
|
rootCause: z.unknown(),
|
|
1311
1496
|
bugOntologyTags: z.unknown()
|
|
1312
|
-
}
|
|
1497
|
+
})
|
|
1313
1498
|
},
|
|
1314
1499
|
async (args) => {
|
|
1315
1500
|
const { headers } = await projectScopeHeaders(args.project_id);
|
|
@@ -1338,10 +1523,10 @@ function createMushiServer(config) {
|
|
|
1338
1523
|
title: titleOf("get_fix_timeline"),
|
|
1339
1524
|
description: descOf("get_fix_timeline"),
|
|
1340
1525
|
annotations: annotationsFor("get_fix_timeline"),
|
|
1341
|
-
inputSchema: {
|
|
1526
|
+
inputSchema: z.object({
|
|
1342
1527
|
fixId: z.string().describe("fix_attempt UUID"),
|
|
1343
1528
|
project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
|
|
1344
|
-
}
|
|
1529
|
+
})
|
|
1345
1530
|
},
|
|
1346
1531
|
async (args) => {
|
|
1347
1532
|
const { headers } = await projectScopeHeaders(args.project_id);
|
|
@@ -1354,7 +1539,7 @@ function createMushiServer(config) {
|
|
|
1354
1539
|
title: titleOf("get_blast_radius"),
|
|
1355
1540
|
description: descOf("get_blast_radius"),
|
|
1356
1541
|
annotations: annotationsFor("get_blast_radius"),
|
|
1357
|
-
inputSchema: { nodeId: z.string().describe("Graph node UUID") }
|
|
1542
|
+
inputSchema: z.object({ nodeId: z.string().describe("Graph node UUID") })
|
|
1358
1543
|
},
|
|
1359
1544
|
async (args) => jsonText(await apiCall(`/v1/admin/graph/blast-radius/${args.nodeId}`))
|
|
1360
1545
|
);
|
|
@@ -1364,14 +1549,14 @@ function createMushiServer(config) {
|
|
|
1364
1549
|
title: titleOf("get_knowledge_graph"),
|
|
1365
1550
|
description: descOf("get_knowledge_graph"),
|
|
1366
1551
|
annotations: annotationsFor("get_knowledge_graph"),
|
|
1367
|
-
inputSchema: {
|
|
1552
|
+
inputSchema: z.object({
|
|
1368
1553
|
seed: z.string().describe("Starting graph node id or human-readable label to traverse from"),
|
|
1369
1554
|
depth: z.number().optional().describe("BFS hops outward from seed \u2014 clamped to 4 in the handler (default 2)")
|
|
1370
|
-
},
|
|
1371
|
-
outputSchema: {
|
|
1555
|
+
}),
|
|
1556
|
+
outputSchema: z.object({
|
|
1372
1557
|
nodes: z.array(z.unknown()).describe("Graph nodes within the depth budget"),
|
|
1373
1558
|
edges: z.array(z.unknown()).describe("Edges connecting the returned nodes")
|
|
1374
|
-
}
|
|
1559
|
+
})
|
|
1375
1560
|
},
|
|
1376
1561
|
async (args) => {
|
|
1377
1562
|
const params = new URLSearchParams({
|
|
@@ -1390,16 +1575,16 @@ function createMushiServer(config) {
|
|
|
1390
1575
|
title: titleOf("get_graph_neighborhood"),
|
|
1391
1576
|
description: descOf("get_graph_neighborhood"),
|
|
1392
1577
|
annotations: annotationsFor("get_graph_neighborhood"),
|
|
1393
|
-
inputSchema: {
|
|
1578
|
+
inputSchema: z.object({
|
|
1394
1579
|
seed: z.string().describe(
|
|
1395
1580
|
"Starting graph node id or label to expand around (e.g. an inventory Action id or component name)"
|
|
1396
1581
|
),
|
|
1397
1582
|
depth: z.number().int().min(1).max(4).optional().describe("BFS hops to traverse outward. Default 2; clamped to a max of 4.")
|
|
1398
|
-
},
|
|
1399
|
-
outputSchema: {
|
|
1583
|
+
}),
|
|
1584
|
+
outputSchema: z.object({
|
|
1400
1585
|
nodes: z.array(z.unknown()).describe("Graph nodes within the depth budget"),
|
|
1401
1586
|
edges: z.array(z.unknown()).describe("Edges connecting the returned nodes")
|
|
1402
|
-
}
|
|
1587
|
+
})
|
|
1403
1588
|
},
|
|
1404
1589
|
async (args) => {
|
|
1405
1590
|
const params = new URLSearchParams({
|
|
@@ -1418,12 +1603,12 @@ function createMushiServer(config) {
|
|
|
1418
1603
|
title: titleOf("get_graph_node"),
|
|
1419
1604
|
description: descOf("get_graph_node"),
|
|
1420
1605
|
annotations: annotationsFor("get_graph_node"),
|
|
1421
|
-
inputSchema: {
|
|
1606
|
+
inputSchema: z.object({
|
|
1422
1607
|
nodeId: z.string().describe("The graph_nodes.id of the node to fetch (UUID).")
|
|
1423
|
-
},
|
|
1424
|
-
outputSchema: {
|
|
1608
|
+
}),
|
|
1609
|
+
outputSchema: z.object({
|
|
1425
1610
|
node: z.unknown().describe("Single graph_nodes row including metadata (Action nodes carry v2 status)")
|
|
1426
|
-
}
|
|
1611
|
+
})
|
|
1427
1612
|
},
|
|
1428
1613
|
async (args) => {
|
|
1429
1614
|
const data = await apiCall(`/v1/admin/graph/node/${args.nodeId}`);
|
|
@@ -1436,9 +1621,9 @@ function createMushiServer(config) {
|
|
|
1436
1621
|
title: titleOf("get_inventory"),
|
|
1437
1622
|
description: descOf("get_inventory"),
|
|
1438
1623
|
annotations: annotationsFor("get_inventory"),
|
|
1439
|
-
inputSchema: {
|
|
1624
|
+
inputSchema: z.object({
|
|
1440
1625
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted")
|
|
1441
|
-
}
|
|
1626
|
+
})
|
|
1442
1627
|
},
|
|
1443
1628
|
async (args) => {
|
|
1444
1629
|
const pid = args.projectId ?? projectId;
|
|
@@ -1453,11 +1638,11 @@ function createMushiServer(config) {
|
|
|
1453
1638
|
title: titleOf("diff_inventory"),
|
|
1454
1639
|
description: descOf("diff_inventory"),
|
|
1455
1640
|
annotations: annotationsFor("diff_inventory"),
|
|
1456
|
-
inputSchema: {
|
|
1641
|
+
inputSchema: z.object({
|
|
1457
1642
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project"),
|
|
1458
1643
|
fromSha: z.string().describe("Older commit SHA (the baseline to diff from)"),
|
|
1459
1644
|
toSha: z.string().describe("Newer commit SHA (the candidate to diff to)")
|
|
1460
|
-
}
|
|
1645
|
+
})
|
|
1461
1646
|
},
|
|
1462
1647
|
async (args) => {
|
|
1463
1648
|
const pid = args.projectId ?? projectId;
|
|
@@ -1473,15 +1658,15 @@ function createMushiServer(config) {
|
|
|
1473
1658
|
title: titleOf("list_gate_findings"),
|
|
1474
1659
|
description: descOf("list_gate_findings"),
|
|
1475
1660
|
annotations: annotationsFor("list_gate_findings"),
|
|
1476
|
-
inputSchema: {
|
|
1661
|
+
inputSchema: z.object({
|
|
1477
1662
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project"),
|
|
1478
1663
|
gate: z.string().optional().describe("Filter to one gate id: dead-handler | mock-leak | crawl | status-claim"),
|
|
1479
1664
|
severity: z.string().optional().describe("Minimum severity to include: low | medium | high | critical")
|
|
1480
|
-
},
|
|
1481
|
-
outputSchema: {
|
|
1665
|
+
}),
|
|
1666
|
+
outputSchema: z.object({
|
|
1482
1667
|
runs: z.array(z.unknown()).describe("Recent gate_runs rows, newest first"),
|
|
1483
1668
|
findings: z.array(z.unknown()).describe("gate_findings rows for those runs")
|
|
1484
|
-
}
|
|
1669
|
+
})
|
|
1485
1670
|
},
|
|
1486
1671
|
async (args) => {
|
|
1487
1672
|
const pid = args.projectId ?? projectId;
|
|
@@ -1507,21 +1692,35 @@ function createMushiServer(config) {
|
|
|
1507
1692
|
title: titleOf("suggest_fix"),
|
|
1508
1693
|
description: descOf("suggest_fix"),
|
|
1509
1694
|
annotations: annotationsFor("suggest_fix"),
|
|
1510
|
-
inputSchema: {
|
|
1695
|
+
inputSchema: z.object({
|
|
1511
1696
|
reportId: z.string().describe("Report UUID to read the Stage-2 suggested-fix slice for")
|
|
1512
|
-
},
|
|
1513
|
-
outputSchema: {
|
|
1697
|
+
}),
|
|
1698
|
+
outputSchema: z.object({
|
|
1514
1699
|
reportId: z.string().describe("The report this slice was read from"),
|
|
1515
1700
|
rootCause: z.unknown().describe("Stage-2 root-cause hint, or null if not yet classified"),
|
|
1516
1701
|
suggestedFix: z.unknown().describe("Stage-2 suggested fix, or null if not yet classified"),
|
|
1517
1702
|
reproductionSteps: z.unknown().describe("Reproduction steps recorded on the report (array; [] if none)"),
|
|
1518
1703
|
summary: z.unknown().describe("One-line report summary, or null"),
|
|
1519
|
-
component: z.unknown().describe("Component/page the bug was attributed to, or null")
|
|
1520
|
-
|
|
1704
|
+
component: z.unknown().describe("Component/page the bug was attributed to, or null"),
|
|
1705
|
+
note: z.string().optional().describe(
|
|
1706
|
+
"Present when no Stage-2 analysis exists \u2014 explains why and what to call instead"
|
|
1707
|
+
)
|
|
1708
|
+
})
|
|
1521
1709
|
},
|
|
1522
1710
|
async (args) => {
|
|
1523
1711
|
const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
1524
1712
|
const s2 = report.stage2_analysis;
|
|
1713
|
+
if (!s2 || s2.rootCause == null && s2.suggestedFix == null) {
|
|
1714
|
+
return jsonResult({
|
|
1715
|
+
reportId: args.reportId,
|
|
1716
|
+
rootCause: null,
|
|
1717
|
+
suggestedFix: null,
|
|
1718
|
+
reproductionSteps: report.reproduction_steps ?? [],
|
|
1719
|
+
summary: report.summary ?? null,
|
|
1720
|
+
component: report.component ?? null,
|
|
1721
|
+
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."
|
|
1722
|
+
});
|
|
1723
|
+
}
|
|
1525
1724
|
return jsonResult({
|
|
1526
1725
|
reportId: args.reportId,
|
|
1527
1726
|
rootCause: s2?.rootCause ?? null,
|
|
@@ -1538,11 +1737,11 @@ function createMushiServer(config) {
|
|
|
1538
1737
|
title: titleOf("run_nl_query"),
|
|
1539
1738
|
description: descOf("run_nl_query"),
|
|
1540
1739
|
annotations: annotationsFor("run_nl_query"),
|
|
1541
|
-
inputSchema: {
|
|
1740
|
+
inputSchema: z.object({
|
|
1542
1741
|
question: z.string().describe(
|
|
1543
1742
|
'Question in plain English, e.g. "Which components had the most critical bugs this week?"'
|
|
1544
1743
|
)
|
|
1545
|
-
}
|
|
1744
|
+
})
|
|
1546
1745
|
},
|
|
1547
1746
|
async (args) => {
|
|
1548
1747
|
const data = await apiCall("/v1/admin/query", {
|
|
@@ -1558,14 +1757,14 @@ function createMushiServer(config) {
|
|
|
1558
1757
|
title: titleOf("diagnose_setup"),
|
|
1559
1758
|
description: descOf("diagnose_setup"),
|
|
1560
1759
|
annotations: annotationsFor("diagnose_setup"),
|
|
1561
|
-
inputSchema: {
|
|
1760
|
+
inputSchema: z.object({
|
|
1562
1761
|
mode: z.enum(["full", "ingest", "dispatch"]).optional().describe(
|
|
1563
1762
|
"full (default) = ingest + dispatch; ingest = SDK pipeline only; dispatch = fix preflight only."
|
|
1564
1763
|
),
|
|
1565
1764
|
project_id: z.string().optional().describe("Project UUID for dispatch checks (defaults to configured project)."),
|
|
1566
1765
|
projectId: z.string().optional().describe("Alias for project_id.")
|
|
1567
|
-
},
|
|
1568
|
-
outputSchema: {
|
|
1766
|
+
}),
|
|
1767
|
+
outputSchema: z.object({
|
|
1569
1768
|
mode: z.string(),
|
|
1570
1769
|
ready: z.boolean(),
|
|
1571
1770
|
summary: z.string(),
|
|
@@ -1573,7 +1772,7 @@ function createMushiServer(config) {
|
|
|
1573
1772
|
ingest: z.unknown().optional(),
|
|
1574
1773
|
dispatch: z.unknown().optional(),
|
|
1575
1774
|
connection: z.unknown().optional()
|
|
1576
|
-
}
|
|
1775
|
+
})
|
|
1577
1776
|
},
|
|
1578
1777
|
async (args) => {
|
|
1579
1778
|
const mode = args.mode ?? "full";
|
|
@@ -1712,17 +1911,71 @@ function createMushiServer(config) {
|
|
|
1712
1911
|
});
|
|
1713
1912
|
}
|
|
1714
1913
|
);
|
|
1914
|
+
server.registerTool(
|
|
1915
|
+
"check_sdk_version",
|
|
1916
|
+
{
|
|
1917
|
+
title: titleOf("check_sdk_version"),
|
|
1918
|
+
description: descOf("check_sdk_version"),
|
|
1919
|
+
annotations: annotationsFor("check_sdk_version"),
|
|
1920
|
+
inputSchema: z.object({
|
|
1921
|
+
package: z.string().optional().describe(
|
|
1922
|
+
"npm package name (default @mushi-mushi/web). Examples: @mushi-mushi/web, @mushi-mushi/node, @mushi-mushi/core."
|
|
1923
|
+
),
|
|
1924
|
+
current: z.string().optional().describe("Installed version from package.json, if known (e.g. 1.27.0).")
|
|
1925
|
+
}),
|
|
1926
|
+
outputSchema: z.object({
|
|
1927
|
+
package: z.string(),
|
|
1928
|
+
latest: z.string().optional(),
|
|
1929
|
+
current: z.string().optional(),
|
|
1930
|
+
outdated: z.boolean().optional(),
|
|
1931
|
+
suggestedActions: z.array(
|
|
1932
|
+
z.object({
|
|
1933
|
+
type: z.literal("tool_call"),
|
|
1934
|
+
toolName: z.string(),
|
|
1935
|
+
arguments: z.record(z.string(), z.unknown()),
|
|
1936
|
+
reason: z.string()
|
|
1937
|
+
})
|
|
1938
|
+
).optional()
|
|
1939
|
+
})
|
|
1940
|
+
},
|
|
1941
|
+
async (args) => {
|
|
1942
|
+
const pkg = args.package?.trim() || "@mushi-mushi/web";
|
|
1943
|
+
const query = new URLSearchParams({ package: pkg }).toString();
|
|
1944
|
+
const data = await apiCall(
|
|
1945
|
+
`/v1/sdk/latest-version?${query}`
|
|
1946
|
+
);
|
|
1947
|
+
const latest = data.latest ?? data.version;
|
|
1948
|
+
const current = args.current;
|
|
1949
|
+
const outdated = current && latest ? current !== latest : void 0;
|
|
1950
|
+
return jsonResult({
|
|
1951
|
+
package: data.package ?? pkg,
|
|
1952
|
+
latest,
|
|
1953
|
+
current,
|
|
1954
|
+
outdated,
|
|
1955
|
+
...outdated ? {
|
|
1956
|
+
suggestedActions: [
|
|
1957
|
+
{
|
|
1958
|
+
type: "tool_call",
|
|
1959
|
+
toolName: "search_mushi_docs",
|
|
1960
|
+
arguments: { query: `${pkg} upgrade mushi-sdk-upgrade` },
|
|
1961
|
+
reason: "Read current upgrade notes, then apply the mushi-sdk-upgrade skill. This tool does not bump the pin."
|
|
1962
|
+
}
|
|
1963
|
+
]
|
|
1964
|
+
} : {}
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
);
|
|
1715
1968
|
server.registerTool(
|
|
1716
1969
|
"search_mushi_docs",
|
|
1717
1970
|
{
|
|
1718
1971
|
title: titleOf("search_mushi_docs"),
|
|
1719
1972
|
description: descOf("search_mushi_docs"),
|
|
1720
1973
|
annotations: annotationsFor("search_mushi_docs"),
|
|
1721
|
-
inputSchema: {
|
|
1974
|
+
inputSchema: z.object({
|
|
1722
1975
|
query: z.string().describe("Keywords to search official Mushi docs (guides, MCP, inventory, QA)."),
|
|
1723
1976
|
limit: z.number().int().min(1).max(20).optional().describe("Max results (default 8).")
|
|
1724
|
-
},
|
|
1725
|
-
outputSchema: {
|
|
1977
|
+
}),
|
|
1978
|
+
outputSchema: z.object({
|
|
1726
1979
|
query: z.string(),
|
|
1727
1980
|
results: z.array(
|
|
1728
1981
|
z.object({
|
|
@@ -1732,7 +1985,7 @@ function createMushiServer(config) {
|
|
|
1732
1985
|
score: z.number()
|
|
1733
1986
|
})
|
|
1734
1987
|
)
|
|
1735
|
-
}
|
|
1988
|
+
})
|
|
1736
1989
|
},
|
|
1737
1990
|
async (args) => {
|
|
1738
1991
|
const query = args.query ?? "";
|
|
@@ -1753,11 +2006,11 @@ function createMushiServer(config) {
|
|
|
1753
2006
|
description: descOf("list_projects"),
|
|
1754
2007
|
annotations: annotationsFor("list_projects"),
|
|
1755
2008
|
inputSchema: NO_ARG_INPUT,
|
|
1756
|
-
outputSchema: {
|
|
2009
|
+
outputSchema: z.object({
|
|
1757
2010
|
projects: z.array(z.unknown()),
|
|
1758
2011
|
total: z.number().optional(),
|
|
1759
2012
|
_multi_project_hint: z.string().optional()
|
|
1760
|
-
}
|
|
2013
|
+
})
|
|
1761
2014
|
},
|
|
1762
2015
|
async () => {
|
|
1763
2016
|
const data = await apiCall("/v1/admin/mcp/projects");
|
|
@@ -1774,7 +2027,7 @@ function createMushiServer(config) {
|
|
|
1774
2027
|
description: descOf("get_account_overview"),
|
|
1775
2028
|
annotations: annotationsFor("get_account_overview"),
|
|
1776
2029
|
inputSchema: NO_ARG_INPUT,
|
|
1777
|
-
outputSchema: {
|
|
2030
|
+
outputSchema: z.object({
|
|
1778
2031
|
projects: z.array(z.unknown()),
|
|
1779
2032
|
total: z.number(),
|
|
1780
2033
|
active_project_id: z.string().nullable(),
|
|
@@ -1782,7 +2035,7 @@ function createMushiServer(config) {
|
|
|
1782
2035
|
resourceCount: z.number().optional(),
|
|
1783
2036
|
promptCount: z.number().optional(),
|
|
1784
2037
|
multi_project_hint: z.string()
|
|
1785
|
-
}
|
|
2038
|
+
})
|
|
1786
2039
|
},
|
|
1787
2040
|
async () => {
|
|
1788
2041
|
const data = await apiCall("/v1/admin/mcp/account-overview");
|
|
@@ -1804,9 +2057,9 @@ function createMushiServer(config) {
|
|
|
1804
2057
|
title: titleOf("get_project_context"),
|
|
1805
2058
|
description: descOf("get_project_context"),
|
|
1806
2059
|
annotations: annotationsFor("get_project_context"),
|
|
1807
|
-
inputSchema: {
|
|
2060
|
+
inputSchema: z.object({
|
|
1808
2061
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project.")
|
|
1809
|
-
}
|
|
2062
|
+
})
|
|
1810
2063
|
},
|
|
1811
2064
|
async (args) => {
|
|
1812
2065
|
const pid = args.project_id ?? projectId;
|
|
@@ -1828,13 +2081,13 @@ function createMushiServer(config) {
|
|
|
1828
2081
|
title: titleOf("get_pipeline_logs"),
|
|
1829
2082
|
description: descOf("get_pipeline_logs"),
|
|
1830
2083
|
annotations: annotationsFor("get_pipeline_logs"),
|
|
1831
|
-
inputSchema: {
|
|
2084
|
+
inputSchema: z.object({
|
|
1832
2085
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
|
|
1833
2086
|
service: z.enum(["fix-worker", "qa-story-runner", "pipeline", "all"]).optional().describe("Pipeline service to filter (default: all)."),
|
|
1834
2087
|
since: z.string().optional().describe("ISO-8601 timestamp \u2014 return only entries after this time."),
|
|
1835
2088
|
limit: z.number().optional().describe("Max entries to return (default 50, max 200)."),
|
|
1836
2089
|
level: z.enum(["info", "warn", "error", "fatal"]).optional().describe("Minimum severity level (default: warn).")
|
|
1837
|
-
}
|
|
2090
|
+
})
|
|
1838
2091
|
},
|
|
1839
2092
|
async (args) => {
|
|
1840
2093
|
const pid = args.project_id ?? projectId;
|
|
@@ -1854,9 +2107,9 @@ function createMushiServer(config) {
|
|
|
1854
2107
|
title: titleOf("get_report_evidence"),
|
|
1855
2108
|
description: descOf("get_report_evidence"),
|
|
1856
2109
|
annotations: annotationsFor("get_report_evidence"),
|
|
1857
|
-
inputSchema: {
|
|
2110
|
+
inputSchema: z.object({
|
|
1858
2111
|
report_id: z.string().describe("Report UUID.")
|
|
1859
|
-
}
|
|
2112
|
+
})
|
|
1860
2113
|
},
|
|
1861
2114
|
async (args) => {
|
|
1862
2115
|
const [reportRes, timelineRes] = await Promise.allSettled([
|
|
@@ -1904,12 +2157,12 @@ function createMushiServer(config) {
|
|
|
1904
2157
|
title: titleOf("triage_issue"),
|
|
1905
2158
|
description: descOf("triage_issue"),
|
|
1906
2159
|
annotations: annotationsFor("triage_issue"),
|
|
1907
|
-
inputSchema: {
|
|
2160
|
+
inputSchema: z.object({
|
|
1908
2161
|
report_id: z.string().uuid().describe("Report UUID to triage."),
|
|
1909
2162
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
|
|
1910
2163
|
include_logs: z.boolean().optional().describe("Include recent pipeline logs in triage packet (default: true).")
|
|
1911
|
-
},
|
|
1912
|
-
outputSchema: {
|
|
2164
|
+
}),
|
|
2165
|
+
outputSchema: z.object({
|
|
1913
2166
|
report_id: z.string(),
|
|
1914
2167
|
severity: z.unknown(),
|
|
1915
2168
|
category: z.unknown(),
|
|
@@ -1922,7 +2175,7 @@ function createMushiServer(config) {
|
|
|
1922
2175
|
recent_logs: z.unknown().nullable(),
|
|
1923
2176
|
recommended_actions: z.array(z.unknown()),
|
|
1924
2177
|
triage_summary: z.string()
|
|
1925
|
-
}
|
|
2178
|
+
})
|
|
1926
2179
|
},
|
|
1927
2180
|
async (args) => {
|
|
1928
2181
|
const { projectId: pid, headers } = await projectScopeHeaders(args.project_id);
|
|
@@ -1952,12 +2205,31 @@ function createMushiServer(config) {
|
|
|
1952
2205
|
const category = report ? report.category : "unknown";
|
|
1953
2206
|
const status = report ? report.status : "unknown";
|
|
1954
2207
|
const actions = [];
|
|
1955
|
-
|
|
2208
|
+
const processingError = report ? report.processing_error : null;
|
|
2209
|
+
const attempts = report ? report.fix_attempts ?? [] : [];
|
|
2210
|
+
const lastAttempt = attempts.length > 0 ? attempts[attempts.length - 1] : null;
|
|
2211
|
+
const lastAttemptStatus = lastAttempt ? String(lastAttempt.status ?? "") : "";
|
|
2212
|
+
const autofixBlocked = typeof processingError === "string" && processingError.startsWith("autofix_blocked:") || lastAttemptStatus.startsWith("skipped") || lastAttemptStatus === "failed";
|
|
2213
|
+
if (autofixBlocked && status !== "fixing" && status !== "fixed") {
|
|
2214
|
+
const blockReason = typeof processingError === "string" && processingError || String(lastAttempt?.error ?? "previous fix attempt was skipped or failed");
|
|
2215
|
+
actions.push({
|
|
2216
|
+
action: "unblock_autofix",
|
|
2217
|
+
reason: `Auto-fix is blocked: ${blockReason}. Resolve the blocker (settings/integration/budget), then re-dispatch.`,
|
|
2218
|
+
tool: "diagnose_setup",
|
|
2219
|
+
args: { mode: "dispatch" }
|
|
2220
|
+
});
|
|
2221
|
+
actions.push({
|
|
2222
|
+
action: "redispatch_after_unblock",
|
|
2223
|
+
reason: "Once the blocker above is resolved, dispatch a fresh fix attempt.",
|
|
2224
|
+
tool: "dispatch_fix",
|
|
2225
|
+
args: { reportId: args.report_id }
|
|
2226
|
+
});
|
|
2227
|
+
} else if (status === "new" || status === "classified") {
|
|
1956
2228
|
actions.push({
|
|
1957
2229
|
action: "dispatch_fix",
|
|
1958
|
-
reason: "Report is classified
|
|
2230
|
+
reason: "Report is classified and no fix has been attempted",
|
|
1959
2231
|
tool: "dispatch_fix",
|
|
1960
|
-
args: { reportId: args.report_id
|
|
2232
|
+
args: { reportId: args.report_id }
|
|
1961
2233
|
});
|
|
1962
2234
|
} else if (status === "fixing") {
|
|
1963
2235
|
actions.push({
|
|
@@ -2027,7 +2299,7 @@ function createMushiServer(config) {
|
|
|
2027
2299
|
title: titleOf("submit_fix_result"),
|
|
2028
2300
|
description: descOf("submit_fix_result"),
|
|
2029
2301
|
annotations: annotationsFor("submit_fix_result"),
|
|
2030
|
-
inputSchema: {
|
|
2302
|
+
inputSchema: z.object({
|
|
2031
2303
|
reportId: z.string().describe("Report UUID to attach this external fix attempt to"),
|
|
2032
2304
|
branch: z.string().describe("Git branch name where the fix was implemented"),
|
|
2033
2305
|
prUrl: z.string().optional().describe("GitHub pull request URL, if a PR was opened"),
|
|
@@ -2035,43 +2307,136 @@ function createMushiServer(config) {
|
|
|
2035
2307
|
linesChanged: z.number().describe("Total lines added + removed across filesChanged"),
|
|
2036
2308
|
summary: z.string().describe("One-line human summary of what the fix changed"),
|
|
2037
2309
|
idempotencyKey: z.string().uuid().optional().describe(
|
|
2038
|
-
"Optional UUID \u2014 resend the same key to safely retry without creating duplicate fix rows"
|
|
2310
|
+
"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."
|
|
2039
2311
|
)
|
|
2040
|
-
},
|
|
2041
|
-
outputSchema: {
|
|
2312
|
+
}),
|
|
2313
|
+
outputSchema: z.object({
|
|
2042
2314
|
ok: z.boolean().describe("True when the fix_attempt row was created and marked completed"),
|
|
2043
2315
|
fixId: z.string().describe("UUID of the new fix_attempt row")
|
|
2044
|
-
}
|
|
2316
|
+
})
|
|
2045
2317
|
},
|
|
2046
2318
|
async (args) => {
|
|
2047
|
-
const idemKey = args.idempotencyKey ??
|
|
2319
|
+
const idemKey = args.idempotencyKey ?? await stableUuidFrom(IDEMPOTENCY_NAMESPACE, args.reportId, args.branch, args.prUrl ?? "");
|
|
2048
2320
|
const created = await apiCall("/v1/admin/fixes", {
|
|
2049
2321
|
method: "POST",
|
|
2050
2322
|
headers: { "Idempotency-Key": idemKey },
|
|
2051
2323
|
body: JSON.stringify({ reportId: args.reportId, agent: "mcp" })
|
|
2052
2324
|
});
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2325
|
+
try {
|
|
2326
|
+
await apiCall(`/v1/admin/fixes/${created.fixId}`, {
|
|
2327
|
+
method: "PATCH",
|
|
2328
|
+
body: JSON.stringify({
|
|
2329
|
+
status: "completed",
|
|
2330
|
+
branch: args.branch,
|
|
2331
|
+
pr_url: args.prUrl,
|
|
2332
|
+
files_changed: args.filesChanged,
|
|
2333
|
+
lines_changed: args.linesChanged,
|
|
2334
|
+
summary: args.summary,
|
|
2335
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
2336
|
+
})
|
|
2337
|
+
});
|
|
2338
|
+
} catch (err) {
|
|
2339
|
+
const status = err instanceof MushiApiError ? err.status : 500;
|
|
2340
|
+
throw new MushiApiError(
|
|
2341
|
+
status,
|
|
2342
|
+
"FIX_PATCH_FAILED",
|
|
2343
|
+
`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.`
|
|
2344
|
+
);
|
|
2345
|
+
}
|
|
2065
2346
|
return jsonResult({ ok: true, fixId: created.fixId });
|
|
2066
2347
|
}
|
|
2067
2348
|
);
|
|
2349
|
+
server.registerTool(
|
|
2350
|
+
"triage_next_steps",
|
|
2351
|
+
{
|
|
2352
|
+
title: titleOf("triage_next_steps"),
|
|
2353
|
+
description: descOf("triage_next_steps"),
|
|
2354
|
+
annotations: annotationsFor("triage_next_steps"),
|
|
2355
|
+
inputSchema: z.object({
|
|
2356
|
+
project_id: z.string().uuid().optional().describe("Project UUID (defaults to the configured project).")
|
|
2357
|
+
}),
|
|
2358
|
+
outputSchema: z.object({
|
|
2359
|
+
steps: z.array(
|
|
2360
|
+
z.object({
|
|
2361
|
+
priority: z.number(),
|
|
2362
|
+
action: z.string(),
|
|
2363
|
+
reason: z.string(),
|
|
2364
|
+
tool: z.string().optional(),
|
|
2365
|
+
args: z.record(z.string(), z.unknown()).optional()
|
|
2366
|
+
})
|
|
2367
|
+
),
|
|
2368
|
+
summary: z.string()
|
|
2369
|
+
})
|
|
2370
|
+
},
|
|
2371
|
+
async (args) => {
|
|
2372
|
+
const pid = await resolveProjectId(args.project_id);
|
|
2373
|
+
const extraHeaders = pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
|
|
2374
|
+
const data = await apiCall(
|
|
2375
|
+
"/v1/admin/reports?limit=100",
|
|
2376
|
+
{ headers: extraHeaders }
|
|
2377
|
+
);
|
|
2378
|
+
const reports = data.reports ?? [];
|
|
2379
|
+
const isCron = (r) => typeof r.reporter_token_hash === "string" && r.reporter_token_hash.startsWith("cron:");
|
|
2380
|
+
const isBlocked = (r) => typeof r.processing_error === "string" && r.processing_error.startsWith("autofix_blocked:");
|
|
2381
|
+
const label = (r) => String(r.title ?? r.summary ?? r.description ?? "").slice(0, 90);
|
|
2382
|
+
const steps = [];
|
|
2383
|
+
let priority = 1;
|
|
2384
|
+
const blocked = reports.filter(isBlocked);
|
|
2385
|
+
if (blocked.length > 0) {
|
|
2386
|
+
steps.push({
|
|
2387
|
+
priority: priority++,
|
|
2388
|
+
action: `Unblock auto-fix (${blocked.length} report${blocked.length === 1 ? "" : "s"} blocked)`,
|
|
2389
|
+
reason: String(blocked[0].processing_error).slice(0, 200),
|
|
2390
|
+
tool: "diagnose_setup",
|
|
2391
|
+
args: { mode: "dispatch" }
|
|
2392
|
+
});
|
|
2393
|
+
}
|
|
2394
|
+
const fixing = reports.filter((r) => r.status === "fixing");
|
|
2395
|
+
for (const r of fixing.slice(0, 2)) {
|
|
2396
|
+
steps.push({
|
|
2397
|
+
priority: priority++,
|
|
2398
|
+
action: `Shepherd in-flight fix: ${label(r)}`,
|
|
2399
|
+
reason: "A fix branch/PR is open \u2014 check CI and merge when green.",
|
|
2400
|
+
tool: "get_report_timeline",
|
|
2401
|
+
args: { report_id: r.id }
|
|
2402
|
+
});
|
|
2403
|
+
}
|
|
2404
|
+
const openUserReports = reports.filter(
|
|
2405
|
+
(r) => (r.status === "new" || r.status === "classified") && !isCron(r) && !isBlocked(r)
|
|
2406
|
+
).sort((a, b) => {
|
|
2407
|
+
const rank = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
2408
|
+
return (rank[String(a.severity)] ?? 4) - (rank[String(b.severity)] ?? 4);
|
|
2409
|
+
});
|
|
2410
|
+
for (const r of openUserReports.slice(0, Math.max(0, 4 - steps.length))) {
|
|
2411
|
+
steps.push({
|
|
2412
|
+
priority: priority++,
|
|
2413
|
+
action: `Triage [${r.severity}] ${label(r)}`,
|
|
2414
|
+
reason: "User-felt report awaiting triage \u2014 review the packet, then dispatch or dismiss.",
|
|
2415
|
+
tool: "triage_issue",
|
|
2416
|
+
args: { report_id: r.id }
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
const chores = reports.filter((r) => r.status === "classified" && isCron(r) && !isBlocked(r));
|
|
2420
|
+
if (chores.length > 0 && steps.length < 5) {
|
|
2421
|
+
steps.push({
|
|
2422
|
+
priority: priority++,
|
|
2423
|
+
action: `Batch ${chores.length} maintenance chore${chores.length === 1 ? "" : "s"} (dependency bumps)`,
|
|
2424
|
+
reason: "Robot-filed modernization reports \u2014 batch-dispatch or dismiss in one sitting; do not let them crowd out user bugs.",
|
|
2425
|
+
tool: "get_recent_reports",
|
|
2426
|
+
args: { status: "classified" }
|
|
2427
|
+
});
|
|
2428
|
+
}
|
|
2429
|
+
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 ");
|
|
2430
|
+
return jsonResult({ steps, summary });
|
|
2431
|
+
}
|
|
2432
|
+
);
|
|
2068
2433
|
server.registerTool(
|
|
2069
2434
|
"dispatch_fix",
|
|
2070
2435
|
{
|
|
2071
2436
|
title: titleOf("dispatch_fix"),
|
|
2072
2437
|
description: descOf("dispatch_fix"),
|
|
2073
2438
|
annotations: annotationsFor("dispatch_fix"),
|
|
2074
|
-
inputSchema: {
|
|
2439
|
+
inputSchema: z.object({
|
|
2075
2440
|
reportId: z.string().uuid().describe("Report UUID to fix"),
|
|
2076
2441
|
agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
|
|
2077
2442
|
idempotencyKey: z.string().uuid().optional().describe(
|
|
@@ -2080,19 +2445,19 @@ function createMushiServer(config) {
|
|
|
2080
2445
|
inventoryActionNodeId: z.string().uuid().optional().describe(
|
|
2081
2446
|
"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."
|
|
2082
2447
|
)
|
|
2083
|
-
},
|
|
2084
|
-
outputSchema: {
|
|
2448
|
+
}),
|
|
2449
|
+
outputSchema: z.object({
|
|
2085
2450
|
fixId: z.string(),
|
|
2086
2451
|
status: z.string()
|
|
2087
|
-
}
|
|
2452
|
+
})
|
|
2088
2453
|
},
|
|
2089
|
-
async (args,
|
|
2090
|
-
if (
|
|
2454
|
+
async (args, ctx) => {
|
|
2455
|
+
if (ctx?.mcpReq.notify && ctx?.mcpReq._meta?.progressToken) {
|
|
2091
2456
|
try {
|
|
2092
|
-
await
|
|
2457
|
+
await ctx.mcpReq.notify({
|
|
2093
2458
|
method: "notifications/progress",
|
|
2094
2459
|
params: {
|
|
2095
|
-
progressToken:
|
|
2460
|
+
progressToken: ctx.mcpReq._meta.progressToken,
|
|
2096
2461
|
progress: 0,
|
|
2097
2462
|
total: 100,
|
|
2098
2463
|
message: "Dispatching Mushi fix orchestrator\u2026"
|
|
@@ -2101,7 +2466,7 @@ function createMushiServer(config) {
|
|
|
2101
2466
|
} catch {
|
|
2102
2467
|
}
|
|
2103
2468
|
}
|
|
2104
|
-
const
|
|
2469
|
+
const dispatch = await apiCall("/v1/admin/fixes/dispatch", {
|
|
2105
2470
|
method: "POST",
|
|
2106
2471
|
headers: args.idempotencyKey ? { "Idempotency-Key": args.idempotencyKey } : void 0,
|
|
2107
2472
|
body: JSON.stringify({
|
|
@@ -2111,7 +2476,12 @@ function createMushiServer(config) {
|
|
|
2111
2476
|
...projectId ? { projectId } : {}
|
|
2112
2477
|
})
|
|
2113
2478
|
});
|
|
2114
|
-
|
|
2479
|
+
if (!dispatch?.dispatchId) {
|
|
2480
|
+
throw new Error(
|
|
2481
|
+
`Dispatch response missing dispatchId \u2014 got: ${JSON.stringify(dispatch).slice(0, 300)}`
|
|
2482
|
+
);
|
|
2483
|
+
}
|
|
2484
|
+
return jsonResult({ fixId: dispatch.dispatchId, status: dispatch.status ?? "queued" });
|
|
2115
2485
|
}
|
|
2116
2486
|
);
|
|
2117
2487
|
server.registerTool(
|
|
@@ -2120,13 +2490,13 @@ function createMushiServer(config) {
|
|
|
2120
2490
|
title: titleOf("trigger_judge"),
|
|
2121
2491
|
description: descOf("trigger_judge"),
|
|
2122
2492
|
annotations: annotationsFor("trigger_judge"),
|
|
2123
|
-
inputSchema: {
|
|
2493
|
+
inputSchema: z.object({
|
|
2124
2494
|
limit: z.number().optional().describe("Max reports to judge in this batch (default 25, max 100)"),
|
|
2125
2495
|
projectId: z.string().optional().describe("Restrict to one project when the API key owns multiple")
|
|
2126
|
-
},
|
|
2127
|
-
outputSchema: {
|
|
2496
|
+
}),
|
|
2497
|
+
outputSchema: z.object({
|
|
2128
2498
|
dispatched: z.number().describe("Number of judge-batch jobs dispatched (one per accessible project)")
|
|
2129
|
-
}
|
|
2499
|
+
})
|
|
2130
2500
|
},
|
|
2131
2501
|
async (args) => {
|
|
2132
2502
|
const data = await apiCall("/v1/admin/judge/run", {
|
|
@@ -2145,10 +2515,10 @@ function createMushiServer(config) {
|
|
|
2145
2515
|
title: titleOf("test_gen_from_report"),
|
|
2146
2516
|
description: descOf("test_gen_from_report"),
|
|
2147
2517
|
annotations: annotationsFor("test_gen_from_report"),
|
|
2148
|
-
inputSchema: {
|
|
2518
|
+
inputSchema: z.object({
|
|
2149
2519
|
reportId: z.string().describe("Report UUID to turn into a Playwright PR"),
|
|
2150
2520
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project")
|
|
2151
|
-
}
|
|
2521
|
+
})
|
|
2152
2522
|
},
|
|
2153
2523
|
async (args) => {
|
|
2154
2524
|
const pid = args.projectId ?? projectId;
|
|
@@ -2174,16 +2544,16 @@ function createMushiServer(config) {
|
|
|
2174
2544
|
title: titleOf("merge_fix"),
|
|
2175
2545
|
description: descOf("merge_fix"),
|
|
2176
2546
|
annotations: annotationsFor("merge_fix"),
|
|
2177
|
-
inputSchema: {
|
|
2547
|
+
inputSchema: z.object({
|
|
2178
2548
|
fixId: z.string().describe("Fix attempt UUID whose GitHub PR should be squash-merged"),
|
|
2179
2549
|
mergeMethod: z.enum(["squash", "merge", "rebase"]).optional().describe("GitHub merge method (default squash)")
|
|
2180
|
-
},
|
|
2181
|
-
outputSchema: {
|
|
2550
|
+
}),
|
|
2551
|
+
outputSchema: z.object({
|
|
2182
2552
|
merged: z.boolean().optional().describe("True when GitHub accepted the merge in this call"),
|
|
2183
2553
|
alreadyMerged: z.boolean().optional().describe("True when the PR was already merged (idempotent no-op)"),
|
|
2184
2554
|
reportId: z.string().describe("Report UUID linked to this fix attempt"),
|
|
2185
2555
|
reportStatus: z.string().describe("Report workflow status after merge bookkeeping")
|
|
2186
|
-
}
|
|
2556
|
+
})
|
|
2187
2557
|
},
|
|
2188
2558
|
async (args) => {
|
|
2189
2559
|
const data = await apiCall(`/v1/admin/fixes/${args.fixId}/merge`, {
|
|
@@ -2199,14 +2569,14 @@ function createMushiServer(config) {
|
|
|
2199
2569
|
title: titleOf("refresh_ci"),
|
|
2200
2570
|
description: descOf("refresh_ci"),
|
|
2201
2571
|
annotations: annotationsFor("refresh_ci"),
|
|
2202
|
-
inputSchema: {
|
|
2572
|
+
inputSchema: z.object({
|
|
2203
2573
|
fixId: z.string().describe("Fix attempt UUID whose PR check-runs should be re-polled")
|
|
2204
|
-
},
|
|
2205
|
-
outputSchema: {
|
|
2574
|
+
}),
|
|
2575
|
+
outputSchema: z.object({
|
|
2206
2576
|
check_run_status: z.string().nullable().describe("GitHub check run status after refresh"),
|
|
2207
2577
|
check_run_conclusion: z.string().nullable().describe("success | failure | neutral | null while pending"),
|
|
2208
2578
|
check_run_updated_at: z.string().nullable().describe("ISO timestamp when CI status was last persisted")
|
|
2209
|
-
}
|
|
2579
|
+
})
|
|
2210
2580
|
},
|
|
2211
2581
|
async (args) => {
|
|
2212
2582
|
const data = await apiCall(`/v1/admin/fixes/${args.fixId}/refresh-ci`, { method: "POST" });
|
|
@@ -2219,13 +2589,13 @@ function createMushiServer(config) {
|
|
|
2219
2589
|
title: titleOf("reopen_report"),
|
|
2220
2590
|
description: descOf("reopen_report"),
|
|
2221
2591
|
annotations: annotationsFor("reopen_report"),
|
|
2222
|
-
inputSchema: {
|
|
2592
|
+
inputSchema: z.object({
|
|
2223
2593
|
reportId: z.string().describe("Report UUID to move back to reopened status"),
|
|
2224
2594
|
note: z.string().optional().describe("Operator note recorded on the reopen transition")
|
|
2225
|
-
},
|
|
2226
|
-
outputSchema: {
|
|
2595
|
+
}),
|
|
2596
|
+
outputSchema: z.object({
|
|
2227
2597
|
report: z.unknown().describe("Updated report row with status=reopened")
|
|
2228
|
-
}
|
|
2598
|
+
})
|
|
2229
2599
|
},
|
|
2230
2600
|
async (args) => {
|
|
2231
2601
|
const report = await apiCall(`/v1/sync/reports/${args.reportId}`, {
|
|
@@ -2241,11 +2611,16 @@ function createMushiServer(config) {
|
|
|
2241
2611
|
title: titleOf("transition_status"),
|
|
2242
2612
|
description: descOf("transition_status"),
|
|
2243
2613
|
annotations: annotationsFor("transition_status"),
|
|
2244
|
-
inputSchema: {
|
|
2614
|
+
inputSchema: z.object({
|
|
2245
2615
|
reportId: z.string().describe("Report UUID"),
|
|
2246
2616
|
status: z.enum([
|
|
2247
2617
|
"pending",
|
|
2248
2618
|
"classified",
|
|
2619
|
+
// Workflow states added 20260520600000 but missing from this
|
|
2620
|
+
// enum ever since (2026-08-16 audit P2-2) — agents could not
|
|
2621
|
+
// mark a report as being looked at.
|
|
2622
|
+
"triaged",
|
|
2623
|
+
"in_progress",
|
|
2249
2624
|
"grouped",
|
|
2250
2625
|
"fixing",
|
|
2251
2626
|
"fixed",
|
|
@@ -2253,9 +2628,9 @@ function createMushiServer(config) {
|
|
|
2253
2628
|
"verified",
|
|
2254
2629
|
"reopened",
|
|
2255
2630
|
"dismissed"
|
|
2256
|
-
]).describe("Target status"),
|
|
2631
|
+
]).describe("Target status (resolved is stored as fixed)"),
|
|
2257
2632
|
reason: z.string().optional().describe("Reason for the transition (audit trail)")
|
|
2258
|
-
}
|
|
2633
|
+
})
|
|
2259
2634
|
},
|
|
2260
2635
|
async (args) => {
|
|
2261
2636
|
const data = await apiCall(`/v1/admin/reports/${args.reportId}`, {
|
|
@@ -2271,9 +2646,9 @@ function createMushiServer(config) {
|
|
|
2271
2646
|
title: titleOf("setup_repo_for_mushi"),
|
|
2272
2647
|
description: descOf("setup_repo_for_mushi"),
|
|
2273
2648
|
annotations: annotationsFor("setup_repo_for_mushi"),
|
|
2274
|
-
inputSchema: {
|
|
2649
|
+
inputSchema: z.object({
|
|
2275
2650
|
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project")
|
|
2276
|
-
}
|
|
2651
|
+
})
|
|
2277
2652
|
},
|
|
2278
2653
|
async (args) => {
|
|
2279
2654
|
const pid = args.projectId ?? projectId;
|
|
@@ -2296,12 +2671,12 @@ function createMushiServer(config) {
|
|
|
2296
2671
|
title: titleOf("query_lessons"),
|
|
2297
2672
|
description: descOf("query_lessons"),
|
|
2298
2673
|
annotations: annotationsFor("query_lessons"),
|
|
2299
|
-
inputSchema: {
|
|
2674
|
+
inputSchema: z.object({
|
|
2300
2675
|
diff_text: z.string().describe("The PR diff, code snippet, or description of the change being made."),
|
|
2301
2676
|
max_tokens: z.number().optional().describe("Maximum tokens for returned lessons context (default 3000, max 8000)."),
|
|
2302
2677
|
top_k: z.number().optional().describe("Max number of lessons to return (default 15, max 50)."),
|
|
2303
2678
|
project_id: z.string().optional().describe("Project UUID. Defaults to configured project.")
|
|
2304
|
-
}
|
|
2679
|
+
})
|
|
2305
2680
|
},
|
|
2306
2681
|
async (args) => {
|
|
2307
2682
|
const pid = args.project_id ?? projectId;
|
|
@@ -2323,14 +2698,14 @@ function createMushiServer(config) {
|
|
|
2323
2698
|
title: titleOf("list_lessons"),
|
|
2324
2699
|
description: descOf("list_lessons"),
|
|
2325
2700
|
annotations: annotationsFor("list_lessons"),
|
|
2326
|
-
inputSchema: {
|
|
2701
|
+
inputSchema: z.object({
|
|
2327
2702
|
severity: z.enum(["info", "warn", "critical"]).optional().describe("Filter to one severity level"),
|
|
2328
2703
|
limit: z.number().optional().describe("Max lessons to return (default 50, max 200)"),
|
|
2329
2704
|
project_id: z.string().optional().describe("Project UUID \u2014 defaults to configured project")
|
|
2330
|
-
},
|
|
2331
|
-
outputSchema: {
|
|
2705
|
+
}),
|
|
2706
|
+
outputSchema: z.object({
|
|
2332
2707
|
lessons: z.array(z.unknown()).describe("Promoted lesson rows ordered by frequency")
|
|
2333
|
-
}
|
|
2708
|
+
})
|
|
2334
2709
|
},
|
|
2335
2710
|
async (args) => {
|
|
2336
2711
|
const pid = args.project_id ?? projectId;
|
|
@@ -2342,7 +2717,7 @@ function createMushiServer(config) {
|
|
|
2342
2717
|
return wrappedJsonResult({ lessons }, "lesson text");
|
|
2343
2718
|
}
|
|
2344
2719
|
);
|
|
2345
|
-
server.
|
|
2720
|
+
server.registerResource(
|
|
2346
2721
|
"project_stats",
|
|
2347
2722
|
"project://stats",
|
|
2348
2723
|
{ description: "Report counts, category breakdown, severity distribution" },
|
|
@@ -2356,7 +2731,7 @@ function createMushiServer(config) {
|
|
|
2356
2731
|
]
|
|
2357
2732
|
})
|
|
2358
2733
|
);
|
|
2359
|
-
server.
|
|
2734
|
+
server.registerResource(
|
|
2360
2735
|
"project_settings",
|
|
2361
2736
|
"project://settings",
|
|
2362
2737
|
{
|
|
@@ -2372,7 +2747,7 @@ function createMushiServer(config) {
|
|
|
2372
2747
|
]
|
|
2373
2748
|
})
|
|
2374
2749
|
);
|
|
2375
|
-
server.
|
|
2750
|
+
server.registerResource(
|
|
2376
2751
|
"project_dashboard",
|
|
2377
2752
|
"project://dashboard",
|
|
2378
2753
|
{
|
|
@@ -2394,10 +2769,10 @@ function createMushiServer(config) {
|
|
|
2394
2769
|
title: titleOf("list_top_contributors"),
|
|
2395
2770
|
description: descOf("list_top_contributors"),
|
|
2396
2771
|
annotations: annotationsFor("list_top_contributors"),
|
|
2397
|
-
inputSchema: {
|
|
2772
|
+
inputSchema: z.object({
|
|
2398
2773
|
limit: z.number().int().min(1).max(100).optional().default(10).describe("Max rows to return (default 10, max 100)"),
|
|
2399
2774
|
range: z.enum(["30d", "90d", "all"]).optional().default("30d").describe("Time window for points calculation")
|
|
2400
|
-
}
|
|
2775
|
+
})
|
|
2401
2776
|
},
|
|
2402
2777
|
async ({ limit, range }) => ({
|
|
2403
2778
|
content: [
|
|
@@ -2418,11 +2793,11 @@ function createMushiServer(config) {
|
|
|
2418
2793
|
title: titleOf("award_bonus_points"),
|
|
2419
2794
|
description: descOf("award_bonus_points"),
|
|
2420
2795
|
annotations: annotationsFor("award_bonus_points"),
|
|
2421
|
-
inputSchema: {
|
|
2796
|
+
inputSchema: z.object({
|
|
2422
2797
|
external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
|
|
2423
2798
|
points: z.number().int().min(1).max(5e4).describe("Bonus points to award (max 50,000 per call)"),
|
|
2424
2799
|
reason: z.string().max(200).describe("Human-readable reason, logged to end_user_activity")
|
|
2425
|
-
}
|
|
2800
|
+
})
|
|
2426
2801
|
},
|
|
2427
2802
|
async ({ external_user_id, points, reason }) => ({
|
|
2428
2803
|
content: [
|
|
@@ -2447,11 +2822,11 @@ function createMushiServer(config) {
|
|
|
2447
2822
|
title: titleOf("set_tier"),
|
|
2448
2823
|
description: descOf("set_tier"),
|
|
2449
2824
|
annotations: annotationsFor("set_tier"),
|
|
2450
|
-
inputSchema: {
|
|
2825
|
+
inputSchema: z.object({
|
|
2451
2826
|
external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
|
|
2452
2827
|
tier_slug: z.string().describe('Tier slug to assign, e.g. "champion", "contributor", "explorer"'),
|
|
2453
2828
|
reason: z.string().max(200).optional().describe("Optional reason for manual override")
|
|
2454
|
-
}
|
|
2829
|
+
})
|
|
2455
2830
|
},
|
|
2456
2831
|
async ({ external_user_id, tier_slug, reason }) => ({
|
|
2457
2832
|
content: [
|
|
@@ -2470,7 +2845,7 @@ function createMushiServer(config) {
|
|
|
2470
2845
|
]
|
|
2471
2846
|
})
|
|
2472
2847
|
);
|
|
2473
|
-
server.
|
|
2848
|
+
server.registerResource(
|
|
2474
2849
|
"privacy_status",
|
|
2475
2850
|
"privacy://status",
|
|
2476
2851
|
{
|
|
@@ -2486,7 +2861,7 @@ function createMushiServer(config) {
|
|
|
2486
2861
|
]
|
|
2487
2862
|
})
|
|
2488
2863
|
);
|
|
2489
|
-
server.
|
|
2864
|
+
server.registerResource(
|
|
2490
2865
|
"evolution_history",
|
|
2491
2866
|
"evolution://history",
|
|
2492
2867
|
{
|
|
@@ -2508,7 +2883,7 @@ function createMushiServer(config) {
|
|
|
2508
2883
|
]
|
|
2509
2884
|
})
|
|
2510
2885
|
);
|
|
2511
|
-
server.
|
|
2886
|
+
server.registerResource(
|
|
2512
2887
|
"project_integration_health",
|
|
2513
2888
|
"project://integration-health",
|
|
2514
2889
|
{
|
|
@@ -2524,7 +2899,7 @@ function createMushiServer(config) {
|
|
|
2524
2899
|
]
|
|
2525
2900
|
})
|
|
2526
2901
|
);
|
|
2527
|
-
server.
|
|
2902
|
+
server.registerResource(
|
|
2528
2903
|
"inventory_current",
|
|
2529
2904
|
"inventory://current",
|
|
2530
2905
|
{
|
|
@@ -2549,9 +2924,9 @@ function createMushiServer(config) {
|
|
|
2549
2924
|
title: titleOf("activation_status"),
|
|
2550
2925
|
description: descOf("activation_status"),
|
|
2551
2926
|
annotations: annotationsFor("activation_status"),
|
|
2552
|
-
inputSchema: {
|
|
2927
|
+
inputSchema: z.object({
|
|
2553
2928
|
project_id: z.string().optional().describe("Project UUID (defaults to the configured project).")
|
|
2554
|
-
}
|
|
2929
|
+
})
|
|
2555
2930
|
},
|
|
2556
2931
|
async (args) => {
|
|
2557
2932
|
const pid = args.project_id ?? projectId;
|
|
@@ -2559,7 +2934,7 @@ function createMushiServer(config) {
|
|
|
2559
2934
|
return jsonResult(await apiCall(`/v1/admin/activation${qs}`));
|
|
2560
2935
|
}
|
|
2561
2936
|
);
|
|
2562
|
-
server.
|
|
2937
|
+
server.registerResource(
|
|
2563
2938
|
"activation_status",
|
|
2564
2939
|
"mushi://activation",
|
|
2565
2940
|
{
|
|
@@ -2578,10 +2953,12 @@ function createMushiServer(config) {
|
|
|
2578
2953
|
};
|
|
2579
2954
|
}
|
|
2580
2955
|
);
|
|
2581
|
-
server.
|
|
2956
|
+
server.registerPrompt(
|
|
2582
2957
|
"summarize_report_for_fix",
|
|
2583
|
-
|
|
2584
|
-
|
|
2958
|
+
{
|
|
2959
|
+
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.",
|
|
2960
|
+
argsSchema: z.object({ reportId: z.string().describe("The report UUID to summarize") })
|
|
2961
|
+
},
|
|
2585
2962
|
({ reportId }) => ({
|
|
2586
2963
|
messages: [
|
|
2587
2964
|
{
|
|
@@ -2606,10 +2983,12 @@ Lead with the fix. Skip the preamble.`
|
|
|
2606
2983
|
]
|
|
2607
2984
|
})
|
|
2608
2985
|
);
|
|
2609
|
-
server.
|
|
2986
|
+
server.registerPrompt(
|
|
2610
2987
|
"explain_judge_result",
|
|
2611
|
-
|
|
2612
|
-
|
|
2988
|
+
{
|
|
2989
|
+
description: "Turn raw Sonnet-as-Judge scores into ship / iterate / dismiss guidance. Use after a fix attempt has been judged.",
|
|
2990
|
+
argsSchema: z.object({ fixId: z.string().describe("The fix_attempt UUID to explain") })
|
|
2991
|
+
},
|
|
2613
2992
|
({ fixId }) => ({
|
|
2614
2993
|
messages: [
|
|
2615
2994
|
{
|
|
@@ -2630,10 +3009,12 @@ No preamble, no score-by-score recap \u2014 the numbers are in the tool output.`
|
|
|
2630
3009
|
]
|
|
2631
3010
|
})
|
|
2632
3011
|
);
|
|
2633
|
-
server.
|
|
3012
|
+
server.registerPrompt(
|
|
2634
3013
|
"triage_next_steps",
|
|
2635
|
-
|
|
2636
|
-
|
|
3014
|
+
{
|
|
3015
|
+
description: 'Answer "what should I focus on right now?" \u2014 five-item markdown list drawn from the dashboard + recent classified queue.',
|
|
3016
|
+
argsSchema: z.object({})
|
|
3017
|
+
},
|
|
2637
3018
|
() => ({
|
|
2638
3019
|
messages: [
|
|
2639
3020
|
{
|
|
@@ -2653,10 +3034,12 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
|
|
|
2653
3034
|
]
|
|
2654
3035
|
})
|
|
2655
3036
|
);
|
|
2656
|
-
server.
|
|
3037
|
+
server.registerPrompt(
|
|
2657
3038
|
"mushi_setup",
|
|
2658
|
-
|
|
2659
|
-
|
|
3039
|
+
{
|
|
3040
|
+
description: "Diagnose why Mushi setup is stuck and return the single next command or console step to unblock it.",
|
|
3041
|
+
argsSchema: z.object({})
|
|
3042
|
+
},
|
|
2660
3043
|
() => ({
|
|
2661
3044
|
messages: [
|
|
2662
3045
|
{
|
|
@@ -2686,13 +3069,13 @@ Be specific. No generic advice.`
|
|
|
2686
3069
|
title: titleOf("map_user_stories", TDD_TOOL_CATALOG),
|
|
2687
3070
|
description: descOf("map_user_stories", TDD_TOOL_CATALOG),
|
|
2688
3071
|
annotations: annotationsFor("map_user_stories", TDD_TOOL_CATALOG),
|
|
2689
|
-
inputSchema: {
|
|
3072
|
+
inputSchema: z.object({
|
|
2690
3073
|
projectId: z.string().describe("Project id to map stories for"),
|
|
2691
3074
|
baseUrl: z.string().url().describe("Live app URL to crawl"),
|
|
2692
3075
|
maxPages: z.number().int().min(1).max(50).optional().describe("Max pages to crawl (default 20)"),
|
|
2693
3076
|
provider: z.enum(["firecrawl", "browserbase"]).optional().describe("Crawl provider (default: firecrawl)"),
|
|
2694
3077
|
cursorCloudRefine: z.boolean().optional().describe("Dispatch Cursor Cloud agent to refine and open a PR")
|
|
2695
|
-
}
|
|
3078
|
+
})
|
|
2696
3079
|
},
|
|
2697
3080
|
async ({ projectId: projectId2, baseUrl, maxPages, provider, cursorCloudRefine }) => {
|
|
2698
3081
|
if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
|
|
@@ -2717,9 +3100,9 @@ Be specific. No generic advice.`
|
|
|
2717
3100
|
title: titleOf("get_map_run_status", TDD_TOOL_CATALOG),
|
|
2718
3101
|
description: descOf("get_map_run_status", TDD_TOOL_CATALOG),
|
|
2719
3102
|
annotations: annotationsFor("get_map_run_status", TDD_TOOL_CATALOG),
|
|
2720
|
-
inputSchema: {
|
|
3103
|
+
inputSchema: z.object({
|
|
2721
3104
|
projectId: z.string().describe("Project id")
|
|
2722
|
-
}
|
|
3105
|
+
})
|
|
2723
3106
|
},
|
|
2724
3107
|
async ({ projectId: projectId2 }) => {
|
|
2725
3108
|
const data = await apiCall(`/v1/admin/inventory/${projectId2}/map-runs`);
|
|
@@ -2732,7 +3115,7 @@ Be specific. No generic advice.`
|
|
|
2732
3115
|
title: titleOf("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
2733
3116
|
description: descOf("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
2734
3117
|
annotations: annotationsFor("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
2735
|
-
inputSchema: {
|
|
3118
|
+
inputSchema: z.object({
|
|
2736
3119
|
projectId: z.string().describe("Project UUID that owns the inventory story"),
|
|
2737
3120
|
storyNodeId: z.string().describe(
|
|
2738
3121
|
"User story node id/slug from accepted inventory (from map_user_stories or inventory.yaml)"
|
|
@@ -2740,13 +3123,13 @@ Be specific. No generic advice.`
|
|
|
2740
3123
|
automationMode: z.enum(["auto", "review", "approve"]).optional().describe("Approval gate for the generated qa_story (default review \u2192 pending_review)"),
|
|
2741
3124
|
baseUrl: z.string().url().optional().describe("Override the live app URL the Playwright test should target"),
|
|
2742
3125
|
openPr: z.boolean().optional().describe("Open a draft GitHub PR with the generated spec (default true)")
|
|
2743
|
-
},
|
|
2744
|
-
outputSchema: {
|
|
3126
|
+
}),
|
|
3127
|
+
outputSchema: z.object({
|
|
2745
3128
|
qaStoryId: z.string().describe("UUID of the inserted qa_stories row"),
|
|
2746
3129
|
prUrl: z.string().nullable().describe("GitHub PR URL when openPr=true and GitHub is connected"),
|
|
2747
3130
|
approvalStatus: z.string().describe("pending_review | approved \u2014 follows automationMode and project defaults"),
|
|
2748
3131
|
needsHumanReview: z.boolean().describe("True when the story requires operator approval before scheduled runs")
|
|
2749
|
-
}
|
|
3132
|
+
})
|
|
2750
3133
|
},
|
|
2751
3134
|
async ({ projectId: projectId2, storyNodeId, automationMode, baseUrl, openPr }) => {
|
|
2752
3135
|
if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
|
|
@@ -2767,9 +3150,9 @@ Be specific. No generic advice.`
|
|
|
2767
3150
|
title: titleOf("improve_qa_story", TDD_TOOL_CATALOG),
|
|
2768
3151
|
description: descOf("improve_qa_story", TDD_TOOL_CATALOG),
|
|
2769
3152
|
annotations: annotationsFor("improve_qa_story", TDD_TOOL_CATALOG),
|
|
2770
|
-
inputSchema: {
|
|
3153
|
+
inputSchema: z.object({
|
|
2771
3154
|
projectId: z.string().optional().describe("Project id (omit to run across all projects)")
|
|
2772
|
-
}
|
|
3155
|
+
})
|
|
2773
3156
|
},
|
|
2774
3157
|
async ({ projectId: projectId2 }) => {
|
|
2775
3158
|
const data = await apiCall("/v1/admin/pdca/improve-qa-stories", {
|
|
@@ -2785,10 +3168,10 @@ Be specific. No generic advice.`
|
|
|
2785
3168
|
title: titleOf("run_qa_story", TDD_TOOL_CATALOG),
|
|
2786
3169
|
description: descOf("run_qa_story", TDD_TOOL_CATALOG),
|
|
2787
3170
|
annotations: annotationsFor("run_qa_story", TDD_TOOL_CATALOG),
|
|
2788
|
-
inputSchema: {
|
|
3171
|
+
inputSchema: z.object({
|
|
2789
3172
|
projectId: z.string().describe("Project id"),
|
|
2790
3173
|
qaStoryId: z.string().describe("qa_story id to run")
|
|
2791
|
-
}
|
|
3174
|
+
})
|
|
2792
3175
|
},
|
|
2793
3176
|
async ({ projectId: projectId2, qaStoryId }) => {
|
|
2794
3177
|
const data = await apiCall(
|
|
@@ -2804,9 +3187,9 @@ Be specific. No generic advice.`
|
|
|
2804
3187
|
title: titleOf("list_byok_keys", TDD_TOOL_CATALOG),
|
|
2805
3188
|
description: descOf("list_byok_keys", TDD_TOOL_CATALOG),
|
|
2806
3189
|
annotations: annotationsFor("list_byok_keys", TDD_TOOL_CATALOG),
|
|
2807
|
-
inputSchema: {
|
|
3190
|
+
inputSchema: z.object({
|
|
2808
3191
|
projectId: z.string().describe("Project id")
|
|
2809
|
-
}
|
|
3192
|
+
})
|
|
2810
3193
|
},
|
|
2811
3194
|
async ({ projectId: projectId2 }) => {
|
|
2812
3195
|
const data = await apiCall(
|
|
@@ -2821,14 +3204,14 @@ Be specific. No generic advice.`
|
|
|
2821
3204
|
title: titleOf("add_byok_key", TDD_TOOL_CATALOG),
|
|
2822
3205
|
description: descOf("add_byok_key", TDD_TOOL_CATALOG),
|
|
2823
3206
|
annotations: annotationsFor("add_byok_key", TDD_TOOL_CATALOG),
|
|
2824
|
-
inputSchema: {
|
|
3207
|
+
inputSchema: z.object({
|
|
2825
3208
|
projectId: z.string().describe("Project id"),
|
|
2826
3209
|
provider: z.enum(["anthropic", "openai", "firecrawl", "browserbase", "cursor"]).describe("Provider slug"),
|
|
2827
3210
|
key: z.string().min(8).max(4096).describe("The API key value to add"),
|
|
2828
3211
|
label: z.string().optional().describe("Human-readable label for this key"),
|
|
2829
3212
|
priority: z.number().int().min(0).max(1e4).optional().describe("Priority for ordering (lower = higher priority)"),
|
|
2830
3213
|
baseUrl: z.string().url().max(2048).optional().describe("Allow-listed OpenAI-compatible HTTPS base URL")
|
|
2831
|
-
}
|
|
3214
|
+
})
|
|
2832
3215
|
},
|
|
2833
3216
|
async ({ projectId: projectId2, provider, key, label, priority, baseUrl }) => {
|
|
2834
3217
|
const data = await apiCall("/v1/admin/byok/keys", {
|
|
@@ -2851,10 +3234,10 @@ Be specific. No generic advice.`
|
|
|
2851
3234
|
title: titleOf("test_byok_key", TDD_TOOL_CATALOG),
|
|
2852
3235
|
description: descOf("test_byok_key", TDD_TOOL_CATALOG),
|
|
2853
3236
|
annotations: annotationsFor("test_byok_key", TDD_TOOL_CATALOG),
|
|
2854
|
-
inputSchema: {
|
|
3237
|
+
inputSchema: z.object({
|
|
2855
3238
|
projectId: z.string().describe("Project id"),
|
|
2856
3239
|
keyId: z.string().uuid().describe("Pooled BYOK key id")
|
|
2857
|
-
}
|
|
3240
|
+
})
|
|
2858
3241
|
},
|
|
2859
3242
|
async ({ keyId }) => {
|
|
2860
3243
|
const data = await apiCall(`/v1/admin/byok/keys/${encodeURIComponent(keyId)}/test`, {
|
|
@@ -2869,10 +3252,10 @@ Be specific. No generic advice.`
|
|
|
2869
3252
|
title: titleOf("remove_byok_key", TDD_TOOL_CATALOG),
|
|
2870
3253
|
description: descOf("remove_byok_key", TDD_TOOL_CATALOG),
|
|
2871
3254
|
annotations: annotationsFor("remove_byok_key", TDD_TOOL_CATALOG),
|
|
2872
|
-
inputSchema: {
|
|
3255
|
+
inputSchema: z.object({
|
|
2873
3256
|
projectId: z.string().describe("Project id"),
|
|
2874
3257
|
keyId: z.string().uuid().describe("Pooled BYOK key UUID from list_byok_keys")
|
|
2875
|
-
}
|
|
3258
|
+
})
|
|
2876
3259
|
},
|
|
2877
3260
|
async ({ keyId }) => {
|
|
2878
3261
|
const data = await apiCall(`/v1/admin/byok/keys/${encodeURIComponent(keyId)}`, {
|
|
@@ -2887,9 +3270,9 @@ Be specific. No generic advice.`
|
|
|
2887
3270
|
title: titleOf("list_pending_review_stories", TDD_TOOL_CATALOG),
|
|
2888
3271
|
description: descOf("list_pending_review_stories", TDD_TOOL_CATALOG),
|
|
2889
3272
|
annotations: annotationsFor("list_pending_review_stories", TDD_TOOL_CATALOG),
|
|
2890
|
-
inputSchema: {
|
|
3273
|
+
inputSchema: z.object({
|
|
2891
3274
|
projectId: z.string().describe("Project id")
|
|
2892
|
-
}
|
|
3275
|
+
})
|
|
2893
3276
|
},
|
|
2894
3277
|
async ({ projectId: projectId2 }) => {
|
|
2895
3278
|
const data = await apiCall(
|
|
@@ -2904,11 +3287,11 @@ Be specific. No generic advice.`
|
|
|
2904
3287
|
title: titleOf("approve_qa_story", TDD_TOOL_CATALOG),
|
|
2905
3288
|
description: descOf("approve_qa_story", TDD_TOOL_CATALOG),
|
|
2906
3289
|
annotations: annotationsFor("approve_qa_story", TDD_TOOL_CATALOG),
|
|
2907
|
-
inputSchema: {
|
|
3290
|
+
inputSchema: z.object({
|
|
2908
3291
|
projectId: z.string().describe("Project id"),
|
|
2909
3292
|
qaStoryId: z.string().describe("QA story id to approve or reject"),
|
|
2910
3293
|
status: z.enum(["approved", "rejected"]).describe("New approval status")
|
|
2911
|
-
}
|
|
3294
|
+
})
|
|
2912
3295
|
},
|
|
2913
3296
|
async ({ projectId: projectId2, qaStoryId, status }) => {
|
|
2914
3297
|
const data = await apiCall(
|
|
@@ -2924,11 +3307,11 @@ Be specific. No generic advice.`
|
|
|
2924
3307
|
title: titleOf("reply_to_reporter", TDD_TOOL_CATALOG),
|
|
2925
3308
|
description: descOf("reply_to_reporter", TDD_TOOL_CATALOG),
|
|
2926
3309
|
annotations: annotationsFor("reply_to_reporter", TDD_TOOL_CATALOG),
|
|
2927
|
-
inputSchema: {
|
|
3310
|
+
inputSchema: z.object({
|
|
2928
3311
|
reportId: z.string().describe("Report id to reply to"),
|
|
2929
3312
|
message: z.string().min(1).max(1e4).describe("Message text to send to the reporter"),
|
|
2930
3313
|
authorName: z.string().optional().describe('Display name for the admin sender (default: "Mushi Admin")')
|
|
2931
|
-
}
|
|
3314
|
+
})
|
|
2932
3315
|
},
|
|
2933
3316
|
async ({ reportId, message, authorName }) => {
|
|
2934
3317
|
const data = await apiCall(`/v1/sync/reports/${reportId}/reply`, {
|
|
@@ -2944,10 +3327,10 @@ Be specific. No generic advice.`
|
|
|
2944
3327
|
title: titleOf("list_qa_story_runs", TDD_TOOL_CATALOG),
|
|
2945
3328
|
description: descOf("list_qa_story_runs", TDD_TOOL_CATALOG),
|
|
2946
3329
|
annotations: annotationsFor("list_qa_story_runs", TDD_TOOL_CATALOG),
|
|
2947
|
-
inputSchema: {
|
|
3330
|
+
inputSchema: z.object({
|
|
2948
3331
|
storyId: z.string().describe("QA story id (uuid)"),
|
|
2949
3332
|
limit: z.number().int().min(1).max(50).optional().default(10).describe("Max runs to return (default 10)")
|
|
2950
|
-
}
|
|
3333
|
+
})
|
|
2951
3334
|
},
|
|
2952
3335
|
async ({ storyId, limit }) => {
|
|
2953
3336
|
const data = await apiCall(
|
|
@@ -2962,10 +3345,10 @@ Be specific. No generic advice.`
|
|
|
2962
3345
|
title: titleOf("get_qa_story_run", TDD_TOOL_CATALOG),
|
|
2963
3346
|
description: descOf("get_qa_story_run", TDD_TOOL_CATALOG),
|
|
2964
3347
|
annotations: annotationsFor("get_qa_story_run", TDD_TOOL_CATALOG),
|
|
2965
|
-
inputSchema: {
|
|
3348
|
+
inputSchema: z.object({
|
|
2966
3349
|
storyId: z.string().describe("QA story id (uuid)"),
|
|
2967
3350
|
runId: z.string().describe("Run id (uuid) to fetch detail for")
|
|
2968
|
-
}
|
|
3351
|
+
})
|
|
2969
3352
|
},
|
|
2970
3353
|
async ({ storyId, runId }) => {
|
|
2971
3354
|
const data = await apiCall(
|
|
@@ -2988,9 +3371,9 @@ Be specific. No generic advice.`
|
|
|
2988
3371
|
title: titleOf("test_notification_channel", TDD_TOOL_CATALOG),
|
|
2989
3372
|
description: descOf("test_notification_channel", TDD_TOOL_CATALOG),
|
|
2990
3373
|
annotations: annotationsFor("test_notification_channel", TDD_TOOL_CATALOG),
|
|
2991
|
-
inputSchema: {
|
|
3374
|
+
inputSchema: z.object({
|
|
2992
3375
|
kind: z.enum(["slack", "discord"]).describe("Notification channel kind to test")
|
|
2993
|
-
}
|
|
3376
|
+
})
|
|
2994
3377
|
},
|
|
2995
3378
|
async ({ kind }) => {
|
|
2996
3379
|
const data = await apiCall(
|
|
@@ -3006,9 +3389,9 @@ Be specific. No generic advice.`
|
|
|
3006
3389
|
title: titleOf("run_fullstack_audit"),
|
|
3007
3390
|
description: descOf("run_fullstack_audit"),
|
|
3008
3391
|
annotations: annotationsFor("run_fullstack_audit"),
|
|
3009
|
-
inputSchema: {
|
|
3392
|
+
inputSchema: z.object({
|
|
3010
3393
|
project_id: z.string().optional().describe("Project ID to audit. Defaults to the configured project.")
|
|
3011
|
-
}
|
|
3394
|
+
})
|
|
3012
3395
|
},
|
|
3013
3396
|
async ({ project_id }) => {
|
|
3014
3397
|
const pid = project_id ?? config.projectId;
|
|
@@ -3026,10 +3409,10 @@ Be specific. No generic advice.`
|
|
|
3026
3409
|
title: titleOf("get_backend_health"),
|
|
3027
3410
|
description: descOf("get_backend_health"),
|
|
3028
3411
|
annotations: annotationsFor("get_backend_health"),
|
|
3029
|
-
inputSchema: {
|
|
3412
|
+
inputSchema: z.object({
|
|
3030
3413
|
project_id: z.string().optional().describe("Project ID. Defaults to the configured project."),
|
|
3031
3414
|
include_logs: z.boolean().optional().describe("Whether to include recent backend error logs (default: true).")
|
|
3032
|
-
}
|
|
3415
|
+
})
|
|
3033
3416
|
},
|
|
3034
3417
|
async ({ project_id, include_logs = true }) => {
|
|
3035
3418
|
const pid = project_id ?? config.projectId;
|
|
@@ -3053,9 +3436,9 @@ Be specific. No generic advice.`
|
|
|
3053
3436
|
title: titleOf("get_usage"),
|
|
3054
3437
|
description: descOf("get_usage"),
|
|
3055
3438
|
annotations: annotationsFor("get_usage"),
|
|
3056
|
-
inputSchema: {
|
|
3439
|
+
inputSchema: z.object({
|
|
3057
3440
|
project_id: z.string().optional().describe("Project ID. Defaults to the configured project.")
|
|
3058
|
-
}
|
|
3441
|
+
})
|
|
3059
3442
|
},
|
|
3060
3443
|
async ({ project_id }) => {
|
|
3061
3444
|
const pid = project_id ?? config.projectId;
|
|
@@ -3070,12 +3453,12 @@ Be specific. No generic advice.`
|
|
|
3070
3453
|
title: titleOf("list_skills"),
|
|
3071
3454
|
description: descOf("list_skills"),
|
|
3072
3455
|
annotations: annotationsFor("list_skills"),
|
|
3073
|
-
inputSchema: {
|
|
3456
|
+
inputSchema: z.object({
|
|
3074
3457
|
category: z.string().optional().describe("Filter by category: workflow, debug, test, audit, enhance, \u2026"),
|
|
3075
3458
|
search: z.string().optional().describe("Free-text search across slug, title, description"),
|
|
3076
3459
|
page: z.number().optional().describe("Page number (default 1)"),
|
|
3077
3460
|
limit: z.number().optional().describe("Max results per page (default 200, max 200)")
|
|
3078
|
-
}
|
|
3461
|
+
})
|
|
3079
3462
|
},
|
|
3080
3463
|
async (args) => {
|
|
3081
3464
|
const qs = new URLSearchParams();
|
|
@@ -3094,9 +3477,9 @@ Be specific. No generic advice.`
|
|
|
3094
3477
|
title: titleOf("get_skill"),
|
|
3095
3478
|
description: descOf("get_skill"),
|
|
3096
3479
|
annotations: annotationsFor("get_skill"),
|
|
3097
|
-
inputSchema: {
|
|
3480
|
+
inputSchema: z.object({
|
|
3098
3481
|
slug: z.string().describe('Skill slug, e.g. "workflow-fix-and-ship"')
|
|
3099
|
-
}
|
|
3482
|
+
})
|
|
3100
3483
|
},
|
|
3101
3484
|
async (args) => {
|
|
3102
3485
|
const data = await apiCall(`/v1/admin/skills/${args.slug}`);
|
|
@@ -3109,14 +3492,14 @@ Be specific. No generic advice.`
|
|
|
3109
3492
|
title: titleOf("start_skill_pipeline"),
|
|
3110
3493
|
description: descOf("start_skill_pipeline"),
|
|
3111
3494
|
annotations: annotationsFor("start_skill_pipeline"),
|
|
3112
|
-
inputSchema: {
|
|
3495
|
+
inputSchema: z.object({
|
|
3113
3496
|
root_skill_slug: z.string().describe('Root skill slug to run, e.g. "workflow-fix-and-ship"'),
|
|
3114
3497
|
report_id: z.string().optional().describe("Report UUID to attach the pipeline to"),
|
|
3115
3498
|
mode: z.enum(["handoff", "cloud"]).optional().describe(
|
|
3116
3499
|
"handoff (default): get context packet for local agent. cloud: auto-dispatch via Cursor Cloud."
|
|
3117
3500
|
),
|
|
3118
3501
|
project_id: z.string().optional().describe("Project UUID. Falls back to the configured project.")
|
|
3119
|
-
}
|
|
3502
|
+
})
|
|
3120
3503
|
},
|
|
3121
3504
|
async (args) => {
|
|
3122
3505
|
const resolvedProjectId = args.project_id ?? projectId;
|
|
@@ -3134,9 +3517,9 @@ Be specific. No generic advice.`
|
|
|
3134
3517
|
title: titleOf("get_pipeline_run"),
|
|
3135
3518
|
description: descOf("get_pipeline_run"),
|
|
3136
3519
|
annotations: annotationsFor("get_pipeline_run"),
|
|
3137
|
-
inputSchema: {
|
|
3520
|
+
inputSchema: z.object({
|
|
3138
3521
|
run_id: z.string().describe("Pipeline run UUID")
|
|
3139
|
-
}
|
|
3522
|
+
})
|
|
3140
3523
|
},
|
|
3141
3524
|
async (args) => {
|
|
3142
3525
|
const data = await apiCall(`/v1/admin/skills/pipelines/${args.run_id}`);
|
|
@@ -3149,14 +3532,14 @@ Be specific. No generic advice.`
|
|
|
3149
3532
|
title: titleOf("checkin_pipeline_step"),
|
|
3150
3533
|
description: descOf("checkin_pipeline_step"),
|
|
3151
3534
|
annotations: annotationsFor("checkin_pipeline_step"),
|
|
3152
|
-
inputSchema: {
|
|
3535
|
+
inputSchema: z.object({
|
|
3153
3536
|
run_id: z.string().describe("Pipeline run UUID"),
|
|
3154
3537
|
step_index: z.number().describe("Step index (0-based)"),
|
|
3155
3538
|
status: z.enum(["running", "passed", "failed", "skipped"]).describe("Step status"),
|
|
3156
3539
|
notes: z.string().optional().describe("Optional notes or output summary"),
|
|
3157
3540
|
pr_url: z.string().optional().describe("PR URL opened during this step"),
|
|
3158
3541
|
agent_ref: z.string().optional().describe("Cursor agentId or external agent reference")
|
|
3159
|
-
}
|
|
3542
|
+
})
|
|
3160
3543
|
},
|
|
3161
3544
|
async (args) => {
|
|
3162
3545
|
const { run_id, step_index, ...body } = args;
|
|
@@ -3173,13 +3556,13 @@ Be specific. No generic advice.`
|
|
|
3173
3556
|
title: titleOf("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
3174
3557
|
description: descOf("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
3175
3558
|
annotations: annotationsFor("ask_codebase", CODEBASE_TOOL_CATALOG),
|
|
3176
|
-
inputSchema: {
|
|
3559
|
+
inputSchema: z.object({
|
|
3177
3560
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
3178
3561
|
question: z.string().describe("Plain-English question about the repo"),
|
|
3179
3562
|
thread_id: z.string().optional().describe("Optional thread UUID to continue a conversation"),
|
|
3180
3563
|
file_path: z.string().optional().describe("Optional file path focus"),
|
|
3181
3564
|
symbol_name: z.string().optional().describe("Optional symbol name focus")
|
|
3182
|
-
}
|
|
3565
|
+
})
|
|
3183
3566
|
},
|
|
3184
3567
|
async (args) => {
|
|
3185
3568
|
const pid = args.project_id ?? projectId;
|
|
@@ -3207,12 +3590,12 @@ Be specific. No generic advice.`
|
|
|
3207
3590
|
title: titleOf("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
3208
3591
|
description: descOf("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
3209
3592
|
annotations: annotationsFor("get_file_summary", CODEBASE_TOOL_CATALOG),
|
|
3210
|
-
inputSchema: {
|
|
3593
|
+
inputSchema: z.object({
|
|
3211
3594
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
3212
3595
|
file_path: z.string().describe("Indexed file path"),
|
|
3213
3596
|
symbol_name: z.string().optional().describe("Optional symbol name within the file"),
|
|
3214
3597
|
force: z.boolean().optional().describe("Bypass cache and regenerate")
|
|
3215
|
-
}
|
|
3598
|
+
})
|
|
3216
3599
|
},
|
|
3217
3600
|
async (args) => {
|
|
3218
3601
|
const pid = args.project_id ?? projectId;
|
|
@@ -3230,10 +3613,10 @@ Be specific. No generic advice.`
|
|
|
3230
3613
|
title: titleOf("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
3231
3614
|
description: descOf("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
3232
3615
|
annotations: annotationsFor("get_codebase_tour", CODEBASE_TOOL_CATALOG),
|
|
3233
|
-
inputSchema: {
|
|
3616
|
+
inputSchema: z.object({
|
|
3234
3617
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
3235
3618
|
force: z.boolean().optional().describe("Bypass cache and regenerate")
|
|
3236
|
-
}
|
|
3619
|
+
})
|
|
3237
3620
|
},
|
|
3238
3621
|
async (args) => {
|
|
3239
3622
|
const pid = args.project_id ?? projectId;
|
|
@@ -3249,17 +3632,17 @@ Be specific. No generic advice.`
|
|
|
3249
3632
|
title: titleOf("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
3250
3633
|
description: descOf("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
3251
3634
|
annotations: annotationsFor("search_codebase", CODEBASE_TOOL_CATALOG),
|
|
3252
|
-
inputSchema: {
|
|
3635
|
+
inputSchema: z.object({
|
|
3253
3636
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
3254
3637
|
query: z.string().describe("Plain-English search query against indexed source files"),
|
|
3255
3638
|
k: z.number().int().min(1).max(20).optional().describe("Max hits to return (default 8, max 20)"),
|
|
3256
3639
|
scope_prefix: z.string().optional().describe("Optional repo subdirectory prefix to limit search scope")
|
|
3257
|
-
},
|
|
3258
|
-
outputSchema: {
|
|
3640
|
+
}),
|
|
3641
|
+
outputSchema: z.object({
|
|
3259
3642
|
results: z.array(z.unknown()).describe("Ranked file/symbol hits with paths, line ranges, and similarity"),
|
|
3260
3643
|
query: z.string().describe("Normalized query string that was searched"),
|
|
3261
3644
|
mode: z.string().optional().describe("semantic (embeddings) or name (path/symbol match)")
|
|
3262
|
-
}
|
|
3645
|
+
})
|
|
3263
3646
|
},
|
|
3264
3647
|
async (args) => {
|
|
3265
3648
|
const pid = args.project_id ?? projectId;
|
|
@@ -3284,11 +3667,11 @@ Be specific. No generic advice.`
|
|
|
3284
3667
|
title: titleOf("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
3285
3668
|
description: descOf("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
3286
3669
|
annotations: annotationsFor("get_codebase_domains", CODEBASE_TOOL_CATALOG),
|
|
3287
|
-
inputSchema: {
|
|
3670
|
+
inputSchema: z.object({
|
|
3288
3671
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
3289
3672
|
force: z.boolean().optional().describe("Bypass cache and regenerate"),
|
|
3290
3673
|
scope_prefix: z.string().optional().describe("Optional subdirectory scope prefix")
|
|
3291
|
-
}
|
|
3674
|
+
})
|
|
3292
3675
|
},
|
|
3293
3676
|
async (args) => {
|
|
3294
3677
|
const pid = args.project_id ?? projectId;
|
|
@@ -3307,20 +3690,20 @@ Be specific. No generic advice.`
|
|
|
3307
3690
|
title: titleOf("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
3308
3691
|
description: descOf("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
3309
3692
|
annotations: annotationsFor("analyze_codebase_impact", CODEBASE_TOOL_CATALOG),
|
|
3310
|
-
inputSchema: {
|
|
3693
|
+
inputSchema: z.object({
|
|
3311
3694
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)"),
|
|
3312
3695
|
paths: z.array(z.string()).optional().describe("Changed repo-relative file paths to analyze"),
|
|
3313
3696
|
source: z.enum(["last_push", "compare", "fix"]).optional().describe("Auto-resolve changed paths instead of manual paths"),
|
|
3314
3697
|
compare: z.string().optional().describe("GitHub compare range base...head when source=compare"),
|
|
3315
3698
|
fix_id: z.string().optional().describe("Fix attempt UUID \u2014 uses that PR's changed files when source=fix")
|
|
3316
|
-
},
|
|
3317
|
-
outputSchema: {
|
|
3699
|
+
}),
|
|
3700
|
+
outputSchema: z.object({
|
|
3318
3701
|
changed_paths: z.array(z.string()).describe("Resolved set of changed paths analyzed"),
|
|
3319
3702
|
source: z.string().describe("How changed_paths were resolved (manual, last_push, compare, fix, \u2026)"),
|
|
3320
3703
|
affected_file_paths: z.array(z.string()).describe("Indexed files that import or depend on changed paths"),
|
|
3321
3704
|
affected_node_ids: z.array(z.string()).describe("Codebase graph node ids impacted by the change"),
|
|
3322
3705
|
meta: z.unknown().nullable().optional().describe("Extra resolution metadata when auto-sourced")
|
|
3323
|
-
}
|
|
3706
|
+
})
|
|
3324
3707
|
},
|
|
3325
3708
|
async (args) => {
|
|
3326
3709
|
const pid = args.project_id ?? projectId;
|
|
@@ -3347,9 +3730,9 @@ Be specific. No generic advice.`
|
|
|
3347
3730
|
title: titleOf("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
3348
3731
|
description: descOf("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
3349
3732
|
annotations: annotationsFor("analyze_wiki_knowledge", CODEBASE_TOOL_CATALOG),
|
|
3350
|
-
inputSchema: {
|
|
3733
|
+
inputSchema: z.object({
|
|
3351
3734
|
project_id: z.string().optional().describe("Project UUID (defaults to configured project)")
|
|
3352
|
-
}
|
|
3735
|
+
})
|
|
3353
3736
|
},
|
|
3354
3737
|
async (args) => {
|
|
3355
3738
|
const pid = args.project_id ?? projectId;
|
|
@@ -3363,11 +3746,11 @@ Be specific. No generic advice.`
|
|
|
3363
3746
|
{
|
|
3364
3747
|
title: titleOf("use_mushi"),
|
|
3365
3748
|
description: descOf("use_mushi"),
|
|
3366
|
-
inputSchema: {
|
|
3749
|
+
inputSchema: z.object({
|
|
3367
3750
|
intent: z.string().optional().describe(
|
|
3368
3751
|
'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.'
|
|
3369
3752
|
)
|
|
3370
|
-
},
|
|
3753
|
+
}),
|
|
3371
3754
|
annotations: annotationsFor("use_mushi")
|
|
3372
3755
|
},
|
|
3373
3756
|
async (args) => {
|
|
@@ -3470,28 +3853,30 @@ Be specific. No generic advice.`
|
|
|
3470
3853
|
|
|
3471
3854
|
// src/index.ts
|
|
3472
3855
|
import * as Sentry from "@sentry/node";
|
|
3473
|
-
|
|
3474
|
-
args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ") + "\n"
|
|
3475
|
-
);
|
|
3476
|
-
console.log = _writeStderr;
|
|
3477
|
-
console.warn = _writeStderr;
|
|
3856
|
+
installStdoutGuard();
|
|
3478
3857
|
var require2 = createRequire(import.meta.url);
|
|
3479
3858
|
var VERSION = require2("../package.json").version;
|
|
3480
3859
|
var log = createLogger2({ scope: "mushi:mcp", level: "info", destination: "stderr" });
|
|
3860
|
+
function resolveCliConfigPath() {
|
|
3861
|
+
const xdg = process.env.XDG_CONFIG_HOME;
|
|
3862
|
+
const appData = process.env.APPDATA;
|
|
3863
|
+
const base = xdg && xdg.length > 0 ? xdg : process.platform === "win32" && appData && appData.length > 0 ? appData : join(homedir(), ".config");
|
|
3864
|
+
return join(base, "mushi", "config.json");
|
|
3865
|
+
}
|
|
3481
3866
|
function readCliConfig() {
|
|
3867
|
+
const configPath = resolveCliConfigPath();
|
|
3482
3868
|
try {
|
|
3483
|
-
const
|
|
3484
|
-
const appData = process.env.APPDATA;
|
|
3485
|
-
const base = xdg && xdg.length > 0 ? xdg : process.platform === "win32" && appData && appData.length > 0 ? appData : join(homedir(), ".config");
|
|
3486
|
-
const raw = readFileSync(join(base, "mushi", "config.json"), "utf8");
|
|
3869
|
+
const raw = readFileSync(configPath, "utf8");
|
|
3487
3870
|
const parsed = JSON.parse(raw);
|
|
3488
3871
|
return {
|
|
3489
3872
|
apiKey: typeof parsed.apiKey === "string" ? parsed.apiKey : void 0,
|
|
3490
3873
|
projectId: typeof parsed.projectId === "string" ? parsed.projectId : void 0,
|
|
3491
|
-
endpoint: typeof parsed.endpoint === "string" ? parsed.endpoint : void 0
|
|
3874
|
+
endpoint: typeof parsed.endpoint === "string" ? parsed.endpoint : void 0,
|
|
3875
|
+
path: configPath,
|
|
3876
|
+
found: true
|
|
3492
3877
|
};
|
|
3493
3878
|
} catch {
|
|
3494
|
-
return {};
|
|
3879
|
+
return { path: configPath, found: false };
|
|
3495
3880
|
}
|
|
3496
3881
|
}
|
|
3497
3882
|
var CLI_CONFIG = readCliConfig();
|
|
@@ -3510,11 +3895,49 @@ if (MCP_SENTRY_DSN) {
|
|
|
3510
3895
|
tracesSampleRate: 0
|
|
3511
3896
|
});
|
|
3512
3897
|
}
|
|
3898
|
+
function missingApiKeyReport() {
|
|
3899
|
+
const envState = process.env.MUSHI_API_KEY === void 0 ? "not set" : process.env.MUSHI_API_KEY.trim() === "" ? "set but empty" : "set";
|
|
3900
|
+
const cliState = !CLI_CONFIG.found ? "no file at this path" : CLI_CONFIG.apiKey ? "present" : 'file exists but has no "apiKey" field';
|
|
3901
|
+
return [
|
|
3902
|
+
"",
|
|
3903
|
+
"[mushi-mcp] FATAL: no API key \u2014 the MCP server cannot serve a single tool call.",
|
|
3904
|
+
"",
|
|
3905
|
+
" Sources checked, in precedence order:",
|
|
3906
|
+
` 1. env MUSHI_API_KEY \u2192 ${envState}`,
|
|
3907
|
+
` 2. CLI config file \u2192 ${cliState}`,
|
|
3908
|
+
` ${CLI_CONFIG.path}`,
|
|
3909
|
+
"",
|
|
3910
|
+
" Fix either one:",
|
|
3911
|
+
" \u2022 Run `mushi login` (writes the config file above), or",
|
|
3912
|
+
' \u2022 Add the key to the "env" block of your MCP client config',
|
|
3913
|
+
" (.cursor/mcp.json \xB7 claude_desktop_config.json \xB7 .vscode/mcp.json):",
|
|
3914
|
+
"",
|
|
3915
|
+
' { "mcpServers": { "mushi-mushi": {',
|
|
3916
|
+
' "command": "npx", "args": ["-y", "@mushi-mushi/mcp"],',
|
|
3917
|
+
' "env": { "MUSHI_API_KEY": "mushi_\u2026", "MUSHI_PROJECT_ID": "<uuid>" } } } }',
|
|
3918
|
+
"",
|
|
3919
|
+
" A key exported in your shell does NOT reach the server: MCP clients",
|
|
3920
|
+
" spawn this process with only the env block they are given.",
|
|
3921
|
+
"",
|
|
3922
|
+
" Other env vars this server reads:",
|
|
3923
|
+
` MUSHI_API_ENDPOINT ${process.env.MUSHI_API_ENDPOINT?.trim() ? "= " + process.env.MUSHI_API_ENDPOINT.trim() : `unset \u2192 ${API_ENDPOINT}`}`,
|
|
3924
|
+
` MUSHI_PROJECT_ID ${PROJECT_ID ? "= " + PROJECT_ID : "unset (account mode)"}`,
|
|
3925
|
+
" MUSHI_SCOPES optional CSV: mcp:read,mcp:write",
|
|
3926
|
+
' MUSHI_FEATURES optional CSV of tool groups, or "all"',
|
|
3927
|
+
" MUSHI_MCP_TIMEOUT_MS optional per-request timeout in ms (default 15000)",
|
|
3928
|
+
"",
|
|
3929
|
+
" Mint a key: Console \u2192 Settings \u2192 API keys.",
|
|
3930
|
+
""
|
|
3931
|
+
].join("\n");
|
|
3932
|
+
}
|
|
3513
3933
|
async function main() {
|
|
3514
3934
|
if (!API_KEY) {
|
|
3515
|
-
|
|
3516
|
-
|
|
3517
|
-
|
|
3935
|
+
process.stderr.write(missingApiKeyReport());
|
|
3936
|
+
log.fatal("No API key found \u2014 set MUSHI_API_KEY, or run `mushi login`.", {
|
|
3937
|
+
cliConfigPath: CLI_CONFIG.path,
|
|
3938
|
+
cliConfigFound: CLI_CONFIG.found,
|
|
3939
|
+
endpoint: API_ENDPOINT
|
|
3940
|
+
});
|
|
3518
3941
|
process.exit(1);
|
|
3519
3942
|
}
|
|
3520
3943
|
if (!process.env.MUSHI_API_KEY && CLI_CONFIG.apiKey) {
|
|
@@ -3543,24 +3966,33 @@ async function main() {
|
|
|
3543
3966
|
hasProjectId: !!PROJECT_ID,
|
|
3544
3967
|
scopes: SCOPES.join(",")
|
|
3545
3968
|
});
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3969
|
+
let server;
|
|
3970
|
+
const handle = serveStdio(
|
|
3971
|
+
(ctx) => {
|
|
3972
|
+
server = createMushiServer({
|
|
3973
|
+
version: VERSION,
|
|
3974
|
+
apiEndpoint: API_ENDPOINT,
|
|
3975
|
+
apiKey: API_KEY,
|
|
3976
|
+
projectId: PROJECT_ID || void 0,
|
|
3977
|
+
scopes: SCOPES,
|
|
3978
|
+
features: FEATURES
|
|
3979
|
+
});
|
|
3980
|
+
log.debug("[mushi-mcp] server instance created", { era: ctx.era });
|
|
3981
|
+
return server;
|
|
3982
|
+
},
|
|
3983
|
+
{
|
|
3984
|
+
onerror: (err) => log.error("stdio transport error", { err: String(err) })
|
|
3985
|
+
}
|
|
3986
|
+
);
|
|
3556
3987
|
let shuttingDown = false;
|
|
3557
3988
|
let pollTimer;
|
|
3558
3989
|
const shutdown = (exitCode) => {
|
|
3559
3990
|
if (shuttingDown) return;
|
|
3560
3991
|
shuttingDown = true;
|
|
3561
3992
|
if (pollTimer) clearInterval(pollTimer);
|
|
3562
|
-
void
|
|
3993
|
+
void handle.close().finally(() => process.exit(exitCode));
|
|
3563
3994
|
};
|
|
3995
|
+
setActiveShutdown(shutdown);
|
|
3564
3996
|
process.stdin.on("end", () => shutdown(0));
|
|
3565
3997
|
process.stdin.on("close", () => shutdown(0));
|
|
3566
3998
|
process.on("SIGINT", () => shutdown(0));
|
|
@@ -3590,7 +4022,7 @@ async function main() {
|
|
|
3590
4022
|
const data = await res.json();
|
|
3591
4023
|
const updatedAt = data?.data?.updatedAt ?? null;
|
|
3592
4024
|
if (updatedAt && updatedAt !== lastInventoryAt) {
|
|
3593
|
-
if (lastInventoryAt !== null) {
|
|
4025
|
+
if (lastInventoryAt !== null && server) {
|
|
3594
4026
|
await server.server.sendResourceUpdated({ uri: "inventory://current" });
|
|
3595
4027
|
log.info("inventory://current updated \u2014 notified subscribers", { updatedAt });
|
|
3596
4028
|
}
|
|
@@ -3607,6 +4039,49 @@ async function main() {
|
|
|
3607
4039
|
pollTimer.unref();
|
|
3608
4040
|
}
|
|
3609
4041
|
}
|
|
4042
|
+
var activeShutdown;
|
|
4043
|
+
var setActiveShutdown = (fn) => {
|
|
4044
|
+
activeShutdown = fn;
|
|
4045
|
+
};
|
|
4046
|
+
var exitNow = (code) => {
|
|
4047
|
+
if (activeShutdown) activeShutdown(code);
|
|
4048
|
+
else process.exit(code);
|
|
4049
|
+
};
|
|
4050
|
+
var CRASH_WINDOW_MS = 1e4;
|
|
4051
|
+
var CRASH_LIMIT = 20;
|
|
4052
|
+
var crashTimes = [];
|
|
4053
|
+
function isCrashLooping() {
|
|
4054
|
+
const now = Date.now();
|
|
4055
|
+
crashTimes = crashTimes.filter((t) => now - t < CRASH_WINDOW_MS);
|
|
4056
|
+
crashTimes.push(now);
|
|
4057
|
+
return crashTimes.length >= CRASH_LIMIT;
|
|
4058
|
+
}
|
|
4059
|
+
process.on("unhandledRejection", (reason) => {
|
|
4060
|
+
log.error("unhandledRejection \u2014 server kept alive", {
|
|
4061
|
+
err: reason instanceof Error ? `${reason.name}: ${reason.message}` : String(reason),
|
|
4062
|
+
stack: reason instanceof Error ? reason.stack?.split("\n").slice(0, 5).join(" | ") : void 0
|
|
4063
|
+
});
|
|
4064
|
+
if (isCrashLooping()) {
|
|
4065
|
+
log.fatal("too many unhandled rejections in 10s \u2014 exiting so the client can respawn");
|
|
4066
|
+
exitNow(1);
|
|
4067
|
+
}
|
|
4068
|
+
});
|
|
4069
|
+
process.on("uncaughtException", (err) => {
|
|
4070
|
+
if (err?.code === "EPIPE" || err?.code === "ERR_STREAM_DESTROYED" || err?.code === "ERR_STREAM_WRITE_AFTER_END") {
|
|
4071
|
+
log.info("stdio pipe closed by the client \u2014 shutting down", { code: err.code });
|
|
4072
|
+
exitNow(0);
|
|
4073
|
+
return;
|
|
4074
|
+
}
|
|
4075
|
+
log.error("uncaughtException \u2014 server kept alive", {
|
|
4076
|
+
err: `${err?.name ?? "Error"}: ${err?.message ?? String(err)}`,
|
|
4077
|
+
code: err?.code,
|
|
4078
|
+
stack: err?.stack?.split("\n").slice(0, 5).join(" | ")
|
|
4079
|
+
});
|
|
4080
|
+
if (isCrashLooping()) {
|
|
4081
|
+
log.fatal("too many uncaught exceptions in 10s \u2014 exiting so the client can respawn");
|
|
4082
|
+
exitNow(1);
|
|
4083
|
+
}
|
|
4084
|
+
});
|
|
3610
4085
|
main().catch((err) => {
|
|
3611
4086
|
log.fatal("MCP server crashed", { err: String(err) });
|
|
3612
4087
|
process.exit(1);
|