@mushi-mushi/mcp 0.8.0 → 0.10.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 +429 -115
- 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,88 @@ 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."
|
|
218
|
+
}
|
|
219
|
+
];
|
|
220
|
+
var TDD_TOOL_CATALOG = [
|
|
221
|
+
{
|
|
222
|
+
name: "map_user_stories",
|
|
223
|
+
title: "Map user stories from live app",
|
|
224
|
+
description: 'Crawl a live application URL with Firecrawl/Browserbase and ask Claude to draft an inventory.yaml with pages and user stories. Creates a story_map_run row for progress tracking, then writes an inventory_proposals row (source=live_crawl). Optionally dispatches a Cursor Cloud agent to refine the draft and open a PR. Returns { runId, status: "pending" } immediately \u2014 poll get_map_run_status for progress.',
|
|
225
|
+
scope: "mcp:write",
|
|
226
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
227
|
+
useCase: "Map the user stories in my live app automatically without writing YAML by hand."
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: "get_map_run_status",
|
|
231
|
+
title: "Story map run status",
|
|
232
|
+
description: "Get the status and results of a story_map_run (pending \u2192 running \u2192 completed/failed). Returns pages_crawled, proposal_id (once done), and cursor_pr_url if Cursor Cloud refined the draft.",
|
|
233
|
+
scope: "mcp:read",
|
|
234
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
235
|
+
useCase: "Is my story mapping crawl done yet?"
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
name: "generate_tdd_from_story",
|
|
239
|
+
title: "Generate TDD test from user story",
|
|
240
|
+
description: "Given a user story id (from the accepted inventory), ask Claude to write a full Playwright TypeScript test. Inserts a qa_stories row (source=test_gen_from_story) with approval_status driven by automation_mode. Optionally opens a draft GitHub PR. Returns { qaStoryId, prUrl, approvalStatus, needsHumanReview }.",
|
|
241
|
+
scope: "mcp:write",
|
|
242
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
243
|
+
useCase: "Generate a Playwright test for this user story."
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: "improve_qa_story",
|
|
247
|
+
title: "PDCA auto-improve a failing QA story",
|
|
248
|
+
description: "Trigger the PDCA improver for a specific project. Finds recently failed qa_story_runs and uses Claude to write improved test scripts. New tests are created with source=pdca and approval gated by the original story's automation_mode.",
|
|
249
|
+
scope: "mcp:write",
|
|
250
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
251
|
+
useCase: "Fix my failing QA tests automatically."
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
name: "run_qa_story",
|
|
255
|
+
title: "Trigger a manual QA story run",
|
|
256
|
+
description: 'Queue a manual run for an enabled + approved qa_story. Returns the run id immediately; poll qa_story_runs or use get_report_detail for progress. Equivalent to "Run now" in the console.',
|
|
257
|
+
scope: "mcp:write",
|
|
258
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
259
|
+
useCase: "Run the login flow test right now."
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
name: "list_byok_keys",
|
|
263
|
+
title: "List BYOK API key pool",
|
|
264
|
+
description: "List all BYOK API keys for the project, grouped by provider. Shows label, priority, status, and cooldown. Never returns the raw key value \u2014 only metadata. Use this to see which keys are active or exhausted.",
|
|
265
|
+
scope: "mcp:read",
|
|
266
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
267
|
+
useCase: "Which API keys are active and which are rate-limited?"
|
|
268
|
+
},
|
|
269
|
+
{
|
|
270
|
+
name: "add_byok_key",
|
|
271
|
+
title: "Add a BYOK API key",
|
|
272
|
+
description: "Add a new API key to the project's BYOK pool for a given provider (anthropic, openai, firecrawl, browserbase, cursor). Specify label and priority for ordering. The key is stored encrypted in Supabase Vault.",
|
|
273
|
+
scope: "mcp:write",
|
|
274
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
275
|
+
useCase: "Add a backup Anthropic key to the pool."
|
|
276
|
+
},
|
|
277
|
+
{
|
|
278
|
+
name: "list_pending_review_stories",
|
|
279
|
+
title: "List QA stories pending review",
|
|
280
|
+
description: "Get the queue of TDD tests that were auto-generated and are waiting for human approval before they run in the QA schedule.",
|
|
281
|
+
scope: "mcp:read",
|
|
282
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
283
|
+
useCase: "What TDD tests need my approval today?"
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
name: "approve_qa_story",
|
|
287
|
+
title: "Approve or reject a pending QA story",
|
|
288
|
+
description: "Approve or reject a qa_story that is in pending_review. Approved stories are enabled in the QA schedule immediately.",
|
|
289
|
+
scope: "mcp:write",
|
|
290
|
+
hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
|
|
291
|
+
useCase: "Approve this auto-generated test."
|
|
201
292
|
}
|
|
202
293
|
];
|
|
203
294
|
|
|
@@ -217,7 +308,9 @@ var MushiApiError = class extends Error {
|
|
|
217
308
|
function createMushiServer(config) {
|
|
218
309
|
const { version, apiEndpoint, apiKey, projectId } = config;
|
|
219
310
|
const doFetch = config.fetch ?? globalThis.fetch;
|
|
220
|
-
|
|
311
|
+
if (config.scopes !== void 0 && config.scopes.length === 0) {
|
|
312
|
+
return new McpServer({ name: "mushi-mushi", version });
|
|
313
|
+
}
|
|
221
314
|
async function apiCall(path, options) {
|
|
222
315
|
const res = await doFetch(`${apiEndpoint}${path}`, {
|
|
223
316
|
...options,
|
|
@@ -263,12 +356,19 @@ function createMushiServer(config) {
|
|
|
263
356
|
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
264
357
|
};
|
|
265
358
|
}
|
|
359
|
+
function jsonResult(value) {
|
|
360
|
+
return {
|
|
361
|
+
content: [{ type: "text", text: JSON.stringify(value, null, 2) }],
|
|
362
|
+
structuredContent: value
|
|
363
|
+
};
|
|
364
|
+
}
|
|
266
365
|
const server = new McpServer({
|
|
267
366
|
name: "mushi-mushi",
|
|
268
367
|
version
|
|
269
368
|
});
|
|
270
|
-
|
|
271
|
-
|
|
369
|
+
const ALL_TOOL_CATALOG = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG];
|
|
370
|
+
function annotationsFor(name, catalog = ALL_TOOL_CATALOG) {
|
|
371
|
+
const spec = catalog.find((t) => t.name === name);
|
|
272
372
|
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
273
373
|
const a = {
|
|
274
374
|
title: spec.title,
|
|
@@ -279,33 +379,17 @@ function createMushiServer(config) {
|
|
|
279
379
|
if (spec.hints.openWorld !== void 0) a.openWorldHint = spec.hints.openWorld;
|
|
280
380
|
return a;
|
|
281
381
|
}
|
|
282
|
-
function descOf(name) {
|
|
283
|
-
const spec =
|
|
382
|
+
function descOf(name, catalog = ALL_TOOL_CATALOG) {
|
|
383
|
+
const spec = catalog.find((t) => t.name === name);
|
|
284
384
|
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
285
385
|
return spec.description;
|
|
286
386
|
}
|
|
287
|
-
function titleOf(name) {
|
|
288
|
-
const spec =
|
|
387
|
+
function titleOf(name, catalog = ALL_TOOL_CATALOG) {
|
|
388
|
+
const spec = catalog.find((t) => t.name === name);
|
|
289
389
|
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
290
390
|
return spec.title;
|
|
291
391
|
}
|
|
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(
|
|
392
|
+
server.registerTool(
|
|
309
393
|
"get_recent_reports",
|
|
310
394
|
{
|
|
311
395
|
title: titleOf("get_recent_reports"),
|
|
@@ -317,12 +401,9 @@ function createMushiServer(config) {
|
|
|
317
401
|
severity: z.string().optional().describe("Filter by severity: critical, high, medium, low"),
|
|
318
402
|
limit: z.number().optional().describe("Max reports to return (default 20, max 100)")
|
|
319
403
|
},
|
|
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
404
|
outputSchema: {
|
|
324
|
-
reports: z.array(z.
|
|
325
|
-
total: z.number()
|
|
405
|
+
reports: z.array(z.unknown()),
|
|
406
|
+
total: z.number()
|
|
326
407
|
}
|
|
327
408
|
},
|
|
328
409
|
async (args) => {
|
|
@@ -332,13 +413,10 @@ function createMushiServer(config) {
|
|
|
332
413
|
if (args.severity) params.set("severity", args.severity);
|
|
333
414
|
params.set("limit", String(Math.min(args.limit ?? 20, 100)));
|
|
334
415
|
const data = await apiCall(`/v1/admin/reports?${params}`);
|
|
335
|
-
return jsonResult(
|
|
336
|
-
reports: data.reports ?? [],
|
|
337
|
-
total: data.total ?? 0
|
|
338
|
-
});
|
|
416
|
+
return jsonResult(data);
|
|
339
417
|
}
|
|
340
418
|
);
|
|
341
|
-
|
|
419
|
+
server.registerTool(
|
|
342
420
|
"get_report_detail",
|
|
343
421
|
{
|
|
344
422
|
title: titleOf("get_report_detail"),
|
|
@@ -348,7 +426,7 @@ function createMushiServer(config) {
|
|
|
348
426
|
},
|
|
349
427
|
async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}`))
|
|
350
428
|
);
|
|
351
|
-
|
|
429
|
+
server.registerTool(
|
|
352
430
|
"search_reports",
|
|
353
431
|
{
|
|
354
432
|
title: titleOf("search_reports"),
|
|
@@ -360,7 +438,7 @@ function createMushiServer(config) {
|
|
|
360
438
|
threshold: z.number().optional().describe("Similarity threshold 0..1, default 0.2")
|
|
361
439
|
},
|
|
362
440
|
outputSchema: {
|
|
363
|
-
results: z.array(z.
|
|
441
|
+
results: z.array(z.unknown())
|
|
364
442
|
}
|
|
365
443
|
},
|
|
366
444
|
async (args) => {
|
|
@@ -373,10 +451,10 @@ function createMushiServer(config) {
|
|
|
373
451
|
...projectId ? { projectId } : {}
|
|
374
452
|
})
|
|
375
453
|
});
|
|
376
|
-
return jsonResult(
|
|
454
|
+
return jsonResult(data);
|
|
377
455
|
}
|
|
378
456
|
);
|
|
379
|
-
|
|
457
|
+
server.registerTool(
|
|
380
458
|
"get_similar_bugs",
|
|
381
459
|
{
|
|
382
460
|
title: titleOf("get_similar_bugs"),
|
|
@@ -385,9 +463,6 @@ function createMushiServer(config) {
|
|
|
385
463
|
inputSchema: {
|
|
386
464
|
query: z.string().describe("Component name, page path, or bug description"),
|
|
387
465
|
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
466
|
}
|
|
392
467
|
},
|
|
393
468
|
async (args) => {
|
|
@@ -400,10 +475,10 @@ function createMushiServer(config) {
|
|
|
400
475
|
...projectId ? { projectId } : {}
|
|
401
476
|
})
|
|
402
477
|
});
|
|
403
|
-
return
|
|
478
|
+
return jsonText(data);
|
|
404
479
|
}
|
|
405
480
|
);
|
|
406
|
-
|
|
481
|
+
server.registerTool(
|
|
407
482
|
"get_fix_context",
|
|
408
483
|
{
|
|
409
484
|
title: titleOf("get_fix_context"),
|
|
@@ -422,7 +497,7 @@ function createMushiServer(config) {
|
|
|
422
497
|
});
|
|
423
498
|
}
|
|
424
499
|
);
|
|
425
|
-
|
|
500
|
+
server.registerTool(
|
|
426
501
|
"get_fix_timeline",
|
|
427
502
|
{
|
|
428
503
|
title: titleOf("get_fix_timeline"),
|
|
@@ -432,7 +507,7 @@ function createMushiServer(config) {
|
|
|
432
507
|
},
|
|
433
508
|
async (args) => jsonText(await apiCall(`/v1/admin/fixes/${args.fixId}/timeline`))
|
|
434
509
|
);
|
|
435
|
-
|
|
510
|
+
server.registerTool(
|
|
436
511
|
"get_blast_radius",
|
|
437
512
|
{
|
|
438
513
|
title: titleOf("get_blast_radius"),
|
|
@@ -442,7 +517,7 @@ function createMushiServer(config) {
|
|
|
442
517
|
},
|
|
443
518
|
async (args) => jsonText(await apiCall(`/v1/admin/graph/blast-radius/${args.nodeId}`))
|
|
444
519
|
);
|
|
445
|
-
|
|
520
|
+
server.registerTool(
|
|
446
521
|
"get_knowledge_graph",
|
|
447
522
|
{
|
|
448
523
|
title: titleOf("get_knowledge_graph"),
|
|
@@ -461,7 +536,7 @@ function createMushiServer(config) {
|
|
|
461
536
|
return jsonText(await apiCall(`/v1/admin/graph/traverse?${params}`));
|
|
462
537
|
}
|
|
463
538
|
);
|
|
464
|
-
|
|
539
|
+
server.registerTool(
|
|
465
540
|
"graph_neighborhood",
|
|
466
541
|
{
|
|
467
542
|
title: titleOf("graph_neighborhood"),
|
|
@@ -480,7 +555,7 @@ function createMushiServer(config) {
|
|
|
480
555
|
return jsonText(await apiCall(`/v1/admin/graph/traverse?${params}`));
|
|
481
556
|
}
|
|
482
557
|
);
|
|
483
|
-
|
|
558
|
+
server.registerTool(
|
|
484
559
|
"graph_node_status",
|
|
485
560
|
{
|
|
486
561
|
title: titleOf("graph_node_status"),
|
|
@@ -490,7 +565,7 @@ function createMushiServer(config) {
|
|
|
490
565
|
},
|
|
491
566
|
async (args) => jsonText(await apiCall(`/v1/admin/graph/node/${args.nodeId}`))
|
|
492
567
|
);
|
|
493
|
-
|
|
568
|
+
server.registerTool(
|
|
494
569
|
"inventory_get",
|
|
495
570
|
{
|
|
496
571
|
title: titleOf("inventory_get"),
|
|
@@ -506,7 +581,7 @@ function createMushiServer(config) {
|
|
|
506
581
|
return jsonText(await apiCall(`/v1/admin/inventory/${pid}`));
|
|
507
582
|
}
|
|
508
583
|
);
|
|
509
|
-
|
|
584
|
+
server.registerTool(
|
|
510
585
|
"inventory_diff",
|
|
511
586
|
{
|
|
512
587
|
title: titleOf("inventory_diff"),
|
|
@@ -525,7 +600,7 @@ function createMushiServer(config) {
|
|
|
525
600
|
return jsonText(await apiCall(`/v1/admin/inventory/${pid}/diff?${q}`));
|
|
526
601
|
}
|
|
527
602
|
);
|
|
528
|
-
|
|
603
|
+
server.registerTool(
|
|
529
604
|
"inventory_findings",
|
|
530
605
|
{
|
|
531
606
|
title: titleOf("inventory_findings"),
|
|
@@ -547,7 +622,7 @@ function createMushiServer(config) {
|
|
|
547
622
|
return jsonText(await apiCall(`/v1/admin/inventory/${pid}/findings${suffix}`));
|
|
548
623
|
}
|
|
549
624
|
);
|
|
550
|
-
|
|
625
|
+
server.registerTool(
|
|
551
626
|
"fix_suggest",
|
|
552
627
|
{
|
|
553
628
|
title: titleOf("fix_suggest"),
|
|
@@ -568,7 +643,7 @@ function createMushiServer(config) {
|
|
|
568
643
|
});
|
|
569
644
|
}
|
|
570
645
|
);
|
|
571
|
-
|
|
646
|
+
server.registerTool(
|
|
572
647
|
"run_nl_query",
|
|
573
648
|
{
|
|
574
649
|
title: titleOf("run_nl_query"),
|
|
@@ -584,7 +659,44 @@ function createMushiServer(config) {
|
|
|
584
659
|
return jsonText(data);
|
|
585
660
|
}
|
|
586
661
|
);
|
|
587
|
-
|
|
662
|
+
server.registerTool(
|
|
663
|
+
"setup_check",
|
|
664
|
+
{
|
|
665
|
+
title: titleOf("setup_check"),
|
|
666
|
+
description: descOf("setup_check"),
|
|
667
|
+
annotations: annotationsFor("setup_check"),
|
|
668
|
+
inputSchema: {
|
|
669
|
+
projectId: z.string().optional().describe(
|
|
670
|
+
"Project UUID to check. Falls back to the projectId the server was initialised with."
|
|
671
|
+
)
|
|
672
|
+
}
|
|
673
|
+
},
|
|
674
|
+
async (args) => {
|
|
675
|
+
const resolvedId = args.projectId ?? projectId;
|
|
676
|
+
if (!resolvedId) {
|
|
677
|
+
return jsonText({
|
|
678
|
+
ok: false,
|
|
679
|
+
error: "No projectId provided and none configured on the MCP server. Pass projectId explicitly."
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
const data = await apiCall(`/v1/admin/projects/${resolvedId}/preflight`);
|
|
683
|
+
const summary = data.checks.map((c) => ({
|
|
684
|
+
check: c.key,
|
|
685
|
+
label: c.label,
|
|
686
|
+
passed: c.ready,
|
|
687
|
+
hint: c.hint,
|
|
688
|
+
fixPath: c.fixHref
|
|
689
|
+
}));
|
|
690
|
+
return jsonText({
|
|
691
|
+
ready: data.ready,
|
|
692
|
+
repoUrl: data.repoUrl ?? null,
|
|
693
|
+
checks: summary,
|
|
694
|
+
// Human-readable summary for agents that paste the result into a prompt
|
|
695
|
+
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(", ")}.`
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
);
|
|
699
|
+
server.registerTool(
|
|
588
700
|
"submit_fix_result",
|
|
589
701
|
{
|
|
590
702
|
title: titleOf("submit_fix_result"),
|
|
@@ -619,7 +731,7 @@ function createMushiServer(config) {
|
|
|
619
731
|
return jsonText({ ok: true, fixId: created.fixId });
|
|
620
732
|
}
|
|
621
733
|
);
|
|
622
|
-
|
|
734
|
+
server.registerTool(
|
|
623
735
|
"dispatch_fix",
|
|
624
736
|
{
|
|
625
737
|
title: titleOf("dispatch_fix"),
|
|
@@ -627,20 +739,13 @@ function createMushiServer(config) {
|
|
|
627
739
|
annotations: annotationsFor("dispatch_fix"),
|
|
628
740
|
inputSchema: {
|
|
629
741
|
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").'),
|
|
742
|
+
agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
|
|
633
743
|
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
744
|
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
745
|
},
|
|
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
746
|
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")
|
|
747
|
+
fixId: z.string(),
|
|
748
|
+
status: z.string()
|
|
644
749
|
}
|
|
645
750
|
},
|
|
646
751
|
async (args, extra) => {
|
|
@@ -658,31 +763,20 @@ function createMushiServer(config) {
|
|
|
658
763
|
} catch {
|
|
659
764
|
}
|
|
660
765
|
}
|
|
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 } : {}
|
|
766
|
+
const data = await apiCall("/v1/admin/fixes/dispatch", {
|
|
767
|
+
method: "POST",
|
|
768
|
+
headers: args.idempotencyKey ? { "Idempotency-Key": args.idempotencyKey } : void 0,
|
|
769
|
+
body: JSON.stringify({
|
|
770
|
+
reportId: args.reportId,
|
|
771
|
+
agent: args.agent,
|
|
772
|
+
inventoryActionNodeId: args.inventoryActionNodeId,
|
|
773
|
+
...projectId ? { projectId } : {}
|
|
774
|
+
})
|
|
682
775
|
});
|
|
776
|
+
return jsonResult(data);
|
|
683
777
|
}
|
|
684
778
|
);
|
|
685
|
-
|
|
779
|
+
server.registerTool(
|
|
686
780
|
"trigger_judge",
|
|
687
781
|
{
|
|
688
782
|
title: titleOf("trigger_judge"),
|
|
@@ -704,7 +798,7 @@ function createMushiServer(config) {
|
|
|
704
798
|
return jsonText(data);
|
|
705
799
|
}
|
|
706
800
|
);
|
|
707
|
-
|
|
801
|
+
server.registerTool(
|
|
708
802
|
"test_gen_from_report",
|
|
709
803
|
{
|
|
710
804
|
title: titleOf("test_gen_from_report"),
|
|
@@ -725,7 +819,7 @@ function createMushiServer(config) {
|
|
|
725
819
|
return jsonText(data);
|
|
726
820
|
}
|
|
727
821
|
);
|
|
728
|
-
|
|
822
|
+
server.registerTool(
|
|
729
823
|
"transition_status",
|
|
730
824
|
{
|
|
731
825
|
title: titleOf("transition_status"),
|
|
@@ -745,6 +839,26 @@ function createMushiServer(config) {
|
|
|
745
839
|
return jsonText(data);
|
|
746
840
|
}
|
|
747
841
|
);
|
|
842
|
+
server.registerTool(
|
|
843
|
+
"setup_repo_for_mushi",
|
|
844
|
+
{
|
|
845
|
+
title: titleOf("setup_repo_for_mushi"),
|
|
846
|
+
description: descOf("setup_repo_for_mushi"),
|
|
847
|
+
annotations: annotationsFor("setup_repo_for_mushi"),
|
|
848
|
+
inputSchema: {
|
|
849
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project")
|
|
850
|
+
}
|
|
851
|
+
},
|
|
852
|
+
async (args) => {
|
|
853
|
+
const pid = args.projectId ?? projectId;
|
|
854
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for setup_repo_for_mushi");
|
|
855
|
+
const data = await apiCall(`/v1/admin/projects/${pid}/repo/bootstrap`, {
|
|
856
|
+
method: "POST",
|
|
857
|
+
body: JSON.stringify({})
|
|
858
|
+
});
|
|
859
|
+
return jsonText(data);
|
|
860
|
+
}
|
|
861
|
+
);
|
|
748
862
|
server.resource(
|
|
749
863
|
"project_stats",
|
|
750
864
|
"project://stats",
|
|
@@ -769,17 +883,15 @@ function createMushiServer(config) {
|
|
|
769
883
|
contents: [{ uri: "project://dashboard", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2) }]
|
|
770
884
|
})
|
|
771
885
|
);
|
|
772
|
-
|
|
886
|
+
const rewardsMeta = (name) => annotationsFor(name);
|
|
887
|
+
server.tool(
|
|
773
888
|
"list_top_contributors",
|
|
889
|
+
rewardsMeta("list_top_contributors").title,
|
|
774
890
|
{
|
|
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")
|
|
891
|
+
limit: z.number().int().min(1).max(100).optional().default(10).describe("Max rows to return (default 10, max 100)"),
|
|
892
|
+
range: z.enum(["30d", "90d", "all"]).optional().default("30d").describe("Time window for points calculation")
|
|
782
893
|
},
|
|
894
|
+
rewardsMeta("list_top_contributors"),
|
|
783
895
|
async ({ limit, range }) => ({
|
|
784
896
|
content: [{
|
|
785
897
|
type: "text",
|
|
@@ -791,18 +903,15 @@ function createMushiServer(config) {
|
|
|
791
903
|
}]
|
|
792
904
|
})
|
|
793
905
|
);
|
|
794
|
-
|
|
906
|
+
server.tool(
|
|
795
907
|
"award_bonus_points",
|
|
908
|
+
rewardsMeta("award_bonus_points").title,
|
|
796
909
|
{
|
|
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")
|
|
910
|
+
external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
|
|
911
|
+
points: z.number().int().min(1).max(5e4).describe("Bonus points to award (max 50,000 per call)"),
|
|
912
|
+
reason: z.string().max(200).describe("Human-readable reason, logged to end_user_activity")
|
|
805
913
|
},
|
|
914
|
+
rewardsMeta("award_bonus_points"),
|
|
806
915
|
async ({ external_user_id, points, reason }) => ({
|
|
807
916
|
content: [{
|
|
808
917
|
type: "text",
|
|
@@ -818,18 +927,15 @@ function createMushiServer(config) {
|
|
|
818
927
|
}]
|
|
819
928
|
})
|
|
820
929
|
);
|
|
821
|
-
|
|
930
|
+
server.tool(
|
|
822
931
|
"set_tier",
|
|
932
|
+
rewardsMeta("set_tier").title,
|
|
823
933
|
{
|
|
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")
|
|
934
|
+
external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
|
|
935
|
+
tier_slug: z.string().describe('Tier slug to assign, e.g. "champion", "contributor", "explorer"'),
|
|
936
|
+
reason: z.string().max(200).optional().describe("Optional reason for manual override")
|
|
832
937
|
},
|
|
938
|
+
rewardsMeta("set_tier"),
|
|
833
939
|
async ({ external_user_id, tier_slug, reason }) => ({
|
|
834
940
|
content: [{
|
|
835
941
|
type: "text",
|
|
@@ -845,6 +951,34 @@ function createMushiServer(config) {
|
|
|
845
951
|
}]
|
|
846
952
|
})
|
|
847
953
|
);
|
|
954
|
+
server.resource(
|
|
955
|
+
"privacy_status",
|
|
956
|
+
"privacy://status",
|
|
957
|
+
{
|
|
958
|
+
description: "Returns the privacy posture for this project: storage region, LLM provider, whether BYOK is configured, data retention window, and last audit timestamp."
|
|
959
|
+
},
|
|
960
|
+
async () => ({
|
|
961
|
+
contents: [{
|
|
962
|
+
uri: "privacy://status",
|
|
963
|
+
mimeType: "application/json",
|
|
964
|
+
text: JSON.stringify(await apiCall("/v1/admin/privacy/status"), null, 2)
|
|
965
|
+
}]
|
|
966
|
+
})
|
|
967
|
+
);
|
|
968
|
+
server.resource(
|
|
969
|
+
"evolution_history",
|
|
970
|
+
"evolution://history",
|
|
971
|
+
{
|
|
972
|
+
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."
|
|
973
|
+
},
|
|
974
|
+
async () => ({
|
|
975
|
+
contents: [{
|
|
976
|
+
uri: "evolution://history",
|
|
977
|
+
mimeType: "application/json",
|
|
978
|
+
text: JSON.stringify(await apiCall("/v1/admin/evolution/history"), null, 2)
|
|
979
|
+
}]
|
|
980
|
+
})
|
|
981
|
+
);
|
|
848
982
|
server.resource(
|
|
849
983
|
"project_integration_health",
|
|
850
984
|
"project://integration-health",
|
|
@@ -945,6 +1079,186 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
|
|
|
945
1079
|
}]
|
|
946
1080
|
})
|
|
947
1081
|
);
|
|
1082
|
+
server.registerTool(
|
|
1083
|
+
"map_user_stories",
|
|
1084
|
+
{
|
|
1085
|
+
title: titleOf("map_user_stories", TDD_TOOL_CATALOG),
|
|
1086
|
+
description: descOf("map_user_stories", TDD_TOOL_CATALOG),
|
|
1087
|
+
annotations: annotationsFor("map_user_stories", TDD_TOOL_CATALOG),
|
|
1088
|
+
inputSchema: {
|
|
1089
|
+
projectId: z.string().describe("Project id to map stories for"),
|
|
1090
|
+
baseUrl: z.string().url().describe("Live app URL to crawl"),
|
|
1091
|
+
maxPages: z.number().int().min(1).max(50).optional().describe("Max pages to crawl (default 20)"),
|
|
1092
|
+
provider: z.enum(["firecrawl", "browserbase"]).optional().describe("Crawl provider (default: firecrawl)"),
|
|
1093
|
+
cursorCloudRefine: z.boolean().optional().describe("Dispatch Cursor Cloud agent to refine and open a PR")
|
|
1094
|
+
}
|
|
1095
|
+
},
|
|
1096
|
+
async ({ projectId: projectId2, baseUrl, maxPages, provider, cursorCloudRefine }) => {
|
|
1097
|
+
if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
|
|
1098
|
+
const data = await apiCall(
|
|
1099
|
+
`/v1/admin/inventory/${projectId2}/map-from-live`,
|
|
1100
|
+
{ method: "POST", body: JSON.stringify({ base_url: baseUrl, max_pages: maxPages, provider, cursor_cloud_refine: cursorCloudRefine }) }
|
|
1101
|
+
);
|
|
1102
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1103
|
+
}
|
|
1104
|
+
);
|
|
1105
|
+
server.registerTool(
|
|
1106
|
+
"get_map_run_status",
|
|
1107
|
+
{
|
|
1108
|
+
title: titleOf("get_map_run_status", TDD_TOOL_CATALOG),
|
|
1109
|
+
description: descOf("get_map_run_status", TDD_TOOL_CATALOG),
|
|
1110
|
+
annotations: annotationsFor("get_map_run_status", TDD_TOOL_CATALOG),
|
|
1111
|
+
inputSchema: {
|
|
1112
|
+
projectId: z.string().describe("Project id")
|
|
1113
|
+
}
|
|
1114
|
+
},
|
|
1115
|
+
async ({ projectId: projectId2 }) => {
|
|
1116
|
+
const data = await apiCall(`/v1/admin/inventory/${projectId2}/map-runs`);
|
|
1117
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1118
|
+
}
|
|
1119
|
+
);
|
|
1120
|
+
server.registerTool(
|
|
1121
|
+
"generate_tdd_from_story",
|
|
1122
|
+
{
|
|
1123
|
+
title: titleOf("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
1124
|
+
description: descOf("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
1125
|
+
annotations: annotationsFor("generate_tdd_from_story", TDD_TOOL_CATALOG),
|
|
1126
|
+
inputSchema: {
|
|
1127
|
+
projectId: z.string().describe("Project id"),
|
|
1128
|
+
storyNodeId: z.string().describe("User story id slug from the accepted inventory"),
|
|
1129
|
+
automationMode: z.enum(["auto", "review", "approve"]).optional().describe("Gate mode for the generated test (default: review)"),
|
|
1130
|
+
baseUrl: z.string().url().optional().describe("Override the app base URL"),
|
|
1131
|
+
openPr: z.boolean().optional().describe("Open a draft GitHub PR (default: true)")
|
|
1132
|
+
}
|
|
1133
|
+
},
|
|
1134
|
+
async ({ projectId: projectId2, storyNodeId, automationMode, baseUrl, openPr }) => {
|
|
1135
|
+
if (!projectId2) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required");
|
|
1136
|
+
const data = await apiCall(
|
|
1137
|
+
`/v1/admin/inventory/${projectId2}/stories/${storyNodeId}/generate-test`,
|
|
1138
|
+
{ method: "POST", body: JSON.stringify({ automation_mode: automationMode, base_url: baseUrl, open_pr: openPr }) }
|
|
1139
|
+
);
|
|
1140
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1141
|
+
}
|
|
1142
|
+
);
|
|
1143
|
+
server.registerTool(
|
|
1144
|
+
"improve_qa_story",
|
|
1145
|
+
{
|
|
1146
|
+
title: titleOf("improve_qa_story", TDD_TOOL_CATALOG),
|
|
1147
|
+
description: descOf("improve_qa_story", TDD_TOOL_CATALOG),
|
|
1148
|
+
annotations: annotationsFor("improve_qa_story", TDD_TOOL_CATALOG),
|
|
1149
|
+
inputSchema: {
|
|
1150
|
+
projectId: z.string().optional().describe("Project id (omit to run across all projects)")
|
|
1151
|
+
}
|
|
1152
|
+
},
|
|
1153
|
+
async ({ projectId: projectId2 }) => {
|
|
1154
|
+
const data = await apiCall(
|
|
1155
|
+
"/v1/admin/pdca/improve-qa-stories",
|
|
1156
|
+
{ method: "POST", body: JSON.stringify({ project_id: projectId2 }) }
|
|
1157
|
+
);
|
|
1158
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1159
|
+
}
|
|
1160
|
+
);
|
|
1161
|
+
server.registerTool(
|
|
1162
|
+
"run_qa_story",
|
|
1163
|
+
{
|
|
1164
|
+
title: titleOf("run_qa_story", TDD_TOOL_CATALOG),
|
|
1165
|
+
description: descOf("run_qa_story", TDD_TOOL_CATALOG),
|
|
1166
|
+
annotations: annotationsFor("run_qa_story", TDD_TOOL_CATALOG),
|
|
1167
|
+
inputSchema: {
|
|
1168
|
+
projectId: z.string().describe("Project id"),
|
|
1169
|
+
qaStoryId: z.string().describe("qa_story id to run")
|
|
1170
|
+
}
|
|
1171
|
+
},
|
|
1172
|
+
async ({ projectId: projectId2, qaStoryId }) => {
|
|
1173
|
+
const data = await apiCall(
|
|
1174
|
+
`/v1/admin/projects/${projectId2}/qa-stories/${qaStoryId}/run`,
|
|
1175
|
+
{ method: "POST" }
|
|
1176
|
+
);
|
|
1177
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1178
|
+
}
|
|
1179
|
+
);
|
|
1180
|
+
server.registerTool(
|
|
1181
|
+
"list_byok_keys",
|
|
1182
|
+
{
|
|
1183
|
+
title: titleOf("list_byok_keys", TDD_TOOL_CATALOG),
|
|
1184
|
+
description: descOf("list_byok_keys", TDD_TOOL_CATALOG),
|
|
1185
|
+
annotations: annotationsFor("list_byok_keys", TDD_TOOL_CATALOG),
|
|
1186
|
+
inputSchema: {
|
|
1187
|
+
projectId: z.string().describe("Project id")
|
|
1188
|
+
}
|
|
1189
|
+
},
|
|
1190
|
+
async ({ projectId: projectId2 }) => {
|
|
1191
|
+
const data = await apiCall(`/v1/admin/byok/keys?project_id=${encodeURIComponent(projectId2)}`);
|
|
1192
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1193
|
+
}
|
|
1194
|
+
);
|
|
1195
|
+
server.registerTool(
|
|
1196
|
+
"add_byok_key",
|
|
1197
|
+
{
|
|
1198
|
+
title: titleOf("add_byok_key", TDD_TOOL_CATALOG),
|
|
1199
|
+
description: descOf("add_byok_key", TDD_TOOL_CATALOG),
|
|
1200
|
+
annotations: annotationsFor("add_byok_key", TDD_TOOL_CATALOG),
|
|
1201
|
+
inputSchema: {
|
|
1202
|
+
projectId: z.string().describe("Project id"),
|
|
1203
|
+
provider: z.enum(["anthropic", "openai", "firecrawl", "browserbase", "cursor"]).describe("Provider slug"),
|
|
1204
|
+
key: z.string().min(10).describe("The API key value to add"),
|
|
1205
|
+
label: z.string().optional().describe("Human-readable label for this key"),
|
|
1206
|
+
priority: z.number().int().min(1).max(999).optional().describe("Priority for ordering (lower = higher priority)")
|
|
1207
|
+
}
|
|
1208
|
+
},
|
|
1209
|
+
async ({ projectId: projectId2, provider, key, label, priority }) => {
|
|
1210
|
+
const data = await apiCall(
|
|
1211
|
+
"/v1/admin/byok/keys",
|
|
1212
|
+
{ method: "POST", body: JSON.stringify({ project_id: projectId2, provider_slug: provider, key, label, priority }) }
|
|
1213
|
+
);
|
|
1214
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1215
|
+
}
|
|
1216
|
+
);
|
|
1217
|
+
server.registerTool(
|
|
1218
|
+
"list_pending_review_stories",
|
|
1219
|
+
{
|
|
1220
|
+
title: titleOf("list_pending_review_stories", TDD_TOOL_CATALOG),
|
|
1221
|
+
description: descOf("list_pending_review_stories", TDD_TOOL_CATALOG),
|
|
1222
|
+
annotations: annotationsFor("list_pending_review_stories", TDD_TOOL_CATALOG),
|
|
1223
|
+
inputSchema: {
|
|
1224
|
+
projectId: z.string().describe("Project id")
|
|
1225
|
+
}
|
|
1226
|
+
},
|
|
1227
|
+
async ({ projectId: projectId2 }) => {
|
|
1228
|
+
const data = await apiCall(`/v1/admin/inventory/${projectId2}/stories/pending-review`);
|
|
1229
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1230
|
+
}
|
|
1231
|
+
);
|
|
1232
|
+
server.registerTool(
|
|
1233
|
+
"approve_qa_story",
|
|
1234
|
+
{
|
|
1235
|
+
title: titleOf("approve_qa_story", TDD_TOOL_CATALOG),
|
|
1236
|
+
description: descOf("approve_qa_story", TDD_TOOL_CATALOG),
|
|
1237
|
+
annotations: annotationsFor("approve_qa_story", TDD_TOOL_CATALOG),
|
|
1238
|
+
inputSchema: {
|
|
1239
|
+
projectId: z.string().describe("Project id"),
|
|
1240
|
+
qaStoryId: z.string().describe("QA story id to approve or reject"),
|
|
1241
|
+
status: z.enum(["approved", "rejected"]).describe("New approval status")
|
|
1242
|
+
}
|
|
1243
|
+
},
|
|
1244
|
+
async ({ projectId: projectId2, qaStoryId, status }) => {
|
|
1245
|
+
const data = await apiCall(
|
|
1246
|
+
`/v1/admin/inventory/${projectId2}/stories/${qaStoryId}/approval`,
|
|
1247
|
+
{ method: "PATCH", body: JSON.stringify({ status }) }
|
|
1248
|
+
);
|
|
1249
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
1250
|
+
}
|
|
1251
|
+
);
|
|
1252
|
+
const grantedScopes = config.scopes;
|
|
1253
|
+
if (grantedScopes !== void 0) {
|
|
1254
|
+
const toolRegistry = server._registeredTools;
|
|
1255
|
+
const allSpecs = [...TOOL_CATALOG, ...TDD_TOOL_CATALOG];
|
|
1256
|
+
for (const spec of allSpecs) {
|
|
1257
|
+
if (!grantedScopes.includes(spec.scope)) {
|
|
1258
|
+
toolRegistry[spec.name]?.remove();
|
|
1259
|
+
}
|
|
1260
|
+
}
|
|
1261
|
+
}
|
|
948
1262
|
return server;
|
|
949
1263
|
}
|
|
950
1264
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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
|
+
}
|