@mushi-mushi/mcp 0.16.2 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,7 +2,12 @@
2
2
 
3
3
  > **Your AI wrote it. Mushi tells you why it broke.**
4
4
 
5
- [Model Context Protocol](https://spec.modelcontextprotocol.io/) server that wires Mushi into Cursor, Claude Code, and any MCP client. Pull fix context, run triage, and dispatch fixes without a second LLM vendor key.
5
+ [![Add to Cursor](https://img.shields.io/badge/Add%20to-Cursor-0098FF)](https://kensaur.us/mushi-mushi/docs/connect)
6
+ [![Add to VS Code](https://img.shields.io/badge/Add%20to-VS%20Code-007ACC?logo=visualstudiocode&logoColor=white)](https://kensaur.us/mushi-mushi/docs/connect)
7
+ [![Try the demo — no signup](https://img.shields.io/badge/Try%20the%20demo-no%20signup-E34234)](https://kensaur.us/mushi-mushi/docs/connect)
8
+ [![npm](https://img.shields.io/npm/v/@mushi-mushi/mcp?color=cb3837)](https://www.npmjs.com/package/@mushi-mushi/mcp)
9
+
10
+ [Model Context Protocol](https://spec.modelcontextprotocol.io/) server that wires Mushi into Cursor, Claude Code, and any MCP client. Pull fix context, review bugs, and dispatch fixes without a second LLM vendor key.
6
11
 
7
12
  Wire it into Cursor, Claude Code, or any MCP client in one command:
8
13
 
@@ -259,7 +264,7 @@ Named templates the MCP client surfaces in its slash-menu. Each one bakes in the
259
264
  |---|---|---|---|
260
265
  | `MUSHI_API_KEY` | yes | — | Project API key with `mcp:read` or `mcp:write` scope. Mint one in the admin console → **Projects** (the one-time reveal card has a **Copy as `.env.local`** tab). |
261
266
  | `MUSHI_PROJECT_ID` | yes | — | UUID from the admin console URL (`/projects/<uuid>/...`) or the reveal card. |
262
- | `MUSHI_API_ENDPOINT` | no | `https://api.mushimushi.dev` | Override only if you self-host. Localhost value: `http://localhost:54321/functions/v1/api`. |
267
+ | `MUSHI_API_ENDPOINT` | no | `https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api` | Override only if you self-host. Localhost: `http://localhost:54321/functions/v1/api`. |
263
268
 
264
269
  ### Storing the key in `.env.local`
265
270
 
@@ -269,7 +274,7 @@ The MCP binary reads these three vars from `process.env` on spawn — that means
269
274
 
270
275
  ```bash
271
276
  # Mushi MCP — drop into .env.local (gitignored). The MCP binary picks these up on spawn.
272
- MUSHI_API_ENDPOINT=https://api.mushimushi.dev
277
+ MUSHI_API_ENDPOINT=https://dxptnwrhwsqckaftyymj.supabase.co/functions/v1/api
273
278
  MUSHI_PROJECT_ID=<your-uuid>
274
279
  MUSHI_API_KEY=mushi_live_…
275
280
  ```
@@ -459,6 +464,7 @@ Both are complementary — use Sentry for exception noise, Mushi for "the user s
459
464
 
460
465
  ## See also
461
466
 
467
+ - [GTM distribution runbook](../../docs/marketing/GTM-DISTRIBUTION.md) — MCP registry, Glama, Connect, cursor.directory, release checklist.
462
468
  - [V5.3 whitepaper §2.10](../../MushiMushi_Whitepaper_V5.md) — the agentic fix architecture this server feeds into.
463
469
  - [`@mushi-mushi/agents`](../agents/README.md) — orchestrator that consumes MCP-exposed fix workers.
464
470
 
@@ -470,4 +476,4 @@ MIT
470
476
  <!-- mushi-readme-stats-footer -->
471
477
  ---
472
478
 
473
- <sub>Monorepo scale (June 2026): 47 edge functions · 256 SQL migrations · 13 outbound plugins · 11 inbound adapters · 18 pipeline agents. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
479
+ <sub>Monorepo scale (June 2026): 51 edge functions · 298 SQL migrations · 13 outbound plugins · 11 inbound adapters · 19 pipeline agents. Canonical counts: <a href="https://github.com/kensaurus/mushi-mushi/blob/master/docs/stats.md">docs/stats.md</a> · <code>pnpm docs-stats</code></sub>
package/dist/branding.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  MUSHI_SERVER_METADATA,
6
6
  MUSHI_WEBSITE_URL,
7
7
  mcpIconUrl
8
- } from "./chunk-EHB2BGWX.js";
8
+ } from "./chunk-EANW5YTI.js";
9
9
  export {
10
10
  MUSHI_ICON_PNG_URL,
11
11
  MUSHI_ICON_SVG_URL,
package/dist/catalog.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  RESOURCE_CATALOG,
6
6
  TDD_TOOL_CATALOG,
7
7
  TOOL_CATALOG
8
- } from "./chunk-MM3IMFM4.js";
8
+ } from "./chunk-7UOTJ2LI.js";
9
9
  export {
10
10
  ALL_SCOPES,
11
11
  CODEBASE_TOOL_CATALOG,
@@ -4,10 +4,10 @@ var TOOL_CATALOG = [
4
4
  {
5
5
  name: "get_recent_reports",
6
6
  title: "Recent bug reports",
7
- description: "List recent bug reports with optional filters (status / category / severity). Use this to survey what the triage queue looks like right now.",
7
+ description: "List recent bug reports with optional filters (status / category / severity). Use this to see what is waiting in your reports inbox right now.",
8
8
  scope: "mcp:read",
9
9
  hints: { readOnly: true, idempotent: true, openWorld: true },
10
- useCase: "What landed in my triage queue today?"
10
+ useCase: "What landed in my bug queue today?"
11
11
  },
12
12
  {
13
13
  name: "get_report_detail",
@@ -134,7 +134,7 @@ var TOOL_CATALOG = [
134
134
  {
135
135
  name: "setup_check",
136
136
  title: "Dispatch preflight check",
137
- description: "[Deprecated \u2014 use diagnose_setup with mode=dispatch or diagnose_connection.] 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.",
137
+ description: "[Deprecated \u2014 use diagnose_setup with mode=dispatch or diagnose_connection.] Run the 4 **dispatch-readiness** checks for a project and return their pass/fail status (GitHub repo connected, codebase indexed, your Anthropic API key present, autofix enabled). Also returns the target repo URL when GitHub is connected.",
138
138
  scope: "mcp:read",
139
139
  hints: { readOnly: true, idempotent: true, openWorld: true },
140
140
  useCase: "Is this project ready to auto-fix bugs? What is blocking dispatch?"
@@ -184,7 +184,7 @@ var TOOL_CATALOG = [
184
184
  {
185
185
  name: "dispatch_fix",
186
186
  title: "Dispatch Mushi fix agent",
187
- 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.',
187
+ description: 'Start the Mushi fix agent 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.',
188
188
  scope: "mcp:write",
189
189
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
190
190
  useCase: "Let the in-repo agent attempt this fix for me (or: dispatch a Cursor Cloud Agent)."
@@ -200,7 +200,7 @@ var TOOL_CATALOG = [
200
200
  {
201
201
  name: "test_gen_from_report",
202
202
  title: "Generate Playwright test from report",
203
- description: "POST to inventory test-gen: uses the project BYOK LLM to author a Playwright spec from a classified report and opens a draft PR (internal service orchestration). Requires inventory_v2 + GitHub + LLM keys.",
203
+ description: "Generate a Playwright spec from a classified report using your project LLM key and open a draft PR. Requires inventory_v2 + GitHub + LLM keys.",
204
204
  scope: "mcp:write",
205
205
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
206
206
  useCase: "Turn this regression report into an E2E test PR."
@@ -232,7 +232,7 @@ var TOOL_CATALOG = [
232
232
  {
233
233
  name: "reopen_report",
234
234
  title: "Reopen report (operator)",
235
- description: "Move a report to reopened for regression triage.",
235
+ description: "Move a report to reopened for regression review.",
236
236
  scope: "mcp:write",
237
237
  hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
238
238
  useCase: "Reopen a regression the reporter flagged as not fixed."
@@ -290,10 +290,10 @@ var TOOL_CATALOG = [
290
290
  {
291
291
  name: "get_project_context",
292
292
  title: "Project context snapshot",
293
- description: "Return a rich context snapshot for a project: name, repo URL, SDK heartbeat, ingest status, open report count, autofix readiness, BYOK LLM config, plan tier, and active integration health. Equivalent to a merged preflight + activation + settings read. Agents should call this at the start of a triage session to orient themselves.",
293
+ description: "Return a rich context snapshot for a project: name, repo URL, SDK heartbeat, ingest status, open report count, autofix readiness, your LLM key config, plan tier, and active integration health. Equivalent to a merged preflight + activation + settings read. Agents should call this at the start of a review session to orient themselves.",
294
294
  scope: "mcp:read",
295
295
  hints: { readOnly: true, idempotent: true, openWorld: true },
296
- useCase: "Give me a status overview of this project before I start triaging."
296
+ useCase: "Give me a status overview of this project before I start reviewing bugs."
297
297
  },
298
298
  {
299
299
  name: "get_pipeline_logs",
@@ -314,7 +314,7 @@ var TOOL_CATALOG = [
314
314
  {
315
315
  name: "triage_issue",
316
316
  title: "Triage issue end-to-end",
317
- description: 'Read-only orchestration tool: combines report detail, evidence, similar bugs, fix context, blast radius, and recent pipeline logs for a report into a single structured triage packet. Returns a prioritised list of recommended next actions (investigate, dispatch_fix, group_with, dismiss). Equivalent to a Sentry "Analyze with Seer" flow grounded in user-felt reports. Pass report_id to kick off triage. Call this before dispatch_fix.',
317
+ description: 'Read-only combined tool: merges report detail, evidence, similar bugs, fix context, blast radius, and recent pipeline logs for a report into a single structured review packet. Returns a prioritised list of recommended next actions (investigate, dispatch_fix, group_with, dismiss). Equivalent to a Sentry "Analyze with Seer" flow grounded in user-felt reports. Pass report_id to kick off review. Call this before dispatch_fix.',
318
318
  scope: "mcp:read",
319
319
  hints: { readOnly: true, idempotent: true, openWorld: true },
320
320
  useCase: "Analyze this bug report end-to-end and tell me what to do."
@@ -341,8 +341,8 @@ var RESOURCE_CATALOG = [
341
341
  {
342
342
  name: "project_dashboard",
343
343
  uri: "project://dashboard",
344
- title: "PDCA dashboard snapshot",
345
- description: "PDCA health snapshot \u2014 stage counts, bottleneck, recent activity (same payload the admin console polls).",
344
+ title: "Loop dashboard snapshot",
345
+ description: "Loop health snapshot \u2014 stage counts, bottleneck, recent activity (same payload the admin console polls).",
346
346
  scope: "mcp:read"
347
347
  },
348
348
  {
@@ -363,14 +363,14 @@ var RESOURCE_CATALOG = [
363
363
  name: "privacy_status",
364
364
  uri: "privacy://status",
365
365
  title: "Privacy posture",
366
- description: "Returns the privacy posture for this project: storage region, LLM provider, whether BYOK is configured, data retention window, and last audit timestamp. Agents should read this before dispatching a fix to confirm that client data stays within the expected boundary. Reads as project data does not leave the project's own LLM account when byok_configured=true.",
366
+ description: "Returns the privacy posture for this project: storage region, LLM provider, whether your own LLM key is configured, data retention window, and last audit timestamp. Agents should read this before dispatching a fix to confirm that client data stays within the expected boundary. Reads as project data does not leave the project's own LLM account when byok_configured=true.",
367
367
  scope: "mcp:read"
368
368
  },
369
369
  {
370
370
  name: "evolution_history",
371
371
  uri: "evolution://history",
372
372
  title: "Evolution history",
373
- 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 (flat scores, same bugs re-appearing). Use before triage to understand which bug classes the loop has already learned to handle, and which still need human attention.",
373
+ 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 (flat scores, same bugs re-appearing). Use before review to understand which bug classes the loop has already learned to handle, and which still need human attention.",
374
374
  scope: "mcp:read"
375
375
  },
376
376
  {
@@ -384,7 +384,7 @@ var RESOURCE_CATALOG = [
384
384
  name: "project_integration_health",
385
385
  uri: "project://integration-health",
386
386
  title: "Integration health",
387
- description: "Live health status of every configured BYOK channel (Sentry, GitHub, LangFuse, PagerDuty, \u2026). Orchestrators should check this before dispatching a fix to fail-fast on broken channels rather than burning LLM budget and discovering the failure mid-run.",
387
+ description: "Live health status of every configured integration channel (Sentry, GitHub, LangFuse, PagerDuty, \u2026). Check this before dispatching a fix to fail-fast on broken channels rather than burning LLM budget and discovering the failure mid-run.",
388
388
  scope: "mcp:read"
389
389
  },
390
390
  {
@@ -448,8 +448,8 @@ var TDD_TOOL_CATALOG = [
448
448
  },
449
449
  {
450
450
  name: "improve_qa_story",
451
- title: "PDCA auto-improve a failing QA story",
452
- 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.",
451
+ title: "Auto-improve a failing QA story",
452
+ description: "Find recently failed qa_story_runs and use Claude to write improved test scripts. New tests are created with source=pdca and approval gated by the original story's automation_mode.",
453
453
  scope: "mcp:write",
454
454
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
455
455
  useCase: "Fix my failing QA tests automatically."
@@ -464,16 +464,16 @@ var TDD_TOOL_CATALOG = [
464
464
  },
465
465
  {
466
466
  name: "list_byok_keys",
467
- title: "List BYOK API key pool",
468
- 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.",
467
+ title: "List your API key pool",
468
+ description: "List all project API keys, 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.",
469
469
  scope: "mcp:read",
470
470
  hints: { readOnly: true, idempotent: true, openWorld: true },
471
471
  useCase: "Which API keys are active and which are rate-limited?"
472
472
  },
473
473
  {
474
474
  name: "add_byok_key",
475
- title: "Add a BYOK API key",
476
- 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.",
475
+ title: "Add an API key",
476
+ description: "Add a new API key to the project pool for a given provider (anthropic, openai, firecrawl, browserbase, cursor). Specify label and priority for ordering. The key is stored encrypted in Supabase Vault.",
477
477
  scope: "mcp:write",
478
478
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
479
479
  useCase: "Add a backup Anthropic key to the pool."
@@ -622,7 +622,7 @@ var CODEBASE_TOOL_CATALOG = [
622
622
  {
623
623
  name: "ask_codebase",
624
624
  title: "Ask about the indexed codebase",
625
- description: "Ask a plain-English question about the connected repo. Grounds on pgvector retrieval over project_codebase_files and returns an answer with file:line citations. Requires codebase indexing enabled and an Anthropic or OpenAI BYOK key.",
625
+ description: "Ask a plain-English question about the connected repo. Grounds on pgvector retrieval over project_codebase_files and returns an answer with file:line citations. Requires codebase indexing enabled and your Anthropic or OpenAI API key.",
626
626
  scope: "mcp:write",
627
627
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
628
628
  useCase: "How does authentication work in this repo?"
@@ -3,7 +3,9 @@ var MUSHI_WEBSITE_URL = "https://kensaur.us/mushi-mushi";
3
3
  var MUSHI_ICON_PNG_URL = `${MUSHI_WEBSITE_URL}/integrations/mushi-mark-512.png`;
4
4
  var MUSHI_ICON_SVG_URL = `${MUSHI_WEBSITE_URL}/favicon.svg`;
5
5
  function mcpIconUrl(mcpBaseUrl) {
6
- const base = mcpBaseUrl.replace(/\/+$/, "");
6
+ let end = mcpBaseUrl.length;
7
+ while (end > 0 && mcpBaseUrl.charCodeAt(end - 1) === 47) end--;
8
+ const base = mcpBaseUrl.slice(0, end);
7
9
  return `${base}?icon=1`;
8
10
  }
9
11
  var MUSHI_SERVER_ICONS = [
@@ -11,6 +11,7 @@ var FEATURE_GROUPS = [
11
11
  "audit",
12
12
  "docs",
13
13
  "usage",
14
+ "usage",
14
15
  /** Deprecated aliases kept for one release — hidden unless `legacy` or `all`. */
15
16
  "legacy"
16
17
  ];
@@ -0,0 +1,115 @@
1
+ import { FeatureGroup } from './feature-groups.js';
2
+
3
+ /**
4
+ * FILE: packages/mcp/src/clients.ts
5
+ * PURPOSE: Shared, pure (React-free) client registry + install-config builders for
6
+ * every supported AI agent / IDE client.
7
+ *
8
+ * OVERVIEW:
9
+ * - Exports `MCP_CLIENTS: McpClientDef[]` — one entry per supported client.
10
+ * - Each entry carries metadata (id, label, method, transport) and a pure `build()`
11
+ * function that returns the install artifact (deeplink URL, JSON config, or CLI command).
12
+ * - Both the admin console ConnectStudio and the public docs /connect landing consume
13
+ * this module — the single source of truth so the two surfaces never drift.
14
+ *
15
+ * DEPENDENCIES:
16
+ * - feature-groups.ts (DEFAULT_FEATURE_GROUPS, featuresQueryString, appendFeaturesToUrl)
17
+ * - branding.ts (MUSHI_ICON_PNG_URL)
18
+ *
19
+ * USAGE:
20
+ * import { MCP_CLIENTS } from '@mushi-mushi/mcp/clients'
21
+ * const cursor = MCP_CLIENTS.find(c => c.id === 'cursor')!
22
+ * const result = cursor.build({ projectId, projectName, apiKey, endpoint, mcpHttpUrl })
23
+ * // result.kind === 'deeplink' → window.open(result.url)
24
+ *
25
+ * CLIENT INSTALL MATRIX (verified Jun 2026):
26
+ * - cursor / vscode / vscode-insiders — one-click deeplink
27
+ * - windsurf — copy-config to ~/.codeium/windsurf/mcp_config.json
28
+ * (uses `serverUrl`, NOT `url` — common footgun)
29
+ * - cline — copy-config to cline_mcp_settings.json
30
+ * - claude-code — CLI command: claude mcp add --transport http …
31
+ * - claude-desktop — copy-config to claude_desktop_config.json
32
+ * - zed — copy-config to ~/.config/zed/settings.json
33
+ * - any — hosted Streamable HTTP URL + Bearer key
34
+ *
35
+ * NOTES:
36
+ * - All builders are pure functions — no side-effects, no DOM, no React.
37
+ * - Cursor base64-encodes the config object without a `name` key in the payload.
38
+ * - VS Code includes `type:'stdio'` in the config and `name` as a separate query param.
39
+ * - Windsurf uses `serverUrl` for remote HTTP (NOT `url`) — encoding both would break it.
40
+ * - The `cliIde` field drives `mushi setup --ide <cliIde>` in the CLI lane.
41
+ */
42
+
43
+ interface StdioConfig {
44
+ command: string;
45
+ args: string[];
46
+ env: Record<string, string>;
47
+ icon?: string;
48
+ }
49
+ interface HttpConfig {
50
+ type: 'http';
51
+ url: string;
52
+ headers: Record<string, string>;
53
+ icon?: string;
54
+ }
55
+ type McpClientId = 'cursor' | 'vscode' | 'vscode-insiders' | 'windsurf' | 'cline' | 'claude-code' | 'claude-desktop' | 'zed' | 'any';
56
+ /** The install method determines which UI treatment the console/docs page renders. */
57
+ type McpInstallMethod = 'deeplink' | 'config-json' | 'cli-command' | 'remote-url';
58
+ interface McpBuildInput {
59
+ /** Mushi project UUID — optional when building org-level configs. */
60
+ projectId?: string;
61
+ projectName: string;
62
+ apiKey: string;
63
+ /** Supabase edge function base URL (`MUSHI_API_ENDPOINT`). */
64
+ endpoint: string;
65
+ /** Hosted Streamable HTTP MCP URL (`RESOLVED_MCP_HTTP_URL`). */
66
+ mcpHttpUrl: string;
67
+ /**
68
+ * Restrict the exposed tool groups. Omitted → each client's existing default
69
+ * (the lean `DEFAULT_FEATURE_GROUPS` for stdio deeplinks; all tools for the
70
+ * config-json clients that historically set no `MUSHI_FEATURES`). The public
71
+ * keyless demo passes an explicit read-only subset here.
72
+ */
73
+ features?: readonly FeatureGroup[];
74
+ /**
75
+ * Hosted HTTP transport only: append `?read_only=1` so the server hides and
76
+ * blocks write tools regardless of key scope. Used by the public demo on top
77
+ * of an `mcp:read` key for defence in depth. No-op for stdio configs.
78
+ */
79
+ readOnly?: boolean;
80
+ }
81
+ type McpBuildResult = {
82
+ kind: 'deeplink';
83
+ url: string;
84
+ } | {
85
+ kind: 'config';
86
+ filePath: string;
87
+ json: string;
88
+ } | {
89
+ kind: 'command';
90
+ text: string;
91
+ } | {
92
+ kind: 'remote-url';
93
+ url: string;
94
+ headerSnippet: string;
95
+ };
96
+ interface McpClientDef {
97
+ id: McpClientId;
98
+ label: string;
99
+ /** Short description shown in the client picker. */
100
+ description: string;
101
+ method: McpInstallMethod;
102
+ transport: 'stdio' | 'http';
103
+ /** Drives `mushi setup --ide <cliIde>` in the CLI lane. */
104
+ cliIde?: 'cursor' | 'claude' | 'continue' | 'zed';
105
+ build(input: McpBuildInput): McpBuildResult;
106
+ }
107
+ /** Build a stable, unique, human-readable MCP server slug for a project. */
108
+ declare function projectServerName(projectId: string, projectName: string): string;
109
+ declare function buildStdioConfigObj(projectId: string | undefined, apiKey: string, apiEndpoint: string, features?: readonly string[]): StdioConfig;
110
+ declare function buildHttpConfigObj(projectId: string | undefined, apiKey: string, mcpHttpUrl: string, features?: readonly string[], readOnly?: boolean): HttpConfig;
111
+ declare const MCP_CLIENTS: McpClientDef[];
112
+ /** Look up a client by id — throws if not found. */
113
+ declare function getMcpClient(id: McpClientId): McpClientDef;
114
+
115
+ export { MCP_CLIENTS, type McpBuildInput, type McpBuildResult, type McpClientDef, type McpClientId, type McpInstallMethod, buildHttpConfigObj as buildRawHttpConfig, buildStdioConfigObj as buildRawStdioConfig, getMcpClient, projectServerName };
@@ -0,0 +1,271 @@
1
+ import {
2
+ MUSHI_ICON_PNG_URL
3
+ } from "./chunk-EANW5YTI.js";
4
+ import {
5
+ DEFAULT_FEATURE_GROUPS,
6
+ appendFeaturesToUrl,
7
+ featuresQueryString
8
+ } from "./chunk-PW4AK6SV.js";
9
+
10
+ // src/clients.ts
11
+ function projectServerName(projectId, projectName) {
12
+ const nameSlug = projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 22);
13
+ const idSuffix = projectId.replace(/-/g, "").slice(0, 6);
14
+ return `mushi-${nameSlug}-${idSuffix}`;
15
+ }
16
+ function buildStdioConfigObj(projectId, apiKey, apiEndpoint, features = DEFAULT_FEATURE_GROUPS) {
17
+ const env = {
18
+ MUSHI_API_ENDPOINT: apiEndpoint,
19
+ MUSHI_API_KEY: apiKey,
20
+ MUSHI_FEATURES: featuresQueryString(features)
21
+ };
22
+ if (projectId) env.MUSHI_PROJECT_ID = projectId;
23
+ return {
24
+ command: "npx",
25
+ args: ["-y", "@mushi-mushi/mcp@latest"],
26
+ env,
27
+ icon: MUSHI_ICON_PNG_URL
28
+ };
29
+ }
30
+ function withReadOnly(baseUrl, readOnly) {
31
+ if (!readOnly) return baseUrl;
32
+ const url = new URL(baseUrl);
33
+ url.searchParams.set("read_only", "1");
34
+ return url.toString();
35
+ }
36
+ function buildHttpConfigObj(projectId, apiKey, mcpHttpUrl, features = DEFAULT_FEATURE_GROUPS, readOnly = false) {
37
+ const url = withReadOnly(
38
+ appendFeaturesToUrl(mcpHttpUrl, features),
39
+ readOnly
40
+ );
41
+ const headers = {
42
+ Authorization: `Bearer ${apiKey}`,
43
+ "X-Mushi-Api-Key": apiKey
44
+ };
45
+ if (projectId) headers["X-Mushi-Project-Id"] = projectId;
46
+ return { type: "http", url, headers, icon: MUSHI_ICON_PNG_URL };
47
+ }
48
+ function encodeCursorDeeplink(name, config) {
49
+ const encoded = btoa(JSON.stringify(config));
50
+ return `cursor://anysphere.cursor-deeplink/mcp/install?name=${encodeURIComponent(name)}&config=${encodeURIComponent(encoded)}`;
51
+ }
52
+ function toVsCodeConfig(config) {
53
+ if ("type" in config && config.type === "http") return config;
54
+ return { type: "stdio", ...config };
55
+ }
56
+ function encodeVsCodeDeeplink(scheme, name, config) {
57
+ const withType = toVsCodeConfig(config);
58
+ return `${scheme}:mcp/install?name=${encodeURIComponent(name)}&config=${encodeURIComponent(JSON.stringify(withType))}`;
59
+ }
60
+ var MCP_CLIENTS = [
61
+ // ── Cursor ──────────────────────────────────────────────────────────────────
62
+ {
63
+ id: "cursor",
64
+ label: "Cursor",
65
+ description: "One-click install into your project's .cursor/mcp.json",
66
+ method: "deeplink",
67
+ transport: "stdio",
68
+ cliIde: "cursor",
69
+ build({ projectId, projectName, apiKey, endpoint, features }) {
70
+ const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
71
+ const config = buildStdioConfigObj(projectId, apiKey, endpoint, features);
72
+ return { kind: "deeplink", url: encodeCursorDeeplink(name, config) };
73
+ }
74
+ },
75
+ // ── VS Code ──────────────────────────────────────────────────────────────────
76
+ {
77
+ id: "vscode",
78
+ label: "VS Code",
79
+ description: "One-click install via VS Code MCP extension",
80
+ method: "deeplink",
81
+ transport: "stdio",
82
+ build({ projectId, projectName, apiKey, endpoint, features }) {
83
+ const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
84
+ const config = buildStdioConfigObj(projectId, apiKey, endpoint, features);
85
+ return { kind: "deeplink", url: encodeVsCodeDeeplink("vscode", name, config) };
86
+ }
87
+ },
88
+ // ── VS Code Insiders ─────────────────────────────────────────────────────────
89
+ {
90
+ id: "vscode-insiders",
91
+ label: "VS Code Insiders",
92
+ description: "One-click install via VS Code Insiders MCP extension",
93
+ method: "deeplink",
94
+ transport: "stdio",
95
+ build({ projectId, projectName, apiKey, endpoint, features }) {
96
+ const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
97
+ const config = buildStdioConfigObj(projectId, apiKey, endpoint, features);
98
+ return { kind: "deeplink", url: encodeVsCodeDeeplink("vscode-insiders", name, config) };
99
+ }
100
+ },
101
+ // ── Windsurf ─────────────────────────────────────────────────────────────────
102
+ {
103
+ id: "windsurf",
104
+ label: "Windsurf",
105
+ description: "Copy config into ~/.codeium/windsurf/mcp_config.json",
106
+ method: "config-json",
107
+ transport: "http",
108
+ build({ projectId, projectName, apiKey, mcpHttpUrl, features, readOnly }) {
109
+ const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
110
+ const url = withReadOnly(
111
+ appendFeaturesToUrl(mcpHttpUrl, features ?? DEFAULT_FEATURE_GROUPS),
112
+ readOnly
113
+ );
114
+ const headers = {
115
+ Authorization: `Bearer ${apiKey}`,
116
+ "X-Mushi-Api-Key": apiKey
117
+ };
118
+ if (projectId) headers["X-Mushi-Project-Id"] = projectId;
119
+ const windsurfConfig = { type: "http", serverUrl: url, headers };
120
+ const configJson = JSON.stringify(
121
+ { mcpServers: { [name]: windsurfConfig } },
122
+ null,
123
+ 2
124
+ );
125
+ return {
126
+ kind: "config",
127
+ filePath: "~/.codeium/windsurf/mcp_config.json",
128
+ json: configJson
129
+ };
130
+ }
131
+ },
132
+ // ── Cline ────────────────────────────────────────────────────────────────────
133
+ {
134
+ id: "cline",
135
+ label: "Cline",
136
+ description: "Copy config into cline_mcp_settings.json",
137
+ method: "config-json",
138
+ transport: "stdio",
139
+ build({ projectId, projectName, apiKey, endpoint, features }) {
140
+ const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
141
+ const env = {
142
+ MUSHI_API_ENDPOINT: endpoint,
143
+ MUSHI_API_KEY: apiKey
144
+ };
145
+ if (features) env.MUSHI_FEATURES = featuresQueryString(features);
146
+ if (projectId) env.MUSHI_PROJECT_ID = projectId;
147
+ const clineConfig = {
148
+ mcpServers: {
149
+ [name]: { command: "npx", args: ["-y", "@mushi-mushi/mcp@latest"], env }
150
+ }
151
+ };
152
+ return {
153
+ kind: "config",
154
+ // Cline stores config in the VS Code extension's globalStorage
155
+ filePath: "~/Library/Application Support/Code/User/globalStorage/saoudrizwan.claude-dev/settings/cline_mcp_settings.json",
156
+ json: JSON.stringify(clineConfig, null, 2)
157
+ };
158
+ }
159
+ },
160
+ // ── Claude Code ──────────────────────────────────────────────────────────────
161
+ {
162
+ id: "claude-code",
163
+ label: "Claude Code",
164
+ description: "Add via claude CLI command (HTTP transport)",
165
+ method: "cli-command",
166
+ transport: "http",
167
+ cliIde: "claude",
168
+ build({ projectId, projectName, apiKey, mcpHttpUrl, features, readOnly }) {
169
+ const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
170
+ const url = withReadOnly(
171
+ appendFeaturesToUrl(mcpHttpUrl, features ?? DEFAULT_FEATURE_GROUPS),
172
+ readOnly
173
+ );
174
+ const cmd = `claude mcp add --transport http ${name} "${url}" --header "Authorization: Bearer ${apiKey}"`;
175
+ return { kind: "command", text: cmd };
176
+ }
177
+ },
178
+ // ── Claude Desktop ───────────────────────────────────────────────────────────
179
+ {
180
+ id: "claude-desktop",
181
+ label: "Claude Desktop",
182
+ description: "Copy config into claude_desktop_config.json",
183
+ method: "config-json",
184
+ transport: "stdio",
185
+ build({ projectId, projectName, apiKey, endpoint, features }) {
186
+ const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
187
+ const env = {
188
+ MUSHI_API_ENDPOINT: endpoint,
189
+ MUSHI_API_KEY: apiKey
190
+ };
191
+ if (features) env.MUSHI_FEATURES = featuresQueryString(features);
192
+ if (projectId) env.MUSHI_PROJECT_ID = projectId;
193
+ const desktopConfig = {
194
+ mcpServers: {
195
+ [name]: { command: "npx", args: ["-y", "@mushi-mushi/mcp@latest"], env }
196
+ }
197
+ };
198
+ return {
199
+ kind: "config",
200
+ filePath: "~/Library/Application Support/Claude/claude_desktop_config.json",
201
+ json: JSON.stringify(desktopConfig, null, 2)
202
+ };
203
+ }
204
+ },
205
+ // ── Zed ──────────────────────────────────────────────────────────────────────
206
+ {
207
+ id: "zed",
208
+ label: "Zed",
209
+ description: "Copy config into ~/.config/zed/settings.json",
210
+ method: "config-json",
211
+ transport: "stdio",
212
+ cliIde: "zed",
213
+ build({ projectId, projectName, apiKey, endpoint, features }) {
214
+ const name = projectId ? projectServerName(projectId, projectName) : `mushi-${projectName.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 24)}`;
215
+ const env = {
216
+ MUSHI_API_ENDPOINT: endpoint,
217
+ MUSHI_API_KEY: apiKey
218
+ };
219
+ if (features) env.MUSHI_FEATURES = featuresQueryString(features);
220
+ if (projectId) env.MUSHI_PROJECT_ID = projectId;
221
+ const zedConfig = {
222
+ context_servers: {
223
+ [name]: {
224
+ command: {
225
+ path: "npx",
226
+ args: ["-y", "@mushi-mushi/mcp@latest"],
227
+ env
228
+ },
229
+ settings: {}
230
+ }
231
+ }
232
+ };
233
+ return {
234
+ kind: "config",
235
+ filePath: "~/.config/zed/settings.json",
236
+ json: JSON.stringify(zedConfig, null, 2)
237
+ };
238
+ }
239
+ },
240
+ // ── Any MCP client ───────────────────────────────────────────────────────────
241
+ {
242
+ id: "any",
243
+ label: "Any MCP client",
244
+ description: "Streamable HTTP URL \u2014 works with OpenClaw, Hermes, Gemini CLI, ChatGPT, and more",
245
+ method: "remote-url",
246
+ transport: "http",
247
+ build({ projectId, apiKey, mcpHttpUrl, features, readOnly }) {
248
+ const url = withReadOnly(
249
+ appendFeaturesToUrl(mcpHttpUrl, features ?? DEFAULT_FEATURE_GROUPS),
250
+ readOnly
251
+ );
252
+ const headerSnippet = [
253
+ `Authorization: Bearer ${apiKey}`,
254
+ projectId ? `X-Mushi-Project-Id: ${projectId}` : ""
255
+ ].filter(Boolean).join("\n");
256
+ return { kind: "remote-url", url, headerSnippet };
257
+ }
258
+ }
259
+ ];
260
+ function getMcpClient(id) {
261
+ const client = MCP_CLIENTS.find((c) => c.id === id);
262
+ if (!client) throw new Error(`Unknown MCP client id: ${id}`);
263
+ return client;
264
+ }
265
+ export {
266
+ MCP_CLIENTS,
267
+ buildHttpConfigObj as buildRawHttpConfig,
268
+ buildStdioConfigObj as buildRawStdioConfig,
269
+ getMcpClient,
270
+ projectServerName
271
+ };
@@ -4,7 +4,7 @@
4
4
  * When `features` is omitted → all tools (backward compatible).
5
5
  * New installs default to DEFAULT_FEATURE_GROUPS via deeplink / docs.
6
6
  */
7
- declare const FEATURE_GROUPS: readonly ["triage", "fixes", "inventory", "setup", "qa", "skills", "rewards", "admin", "audit", "docs", "usage", "legacy"];
7
+ declare const FEATURE_GROUPS: readonly ["triage", "fixes", "inventory", "setup", "qa", "skills", "rewards", "admin", "audit", "docs", "usage", "usage", "legacy"];
8
8
  type FeatureGroup = (typeof FEATURE_GROUPS)[number];
9
9
  /** Lean default for new Cursor installs (~45 tools vs 70 full). */
10
10
  declare const DEFAULT_FEATURE_GROUPS: readonly FeatureGroup[];
@@ -9,7 +9,7 @@ import {
9
9
  parseFeaturesCsv,
10
10
  parseFeaturesParam,
11
11
  toolMatchesFeatures
12
- } from "./chunk-ZX3XIJ7S.js";
12
+ } from "./chunk-PW4AK6SV.js";
13
13
  export {
14
14
  ALL_FEATURE_GROUPS,
15
15
  DEFAULT_FEATURE_GROUPS,
package/dist/index.js CHANGED
@@ -11,10 +11,10 @@ var TOOL_CATALOG = [
11
11
  {
12
12
  name: "get_recent_reports",
13
13
  title: "Recent bug reports",
14
- description: "List recent bug reports with optional filters (status / category / severity). Use this to survey what the triage queue looks like right now.",
14
+ description: "List recent bug reports with optional filters (status / category / severity). Use this to see what is waiting in your reports inbox right now.",
15
15
  scope: "mcp:read",
16
16
  hints: { readOnly: true, idempotent: true, openWorld: true },
17
- useCase: "What landed in my triage queue today?"
17
+ useCase: "What landed in my bug queue today?"
18
18
  },
19
19
  {
20
20
  name: "get_report_detail",
@@ -141,7 +141,7 @@ var TOOL_CATALOG = [
141
141
  {
142
142
  name: "setup_check",
143
143
  title: "Dispatch preflight check",
144
- description: "[Deprecated \u2014 use diagnose_setup with mode=dispatch or diagnose_connection.] 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.",
144
+ description: "[Deprecated \u2014 use diagnose_setup with mode=dispatch or diagnose_connection.] Run the 4 **dispatch-readiness** checks for a project and return their pass/fail status (GitHub repo connected, codebase indexed, your Anthropic API key present, autofix enabled). Also returns the target repo URL when GitHub is connected.",
145
145
  scope: "mcp:read",
146
146
  hints: { readOnly: true, idempotent: true, openWorld: true },
147
147
  useCase: "Is this project ready to auto-fix bugs? What is blocking dispatch?"
@@ -191,7 +191,7 @@ var TOOL_CATALOG = [
191
191
  {
192
192
  name: "dispatch_fix",
193
193
  title: "Dispatch Mushi fix agent",
194
- 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.',
194
+ description: 'Start the Mushi fix agent 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.',
195
195
  scope: "mcp:write",
196
196
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
197
197
  useCase: "Let the in-repo agent attempt this fix for me (or: dispatch a Cursor Cloud Agent)."
@@ -207,7 +207,7 @@ var TOOL_CATALOG = [
207
207
  {
208
208
  name: "test_gen_from_report",
209
209
  title: "Generate Playwright test from report",
210
- description: "POST to inventory test-gen: uses the project BYOK LLM to author a Playwright spec from a classified report and opens a draft PR (internal service orchestration). Requires inventory_v2 + GitHub + LLM keys.",
210
+ description: "Generate a Playwright spec from a classified report using your project LLM key and open a draft PR. Requires inventory_v2 + GitHub + LLM keys.",
211
211
  scope: "mcp:write",
212
212
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
213
213
  useCase: "Turn this regression report into an E2E test PR."
@@ -239,7 +239,7 @@ var TOOL_CATALOG = [
239
239
  {
240
240
  name: "reopen_report",
241
241
  title: "Reopen report (operator)",
242
- description: "Move a report to reopened for regression triage.",
242
+ description: "Move a report to reopened for regression review.",
243
243
  scope: "mcp:write",
244
244
  hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
245
245
  useCase: "Reopen a regression the reporter flagged as not fixed."
@@ -297,10 +297,10 @@ var TOOL_CATALOG = [
297
297
  {
298
298
  name: "get_project_context",
299
299
  title: "Project context snapshot",
300
- description: "Return a rich context snapshot for a project: name, repo URL, SDK heartbeat, ingest status, open report count, autofix readiness, BYOK LLM config, plan tier, and active integration health. Equivalent to a merged preflight + activation + settings read. Agents should call this at the start of a triage session to orient themselves.",
300
+ description: "Return a rich context snapshot for a project: name, repo URL, SDK heartbeat, ingest status, open report count, autofix readiness, your LLM key config, plan tier, and active integration health. Equivalent to a merged preflight + activation + settings read. Agents should call this at the start of a review session to orient themselves.",
301
301
  scope: "mcp:read",
302
302
  hints: { readOnly: true, idempotent: true, openWorld: true },
303
- useCase: "Give me a status overview of this project before I start triaging."
303
+ useCase: "Give me a status overview of this project before I start reviewing bugs."
304
304
  },
305
305
  {
306
306
  name: "get_pipeline_logs",
@@ -321,7 +321,7 @@ var TOOL_CATALOG = [
321
321
  {
322
322
  name: "triage_issue",
323
323
  title: "Triage issue end-to-end",
324
- description: 'Read-only orchestration tool: combines report detail, evidence, similar bugs, fix context, blast radius, and recent pipeline logs for a report into a single structured triage packet. Returns a prioritised list of recommended next actions (investigate, dispatch_fix, group_with, dismiss). Equivalent to a Sentry "Analyze with Seer" flow grounded in user-felt reports. Pass report_id to kick off triage. Call this before dispatch_fix.',
324
+ description: 'Read-only combined tool: merges report detail, evidence, similar bugs, fix context, blast radius, and recent pipeline logs for a report into a single structured review packet. Returns a prioritised list of recommended next actions (investigate, dispatch_fix, group_with, dismiss). Equivalent to a Sentry "Analyze with Seer" flow grounded in user-felt reports. Pass report_id to kick off review. Call this before dispatch_fix.',
325
325
  scope: "mcp:read",
326
326
  hints: { readOnly: true, idempotent: true, openWorld: true },
327
327
  useCase: "Analyze this bug report end-to-end and tell me what to do."
@@ -371,8 +371,8 @@ var TDD_TOOL_CATALOG = [
371
371
  },
372
372
  {
373
373
  name: "improve_qa_story",
374
- title: "PDCA auto-improve a failing QA story",
375
- 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.",
374
+ title: "Auto-improve a failing QA story",
375
+ description: "Find recently failed qa_story_runs and use Claude to write improved test scripts. New tests are created with source=pdca and approval gated by the original story's automation_mode.",
376
376
  scope: "mcp:write",
377
377
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
378
378
  useCase: "Fix my failing QA tests automatically."
@@ -387,16 +387,16 @@ var TDD_TOOL_CATALOG = [
387
387
  },
388
388
  {
389
389
  name: "list_byok_keys",
390
- title: "List BYOK API key pool",
391
- 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.",
390
+ title: "List your API key pool",
391
+ description: "List all project API keys, 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.",
392
392
  scope: "mcp:read",
393
393
  hints: { readOnly: true, idempotent: true, openWorld: true },
394
394
  useCase: "Which API keys are active and which are rate-limited?"
395
395
  },
396
396
  {
397
397
  name: "add_byok_key",
398
- title: "Add a BYOK API key",
399
- 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.",
398
+ title: "Add an API key",
399
+ description: "Add a new API key to the project pool for a given provider (anthropic, openai, firecrawl, browserbase, cursor). Specify label and priority for ordering. The key is stored encrypted in Supabase Vault.",
400
400
  scope: "mcp:write",
401
401
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
402
402
  useCase: "Add a backup Anthropic key to the pool."
@@ -545,7 +545,7 @@ var CODEBASE_TOOL_CATALOG = [
545
545
  {
546
546
  name: "ask_codebase",
547
547
  title: "Ask about the indexed codebase",
548
- description: "Ask a plain-English question about the connected repo. Grounds on pgvector retrieval over project_codebase_files and returns an answer with file:line citations. Requires codebase indexing enabled and an Anthropic or OpenAI BYOK key.",
548
+ description: "Ask a plain-English question about the connected repo. Grounds on pgvector retrieval over project_codebase_files and returns an answer with file:line citations. Requires codebase indexing enabled and your Anthropic or OpenAI API key.",
549
549
  scope: "mcp:write",
550
550
  hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
551
551
  useCase: "How does authentication work in this repo?"
@@ -613,6 +613,7 @@ var FEATURE_GROUPS = [
613
613
  "audit",
614
614
  "docs",
615
615
  "usage",
616
+ "usage",
616
617
  /** Deprecated aliases kept for one release — hidden unless `legacy` or `all`. */
617
618
  "legacy"
618
619
  ];
@@ -857,6 +858,7 @@ function searchMushiDocs(query, limit = 8) {
857
858
  }
858
859
 
859
860
  // src/server.ts
861
+ var NO_ARG_INPUT = {};
860
862
  var MushiApiError = class extends Error {
861
863
  constructor(status, code, message) {
862
864
  super(`[${code}] ${message}`);
@@ -933,7 +935,16 @@ function createMushiServer(config) {
933
935
  return body;
934
936
  }
935
937
  async function resolveProjectId(explicitId) {
936
- if (explicitId) return explicitId;
938
+ if (explicitId) {
939
+ if (projectId && explicitId !== projectId) {
940
+ throw new MushiApiError(
941
+ 400,
942
+ "PROJECT_SCOPE_MISMATCH",
943
+ `This MCP entry is bound to project ${projectId}. Either pass projectId="${projectId}", add a second MCP server entry for ${explicitId} (Console \u2192 Connect \u2192 Add to Cursor while that project is selected), or run \`mushi setup --all-projects --ide cursor\` for one entry per repo.`
944
+ );
945
+ }
946
+ return explicitId;
947
+ }
937
948
  if (projectId) return projectId;
938
949
  try {
939
950
  const data = await apiCall(
@@ -964,6 +975,11 @@ function createMushiServer(config) {
964
975
  );
965
976
  }
966
977
  }
978
+ async function projectScopeHeaders(explicitId) {
979
+ const pid = await resolveProjectId(explicitId ?? void 0);
980
+ const headers = { "X-Mushi-Project-Id": pid };
981
+ return { projectId: pid, headers };
982
+ }
967
983
  function jsonText(value) {
968
984
  return {
969
985
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
@@ -1045,11 +1061,15 @@ function createMushiServer(config) {
1045
1061
  title: titleOf("get_report_detail"),
1046
1062
  description: descOf("get_report_detail"),
1047
1063
  annotations: annotationsFor("get_report_detail"),
1048
- inputSchema: { reportId: z.string().describe("The report UUID") },
1064
+ inputSchema: {
1065
+ reportId: z.string().describe("The report UUID"),
1066
+ project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
1067
+ },
1049
1068
  outputSchema: { report: z.unknown() }
1050
1069
  },
1051
1070
  async (args) => {
1052
- const data = await apiCall(`/v1/admin/reports/${args.reportId}`);
1071
+ const { headers } = await projectScopeHeaders(args.project_id);
1072
+ const data = await apiCall(`/v1/admin/reports/${args.reportId}`, { headers });
1053
1073
  const res = jsonResult({ report: data });
1054
1074
  return {
1055
1075
  ...res,
@@ -1075,7 +1095,7 @@ function createMushiServer(config) {
1075
1095
  title: titleOf("get_two_way_comms_health"),
1076
1096
  description: descOf("get_two_way_comms_health"),
1077
1097
  annotations: annotationsFor("get_two_way_comms_health"),
1078
- inputSchema: {}
1098
+ inputSchema: NO_ARG_INPUT
1079
1099
  },
1080
1100
  async () => jsonText(await apiCall("/v1/sync/two-way-health"))
1081
1101
  );
@@ -1140,7 +1160,10 @@ function createMushiServer(config) {
1140
1160
  title: titleOf("get_fix_context"),
1141
1161
  description: descOf("get_fix_context"),
1142
1162
  annotations: annotationsFor("get_fix_context"),
1143
- inputSchema: { reportId: z.string().describe("The report UUID to fix") },
1163
+ inputSchema: {
1164
+ reportId: z.string().describe("The report UUID to fix"),
1165
+ project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
1166
+ },
1144
1167
  outputSchema: {
1145
1168
  report: z.unknown(),
1146
1169
  fixPrompt: z.unknown(),
@@ -1151,7 +1174,10 @@ function createMushiServer(config) {
1151
1174
  }
1152
1175
  },
1153
1176
  async (args) => {
1154
- const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
1177
+ const { headers } = await projectScopeHeaders(args.project_id);
1178
+ const report = await apiCall(`/v1/admin/reports/${args.reportId}`, {
1179
+ headers
1180
+ });
1155
1181
  return jsonResult({
1156
1182
  report,
1157
1183
  // Paste-ready fix prompt composed server-side by composeFixPacket()
@@ -1171,9 +1197,15 @@ function createMushiServer(config) {
1171
1197
  title: titleOf("get_fix_timeline"),
1172
1198
  description: descOf("get_fix_timeline"),
1173
1199
  annotations: annotationsFor("get_fix_timeline"),
1174
- inputSchema: { fixId: z.string().describe("fix_attempt UUID") }
1200
+ inputSchema: {
1201
+ fixId: z.string().describe("fix_attempt UUID"),
1202
+ project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
1203
+ }
1175
1204
  },
1176
- async (args) => jsonText(await apiCall(`/v1/admin/fixes/${args.fixId}/timeline`))
1205
+ async (args) => {
1206
+ const { headers } = await projectScopeHeaders(args.project_id);
1207
+ return jsonText(await apiCall(`/v1/admin/fixes/${args.fixId}/timeline`, { headers }));
1208
+ }
1177
1209
  );
1178
1210
  server.registerTool(
1179
1211
  "get_blast_radius",
@@ -1370,7 +1402,7 @@ function createMushiServer(config) {
1370
1402
  title: titleOf("ingest_setup_check"),
1371
1403
  description: descOf("ingest_setup_check"),
1372
1404
  annotations: annotationsFor("ingest_setup_check"),
1373
- inputSchema: {}
1405
+ inputSchema: NO_ARG_INPUT
1374
1406
  },
1375
1407
  async () => {
1376
1408
  const data = await apiCall("/v1/sync/ingest-setup");
@@ -1464,9 +1496,11 @@ function createMushiServer(config) {
1464
1496
  const failed = preflight.checks.filter((c) => !c.ready);
1465
1497
  dispatchBlock = failed[0]?.hint;
1466
1498
  }
1467
- } catch {
1499
+ } catch (err) {
1468
1500
  dispatchReady = false;
1469
- dispatchBlock = "Could not run dispatch preflight \u2014 verify project_id and API key scope.";
1501
+ const detail = err instanceof Error ? err.message : String(err);
1502
+ dispatchBlock = `Could not run dispatch preflight \u2014 ${detail}`;
1503
+ apiLog.warn("diagnose_setup dispatch preflight failed", { error: detail });
1470
1504
  }
1471
1505
  }
1472
1506
  const ingestFailed = ingest.steps.filter((s) => s.required && !s.complete);
@@ -1490,7 +1524,7 @@ function createMushiServer(config) {
1490
1524
  title: titleOf("diagnose_connection"),
1491
1525
  description: descOf("diagnose_connection"),
1492
1526
  annotations: annotationsFor("diagnose_connection"),
1493
- inputSchema: {},
1527
+ inputSchema: NO_ARG_INPUT,
1494
1528
  outputSchema: {
1495
1529
  ready: z.boolean(),
1496
1530
  healthOk: z.boolean(),
@@ -1643,7 +1677,7 @@ function createMushiServer(config) {
1643
1677
  title: titleOf("list_projects"),
1644
1678
  description: descOf("list_projects"),
1645
1679
  annotations: annotationsFor("list_projects"),
1646
- inputSchema: {},
1680
+ inputSchema: NO_ARG_INPUT,
1647
1681
  outputSchema: {
1648
1682
  projects: z.array(z.unknown()),
1649
1683
  total: z.number().optional(),
@@ -1664,7 +1698,7 @@ function createMushiServer(config) {
1664
1698
  title: titleOf("get_account_overview"),
1665
1699
  description: descOf("get_account_overview"),
1666
1700
  annotations: annotationsFor("get_account_overview"),
1667
- inputSchema: {},
1701
+ inputSchema: NO_ARG_INPUT,
1668
1702
  outputSchema: {
1669
1703
  projects: z.array(z.unknown()),
1670
1704
  total: z.number(),
@@ -1783,7 +1817,7 @@ function createMushiServer(config) {
1783
1817
  description: descOf("triage_issue"),
1784
1818
  annotations: annotationsFor("triage_issue"),
1785
1819
  inputSchema: {
1786
- report_id: z.string().describe("Report UUID to triage."),
1820
+ report_id: z.string().uuid().describe("Report UUID to triage."),
1787
1821
  project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
1788
1822
  include_logs: z.boolean().optional().describe("Include recent pipeline logs in triage packet (default: true).")
1789
1823
  },
@@ -1803,18 +1837,19 @@ function createMushiServer(config) {
1803
1837
  }
1804
1838
  },
1805
1839
  async (args) => {
1806
- const pid = args.project_id ?? projectId;
1840
+ const { projectId: pid, headers } = await projectScopeHeaders(args.project_id);
1807
1841
  const includeLogs = args.include_logs !== false;
1808
1842
  const [reportRes, evidenceRes, similarRes, fixCtxRes, blastRes, logsRes] = await Promise.allSettled([
1809
- apiCall(`/v1/admin/reports/${args.report_id}`),
1810
- apiCall(`/v1/admin/reports/${args.report_id}/timeline`),
1843
+ apiCall(`/v1/admin/reports/${args.report_id}`, { headers }),
1844
+ apiCall(`/v1/admin/reports/${args.report_id}/timeline`, { headers }),
1811
1845
  apiCall(`/v1/admin/reports/similarity`, {
1812
1846
  method: "POST",
1847
+ headers,
1813
1848
  body: JSON.stringify({ report_id: args.report_id })
1814
1849
  }).catch(() => null),
1815
- pid ? apiCall(`/v1/admin/reports/${args.report_id}/fix-context`).catch(() => null) : Promise.resolve(null),
1816
- pid ? apiCall(`/v1/admin/reports/${args.report_id}/blast-radius`).catch(() => null) : Promise.resolve(null),
1817
- includeLogs && pid ? apiCall(`/v1/admin/mcp/logs/${pid}?limit=20&level=warn`).catch(() => null) : Promise.resolve(null)
1850
+ apiCall(`/v1/admin/reports/${args.report_id}/fix-context`, { headers }).catch(() => null),
1851
+ apiCall(`/v1/admin/reports/${args.report_id}/blast-radius`, { headers }).catch(() => null),
1852
+ includeLogs ? apiCall(`/v1/admin/mcp/logs/${pid}?limit=20&level=warn`, { headers }).catch(() => null) : Promise.resolve(null)
1818
1853
  ]);
1819
1854
  const report = reportRes.status === "fulfilled" ? reportRes.value : null;
1820
1855
  const severity = report ? report.severity : "unknown";
@@ -1843,11 +1878,31 @@ function createMushiServer(config) {
1843
1878
  args: { reportId: args.report_id }
1844
1879
  });
1845
1880
  }
1881
+ const partial_errors = [];
1882
+ if (reportRes.status === "rejected") {
1883
+ partial_errors.push(`report: ${String(reportRes.reason)}`);
1884
+ }
1885
+ if (evidenceRes.status === "rejected") {
1886
+ partial_errors.push(`timeline: ${String(evidenceRes.reason)}`);
1887
+ }
1888
+ if (similarRes.status === "rejected") {
1889
+ partial_errors.push(`similarity: ${String(similarRes.reason)}`);
1890
+ }
1891
+ if (fixCtxRes.status === "rejected") {
1892
+ partial_errors.push(`fix_context: ${String(fixCtxRes.reason)}`);
1893
+ }
1894
+ if (blastRes.status === "rejected") {
1895
+ partial_errors.push(`blast_radius: ${String(blastRes.reason)}`);
1896
+ }
1897
+ if (includeLogs && logsRes.status === "rejected") {
1898
+ partial_errors.push(`recent_logs: ${String(logsRes.reason)}`);
1899
+ }
1846
1900
  const result = {
1847
1901
  report_id: args.report_id,
1848
1902
  severity,
1849
1903
  category,
1850
1904
  status,
1905
+ partial_errors,
1851
1906
  report: reportRes.status === "fulfilled" ? reportRes.value : { error: String(reportRes.reason) },
1852
1907
  reporter_thread: evidenceRes.status === "fulfilled" ? evidenceRes.value : null,
1853
1908
  similar_bugs: similarRes.status === "fulfilled" ? similarRes.value : null,
@@ -1912,7 +1967,7 @@ function createMushiServer(config) {
1912
1967
  description: descOf("dispatch_fix"),
1913
1968
  annotations: annotationsFor("dispatch_fix"),
1914
1969
  inputSchema: {
1915
- reportId: z.string().describe("Report UUID to fix"),
1970
+ reportId: z.string().uuid().describe("Report UUID to fix"),
1916
1971
  agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
1917
1972
  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)."),
1918
1973
  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.")
@@ -3079,6 +3134,7 @@ Be specific. No generic advice.`
3079
3134
  }
3080
3135
 
3081
3136
  // src/index.ts
3137
+ import * as Sentry from "@sentry/node";
3082
3138
  var _writeStderr = (...args) => process.stderr.write(
3083
3139
  args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ") + "\n"
3084
3140
  );
@@ -3094,6 +3150,14 @@ var SCOPES_RAW = process.env.MUSHI_SCOPES ?? "";
3094
3150
  var parsedScopes = SCOPES_RAW ? SCOPES_RAW.split(",").map((s) => s.trim()).filter((s) => s === "mcp:read" || s === "mcp:write") : ALL_SCOPES;
3095
3151
  var SCOPES = SCOPES_RAW && parsedScopes.length === 0 ? ALL_SCOPES : parsedScopes;
3096
3152
  var FEATURES = parseFeaturesCsv(process.env.MUSHI_FEATURES);
3153
+ var MCP_SENTRY_DSN = process.env.MUSHI_MCP_SENTRY_DSN?.trim();
3154
+ if (MCP_SENTRY_DSN) {
3155
+ Sentry.init({
3156
+ dsn: MCP_SENTRY_DSN,
3157
+ environment: process.env.MUSHI_SENTRY_ENVIRONMENT ?? process.env.NODE_ENV ?? "development",
3158
+ tracesSampleRate: 0
3159
+ });
3160
+ }
3097
3161
  async function main() {
3098
3162
  if (!API_KEY) {
3099
3163
  log.fatal("MUSHI_API_KEY environment variable is required");
@@ -3149,7 +3213,8 @@ async function main() {
3149
3213
  }
3150
3214
  lastInventoryAt = updatedAt;
3151
3215
  }
3152
- } catch {
3216
+ } catch (pollErr) {
3217
+ log.debug("inventory poll failed", { err: String(pollErr) });
3153
3218
  }
3154
3219
  };
3155
3220
  void pollInventory();
package/dist/server.js CHANGED
@@ -2,13 +2,13 @@ import {
2
2
  CODEBASE_TOOL_CATALOG,
3
3
  TDD_TOOL_CATALOG,
4
4
  TOOL_CATALOG
5
- } from "./chunk-MM3IMFM4.js";
5
+ } from "./chunk-7UOTJ2LI.js";
6
6
  import {
7
7
  MUSHI_SERVER_METADATA
8
- } from "./chunk-EHB2BGWX.js";
8
+ } from "./chunk-EANW5YTI.js";
9
9
  import {
10
10
  toolMatchesFeatures
11
- } from "./chunk-ZX3XIJ7S.js";
11
+ } from "./chunk-PW4AK6SV.js";
12
12
 
13
13
  // src/server.ts
14
14
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -123,6 +123,7 @@ function searchMushiDocs(query, limit = 8) {
123
123
  }
124
124
 
125
125
  // src/server.ts
126
+ var NO_ARG_INPUT = {};
126
127
  var MushiApiError = class extends Error {
127
128
  constructor(status, code, message) {
128
129
  super(`[${code}] ${message}`);
@@ -199,7 +200,16 @@ function createMushiServer(config) {
199
200
  return body;
200
201
  }
201
202
  async function resolveProjectId(explicitId) {
202
- if (explicitId) return explicitId;
203
+ if (explicitId) {
204
+ if (projectId && explicitId !== projectId) {
205
+ throw new MushiApiError(
206
+ 400,
207
+ "PROJECT_SCOPE_MISMATCH",
208
+ `This MCP entry is bound to project ${projectId}. Either pass projectId="${projectId}", add a second MCP server entry for ${explicitId} (Console \u2192 Connect \u2192 Add to Cursor while that project is selected), or run \`mushi setup --all-projects --ide cursor\` for one entry per repo.`
209
+ );
210
+ }
211
+ return explicitId;
212
+ }
203
213
  if (projectId) return projectId;
204
214
  try {
205
215
  const data = await apiCall(
@@ -230,6 +240,11 @@ function createMushiServer(config) {
230
240
  );
231
241
  }
232
242
  }
243
+ async function projectScopeHeaders(explicitId) {
244
+ const pid = await resolveProjectId(explicitId ?? void 0);
245
+ const headers = { "X-Mushi-Project-Id": pid };
246
+ return { projectId: pid, headers };
247
+ }
233
248
  function jsonText(value) {
234
249
  return {
235
250
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
@@ -311,11 +326,15 @@ function createMushiServer(config) {
311
326
  title: titleOf("get_report_detail"),
312
327
  description: descOf("get_report_detail"),
313
328
  annotations: annotationsFor("get_report_detail"),
314
- inputSchema: { reportId: z.string().describe("The report UUID") },
329
+ inputSchema: {
330
+ reportId: z.string().describe("The report UUID"),
331
+ project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
332
+ },
315
333
  outputSchema: { report: z.unknown() }
316
334
  },
317
335
  async (args) => {
318
- const data = await apiCall(`/v1/admin/reports/${args.reportId}`);
336
+ const { headers } = await projectScopeHeaders(args.project_id);
337
+ const data = await apiCall(`/v1/admin/reports/${args.reportId}`, { headers });
319
338
  const res = jsonResult({ report: data });
320
339
  return {
321
340
  ...res,
@@ -341,7 +360,7 @@ function createMushiServer(config) {
341
360
  title: titleOf("get_two_way_comms_health"),
342
361
  description: descOf("get_two_way_comms_health"),
343
362
  annotations: annotationsFor("get_two_way_comms_health"),
344
- inputSchema: {}
363
+ inputSchema: NO_ARG_INPUT
345
364
  },
346
365
  async () => jsonText(await apiCall("/v1/sync/two-way-health"))
347
366
  );
@@ -406,7 +425,10 @@ function createMushiServer(config) {
406
425
  title: titleOf("get_fix_context"),
407
426
  description: descOf("get_fix_context"),
408
427
  annotations: annotationsFor("get_fix_context"),
409
- inputSchema: { reportId: z.string().describe("The report UUID to fix") },
428
+ inputSchema: {
429
+ reportId: z.string().describe("The report UUID to fix"),
430
+ project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
431
+ },
410
432
  outputSchema: {
411
433
  report: z.unknown(),
412
434
  fixPrompt: z.unknown(),
@@ -417,7 +439,10 @@ function createMushiServer(config) {
417
439
  }
418
440
  },
419
441
  async (args) => {
420
- const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
442
+ const { headers } = await projectScopeHeaders(args.project_id);
443
+ const report = await apiCall(`/v1/admin/reports/${args.reportId}`, {
444
+ headers
445
+ });
421
446
  return jsonResult({
422
447
  report,
423
448
  // Paste-ready fix prompt composed server-side by composeFixPacket()
@@ -437,9 +462,15 @@ function createMushiServer(config) {
437
462
  title: titleOf("get_fix_timeline"),
438
463
  description: descOf("get_fix_timeline"),
439
464
  annotations: annotationsFor("get_fix_timeline"),
440
- inputSchema: { fixId: z.string().describe("fix_attempt UUID") }
465
+ inputSchema: {
466
+ fixId: z.string().describe("fix_attempt UUID"),
467
+ project_id: z.string().optional().describe("Project UUID \u2014 required for org-scoped keys with multiple projects.")
468
+ }
441
469
  },
442
- async (args) => jsonText(await apiCall(`/v1/admin/fixes/${args.fixId}/timeline`))
470
+ async (args) => {
471
+ const { headers } = await projectScopeHeaders(args.project_id);
472
+ return jsonText(await apiCall(`/v1/admin/fixes/${args.fixId}/timeline`, { headers }));
473
+ }
443
474
  );
444
475
  server.registerTool(
445
476
  "get_blast_radius",
@@ -636,7 +667,7 @@ function createMushiServer(config) {
636
667
  title: titleOf("ingest_setup_check"),
637
668
  description: descOf("ingest_setup_check"),
638
669
  annotations: annotationsFor("ingest_setup_check"),
639
- inputSchema: {}
670
+ inputSchema: NO_ARG_INPUT
640
671
  },
641
672
  async () => {
642
673
  const data = await apiCall("/v1/sync/ingest-setup");
@@ -730,9 +761,11 @@ function createMushiServer(config) {
730
761
  const failed = preflight.checks.filter((c) => !c.ready);
731
762
  dispatchBlock = failed[0]?.hint;
732
763
  }
733
- } catch {
764
+ } catch (err) {
734
765
  dispatchReady = false;
735
- dispatchBlock = "Could not run dispatch preflight \u2014 verify project_id and API key scope.";
766
+ const detail = err instanceof Error ? err.message : String(err);
767
+ dispatchBlock = `Could not run dispatch preflight \u2014 ${detail}`;
768
+ apiLog.warn("diagnose_setup dispatch preflight failed", { error: detail });
736
769
  }
737
770
  }
738
771
  const ingestFailed = ingest.steps.filter((s) => s.required && !s.complete);
@@ -756,7 +789,7 @@ function createMushiServer(config) {
756
789
  title: titleOf("diagnose_connection"),
757
790
  description: descOf("diagnose_connection"),
758
791
  annotations: annotationsFor("diagnose_connection"),
759
- inputSchema: {},
792
+ inputSchema: NO_ARG_INPUT,
760
793
  outputSchema: {
761
794
  ready: z.boolean(),
762
795
  healthOk: z.boolean(),
@@ -909,7 +942,7 @@ function createMushiServer(config) {
909
942
  title: titleOf("list_projects"),
910
943
  description: descOf("list_projects"),
911
944
  annotations: annotationsFor("list_projects"),
912
- inputSchema: {},
945
+ inputSchema: NO_ARG_INPUT,
913
946
  outputSchema: {
914
947
  projects: z.array(z.unknown()),
915
948
  total: z.number().optional(),
@@ -930,7 +963,7 @@ function createMushiServer(config) {
930
963
  title: titleOf("get_account_overview"),
931
964
  description: descOf("get_account_overview"),
932
965
  annotations: annotationsFor("get_account_overview"),
933
- inputSchema: {},
966
+ inputSchema: NO_ARG_INPUT,
934
967
  outputSchema: {
935
968
  projects: z.array(z.unknown()),
936
969
  total: z.number(),
@@ -1049,7 +1082,7 @@ function createMushiServer(config) {
1049
1082
  description: descOf("triage_issue"),
1050
1083
  annotations: annotationsFor("triage_issue"),
1051
1084
  inputSchema: {
1052
- report_id: z.string().describe("Report UUID to triage."),
1085
+ report_id: z.string().uuid().describe("Report UUID to triage."),
1053
1086
  project_id: z.string().optional().describe("Project UUID. Defaults to configured project."),
1054
1087
  include_logs: z.boolean().optional().describe("Include recent pipeline logs in triage packet (default: true).")
1055
1088
  },
@@ -1069,18 +1102,19 @@ function createMushiServer(config) {
1069
1102
  }
1070
1103
  },
1071
1104
  async (args) => {
1072
- const pid = args.project_id ?? projectId;
1105
+ const { projectId: pid, headers } = await projectScopeHeaders(args.project_id);
1073
1106
  const includeLogs = args.include_logs !== false;
1074
1107
  const [reportRes, evidenceRes, similarRes, fixCtxRes, blastRes, logsRes] = await Promise.allSettled([
1075
- apiCall(`/v1/admin/reports/${args.report_id}`),
1076
- apiCall(`/v1/admin/reports/${args.report_id}/timeline`),
1108
+ apiCall(`/v1/admin/reports/${args.report_id}`, { headers }),
1109
+ apiCall(`/v1/admin/reports/${args.report_id}/timeline`, { headers }),
1077
1110
  apiCall(`/v1/admin/reports/similarity`, {
1078
1111
  method: "POST",
1112
+ headers,
1079
1113
  body: JSON.stringify({ report_id: args.report_id })
1080
1114
  }).catch(() => null),
1081
- pid ? apiCall(`/v1/admin/reports/${args.report_id}/fix-context`).catch(() => null) : Promise.resolve(null),
1082
- pid ? apiCall(`/v1/admin/reports/${args.report_id}/blast-radius`).catch(() => null) : Promise.resolve(null),
1083
- includeLogs && pid ? apiCall(`/v1/admin/mcp/logs/${pid}?limit=20&level=warn`).catch(() => null) : Promise.resolve(null)
1115
+ apiCall(`/v1/admin/reports/${args.report_id}/fix-context`, { headers }).catch(() => null),
1116
+ apiCall(`/v1/admin/reports/${args.report_id}/blast-radius`, { headers }).catch(() => null),
1117
+ includeLogs ? apiCall(`/v1/admin/mcp/logs/${pid}?limit=20&level=warn`, { headers }).catch(() => null) : Promise.resolve(null)
1084
1118
  ]);
1085
1119
  const report = reportRes.status === "fulfilled" ? reportRes.value : null;
1086
1120
  const severity = report ? report.severity : "unknown";
@@ -1109,11 +1143,31 @@ function createMushiServer(config) {
1109
1143
  args: { reportId: args.report_id }
1110
1144
  });
1111
1145
  }
1146
+ const partial_errors = [];
1147
+ if (reportRes.status === "rejected") {
1148
+ partial_errors.push(`report: ${String(reportRes.reason)}`);
1149
+ }
1150
+ if (evidenceRes.status === "rejected") {
1151
+ partial_errors.push(`timeline: ${String(evidenceRes.reason)}`);
1152
+ }
1153
+ if (similarRes.status === "rejected") {
1154
+ partial_errors.push(`similarity: ${String(similarRes.reason)}`);
1155
+ }
1156
+ if (fixCtxRes.status === "rejected") {
1157
+ partial_errors.push(`fix_context: ${String(fixCtxRes.reason)}`);
1158
+ }
1159
+ if (blastRes.status === "rejected") {
1160
+ partial_errors.push(`blast_radius: ${String(blastRes.reason)}`);
1161
+ }
1162
+ if (includeLogs && logsRes.status === "rejected") {
1163
+ partial_errors.push(`recent_logs: ${String(logsRes.reason)}`);
1164
+ }
1112
1165
  const result = {
1113
1166
  report_id: args.report_id,
1114
1167
  severity,
1115
1168
  category,
1116
1169
  status,
1170
+ partial_errors,
1117
1171
  report: reportRes.status === "fulfilled" ? reportRes.value : { error: String(reportRes.reason) },
1118
1172
  reporter_thread: evidenceRes.status === "fulfilled" ? evidenceRes.value : null,
1119
1173
  similar_bugs: similarRes.status === "fulfilled" ? similarRes.value : null,
@@ -1178,7 +1232,7 @@ function createMushiServer(config) {
1178
1232
  description: descOf("dispatch_fix"),
1179
1233
  annotations: annotationsFor("dispatch_fix"),
1180
1234
  inputSchema: {
1181
- reportId: z.string().describe("Report UUID to fix"),
1235
+ reportId: z.string().uuid().describe("Report UUID to fix"),
1182
1236
  agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
1183
1237
  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)."),
1184
1238
  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.")
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mushi-mushi/mcp",
3
3
  "mcpName": "io.github.kensaurus/mushi-mushi",
4
- "version": "0.16.2",
4
+ "version": "0.17.1",
5
5
  "license": "MIT",
6
6
  "description": "MCP server for Mushi Mushi — triage user-felt bugs, pull evidence (screenshots, console logs, network requests), check blast radius, and dispatch fix PRs, all from your AI coding agent",
7
7
  "type": "module",
@@ -25,6 +25,10 @@
25
25
  "./feature-groups": {
26
26
  "types": "./dist/feature-groups.d.ts",
27
27
  "import": "./dist/feature-groups.js"
28
+ },
29
+ "./clients": {
30
+ "types": "./dist/clients.d.ts",
31
+ "import": "./dist/clients.js"
28
32
  }
29
33
  },
30
34
  "main": "./dist/index.js",
@@ -42,8 +46,9 @@
42
46
  ],
43
47
  "dependencies": {
44
48
  "@modelcontextprotocol/sdk": "^1.29.0",
49
+ "@sentry/node": "^10.57.0",
45
50
  "zod": "^4.4.3",
46
- "@mushi-mushi/core": "^1.20.0"
51
+ "@mushi-mushi/core": "^1.22.1"
47
52
  },
48
53
  "devDependencies": {
49
54
  "@types/node": "^22.19.21",