@mushi-mushi/mcp 0.5.0 → 0.8.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 CHANGED
@@ -101,6 +101,17 @@ 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
+
104
115
  ### Adding a brand-new publishable package
105
116
 
106
117
  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,6 +1,8 @@
1
1
  # @mushi-mushi/mcp
2
2
 
3
- [Model Context Protocol](https://spec.modelcontextprotocol.io/) server that exposes Mushi Mushi reports, fixes, and project state to coding agents (Claude Code, Cursor, Codex, Continue, Cline, Zed, Windsurf, and any other MCP-compatible client).
3
+ [Model Context Protocol](https://spec.modelcontextprotocol.io/) server that wires Mushi Mushi's closed-loop bug intelligence into your AI coding agent so Cursor, Claude Code, Copilot, and any other MCP-compatible client can read classified reports, query the lesson library, and dispatch AI-generated fixes directly from the editor.
4
+
5
+ **The evolutionary angle:** the lesson library (`lessons.query` tool) is the agent's institutional memory. Every bug cluster your team has ever named is available to the agent before it touches a single line of code — so it doesn't repeat the same class of mistake the last agent made. That's the "cumulative selection" loop the [main README](https://www.npmjs.com/package/mushi-mushi) describes, delivered to your IDE.
4
6
 
5
7
  > **What this is, and what it isn't**
6
8
  >
@@ -21,9 +23,9 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
21
23
  "command": "npx",
22
24
  "args": ["-y", "@mushi-mushi/mcp@latest"],
23
25
  "env": {
24
- "MUSHI_API_KEY": "key_xxx",
25
- "MUSHI_PROJECT_ID": "proj_xxx",
26
- "MUSHI_API_ENDPOINT": "https://api.mushimushi.dev"
26
+ "MUSHI_API_KEY": "mushi_xxxxxxxxxxxxxxxxxxxx",
27
+ "MUSHI_PROJECT_ID": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
28
+ "MUSHI_API_ENDPOINT": "https://<your-ref>.supabase.co/functions/v1/api"
27
29
  }
28
30
  }
29
31
  }
@@ -32,6 +34,13 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS)
32
34
 
33
35
  Restart Claude Desktop. You should see a hammer icon in the chat input — click it to see the Mushi Mushi tools.
34
36
 
37
+ > **Where do I find these values?**
38
+ > - **`MUSHI_API_KEY`**: [Admin console](https://kensaur.us/mushi-mushi/settings) → **Settings → API Keys**
39
+ > - **`MUSHI_PROJECT_ID`**: [Admin console](https://kensaur.us/mushi-mushi/projects) → click your project → copy the UUID below the project name (e.g. `542b34e0-019e-41fe-b900-7b637717bb86`)
40
+ > - **`MUSHI_API_ENDPOINT`**: [Admin console](https://kensaur.us/mushi-mushi/settings) → **Settings → API Keys** — shown alongside your key
41
+ >
42
+ > Or visit **Admin → MCP** in the console for a one-click pre-filled config snippet.
43
+
35
44
  ### 2. With Cursor
36
45
 
37
46
  In Cursor settings, open **MCP** → **Add new MCP server** and paste:
@@ -40,12 +49,22 @@ In Cursor settings, open **MCP** → **Add new MCP server** and paste:
40
49
  npx -y @mushi-mushi/mcp@latest
41
50
  ```
42
51
 
43
- Set the same three env vars (`MUSHI_API_KEY`, `MUSHI_PROJECT_ID`, optional `MUSHI_API_ENDPOINT`).
52
+ Then set the same three environment variables:
53
+
54
+ | Variable | Where to find it |
55
+ |---|---|
56
+ | `MUSHI_API_KEY` | Admin console → Settings → API Keys |
57
+ | `MUSHI_PROJECT_ID` | Admin console → Projects → click project → UUID below the name |
58
+ | `MUSHI_API_ENDPOINT` | Admin console → Settings → API Keys (shown alongside your key) |
59
+
60
+ Without `MUSHI_PROJECT_ID` the server starts but scoped tools return an empty result with a message pointing you here.
44
61
 
45
62
  ### 3. From the command line
46
63
 
47
64
  ```bash
48
- MUSHI_API_KEY=key_xxx MUSHI_PROJECT_ID=proj_xxx npx -y @mushi-mushi/mcp@latest
65
+ MUSHI_API_KEY=mushi_xxxxxxxxxxxxxxxxxxxx \
66
+ MUSHI_PROJECT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
67
+ npx -y @mushi-mushi/mcp@latest
49
68
  ```
50
69
 
51
70
  The server speaks stdio MCP transport by default — your client launches it as a subprocess.
@@ -59,10 +78,10 @@ The Mushi backend now exposes the same tool catalog over the **Streamable HTTP**
59
78
  {
60
79
  "mcpServers": {
61
80
  "mushi-mushi-hosted": {
62
- "url": "https://api.mushimushi.dev/functions/v1/mcp",
81
+ "url": "https://<your-ref>.supabase.co/functions/v1/mcp",
63
82
  "headers": {
64
- "X-Mushi-Api-Key": "mushi_live_…",
65
- "X-Mushi-Project-Id": "proj_…"
83
+ "X-Mushi-Api-Key": "mushi_xxxxxxxxxxxxxxxxxxxx",
84
+ "X-Mushi-Project": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
66
85
  }
67
86
  }
68
87
  }
package/dist/index.js CHANGED
@@ -5,11 +5,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
5
5
  import { createRequire } from "module";
6
6
  import { createLogger } from "@mushi-mushi/core";
7
7
 
8
- // src/server.ts
9
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
10
- import { z } from "zod";
11
-
12
8
  // src/catalog.ts
9
+ var ALL_SCOPES = ["mcp:read", "mcp:write"];
13
10
  var TOOL_CATALOG = [
14
11
  {
15
12
  name: "get_recent_reports",
@@ -145,10 +142,10 @@ var TOOL_CATALOG = [
145
142
  {
146
143
  name: "dispatch_fix",
147
144
  title: "Dispatch Mushi fix agent",
148
- description: "Dispatch the Mushi agentic fix orchestrator for a classified report. Returns a fix_attempt id; poll get_fix_timeline for progress.",
145
+ description: 'Dispatch the Mushi agentic fix orchestrator for a classified report. Set agent="cursor_cloud" to dispatch a Cursor Cloud Agent that opens a signed draft PR. Returns a fix_attempt id; poll get_fix_timeline for progress.',
149
146
  scope: "mcp:write",
150
147
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
151
- useCase: "Let the in-repo agent attempt this fix for me."
148
+ useCase: "Let the in-repo agent attempt this fix for me (or: dispatch a Cursor Cloud Agent)."
152
149
  },
153
150
  {
154
151
  name: "trigger_judge",
@@ -205,6 +202,8 @@ var TOOL_CATALOG = [
205
202
  ];
206
203
 
207
204
  // src/server.ts
205
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
206
+ import { z } from "zod";
208
207
  var MushiApiError = class extends Error {
209
208
  constructor(status, code, message) {
210
209
  super(`[${code}] ${message}`);
@@ -218,6 +217,7 @@ var MushiApiError = class extends Error {
218
217
  function createMushiServer(config) {
219
218
  const { version, apiEndpoint, apiKey, projectId } = config;
220
219
  const doFetch = config.fetch ?? globalThis.fetch;
220
+ const grantedScopes = new Set(config.scopes ?? ALL_SCOPES);
221
221
  async function apiCall(path, options) {
222
222
  const res = await doFetch(`${apiEndpoint}${path}`, {
223
223
  ...options,
@@ -289,7 +289,23 @@ function createMushiServer(config) {
289
289
  if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
290
290
  return spec.title;
291
291
  }
292
- server.registerTool(
292
+ function shouldRegister(name) {
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(
293
309
  "get_recent_reports",
294
310
  {
295
311
  title: titleOf("get_recent_reports"),
@@ -300,6 +316,13 @@ function createMushiServer(config) {
300
316
  category: z.string().optional().describe("Filter by category: bug, slow, visual, confusing, other"),
301
317
  severity: z.string().optional().describe("Filter by severity: critical, high, medium, low"),
302
318
  limit: z.number().optional().describe("Max reports to return (default 20, max 100)")
319
+ },
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
+ outputSchema: {
324
+ reports: z.array(z.record(z.string(), z.unknown())).describe("Array of report rows"),
325
+ total: z.number().describe("Total matching rows (before limit)")
303
326
  }
304
327
  },
305
328
  async (args) => {
@@ -309,10 +332,13 @@ function createMushiServer(config) {
309
332
  if (args.severity) params.set("severity", args.severity);
310
333
  params.set("limit", String(Math.min(args.limit ?? 20, 100)));
311
334
  const data = await apiCall(`/v1/admin/reports?${params}`);
312
- return jsonText(data);
335
+ return jsonResult({
336
+ reports: data.reports ?? [],
337
+ total: data.total ?? 0
338
+ });
313
339
  }
314
340
  );
315
- server.registerTool(
341
+ registerScopedTool(
316
342
  "get_report_detail",
317
343
  {
318
344
  title: titleOf("get_report_detail"),
@@ -322,7 +348,7 @@ function createMushiServer(config) {
322
348
  },
323
349
  async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}`))
324
350
  );
325
- server.registerTool(
351
+ registerScopedTool(
326
352
  "search_reports",
327
353
  {
328
354
  title: titleOf("search_reports"),
@@ -332,6 +358,9 @@ function createMushiServer(config) {
332
358
  query: z.string().describe("Natural-language search text or component path"),
333
359
  limit: z.number().optional().describe("Max results (default 10, max 50)"),
334
360
  threshold: z.number().optional().describe("Similarity threshold 0..1, default 0.2")
361
+ },
362
+ outputSchema: {
363
+ results: z.array(z.record(z.string(), z.unknown())).describe("Ranked report rows with similarity scores")
335
364
  }
336
365
  },
337
366
  async (args) => {
@@ -344,10 +373,10 @@ function createMushiServer(config) {
344
373
  ...projectId ? { projectId } : {}
345
374
  })
346
375
  });
347
- return jsonText(data);
376
+ return jsonResult({ results: data.results ?? [] });
348
377
  }
349
378
  );
350
- server.registerTool(
379
+ registerScopedTool(
351
380
  "get_similar_bugs",
352
381
  {
353
382
  title: titleOf("get_similar_bugs"),
@@ -356,6 +385,9 @@ function createMushiServer(config) {
356
385
  inputSchema: {
357
386
  query: z.string().describe("Component name, page path, or bug description"),
358
387
  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")
359
391
  }
360
392
  },
361
393
  async (args) => {
@@ -368,10 +400,10 @@ function createMushiServer(config) {
368
400
  ...projectId ? { projectId } : {}
369
401
  })
370
402
  });
371
- return jsonText(data);
403
+ return jsonResult({ results: data.results ?? [] });
372
404
  }
373
405
  );
374
- server.registerTool(
406
+ registerScopedTool(
375
407
  "get_fix_context",
376
408
  {
377
409
  title: titleOf("get_fix_context"),
@@ -390,7 +422,7 @@ function createMushiServer(config) {
390
422
  });
391
423
  }
392
424
  );
393
- server.registerTool(
425
+ registerScopedTool(
394
426
  "get_fix_timeline",
395
427
  {
396
428
  title: titleOf("get_fix_timeline"),
@@ -400,7 +432,7 @@ function createMushiServer(config) {
400
432
  },
401
433
  async (args) => jsonText(await apiCall(`/v1/admin/fixes/${args.fixId}/timeline`))
402
434
  );
403
- server.registerTool(
435
+ registerScopedTool(
404
436
  "get_blast_radius",
405
437
  {
406
438
  title: titleOf("get_blast_radius"),
@@ -410,7 +442,7 @@ function createMushiServer(config) {
410
442
  },
411
443
  async (args) => jsonText(await apiCall(`/v1/admin/graph/blast-radius/${args.nodeId}`))
412
444
  );
413
- server.registerTool(
445
+ registerScopedTool(
414
446
  "get_knowledge_graph",
415
447
  {
416
448
  title: titleOf("get_knowledge_graph"),
@@ -429,7 +461,7 @@ function createMushiServer(config) {
429
461
  return jsonText(await apiCall(`/v1/admin/graph/traverse?${params}`));
430
462
  }
431
463
  );
432
- server.registerTool(
464
+ registerScopedTool(
433
465
  "graph_neighborhood",
434
466
  {
435
467
  title: titleOf("graph_neighborhood"),
@@ -448,7 +480,7 @@ function createMushiServer(config) {
448
480
  return jsonText(await apiCall(`/v1/admin/graph/traverse?${params}`));
449
481
  }
450
482
  );
451
- server.registerTool(
483
+ registerScopedTool(
452
484
  "graph_node_status",
453
485
  {
454
486
  title: titleOf("graph_node_status"),
@@ -458,7 +490,7 @@ function createMushiServer(config) {
458
490
  },
459
491
  async (args) => jsonText(await apiCall(`/v1/admin/graph/node/${args.nodeId}`))
460
492
  );
461
- server.registerTool(
493
+ registerScopedTool(
462
494
  "inventory_get",
463
495
  {
464
496
  title: titleOf("inventory_get"),
@@ -474,7 +506,7 @@ function createMushiServer(config) {
474
506
  return jsonText(await apiCall(`/v1/admin/inventory/${pid}`));
475
507
  }
476
508
  );
477
- server.registerTool(
509
+ registerScopedTool(
478
510
  "inventory_diff",
479
511
  {
480
512
  title: titleOf("inventory_diff"),
@@ -493,7 +525,7 @@ function createMushiServer(config) {
493
525
  return jsonText(await apiCall(`/v1/admin/inventory/${pid}/diff?${q}`));
494
526
  }
495
527
  );
496
- server.registerTool(
528
+ registerScopedTool(
497
529
  "inventory_findings",
498
530
  {
499
531
  title: titleOf("inventory_findings"),
@@ -515,7 +547,7 @@ function createMushiServer(config) {
515
547
  return jsonText(await apiCall(`/v1/admin/inventory/${pid}/findings${suffix}`));
516
548
  }
517
549
  );
518
- server.registerTool(
550
+ registerScopedTool(
519
551
  "fix_suggest",
520
552
  {
521
553
  title: titleOf("fix_suggest"),
@@ -536,7 +568,7 @@ function createMushiServer(config) {
536
568
  });
537
569
  }
538
570
  );
539
- server.registerTool(
571
+ registerScopedTool(
540
572
  "run_nl_query",
541
573
  {
542
574
  title: titleOf("run_nl_query"),
@@ -552,7 +584,7 @@ function createMushiServer(config) {
552
584
  return jsonText(data);
553
585
  }
554
586
  );
555
- server.registerTool(
587
+ registerScopedTool(
556
588
  "submit_fix_result",
557
589
  {
558
590
  title: titleOf("submit_fix_result"),
@@ -587,7 +619,7 @@ function createMushiServer(config) {
587
619
  return jsonText({ ok: true, fixId: created.fixId });
588
620
  }
589
621
  );
590
- server.registerTool(
622
+ registerScopedTool(
591
623
  "dispatch_fix",
592
624
  {
593
625
  title: titleOf("dispatch_fix"),
@@ -595,9 +627,20 @@ function createMushiServer(config) {
595
627
  annotations: annotationsFor("dispatch_fix"),
596
628
  inputSchema: {
597
629
  reportId: z.string().describe("Report UUID to fix"),
598
- agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
630
+ agent: z.enum(["claude_code", "codex", "rest_worker", "mcp", "cursor_cloud"]).optional().describe('Override the agent adapter. Use "cursor_cloud" to dispatch a Cursor Cloud Agent that opens a signed draft PR.'),
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").'),
599
633
  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)."),
600
634
  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
+ },
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
+ outputSchema: {
639
+ fixId: z.string().describe("Newly created fix_attempt UUID"),
640
+ status: z.string().optional().describe("Initial status (queued, running, delegated, \u2026)"),
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")
601
644
  }
602
645
  },
603
646
  async (args, extra) => {
@@ -615,20 +658,31 @@ function createMushiServer(config) {
615
658
  } catch {
616
659
  }
617
660
  }
618
- const data = await apiCall("/v1/admin/fixes/dispatch", {
619
- method: "POST",
620
- headers: args.idempotencyKey ? { "Idempotency-Key": args.idempotencyKey } : void 0,
621
- body: JSON.stringify({
622
- reportId: args.reportId,
623
- agent: args.agent,
624
- inventoryActionNodeId: args.inventoryActionNodeId,
625
- ...projectId ? { projectId } : {}
626
- })
661
+ const resolvedAgent = args.agent ?? (args.backend !== "default" ? args.backend : void 0);
662
+ const data = await apiCall(
663
+ "/v1/admin/fixes/dispatch",
664
+ {
665
+ method: "POST",
666
+ headers: args.idempotencyKey ? { "Idempotency-Key": args.idempotencyKey } : void 0,
667
+ body: JSON.stringify({
668
+ reportId: args.reportId,
669
+ agent: resolvedAgent,
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 } : {}
627
682
  });
628
- return jsonText(data);
629
683
  }
630
684
  );
631
- server.registerTool(
685
+ registerScopedTool(
632
686
  "trigger_judge",
633
687
  {
634
688
  title: titleOf("trigger_judge"),
@@ -650,7 +704,7 @@ function createMushiServer(config) {
650
704
  return jsonText(data);
651
705
  }
652
706
  );
653
- server.registerTool(
707
+ registerScopedTool(
654
708
  "test_gen_from_report",
655
709
  {
656
710
  title: titleOf("test_gen_from_report"),
@@ -671,7 +725,7 @@ function createMushiServer(config) {
671
725
  return jsonText(data);
672
726
  }
673
727
  );
674
- server.registerTool(
728
+ registerScopedTool(
675
729
  "transition_status",
676
730
  {
677
731
  title: titleOf("transition_status"),
@@ -715,7 +769,7 @@ function createMushiServer(config) {
715
769
  contents: [{ uri: "project://dashboard", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2) }]
716
770
  })
717
771
  );
718
- server.registerTool(
772
+ registerScopedTool(
719
773
  "list_top_contributors",
720
774
  {
721
775
  title: titleOf("list_top_contributors"),
@@ -737,7 +791,7 @@ function createMushiServer(config) {
737
791
  }]
738
792
  })
739
793
  );
740
- server.registerTool(
794
+ registerScopedTool(
741
795
  "award_bonus_points",
742
796
  {
743
797
  title: titleOf("award_bonus_points"),
@@ -764,7 +818,7 @@ function createMushiServer(config) {
764
818
  }]
765
819
  })
766
820
  );
767
- server.registerTool(
821
+ registerScopedTool(
768
822
  "set_tier",
769
823
  {
770
824
  title: titleOf("set_tier"),
@@ -901,6 +955,9 @@ var log = createLogger({ scope: "mushi:mcp", level: "info" });
901
955
  var API_ENDPOINT = process.env.MUSHI_API_ENDPOINT ?? "";
902
956
  var API_KEY = process.env.MUSHI_API_KEY ?? "";
903
957
  var PROJECT_ID = process.env.MUSHI_PROJECT_ID ?? "";
958
+ var SCOPES_RAW = process.env.MUSHI_SCOPES ?? "";
959
+ var parsedScopes = SCOPES_RAW ? SCOPES_RAW.split(",").map((s) => s.trim()).filter((s) => s === "mcp:read" || s === "mcp:write") : ALL_SCOPES;
960
+ var SCOPES = SCOPES_RAW && parsedScopes.length === 0 ? ALL_SCOPES : parsedScopes;
904
961
  async function main() {
905
962
  if (!API_KEY) {
906
963
  log.fatal("MUSHI_API_KEY environment variable is required");
@@ -913,15 +970,21 @@ async function main() {
913
970
  }
914
971
  if (!PROJECT_ID) {
915
972
  console.error(
916
- "[mushi-mcp] MUSHI_PROJECT_ID is not set.\n\nTools that scope to a project (get_recent_reports, get_report_detail,\nsearch_reports, etc.) will require you to pass projectId explicitly on\nevery call. To set it once and never pass it again:\n\n 1. Open the Mushi admin console \u2192 Projects\n (https://your-admin-url/projects)\n 2. Find your project \u2014 the UUID chip below the name is the value to use.\n 3. Copy it and set:\n MUSHI_PROJECT_ID=<paste-uuid-here>\n\nYou can also visit Admin \u2192 MCP for a pre-filled .env.local snippet."
973
+ "[mushi-mcp] MUSHI_PROJECT_ID is not set.\n\nTools that scope to a project (get_recent_reports, get_report_detail,\nsearch_reports, etc.) will require you to pass projectId explicitly on\nevery call. To set it once and never pass it again:\n\n 1. Open the Mushi admin console \u2192 Projects\n https://kensaur.us/mushi-mushi/projects\n 2. Click your project \u2014 copy the UUID below the project name.\n 3. Add it to your MCP env config:\n MUSHI_PROJECT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx\n\nOr visit Admin \u2192 MCP for a pre-filled config snippet with your actual UUID."
917
974
  );
918
975
  }
919
- log.info("Starting Mushi MCP server", { version: VERSION, endpoint: API_ENDPOINT || "(unset)", hasProjectId: !!PROJECT_ID });
976
+ log.info("Starting Mushi MCP server", {
977
+ version: VERSION,
978
+ endpoint: API_ENDPOINT || "(unset)",
979
+ hasProjectId: !!PROJECT_ID,
980
+ scopes: SCOPES.join(",")
981
+ });
920
982
  const server = createMushiServer({
921
983
  version: VERSION,
922
984
  apiEndpoint: API_ENDPOINT,
923
985
  apiKey: API_KEY,
924
- projectId: PROJECT_ID || void 0
986
+ projectId: PROJECT_ID || void 0,
987
+ scopes: SCOPES
925
988
  });
926
989
  const transport = new StdioServerTransport();
927
990
  await server.connect(transport);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/mcp",
3
- "version": "0.5.0",
3
+ "version": "0.8.0",
4
4
  "license": "MIT",
5
5
  "description": "MCP server exposing Mushi Mushi reports to coding agents",
6
6
  "type": "module",
@@ -26,7 +26,7 @@
26
26
  "dependencies": {
27
27
  "@modelcontextprotocol/sdk": "^1.29.0",
28
28
  "zod": "^4.4.2",
29
- "@mushi-mushi/core": "^1.1.0"
29
+ "@mushi-mushi/core": "^1.5.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^22.19.17",
@@ -83,6 +83,7 @@
83
83
  "test:smoke": "node scripts/smoke-stdio.mjs",
84
84
  "test:localhost": "node scripts/localhost-e2e.mjs",
85
85
  "demo": "node scripts/demo-terminal-config.mjs",
86
+ "inspector": "npx --yes @modelcontextprotocol/inspector@latest node ./dist/index.js",
86
87
  "typecheck": "tsc --noEmit"
87
88
  }
88
89
  }