@mushi-mushi/mcp 0.15.0 → 0.16.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
@@ -63,7 +63,7 @@ packages/
63
63
  verify/ Fix verification (BSL)
64
64
  apps/
65
65
  admin/ Admin dashboard (React + Tailwind)
66
- docs/ Documentation site (planned)
66
+ docs/ Documentation site (Nextra — apps/docs)
67
67
  tooling/
68
68
  eslint-config/ Shared ESLint flat config
69
69
  tsconfig/ Shared TypeScript configs
@@ -81,6 +81,28 @@ tooling/
81
81
  ```
82
82
  6. Open a pull request
83
83
 
84
+ ### Documentation (docs-as-code)
85
+
86
+ If your PR changes **user-visible SDK or CLI behavior**, update docs in the **same PR**:
87
+
88
+ | Surface | When to update |
89
+ | --- | --- |
90
+ | `packages/*/README.md` | npm-facing install/API/config changes for that package |
91
+ | `apps/docs/content/**/*.mdx` | Long-form guides, quickstarts, admin console docs |
92
+ | Root `README.md` | Marketing stats only when counts change — run `pnpm check:docs-stats` |
93
+
94
+ Guardrails (run locally before pushing):
95
+
96
+ ```bash
97
+ pnpm check:docs-stats # root README stat badges
98
+ pnpm check:sdk-version-matrix # apps/docs/content/sdks/index.mdx versions
99
+ pnpm check:internal-doc-links # dead /foo links in MDX
100
+ pnpm check:bundle-docs # @mushi-mushi/web size-limit vs README
101
+ pnpm gen:llms-txt # regenerate apps/docs/public/llms.txt after nav changes
102
+ ```
103
+
104
+ README changes on npm packages only appear on [npmjs.com](https://www.npmjs.com) after the next **Changesets publish**.
105
+
84
106
  ## Changesets
85
107
 
86
108
  We use [Changesets](https://github.com/changesets/changesets) for versioning. If your PR modifies a published package (`core`, `web`, `react`, `vue`, `svelte`, `angular`, `react-native`, `cli`, `mcp`), add a changeset:
package/README.md CHANGED
@@ -146,7 +146,7 @@ The Mushi backend now exposes the same tool catalog over the **Streamable HTTP**
146
146
 
147
147
  ### Feature groups (`?features=` / `MUSHI_FEATURES`)
148
148
 
149
- Full catalog is **72 tools** (64 core + 8 project-context tools). New installs default to a lean set via:
149
+ Full catalog is **79 tools**. New installs default to a lean set via:
150
150
 
151
151
  - **Stdio:** `MUSHI_FEATURES=triage,fixes,inventory,setup,docs`
152
152
  - **HTTP:** `?features=triage,fixes,inventory,setup,docs` on the MCP URL
@@ -157,6 +157,17 @@ New unified tools: `diagnose_setup` (replaces setup trilogy), `search_mushi_docs
157
157
 
158
158
  Append `?read_only=1` to the URL to hide write tools (Supabase-parity mode).
159
159
 
160
+ ## Programmatic imports
161
+
162
+ For custom MCP hosts or tests:
163
+
164
+ | Subpath | Exports |
165
+ | --- | --- |
166
+ | `@mushi-mushi/mcp/catalog` | `TOOL_CATALOG`, scopes, tool specs |
167
+ | `@mushi-mushi/mcp/server` | `createMushiServer()` |
168
+ | `@mushi-mushi/mcp/branding` | Icons and server metadata |
169
+ | `@mushi-mushi/mcp/feature-groups` | `MUSHI_FEATURES` filtering |
170
+
160
171
  ## Cursor config hygiene
161
172
 
162
173
  1. **One global MCP config** — keep `mushi` in `~/.cursor/mcp.json` only. Do **not** duplicate `mushi` / `mushi-stdio` in per-project `.cursor/mcp.json` (prevents connection storms). Project repos may ship `{}` or a comment pointing to the global file.
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-FD6GVGQS.js";
8
+ } from "./chunk-PZEGN4XD.js";
9
9
  export {
10
10
  ALL_SCOPES,
11
11
  CODEBASE_TOOL_CATALOG,
@@ -274,7 +274,7 @@ var TOOL_CATALOG = [
274
274
  {
275
275
  name: "list_projects",
276
276
  title: "List accessible projects",
277
- description: `List the Mushi projects accessible to this API key. For project-scoped keys (the typical case), returns a single-item list with the bound project's id, name, created_at. Use this when MUSHI_PROJECT_ID is not configured \u2014 call list_projects first, then pass the id to other tools. Multi-project note: each Mushi project uses its own API key. To connect multiple projects, run "Add to Cursor" in the Mushi console for each project \u2014 each deeplink creates a uniquely-named MCP server entry (mushi-{slug}-{id}). You can then call list_projects on each named server to confirm which project it serves.`,
277
+ description: "List all Mushi projects accessible to this API key. For project-scoped keys returns a single-item list; for org-scoped keys (account mode) returns every project owned by this account. Call this first when MUSHI_PROJECT_ID is not configured \u2014 use the returned id with subsequent tool calls. If multiple projects are returned, pass the target projectId explicitly to project-scoped tools. Multi-project tip: run `mushi setup --all-projects` in your terminal to create one named MCP server entry per project in .cursor/mcp.json.",
278
278
  scope: "mcp:read",
279
279
  hints: { readOnly: true, idempotent: true, openWorld: true },
280
280
  useCase: "Which projects can I access with this API key?"
@@ -282,7 +282,7 @@ var TOOL_CATALOG = [
282
282
  {
283
283
  name: "get_account_overview",
284
284
  title: "Account overview \u2014 all projects",
285
- description: "Return a summary of every Mushi project accessible to this API key, including report counts, SDK heartbeat status, and the last ingest timestamp for each. For project-scoped keys this is a one-item list; for future org-level keys it lists every project. Also includes a hint explaining how to connect additional projects to your IDE. Call this at the start of a multi-repo or multi-app triage session to orient yourself.",
285
+ description: "Return an enriched summary of every Mushi project accessible to this API key: id, name, recent report count (last 30 days), number of connected MCP keys, and last-seen heartbeat timestamp. For project-scoped keys this is a one-item list; for org-scoped (account) keys it lists every owned project. Also includes toolCount, resourceCount, promptCount so agents know how many tools are available. Call this at the start of a multi-repo or multi-app triage session to orient yourself.",
286
286
  scope: "mcp:read",
287
287
  hints: { readOnly: true, idempotent: true, openWorld: true },
288
288
  useCase: "Show me all my Mushi projects and their health at a glance."
package/dist/index.js CHANGED
@@ -281,7 +281,7 @@ var TOOL_CATALOG = [
281
281
  {
282
282
  name: "list_projects",
283
283
  title: "List accessible projects",
284
- description: `List the Mushi projects accessible to this API key. For project-scoped keys (the typical case), returns a single-item list with the bound project's id, name, created_at. Use this when MUSHI_PROJECT_ID is not configured \u2014 call list_projects first, then pass the id to other tools. Multi-project note: each Mushi project uses its own API key. To connect multiple projects, run "Add to Cursor" in the Mushi console for each project \u2014 each deeplink creates a uniquely-named MCP server entry (mushi-{slug}-{id}). You can then call list_projects on each named server to confirm which project it serves.`,
284
+ description: "List all Mushi projects accessible to this API key. For project-scoped keys returns a single-item list; for org-scoped keys (account mode) returns every project owned by this account. Call this first when MUSHI_PROJECT_ID is not configured \u2014 use the returned id with subsequent tool calls. If multiple projects are returned, pass the target projectId explicitly to project-scoped tools. Multi-project tip: run `mushi setup --all-projects` in your terminal to create one named MCP server entry per project in .cursor/mcp.json.",
285
285
  scope: "mcp:read",
286
286
  hints: { readOnly: true, idempotent: true, openWorld: true },
287
287
  useCase: "Which projects can I access with this API key?"
@@ -289,7 +289,7 @@ var TOOL_CATALOG = [
289
289
  {
290
290
  name: "get_account_overview",
291
291
  title: "Account overview \u2014 all projects",
292
- description: "Return a summary of every Mushi project accessible to this API key, including report counts, SDK heartbeat status, and the last ingest timestamp for each. For project-scoped keys this is a one-item list; for future org-level keys it lists every project. Also includes a hint explaining how to connect additional projects to your IDE. Call this at the start of a multi-repo or multi-app triage session to orient yourself.",
292
+ description: "Return an enriched summary of every Mushi project accessible to this API key: id, name, recent report count (last 30 days), number of connected MCP keys, and last-seen heartbeat timestamp. For project-scoped keys this is a one-item list; for org-scoped (account) keys it lists every owned project. Also includes toolCount, resourceCount, promptCount so agents know how many tools are available. Call this at the start of a multi-repo or multi-app triage session to orient yourself.",
293
293
  scope: "mcp:read",
294
294
  hints: { readOnly: true, idempotent: true, openWorld: true },
295
295
  useCase: "Show me all my Mushi projects and their health at a glance."
@@ -749,14 +749,26 @@ var MUSHI_DOCS_INDEX = [
749
749
  {
750
750
  title: "MCP setup",
751
751
  path: `${BASE}/guides/mcp-setup`,
752
- keywords: ["mcp", "cursor", "api key", "stdio", "http"],
753
- excerpt: "Wire Mushi MCP into Cursor, Claude, or any MCP client."
752
+ keywords: ["mcp", "cursor", "api key", "stdio", "http", "multi-project", "account mode", "install", "setup", "deeplink"],
753
+ excerpt: "Wire Mushi MCP into Cursor, Claude, or any MCP client. Supports per-project and account-level keys."
754
754
  },
755
755
  {
756
756
  title: "API key scopes",
757
757
  path: `${BASE}/guides/api-keys`,
758
- keywords: ["api key", "mcp:read", "mcp:write", "scopes", "security"],
759
- excerpt: "Separate SDK ingest keys from MCP read/write keys."
758
+ keywords: ["api key", "mcp:read", "mcp:write", "scopes", "security", "account key", "org-scoped", "multi-project"],
759
+ excerpt: "Separate SDK ingest keys from MCP read/write keys. Mint an org-scoped account key for multi-project access."
760
+ },
761
+ {
762
+ title: "Account key (org-scoped)",
763
+ path: `${BASE}/guides/mcp-setup`,
764
+ keywords: ["account key", "org-scoped", "multi-project", "MUSHI_PROJECT_ID", "all projects", "mint-org-key", "get_account_overview"],
765
+ excerpt: "Mint one org-scoped key that covers all your projects; no MUSHI_PROJECT_ID needed. The agent calls get_account_overview to discover projects."
766
+ },
767
+ {
768
+ title: "Cursor MCP troubleshooting",
769
+ path: `${BASE}/guides/mcp-setup`,
770
+ keywords: ["cursor", "mcp.json", "red badge", "transport error", "stdout", "stderr", "json-rpc", "INVALID_TOKEN", "syntax helper", "diagnose_connection"],
771
+ excerpt: "Cursor stdio MCP requires stdout to contain only JSON-RPC. Use the console syntax helper, restart Cursor MCP, then run diagnose_connection."
760
772
  },
761
773
  {
762
774
  title: "Judge loop",
@@ -848,7 +860,8 @@ function createMushiServer(config) {
848
860
  const doFetch = config.fetch ?? globalThis.fetch;
849
861
  const apiLog = createLogger({
850
862
  scope: "mushi:mcp:api",
851
- level: process.env.MUSHI_LOG_LEVEL ?? "info"
863
+ level: process.env.MUSHI_LOG_LEVEL ?? "info",
864
+ destination: "stderr"
852
865
  });
853
866
  if (config.scopes !== void 0 && config.scopes.length === 0) {
854
867
  return new McpServer({
@@ -907,6 +920,38 @@ function createMushiServer(config) {
907
920
  apiLog.info("api.done", { path, requestId, status: res.status, durationMs });
908
921
  return body;
909
922
  }
923
+ async function resolveProjectId(explicitId) {
924
+ if (explicitId) return explicitId;
925
+ if (projectId) return projectId;
926
+ try {
927
+ const data = await apiCall(
928
+ "/v1/admin/mcp/account-overview"
929
+ );
930
+ if (data.total === 1 && data.projects[0]) {
931
+ return data.projects[0].id;
932
+ }
933
+ if (data.total === 0) {
934
+ throw new MushiApiError(
935
+ 400,
936
+ "NO_ACCESSIBLE_PROJECTS",
937
+ "This API key has no accessible projects. Mint an API key on a project in the admin console."
938
+ );
939
+ }
940
+ const projectList = data.projects.map((p) => `${p.name ?? "unnamed"} (${p.id})`).join(", ");
941
+ throw new MushiApiError(
942
+ 400,
943
+ "PROJECT_ID_REQUIRED",
944
+ `Multiple projects are accessible with this key: ${projectList}. Pass projectId explicitly to target a specific project, or set MUSHI_PROJECT_ID in your MCP env config.`
945
+ );
946
+ } catch (err) {
947
+ if (err instanceof MushiApiError) throw err;
948
+ throw new MushiApiError(
949
+ 400,
950
+ "MISSING_PROJECT_ID",
951
+ "projectId is required. Set MUSHI_PROJECT_ID in .cursor/mcp.json or pass it explicitly."
952
+ );
953
+ }
954
+ }
910
955
  function jsonText(value) {
911
956
  return {
912
957
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
@@ -974,8 +1019,8 @@ function createMushiServer(config) {
974
1019
  if (args.category) params.set("category", args.category);
975
1020
  if (args.severity) params.set("severity", args.severity);
976
1021
  params.set("limit", String(Math.min(args.limit ?? 20, 100)));
977
- const pid = args.project_id ?? projectId;
978
- const extraHeaders = pid && pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
1022
+ const pid = await resolveProjectId(args.project_id);
1023
+ const extraHeaders = pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
979
1024
  const data = await apiCall(`/v1/admin/reports?${params}`, {
980
1025
  headers: extraHeaders
981
1026
  });
@@ -1436,6 +1481,7 @@ function createMushiServer(config) {
1436
1481
  dispatchReady: z.boolean(),
1437
1482
  endpoint: z.string().nullable(),
1438
1483
  projectId: z.string().nullable(),
1484
+ accessibleProjectCount: z.number().nullable(),
1439
1485
  issues: z.array(z.unknown()),
1440
1486
  nextAction: z.string(),
1441
1487
  summary: z.string()
@@ -1450,13 +1496,6 @@ function createMushiServer(config) {
1450
1496
  fix: "Run `mushi connect` to write MUSHI_API_KEY into .cursor/mcp.json, then restart the MCP server."
1451
1497
  });
1452
1498
  }
1453
- if (!projectId) {
1454
- issues.push({
1455
- check: "mcp_project_id",
1456
- detail: "No projectId configured on the MCP server",
1457
- fix: "Add MUSHI_PROJECT_ID to .cursor/mcp.json (copy UUID from console \u2192 Projects)."
1458
- });
1459
- }
1460
1499
  if (!apiEndpoint) {
1461
1500
  issues.push({
1462
1501
  check: "mcp_endpoint",
@@ -1488,6 +1527,21 @@ function createMushiServer(config) {
1488
1527
  }
1489
1528
  let ingestReady = false;
1490
1529
  let dispatchReady = false;
1530
+ let accessibleProjectCount = null;
1531
+ if (!projectId && healthOk) {
1532
+ try {
1533
+ const overview = await apiCall("/v1/admin/mcp/account-overview");
1534
+ accessibleProjectCount = overview.total;
1535
+ if (overview.total === 0) {
1536
+ issues.push({
1537
+ check: "no_accessible_projects",
1538
+ detail: "API key has no accessible projects",
1539
+ fix: "Mint an API key on a project (console \u2192 Projects \u2192 API Keys) or add MUSHI_PROJECT_ID to restrict to a single project."
1540
+ });
1541
+ }
1542
+ } catch {
1543
+ }
1544
+ }
1491
1545
  try {
1492
1546
  const ingest = await apiCall("/v1/sync/ingest-setup");
1493
1547
  ingestReady = ingest.ready;
@@ -1530,6 +1584,7 @@ function createMushiServer(config) {
1530
1584
  dispatchReady,
1531
1585
  endpoint: apiEndpoint ?? null,
1532
1586
  projectId: projectId ?? null,
1587
+ accessibleProjectCount,
1533
1588
  issues,
1534
1589
  nextAction,
1535
1590
  summary: ready ? "MCP credentials valid; ingest pipeline ready." : `Connection issue \u2014 ${issues[0]?.check ?? "unknown"}: ${nextAction}`
@@ -1595,16 +1650,24 @@ function createMushiServer(config) {
1595
1650
  inputSchema: {},
1596
1651
  outputSchema: {
1597
1652
  projects: z.array(z.unknown()),
1653
+ total: z.number(),
1598
1654
  active_project_id: z.string().nullable(),
1655
+ toolCount: z.number().optional(),
1656
+ resourceCount: z.number().optional(),
1657
+ promptCount: z.number().optional(),
1599
1658
  multi_project_hint: z.string()
1600
1659
  }
1601
1660
  },
1602
1661
  async () => {
1603
- const data = await apiCall("/v1/admin/mcp/projects");
1662
+ const data = await apiCall("/v1/admin/mcp/account-overview");
1604
1663
  const projects = Array.isArray(data?.projects) ? data.projects : [];
1605
1664
  return jsonResult({
1606
1665
  projects,
1666
+ total: data?.total ?? projects.length,
1607
1667
  active_project_id: projectId ?? null,
1668
+ toolCount: data?.toolCount,
1669
+ resourceCount: data?.resourceCount,
1670
+ promptCount: data?.promptCount,
1608
1671
  multi_project_hint: projects.length <= 1 ? 'You are currently connected to one project. To connect additional Mushi projects, open the Mushi console \u2192 switch to each project \u2192 MCP \u2192 Setup \u2192 "\u26A1 Add to Cursor". Each click adds a uniquely-named entry (mushi-{name}-{id}) to your global ~/.cursor/mcp.json so you can triage reports across all your apps from a single Cursor session.' : `You have access to ${projects.length} projects. To connect any unconnected project, open the Mushi console \u2192 switch to that project \u2192 MCP \u2192 Setup \u2192 "\u26A1 Add to Cursor".`
1609
1672
  });
1610
1673
  }
@@ -2956,9 +3019,14 @@ Be specific. No generic advice.`
2956
3019
  }
2957
3020
 
2958
3021
  // src/index.ts
3022
+ var _writeStderr = (...args) => process.stderr.write(
3023
+ args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ") + "\n"
3024
+ );
3025
+ console.log = _writeStderr;
3026
+ console.warn = _writeStderr;
2959
3027
  var require2 = createRequire(import.meta.url);
2960
3028
  var VERSION = require2("../package.json").version;
2961
- var log = createLogger2({ scope: "mushi:mcp", level: "info" });
3029
+ var log = createLogger2({ scope: "mushi:mcp", level: "info", destination: "stderr" });
2962
3030
  var API_ENDPOINT = process.env.MUSHI_API_ENDPOINT ?? "";
2963
3031
  var API_KEY = process.env.MUSHI_API_KEY ?? "";
2964
3032
  var PROJECT_ID = process.env.MUSHI_PROJECT_ID ?? "";
@@ -2976,13 +3044,15 @@ async function main() {
2976
3044
  "[mushi-mcp] MUSHI_API_ENDPOINT is not set. All tool calls will fail.\nSet MUSHI_API_ENDPOINT to your Supabase edge function URL, e.g. https://xyz.supabase.co/functions/v1/api"
2977
3045
  );
2978
3046
  }
3047
+ const mode = PROJECT_ID ? "single-project" : "account";
2979
3048
  if (!PROJECT_ID) {
2980
- console.error(
2981
- "[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."
3049
+ log.info(
3050
+ "[mushi-mcp] Running in account mode (no MUSHI_PROJECT_ID set). Project-scoped tools accept an explicit projectId argument. Run `get_account_overview` to see accessible projects."
2982
3051
  );
2983
3052
  }
2984
3053
  log.info("Starting Mushi MCP server", {
2985
3054
  version: VERSION,
3055
+ mode,
2986
3056
  endpoint: API_ENDPOINT || "(unset)",
2987
3057
  hasProjectId: !!PROJECT_ID,
2988
3058
  scopes: SCOPES.join(",")
package/dist/server.js CHANGED
@@ -2,7 +2,7 @@ import {
2
2
  CODEBASE_TOOL_CATALOG,
3
3
  TDD_TOOL_CATALOG,
4
4
  TOOL_CATALOG
5
- } from "./chunk-FD6GVGQS.js";
5
+ } from "./chunk-PZEGN4XD.js";
6
6
  import {
7
7
  MUSHI_SERVER_METADATA
8
8
  } from "./chunk-EHB2BGWX.js";
@@ -27,14 +27,26 @@ var MUSHI_DOCS_INDEX = [
27
27
  {
28
28
  title: "MCP setup",
29
29
  path: `${BASE}/guides/mcp-setup`,
30
- keywords: ["mcp", "cursor", "api key", "stdio", "http"],
31
- excerpt: "Wire Mushi MCP into Cursor, Claude, or any MCP client."
30
+ keywords: ["mcp", "cursor", "api key", "stdio", "http", "multi-project", "account mode", "install", "setup", "deeplink"],
31
+ excerpt: "Wire Mushi MCP into Cursor, Claude, or any MCP client. Supports per-project and account-level keys."
32
32
  },
33
33
  {
34
34
  title: "API key scopes",
35
35
  path: `${BASE}/guides/api-keys`,
36
- keywords: ["api key", "mcp:read", "mcp:write", "scopes", "security"],
37
- excerpt: "Separate SDK ingest keys from MCP read/write keys."
36
+ keywords: ["api key", "mcp:read", "mcp:write", "scopes", "security", "account key", "org-scoped", "multi-project"],
37
+ excerpt: "Separate SDK ingest keys from MCP read/write keys. Mint an org-scoped account key for multi-project access."
38
+ },
39
+ {
40
+ title: "Account key (org-scoped)",
41
+ path: `${BASE}/guides/mcp-setup`,
42
+ keywords: ["account key", "org-scoped", "multi-project", "MUSHI_PROJECT_ID", "all projects", "mint-org-key", "get_account_overview"],
43
+ excerpt: "Mint one org-scoped key that covers all your projects; no MUSHI_PROJECT_ID needed. The agent calls get_account_overview to discover projects."
44
+ },
45
+ {
46
+ title: "Cursor MCP troubleshooting",
47
+ path: `${BASE}/guides/mcp-setup`,
48
+ keywords: ["cursor", "mcp.json", "red badge", "transport error", "stdout", "stderr", "json-rpc", "INVALID_TOKEN", "syntax helper", "diagnose_connection"],
49
+ excerpt: "Cursor stdio MCP requires stdout to contain only JSON-RPC. Use the console syntax helper, restart Cursor MCP, then run diagnose_connection."
38
50
  },
39
51
  {
40
52
  title: "Judge loop",
@@ -126,7 +138,8 @@ function createMushiServer(config) {
126
138
  const doFetch = config.fetch ?? globalThis.fetch;
127
139
  const apiLog = createLogger({
128
140
  scope: "mushi:mcp:api",
129
- level: process.env.MUSHI_LOG_LEVEL ?? "info"
141
+ level: process.env.MUSHI_LOG_LEVEL ?? "info",
142
+ destination: "stderr"
130
143
  });
131
144
  if (config.scopes !== void 0 && config.scopes.length === 0) {
132
145
  return new McpServer({
@@ -185,6 +198,38 @@ function createMushiServer(config) {
185
198
  apiLog.info("api.done", { path, requestId, status: res.status, durationMs });
186
199
  return body;
187
200
  }
201
+ async function resolveProjectId(explicitId) {
202
+ if (explicitId) return explicitId;
203
+ if (projectId) return projectId;
204
+ try {
205
+ const data = await apiCall(
206
+ "/v1/admin/mcp/account-overview"
207
+ );
208
+ if (data.total === 1 && data.projects[0]) {
209
+ return data.projects[0].id;
210
+ }
211
+ if (data.total === 0) {
212
+ throw new MushiApiError(
213
+ 400,
214
+ "NO_ACCESSIBLE_PROJECTS",
215
+ "This API key has no accessible projects. Mint an API key on a project in the admin console."
216
+ );
217
+ }
218
+ const projectList = data.projects.map((p) => `${p.name ?? "unnamed"} (${p.id})`).join(", ");
219
+ throw new MushiApiError(
220
+ 400,
221
+ "PROJECT_ID_REQUIRED",
222
+ `Multiple projects are accessible with this key: ${projectList}. Pass projectId explicitly to target a specific project, or set MUSHI_PROJECT_ID in your MCP env config.`
223
+ );
224
+ } catch (err) {
225
+ if (err instanceof MushiApiError) throw err;
226
+ throw new MushiApiError(
227
+ 400,
228
+ "MISSING_PROJECT_ID",
229
+ "projectId is required. Set MUSHI_PROJECT_ID in .cursor/mcp.json or pass it explicitly."
230
+ );
231
+ }
232
+ }
188
233
  function jsonText(value) {
189
234
  return {
190
235
  content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
@@ -252,8 +297,8 @@ function createMushiServer(config) {
252
297
  if (args.category) params.set("category", args.category);
253
298
  if (args.severity) params.set("severity", args.severity);
254
299
  params.set("limit", String(Math.min(args.limit ?? 20, 100)));
255
- const pid = args.project_id ?? projectId;
256
- const extraHeaders = pid && pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
300
+ const pid = await resolveProjectId(args.project_id);
301
+ const extraHeaders = pid !== projectId ? { "X-Mushi-Project-Id": pid } : {};
257
302
  const data = await apiCall(`/v1/admin/reports?${params}`, {
258
303
  headers: extraHeaders
259
304
  });
@@ -714,6 +759,7 @@ function createMushiServer(config) {
714
759
  dispatchReady: z.boolean(),
715
760
  endpoint: z.string().nullable(),
716
761
  projectId: z.string().nullable(),
762
+ accessibleProjectCount: z.number().nullable(),
717
763
  issues: z.array(z.unknown()),
718
764
  nextAction: z.string(),
719
765
  summary: z.string()
@@ -728,13 +774,6 @@ function createMushiServer(config) {
728
774
  fix: "Run `mushi connect` to write MUSHI_API_KEY into .cursor/mcp.json, then restart the MCP server."
729
775
  });
730
776
  }
731
- if (!projectId) {
732
- issues.push({
733
- check: "mcp_project_id",
734
- detail: "No projectId configured on the MCP server",
735
- fix: "Add MUSHI_PROJECT_ID to .cursor/mcp.json (copy UUID from console \u2192 Projects)."
736
- });
737
- }
738
777
  if (!apiEndpoint) {
739
778
  issues.push({
740
779
  check: "mcp_endpoint",
@@ -766,6 +805,21 @@ function createMushiServer(config) {
766
805
  }
767
806
  let ingestReady = false;
768
807
  let dispatchReady = false;
808
+ let accessibleProjectCount = null;
809
+ if (!projectId && healthOk) {
810
+ try {
811
+ const overview = await apiCall("/v1/admin/mcp/account-overview");
812
+ accessibleProjectCount = overview.total;
813
+ if (overview.total === 0) {
814
+ issues.push({
815
+ check: "no_accessible_projects",
816
+ detail: "API key has no accessible projects",
817
+ fix: "Mint an API key on a project (console \u2192 Projects \u2192 API Keys) or add MUSHI_PROJECT_ID to restrict to a single project."
818
+ });
819
+ }
820
+ } catch {
821
+ }
822
+ }
769
823
  try {
770
824
  const ingest = await apiCall("/v1/sync/ingest-setup");
771
825
  ingestReady = ingest.ready;
@@ -808,6 +862,7 @@ function createMushiServer(config) {
808
862
  dispatchReady,
809
863
  endpoint: apiEndpoint ?? null,
810
864
  projectId: projectId ?? null,
865
+ accessibleProjectCount,
811
866
  issues,
812
867
  nextAction,
813
868
  summary: ready ? "MCP credentials valid; ingest pipeline ready." : `Connection issue \u2014 ${issues[0]?.check ?? "unknown"}: ${nextAction}`
@@ -873,16 +928,24 @@ function createMushiServer(config) {
873
928
  inputSchema: {},
874
929
  outputSchema: {
875
930
  projects: z.array(z.unknown()),
931
+ total: z.number(),
876
932
  active_project_id: z.string().nullable(),
933
+ toolCount: z.number().optional(),
934
+ resourceCount: z.number().optional(),
935
+ promptCount: z.number().optional(),
877
936
  multi_project_hint: z.string()
878
937
  }
879
938
  },
880
939
  async () => {
881
- const data = await apiCall("/v1/admin/mcp/projects");
940
+ const data = await apiCall("/v1/admin/mcp/account-overview");
882
941
  const projects = Array.isArray(data?.projects) ? data.projects : [];
883
942
  return jsonResult({
884
943
  projects,
944
+ total: data?.total ?? projects.length,
885
945
  active_project_id: projectId ?? null,
946
+ toolCount: data?.toolCount,
947
+ resourceCount: data?.resourceCount,
948
+ promptCount: data?.promptCount,
886
949
  multi_project_hint: projects.length <= 1 ? 'You are currently connected to one project. To connect additional Mushi projects, open the Mushi console \u2192 switch to each project \u2192 MCP \u2192 Setup \u2192 "\u26A1 Add to Cursor". Each click adds a uniquely-named entry (mushi-{name}-{id}) to your global ~/.cursor/mcp.json so you can triage reports across all your apps from a single Cursor session.' : `You have access to ${projects.length} projects. To connect any unconnected project, open the Mushi console \u2192 switch to that project \u2192 MCP \u2192 Setup \u2192 "\u26A1 Add to Cursor".`
887
950
  });
888
951
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/mcp",
3
- "version": "0.15.0",
3
+ "version": "0.16.0",
4
4
  "license": "MIT",
5
5
  "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",
6
6
  "type": "module",
@@ -42,7 +42,7 @@
42
42
  "dependencies": {
43
43
  "@modelcontextprotocol/sdk": "^1.29.0",
44
44
  "zod": "^4.4.2",
45
- "@mushi-mushi/core": "^1.14.0"
45
+ "@mushi-mushi/core": "^1.15.0"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^22.19.17",