@mushi-mushi/mcp 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +0 -11
- package/README.md +31 -5
- package/dist/index.js +177 -109
- package/package.json +18 -18
package/CONTRIBUTING.md
CHANGED
|
@@ -101,17 +101,6 @@ Releases are fully automated. Maintainers don't run `npm publish` by hand.
|
|
|
101
101
|
|
|
102
102
|
If GitHub's anti-loop protection suppresses the auto re-fire (the squash merge can be attributed to `github-actions[bot]`), trigger the workflow manually: **Actions → release → Run workflow → master**.
|
|
103
103
|
|
|
104
|
-
### Known CI/CD quirks and their automatic safeguards
|
|
105
|
-
|
|
106
|
-
A handful of GitHub-Actions × Changesets edge cases have caused release-pipeline stalls in the past. Each is now caught automatically — keep these in mind when you see the symptom:
|
|
107
|
-
|
|
108
|
-
| Symptom | Root cause | Automatic safeguard |
|
|
109
|
-
| --- | --- | --- |
|
|
110
|
-
| The `Build & Test` required check never registers on the `chore: version packages` PR — the PR stays "Required check missing" forever | `changeset-release/master` is pushed by `github-actions[bot]`. GitHub silently drops the downstream `pull_request` event to prevent bot loops (observed on PR #45, #102, #124). | `ci.yml` now also triggers on `push` to `changeset-release/master`, so `Build & Test` reports against the head commit directly. No empty-commit nudge needed. |
|
|
111
|
-
| Release workflow fails with `No commits between master and changeset-release/master` after merging a PR with a new changeset. | A `.changeset/*.md` file whose YAML frontmatter only targets packages listed in `.changeset/config.json#ignore` (e.g. `@mushi-mushi/server`, `@mushi-mushi/admin`). `changeset version` produces no bumps, the version PR is empty, the next push errors (PR #102 / #121, 2026-05-19). | `pnpm check:changeset-orphans` runs in both `ci.yml` and `release.yml`. PR CI fails with an actionable message naming the orphan file *before* it can reach master. If you legitimately need to record an internal-only change, omit the changeset entirely — the diff lives in git history. |
|
|
112
|
-
| Release workflow's `Audit signatures of installed dependencies` step fails with `npm ETARGET / No matching version found for @mushi-mushi/<pkg>@<ver>` seconds after the publish step succeeded. | npm's registry CDN can take up to ~30s to propagate a freshly-published manifest. The audit step shells out to `npm install` against the just-published version and races the CDN (observed 2026-05-20, run 26149167393). | The audit step retries with exponential backoff (1, 2, 4, 8, 16, 32s — 63s total) before failing. Sigstore signatures are written at publish time, so a one-off audit failure never indicates a corrupted package — `pnpm view <pkg> version` is the ground truth. |
|
|
113
|
-
| Push to `master` after merging a PR doesn't fire the `Release` workflow. | Same anti-loop protection: when a squash merge is attributed to `github-actions[bot]`, GitHub may suppress the downstream `push` trigger. Sporadic — observed twice in the last 60 days. | `release.yml` keeps `workflow_dispatch` as the manual fallback. Recovery: **Actions → Release → Run workflow → master**. The `Build & Verify` job re-runs identically to the auto-fired path. |
|
|
114
|
-
|
|
115
104
|
### Adding a brand-new publishable package
|
|
116
105
|
|
|
117
106
|
Trusted Publisher bindings are configured **per package** on `npmjs.com` and require the package to already exist on the registry. New packages therefore need a one-time bootstrap before OIDC can take over.
|
package/README.md
CHANGED
|
@@ -1,18 +1,44 @@
|
|
|
1
|
-
#
|
|
1
|
+
# mushi-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
> **Sentry sees what code throws. Mushi sees what users feel — and closes the loop with AI.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
[Model Context Protocol](https://spec.modelcontextprotocol.io/) server that wires Mushi's **evolution loop** into your AI coding agent. The loop is already running in your Mushi project:
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
User feels a bug → Mushi captures it → AI triages → AI opens a PR
|
|
9
|
+
→ QA verifies → Judge scores → Lesson library remembers → next agent is smarter
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Wire it into Cursor, Claude Code, or any MCP client in one command:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npx mushi-mushi setup --ide cursor
|
|
16
|
+
# or: npx mushi-mushi setup --ide claude
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
That command reads `~/.mushirc`, writes `.cursor/mcp.json` with the `mushi` server block, and prints "Done — restart Cursor and ask: `list mushi tools`". No copy-pasting environment variables.
|
|
6
20
|
|
|
7
21
|
> **What this is, and what it isn't**
|
|
8
22
|
>
|
|
9
|
-
> - **This package** is the MCP **server** — runs locally next to your editor, talks to the Mushi
|
|
23
|
+
> - **This package** (`mushi-mcp`) is the MCP **server** — runs locally next to your editor, talks to the Mushi API, and presents bug reports as MCP tools/resources to your coding agent. The npm package name is `mushi-mcp`; the scoped alias `@mushi-mushi/mcp` still works.
|
|
10
24
|
> - **`@mushi-mushi/agents`** ships the MCP **client adapter** — used by the autofix orchestrator when your project's `autofix_agent = 'mcp'`. See `packages/agents/src/adapters/mcp.ts`.
|
|
11
25
|
> - The `generic_mcp` adapter shipped before V5.3 was a misnomer (it spoke plain REST). It is now `RestFixWorkerAgent`; the old export is kept as a deprecated alias for one more minor.
|
|
12
26
|
|
|
13
27
|
## Quick start
|
|
14
28
|
|
|
15
|
-
###
|
|
29
|
+
### 0. One-liner (recommended)
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
# First: make sure you've logged in
|
|
33
|
+
npx mushi-mushi login --api-key mushi_xxx --endpoint https://<ref>.supabase.co/functions/v1/api
|
|
34
|
+
|
|
35
|
+
# Then wire your IDE
|
|
36
|
+
npx mushi-mushi setup --ide cursor # Cursor
|
|
37
|
+
npx mushi-mushi setup --ide claude # Claude Code / Claude Desktop
|
|
38
|
+
npx mushi-mushi setup --ide cursor --with-rules # also write .cursorrules
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### 1. With Claude Desktop (manual)
|
|
16
42
|
|
|
17
43
|
Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
18
44
|
|
package/dist/index.js
CHANGED
|
@@ -129,6 +129,15 @@ var TOOL_CATALOG = [
|
|
|
129
129
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
130
130
|
useCase: "What did Stage 2 say we should try for this report?"
|
|
131
131
|
},
|
|
132
|
+
// --- Setup / admin -------------------------------------------------------
|
|
133
|
+
{
|
|
134
|
+
name: "setup_check",
|
|
135
|
+
title: "Dispatch preflight check",
|
|
136
|
+
description: "Run the 4 dispatch-readiness checks for a project and return their pass/fail status (GitHub repo connected, codebase indexed, Anthropic BYOK key present, autofix enabled). Also returns the target repo URL when GitHub is connected. Use this before calling dispatch_fix to understand why a dispatch might fail \u2014 or to validate that the onboarding wizard is complete.",
|
|
137
|
+
scope: "mcp:read",
|
|
138
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
139
|
+
useCase: "Is this project ready to auto-fix bugs? What is blocking me?"
|
|
140
|
+
},
|
|
132
141
|
// --- Write / agentic ----------------------------------------------------
|
|
133
142
|
{
|
|
134
143
|
name: "submit_fix_result",
|
|
@@ -198,6 +207,14 @@ var TOOL_CATALOG = [
|
|
|
198
207
|
scope: "mcp:write",
|
|
199
208
|
hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
|
|
200
209
|
useCase: "Manually promote this user to Champion tier as a thank-you."
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: "setup_repo_for_mushi",
|
|
213
|
+
title: "Bootstrap repo for Mushi",
|
|
214
|
+
description: "Writes the three Mushi bootstrap files into the current repo root: `.cursorrules` (Cursor evolution-loop coding rules), `.mushi/lessons.json` (initial empty lesson cache), and `MUSHI.md` (one-page project contract for agents). Idempotent \u2014 safe to re-run after lessons sync. Requires mcp:write scope. Call this once after connecting the repo; subsequently use `mushi sync-lessons` from CI to keep lessons current.",
|
|
215
|
+
scope: "mcp:write",
|
|
216
|
+
hints: { readOnly: false, destructive: false, idempotent: true, openWorld: false },
|
|
217
|
+
useCase: "Set up this repo for the Mushi evolution loop in one step."
|
|
201
218
|
}
|
|
202
219
|
];
|
|
203
220
|
|
|
@@ -217,7 +234,9 @@ var MushiApiError = class extends Error {
|
|
|
217
234
|
function createMushiServer(config) {
|
|
218
235
|
const { version, apiEndpoint, apiKey, projectId } = config;
|
|
219
236
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
220
|
-
|
|
237
|
+
if (config.scopes !== void 0 && config.scopes.length === 0) {
|
|
238
|
+
return new McpServer({ name: "mushi-mushi", version });
|
|
239
|
+
}
|
|
221
240
|
async function apiCall(path, options) {
|
|
222
241
|
const res = await doFetch(`${apiEndpoint}${path}`, {
|
|
223
242
|
...options,
|
|
@@ -263,6 +282,12 @@ function createMushiServer(config) {
|
|
|
263
282
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
264
283
|
};
|
|
265
284
|
}
|
|
285
|
+
function jsonResult(value) {
|
|
286
|
+
return {
|
|
287
|
+
content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
|
288
|
+
structuredContent: value
|
|
289
|
+
};
|
|
290
|
+
}
|
|
266
291
|
const server = new McpServer({
|
|
267
292
|
name: "mushi-mushi",
|
|
268
293
|
version
|
|
@@ -289,23 +314,7 @@ function createMushiServer(config) {
|
|
|
289
314
|
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
290
315
|
return spec.title;
|
|
291
316
|
}
|
|
292
|
-
|
|
293
|
-
const spec = TOOL_CATALOG.find((t) => t.name === name);
|
|
294
|
-
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
295
|
-
return grantedScopes.has(spec.scope);
|
|
296
|
-
}
|
|
297
|
-
function jsonResult(value) {
|
|
298
|
-
return {
|
|
299
|
-
content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
|
300
|
-
structuredContent: value
|
|
301
|
-
};
|
|
302
|
-
}
|
|
303
|
-
const _serverRegisterTool = server.registerTool.bind(server);
|
|
304
|
-
const registerScopedTool = ((name, ...rest) => {
|
|
305
|
-
if (!shouldRegister(name)) return void 0;
|
|
306
|
-
return _serverRegisterTool(name, ...rest);
|
|
307
|
-
});
|
|
308
|
-
registerScopedTool(
|
|
317
|
+
server.registerTool(
|
|
309
318
|
"get_recent_reports",
|
|
310
319
|
{
|
|
311
320
|
title: titleOf("get_recent_reports"),
|
|
@@ -317,12 +326,9 @@ function createMushiServer(config) {
|
|
|
317
326
|
severity: z.string().optional().describe("Filter by severity: critical, high, medium, low"),
|
|
318
327
|
limit: z.number().optional().describe("Max reports to return (default 20, max 100)")
|
|
319
328
|
},
|
|
320
|
-
// Output schema (MCP 2025-06-18): when set, the SDK validates the
|
|
321
|
-
// tool's `structuredContent` and lets typed clients deserialize
|
|
322
|
-
// without re-parsing the text payload.
|
|
323
329
|
outputSchema: {
|
|
324
|
-
reports: z.array(z.
|
|
325
|
-
total: z.number()
|
|
330
|
+
reports: z.array(z.unknown()),
|
|
331
|
+
total: z.number()
|
|
326
332
|
}
|
|
327
333
|
},
|
|
328
334
|
async (args) => {
|
|
@@ -332,13 +338,10 @@ function createMushiServer(config) {
|
|
|
332
338
|
if (args.severity) params.set("severity", args.severity);
|
|
333
339
|
params.set("limit", String(Math.min(args.limit ?? 20, 100)));
|
|
334
340
|
const data = await apiCall(`/v1/admin/reports?${params}`);
|
|
335
|
-
return jsonResult(
|
|
336
|
-
reports: data.reports ?? [],
|
|
337
|
-
total: data.total ?? 0
|
|
338
|
-
});
|
|
341
|
+
return jsonResult(data);
|
|
339
342
|
}
|
|
340
343
|
);
|
|
341
|
-
|
|
344
|
+
server.registerTool(
|
|
342
345
|
"get_report_detail",
|
|
343
346
|
{
|
|
344
347
|
title: titleOf("get_report_detail"),
|
|
@@ -348,7 +351,7 @@ function createMushiServer(config) {
|
|
|
348
351
|
},
|
|
349
352
|
async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}`))
|
|
350
353
|
);
|
|
351
|
-
|
|
354
|
+
server.registerTool(
|
|
352
355
|
"search_reports",
|
|
353
356
|
{
|
|
354
357
|
title: titleOf("search_reports"),
|
|
@@ -360,7 +363,7 @@ function createMushiServer(config) {
|
|
|
360
363
|
threshold: z.number().optional().describe("Similarity threshold 0..1, default 0.2")
|
|
361
364
|
},
|
|
362
365
|
outputSchema: {
|
|
363
|
-
results: z.array(z.
|
|
366
|
+
results: z.array(z.unknown())
|
|
364
367
|
}
|
|
365
368
|
},
|
|
366
369
|
async (args) => {
|
|
@@ -373,10 +376,10 @@ function createMushiServer(config) {
|
|
|
373
376
|
...projectId ? { projectId } : {}
|
|
374
377
|
})
|
|
375
378
|
});
|
|
376
|
-
return jsonResult(
|
|
379
|
+
return jsonResult(data);
|
|
377
380
|
}
|
|
378
381
|
);
|
|
379
|
-
|
|
382
|
+
server.registerTool(
|
|
380
383
|
"get_similar_bugs",
|
|
381
384
|
{
|
|
382
385
|
title: titleOf("get_similar_bugs"),
|
|
@@ -385,9 +388,6 @@ function createMushiServer(config) {
|
|
|
385
388
|
inputSchema: {
|
|
386
389
|
query: z.string().describe("Component name, page path, or bug description"),
|
|
387
390
|
limit: z.number().optional().describe("Max results (default 5, max 20)")
|
|
388
|
-
},
|
|
389
|
-
outputSchema: {
|
|
390
|
-
results: z.array(z.record(z.string(), z.unknown())).describe("Ranked report rows with similarity scores")
|
|
391
391
|
}
|
|
392
392
|
},
|
|
393
393
|
async (args) => {
|
|
@@ -400,10 +400,10 @@ function createMushiServer(config) {
|
|
|
400
400
|
...projectId ? { projectId } : {}
|
|
401
401
|
})
|
|
402
402
|
});
|
|
403
|
-
return
|
|
403
|
+
return jsonText(data);
|
|
404
404
|
}
|
|
405
405
|
);
|
|
406
|
-
|
|
406
|
+
server.registerTool(
|
|
407
407
|
"get_fix_context",
|
|
408
408
|
{
|
|
409
409
|
title: titleOf("get_fix_context"),
|
|
@@ -422,7 +422,7 @@ function createMushiServer(config) {
|
|
|
422
422
|
});
|
|
423
423
|
}
|
|
424
424
|
);
|
|
425
|
-
|
|
425
|
+
server.registerTool(
|
|
426
426
|
"get_fix_timeline",
|
|
427
427
|
{
|
|
428
428
|
title: titleOf("get_fix_timeline"),
|
|
@@ -432,7 +432,7 @@ function createMushiServer(config) {
|
|
|
432
432
|
},
|
|
433
433
|
async (args) => jsonText(await apiCall(`/v1/admin/fixes/${args.fixId}/timeline`))
|
|
434
434
|
);
|
|
435
|
-
|
|
435
|
+
server.registerTool(
|
|
436
436
|
"get_blast_radius",
|
|
437
437
|
{
|
|
438
438
|
title: titleOf("get_blast_radius"),
|
|
@@ -442,7 +442,7 @@ function createMushiServer(config) {
|
|
|
442
442
|
},
|
|
443
443
|
async (args) => jsonText(await apiCall(`/v1/admin/graph/blast-radius/${args.nodeId}`))
|
|
444
444
|
);
|
|
445
|
-
|
|
445
|
+
server.registerTool(
|
|
446
446
|
"get_knowledge_graph",
|
|
447
447
|
{
|
|
448
448
|
title: titleOf("get_knowledge_graph"),
|
|
@@ -461,7 +461,7 @@ function createMushiServer(config) {
|
|
|
461
461
|
return jsonText(await apiCall(`/v1/admin/graph/traverse?${params}`));
|
|
462
462
|
}
|
|
463
463
|
);
|
|
464
|
-
|
|
464
|
+
server.registerTool(
|
|
465
465
|
"graph_neighborhood",
|
|
466
466
|
{
|
|
467
467
|
title: titleOf("graph_neighborhood"),
|
|
@@ -480,7 +480,7 @@ function createMushiServer(config) {
|
|
|
480
480
|
return jsonText(await apiCall(`/v1/admin/graph/traverse?${params}`));
|
|
481
481
|
}
|
|
482
482
|
);
|
|
483
|
-
|
|
483
|
+
server.registerTool(
|
|
484
484
|
"graph_node_status",
|
|
485
485
|
{
|
|
486
486
|
title: titleOf("graph_node_status"),
|
|
@@ -490,7 +490,7 @@ function createMushiServer(config) {
|
|
|
490
490
|
},
|
|
491
491
|
async (args) => jsonText(await apiCall(`/v1/admin/graph/node/${args.nodeId}`))
|
|
492
492
|
);
|
|
493
|
-
|
|
493
|
+
server.registerTool(
|
|
494
494
|
"inventory_get",
|
|
495
495
|
{
|
|
496
496
|
title: titleOf("inventory_get"),
|
|
@@ -506,7 +506,7 @@ function createMushiServer(config) {
|
|
|
506
506
|
return jsonText(await apiCall(`/v1/admin/inventory/${pid}`));
|
|
507
507
|
}
|
|
508
508
|
);
|
|
509
|
-
|
|
509
|
+
server.registerTool(
|
|
510
510
|
"inventory_diff",
|
|
511
511
|
{
|
|
512
512
|
title: titleOf("inventory_diff"),
|
|
@@ -525,7 +525,7 @@ function createMushiServer(config) {
|
|
|
525
525
|
return jsonText(await apiCall(`/v1/admin/inventory/${pid}/diff?${q}`));
|
|
526
526
|
}
|
|
527
527
|
);
|
|
528
|
-
|
|
528
|
+
server.registerTool(
|
|
529
529
|
"inventory_findings",
|
|
530
530
|
{
|
|
531
531
|
title: titleOf("inventory_findings"),
|
|
@@ -547,7 +547,7 @@ function createMushiServer(config) {
|
|
|
547
547
|
return jsonText(await apiCall(`/v1/admin/inventory/${pid}/findings${suffix}`));
|
|
548
548
|
}
|
|
549
549
|
);
|
|
550
|
-
|
|
550
|
+
server.registerTool(
|
|
551
551
|
"fix_suggest",
|
|
552
552
|
{
|
|
553
553
|
title: titleOf("fix_suggest"),
|
|
@@ -568,7 +568,7 @@ function createMushiServer(config) {
|
|
|
568
568
|
});
|
|
569
569
|
}
|
|
570
570
|
);
|
|
571
|
-
|
|
571
|
+
server.registerTool(
|
|
572
572
|
"run_nl_query",
|
|
573
573
|
{
|
|
574
574
|
title: titleOf("run_nl_query"),
|
|
@@ -584,7 +584,44 @@ function createMushiServer(config) {
|
|
|
584
584
|
return jsonText(data);
|
|
585
585
|
}
|
|
586
586
|
);
|
|
587
|
-
|
|
587
|
+
server.registerTool(
|
|
588
|
+
"setup_check",
|
|
589
|
+
{
|
|
590
|
+
title: titleOf("setup_check"),
|
|
591
|
+
description: descOf("setup_check"),
|
|
592
|
+
annotations: annotationsFor("setup_check"),
|
|
593
|
+
inputSchema: {
|
|
594
|
+
projectId: z.string().optional().describe(
|
|
595
|
+
"Project UUID to check. Falls back to the projectId the server was initialised with."
|
|
596
|
+
)
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
async (args) => {
|
|
600
|
+
const resolvedId = args.projectId ?? projectId;
|
|
601
|
+
if (!resolvedId) {
|
|
602
|
+
return jsonText({
|
|
603
|
+
ok: false,
|
|
604
|
+
error: "No projectId provided and none configured on the MCP server. Pass projectId explicitly."
|
|
605
|
+
});
|
|
606
|
+
}
|
|
607
|
+
const data = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
608
|
+
const summary = data.checks.map((c) => ({
|
|
609
|
+
check: c.key,
|
|
610
|
+
label: c.label,
|
|
611
|
+
passed: c.ready,
|
|
612
|
+
hint: c.hint,
|
|
613
|
+
fixPath: c.fixHref
|
|
614
|
+
}));
|
|
615
|
+
return jsonText({
|
|
616
|
+
ready: data.ready,
|
|
617
|
+
repoUrl: data.repoUrl ?? null,
|
|
618
|
+
checks: summary,
|
|
619
|
+
// Human-readable summary for agents that paste the result into a prompt
|
|
620
|
+
summary: data.ready ? `Project ${resolvedId} is ready to dispatch auto-fixes${data.repoUrl ? ` (target: ${data.repoUrl})` : ""}.` : `Project ${resolvedId} cannot dispatch yet \u2014 ${summary.filter((c) => !c.passed).map((c) => c.label).join(", ")}.`
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
);
|
|
624
|
+
server.registerTool(
|
|
588
625
|
"submit_fix_result",
|
|
589
626
|
{
|
|
590
627
|
title: titleOf("submit_fix_result"),
|
|
@@ -619,7 +656,7 @@ function createMushiServer(config) {
|
|
|
619
656
|
return jsonText({ ok: true, fixId: created.fixId });
|
|
620
657
|
}
|
|
621
658
|
);
|
|
622
|
-
|
|
659
|
+
server.registerTool(
|
|
623
660
|
"dispatch_fix",
|
|
624
661
|
{
|
|
625
662
|
title: titleOf("dispatch_fix"),
|
|
@@ -627,20 +664,13 @@ function createMushiServer(config) {
|
|
|
627
664
|
annotations: annotationsFor("dispatch_fix"),
|
|
628
665
|
inputSchema: {
|
|
629
666
|
reportId: z.string().describe("Report UUID to fix"),
|
|
630
|
-
agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"
|
|
631
|
-
backend: z.enum(["default", "claude_code", "cursor_cloud", "mcp"]).optional().describe("Alias for agent \u2014 prefer agent. When both are set, agent wins."),
|
|
632
|
-
cursorModel: z.string().optional().describe('Optional model override when agent=cursor_cloud (e.g. "composer-latest").'),
|
|
667
|
+
agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
|
|
633
668
|
idempotencyKey: z.string().uuid().optional().describe("Optional RFC 4122 UUID. Resend the same key to safely retry without dispatching a duplicate fix job (Idempotency-Key IETF draft)."),
|
|
634
669
|
inventoryActionNodeId: z.string().uuid().optional().describe("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.")
|
|
635
670
|
},
|
|
636
|
-
// Typed write-tool result. fixId is the cursor for get_fix_timeline so
|
|
637
|
-
// downstream tools can chain without re-parsing the text payload.
|
|
638
671
|
outputSchema: {
|
|
639
|
-
fixId: z.string()
|
|
640
|
-
status: z.string()
|
|
641
|
-
agentId: z.string().optional().describe("Cursor agent ID (bc-\u2026) when agent=cursor_cloud"),
|
|
642
|
-
runId: z.string().optional().describe("Cursor run ID when agent=cursor_cloud"),
|
|
643
|
-
prUrl: z.string().optional().describe("Draft PR URL when agent=cursor_cloud and auto_create_pr=true")
|
|
672
|
+
fixId: z.string(),
|
|
673
|
+
status: z.string()
|
|
644
674
|
}
|
|
645
675
|
},
|
|
646
676
|
async (args, extra) => {
|
|
@@ -658,31 +688,20 @@ function createMushiServer(config) {
|
|
|
658
688
|
} catch {
|
|
659
689
|
}
|
|
660
690
|
}
|
|
661
|
-
const
|
|
662
|
-
|
|
663
|
-
"
|
|
664
|
-
{
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
inventoryActionNodeId: args.inventoryActionNodeId,
|
|
671
|
-
...args.cursorModel ? { cursorModel: args.cursorModel } : {},
|
|
672
|
-
...projectId ? { projectId } : {}
|
|
673
|
-
})
|
|
674
|
-
}
|
|
675
|
-
);
|
|
676
|
-
return jsonResult({
|
|
677
|
-
fixId: data.fixId ?? "",
|
|
678
|
-
...data.status ? { status: data.status } : {},
|
|
679
|
-
...data.agentId ? { agentId: data.agentId } : {},
|
|
680
|
-
...data.runId ? { runId: data.runId } : {},
|
|
681
|
-
...data.prUrl ? { prUrl: data.prUrl } : {}
|
|
691
|
+
const data = await apiCall("/v1/admin/fixes/dispatch", {
|
|
692
|
+
method: "POST",
|
|
693
|
+
headers: args.idempotencyKey ? { "Idempotency-Key": args.idempotencyKey } : void 0,
|
|
694
|
+
body: JSON.stringify({
|
|
695
|
+
reportId: args.reportId,
|
|
696
|
+
agent: args.agent,
|
|
697
|
+
inventoryActionNodeId: args.inventoryActionNodeId,
|
|
698
|
+
...projectId ? { projectId } : {}
|
|
699
|
+
})
|
|
682
700
|
});
|
|
701
|
+
return jsonResult(data);
|
|
683
702
|
}
|
|
684
703
|
);
|
|
685
|
-
|
|
704
|
+
server.registerTool(
|
|
686
705
|
"trigger_judge",
|
|
687
706
|
{
|
|
688
707
|
title: titleOf("trigger_judge"),
|
|
@@ -704,7 +723,7 @@ function createMushiServer(config) {
|
|
|
704
723
|
return jsonText(data);
|
|
705
724
|
}
|
|
706
725
|
);
|
|
707
|
-
|
|
726
|
+
server.registerTool(
|
|
708
727
|
"test_gen_from_report",
|
|
709
728
|
{
|
|
710
729
|
title: titleOf("test_gen_from_report"),
|
|
@@ -725,7 +744,7 @@ function createMushiServer(config) {
|
|
|
725
744
|
return jsonText(data);
|
|
726
745
|
}
|
|
727
746
|
);
|
|
728
|
-
|
|
747
|
+
server.registerTool(
|
|
729
748
|
"transition_status",
|
|
730
749
|
{
|
|
731
750
|
title: titleOf("transition_status"),
|
|
@@ -745,6 +764,26 @@ function createMushiServer(config) {
|
|
|
745
764
|
return jsonText(data);
|
|
746
765
|
}
|
|
747
766
|
);
|
|
767
|
+
server.registerTool(
|
|
768
|
+
"setup_repo_for_mushi",
|
|
769
|
+
{
|
|
770
|
+
title: titleOf("setup_repo_for_mushi"),
|
|
771
|
+
description: descOf("setup_repo_for_mushi"),
|
|
772
|
+
annotations: annotationsFor("setup_repo_for_mushi"),
|
|
773
|
+
inputSchema: {
|
|
774
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project")
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
async (args) => {
|
|
778
|
+
const pid = args.projectId ?? projectId;
|
|
779
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for setup_repo_for_mushi");
|
|
780
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/repo/bootstrap`, {
|
|
781
|
+
method: "POST",
|
|
782
|
+
body: JSON.stringify({})
|
|
783
|
+
});
|
|
784
|
+
return jsonText(data);
|
|
785
|
+
}
|
|
786
|
+
);
|
|
748
787
|
server.resource(
|
|
749
788
|
"project_stats",
|
|
750
789
|
"project://stats",
|
|
@@ -769,17 +808,15 @@ function createMushiServer(config) {
|
|
|
769
808
|
contents: [{ uri: "project://dashboard", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2) }]
|
|
770
809
|
})
|
|
771
810
|
);
|
|
772
|
-
|
|
811
|
+
const rewardsMeta = (name) => annotationsFor(name);
|
|
812
|
+
server.tool(
|
|
773
813
|
"list_top_contributors",
|
|
814
|
+
rewardsMeta("list_top_contributors").title,
|
|
774
815
|
{
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
inputSchema: {
|
|
778
|
-
limit: z.number().int().min(1).max(100).optional().default(10).describe("Max rows to return (default 10, max 100)"),
|
|
779
|
-
range: z.enum(["30d", "90d", "all"]).optional().default("30d").describe("Time window for points calculation")
|
|
780
|
-
},
|
|
781
|
-
annotations: annotationsFor("list_top_contributors")
|
|
816
|
+
limit: z.number().int().min(1).max(100).optional().default(10).describe("Max rows to return (default 10, max 100)"),
|
|
817
|
+
range: z.enum(["30d", "90d", "all"]).optional().default("30d").describe("Time window for points calculation")
|
|
782
818
|
},
|
|
819
|
+
rewardsMeta("list_top_contributors"),
|
|
783
820
|
async ({ limit, range }) => ({
|
|
784
821
|
content: [{
|
|
785
822
|
type: "text",
|
|
@@ -791,18 +828,15 @@ function createMushiServer(config) {
|
|
|
791
828
|
}]
|
|
792
829
|
})
|
|
793
830
|
);
|
|
794
|
-
|
|
831
|
+
server.tool(
|
|
795
832
|
"award_bonus_points",
|
|
833
|
+
rewardsMeta("award_bonus_points").title,
|
|
796
834
|
{
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
|
|
801
|
-
points: z.number().int().min(1).max(5e4).describe("Bonus points to award (max 50,000 per call)"),
|
|
802
|
-
reason: z.string().max(200).describe("Human-readable reason, logged to end_user_activity")
|
|
803
|
-
},
|
|
804
|
-
annotations: annotationsFor("award_bonus_points")
|
|
835
|
+
external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
|
|
836
|
+
points: z.number().int().min(1).max(5e4).describe("Bonus points to award (max 50,000 per call)"),
|
|
837
|
+
reason: z.string().max(200).describe("Human-readable reason, logged to end_user_activity")
|
|
805
838
|
},
|
|
839
|
+
rewardsMeta("award_bonus_points"),
|
|
806
840
|
async ({ external_user_id, points, reason }) => ({
|
|
807
841
|
content: [{
|
|
808
842
|
type: "text",
|
|
@@ -818,18 +852,15 @@ function createMushiServer(config) {
|
|
|
818
852
|
}]
|
|
819
853
|
})
|
|
820
854
|
);
|
|
821
|
-
|
|
855
|
+
server.tool(
|
|
822
856
|
"set_tier",
|
|
857
|
+
rewardsMeta("set_tier").title,
|
|
823
858
|
{
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
|
|
828
|
-
tier_slug: z.string().describe('Tier slug to assign, e.g. "champion", "contributor", "explorer"'),
|
|
829
|
-
reason: z.string().max(200).optional().describe("Optional reason for manual override")
|
|
830
|
-
},
|
|
831
|
-
annotations: annotationsFor("set_tier")
|
|
859
|
+
external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
|
|
860
|
+
tier_slug: z.string().describe('Tier slug to assign, e.g. "champion", "contributor", "explorer"'),
|
|
861
|
+
reason: z.string().max(200).optional().describe("Optional reason for manual override")
|
|
832
862
|
},
|
|
863
|
+
rewardsMeta("set_tier"),
|
|
833
864
|
async ({ external_user_id, tier_slug, reason }) => ({
|
|
834
865
|
content: [{
|
|
835
866
|
type: "text",
|
|
@@ -845,6 +876,34 @@ function createMushiServer(config) {
|
|
|
845
876
|
}]
|
|
846
877
|
})
|
|
847
878
|
);
|
|
879
|
+
server.resource(
|
|
880
|
+
"privacy_status",
|
|
881
|
+
"privacy://status",
|
|
882
|
+
{
|
|
883
|
+
description: "Returns the privacy posture for this project: storage region, LLM provider, whether BYOK is configured, data retention window, and last audit timestamp."
|
|
884
|
+
},
|
|
885
|
+
async () => ({
|
|
886
|
+
contents: [{
|
|
887
|
+
uri: "privacy://status",
|
|
888
|
+
mimeType: "application/json",
|
|
889
|
+
text: JSON.stringify(await apiCall("/v1/admin/privacy/status"), null, 2)
|
|
890
|
+
}]
|
|
891
|
+
})
|
|
892
|
+
);
|
|
893
|
+
server.resource(
|
|
894
|
+
"evolution_history",
|
|
895
|
+
"evolution://history",
|
|
896
|
+
{
|
|
897
|
+
description: "Returns the project's last 30 days of judge scores, prompt promotions, fixed-bug count, and lesson inductions. Agents can read this to see whether the loop is converging (rising judge scores, falling recurrence) or stalling."
|
|
898
|
+
},
|
|
899
|
+
async () => ({
|
|
900
|
+
contents: [{
|
|
901
|
+
uri: "evolution://history",
|
|
902
|
+
mimeType: "application/json",
|
|
903
|
+
text: JSON.stringify(await apiCall("/v1/admin/evolution/history"), null, 2)
|
|
904
|
+
}]
|
|
905
|
+
})
|
|
906
|
+
);
|
|
848
907
|
server.resource(
|
|
849
908
|
"project_integration_health",
|
|
850
909
|
"project://integration-health",
|
|
@@ -945,6 +1004,15 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
|
|
|
945
1004
|
}]
|
|
946
1005
|
})
|
|
947
1006
|
);
|
|
1007
|
+
const grantedScopes = config.scopes;
|
|
1008
|
+
if (grantedScopes !== void 0) {
|
|
1009
|
+
const toolRegistry = server._registeredTools;
|
|
1010
|
+
for (const spec of TOOL_CATALOG) {
|
|
1011
|
+
if (!grantedScopes.includes(spec.scope)) {
|
|
1012
|
+
toolRegistry[spec.name]?.remove();
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
948
1016
|
return server;
|
|
949
1017
|
}
|
|
950
1018
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "MCP server exposing Mushi Mushi reports to coding agents",
|
|
6
6
|
"type": "module",
|
|
@@ -23,18 +23,30 @@
|
|
|
23
23
|
"CODE_OF_CONDUCT.md",
|
|
24
24
|
"SECURITY.md"
|
|
25
25
|
],
|
|
26
|
+
"scripts": {
|
|
27
|
+
"build": "tsup",
|
|
28
|
+
"clean:types": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
29
|
+
"dev": "tsup --watch",
|
|
30
|
+
"lint": "eslint src/",
|
|
31
|
+
"test": "vitest run",
|
|
32
|
+
"test:smoke": "node scripts/smoke-stdio.mjs",
|
|
33
|
+
"test:localhost": "node scripts/localhost-e2e.mjs",
|
|
34
|
+
"demo": "node scripts/demo-terminal-config.mjs",
|
|
35
|
+
"inspector": "npx --yes @modelcontextprotocol/inspector@latest node ./dist/index.js",
|
|
36
|
+
"typecheck": "tsc --noEmit"
|
|
37
|
+
},
|
|
26
38
|
"dependencies": {
|
|
27
39
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
28
|
-
"
|
|
29
|
-
"
|
|
40
|
+
"@mushi-mushi/core": "workspace:^",
|
|
41
|
+
"zod": "^4.4.2"
|
|
30
42
|
},
|
|
31
43
|
"devDependencies": {
|
|
44
|
+
"@mushi-mushi/eslint-config": "workspace:*",
|
|
32
45
|
"@types/node": "^22.19.17",
|
|
33
46
|
"eslint": "^10.3.0",
|
|
34
47
|
"tsup": "^8.5.1",
|
|
35
48
|
"typescript": "^6.0.3",
|
|
36
|
-
"vitest": "^4.1.5"
|
|
37
|
-
"@mushi-mushi/eslint-config": "0.0.0"
|
|
49
|
+
"vitest": "^4.1.5"
|
|
38
50
|
},
|
|
39
51
|
"repository": {
|
|
40
52
|
"type": "git",
|
|
@@ -73,17 +85,5 @@
|
|
|
73
85
|
],
|
|
74
86
|
"engines": {
|
|
75
87
|
"node": ">=20"
|
|
76
|
-
},
|
|
77
|
-
"scripts": {
|
|
78
|
-
"build": "tsup",
|
|
79
|
-
"clean:types": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
80
|
-
"dev": "tsup --watch",
|
|
81
|
-
"lint": "eslint src/",
|
|
82
|
-
"test": "vitest run",
|
|
83
|
-
"test:smoke": "node scripts/smoke-stdio.mjs",
|
|
84
|
-
"test:localhost": "node scripts/localhost-e2e.mjs",
|
|
85
|
-
"demo": "node scripts/demo-terminal-config.mjs",
|
|
86
|
-
"inspector": "npx --yes @modelcontextprotocol/inspector@latest node ./dist/index.js",
|
|
87
|
-
"typecheck": "tsc --noEmit"
|
|
88
88
|
}
|
|
89
|
-
}
|
|
89
|
+
}
|