@mushi-mushi/mcp 0.3.8 → 0.5.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
@@ -91,6 +91,33 @@ pnpm changeset
91
91
 
92
92
  Select the affected packages, the semver bump type, and write a summary. The changeset file gets committed with your PR.
93
93
 
94
+ ## Release flow
95
+
96
+ Releases are fully automated. Maintainers don't run `npm publish` by hand.
97
+
98
+ 1. PRs land on `master` with one or more changeset files in `.changeset/`.
99
+ 2. `release.yml` runs on every push to `master`. It opens (or updates) a `chore: version packages` PR that bumps every affected `package.json`, rolls up the changelogs, and deletes the consumed changesets.
100
+ 3. Merging that "Version Packages" PR re-fires `release.yml`. The publish step authenticates to npm via **OpenID Connect (OIDC) Trusted Publishers** — no long-lived `NPM_TOKEN` is exchanged — and every tarball ships with a **Sigstore provenance attestation** uploaded to the public transparency log.
101
+
102
+ If GitHub's anti-loop protection suppresses the auto re-fire (the squash merge can be attributed to `github-actions[bot]`), trigger the workflow manually: **Actions → release → Run workflow → master**.
103
+
104
+ ### Adding a brand-new publishable package
105
+
106
+ Trusted Publisher bindings are configured **per package** on `npmjs.com` and require the package to already exist on the registry. New packages therefore need a one-time bootstrap before OIDC can take over.
107
+
108
+ 1. Add the package under `packages/<name>/` with a real `version`, `files`, `publishConfig.access: "public"`, `LICENSE`, and the standard fields enforced by `pnpm check:publish-readiness`.
109
+ 2. Build it locally: `pnpm install && pnpm -r build`.
110
+ 3. Mint a short-lived granular access token at `https://www.npmjs.com/settings/<your-user>/tokens/granular-access-tokens/new` — **Bypass 2FA: ON**, **Read and write: All packages**, **Expiration: 7 days**.
111
+ 4. Bootstrap-publish:
112
+ ```bash
113
+ NPM_TOKEN=npm_xxx pnpm bootstrap:new-package
114
+ ```
115
+ The script auto-detects which workspace packages are missing on npm and publishes them via `pnpm publish --no-provenance` (so `workspace:^` specifiers get rewritten to real semver in the tarball).
116
+ 5. The script prints one URL per freshly-published package. Open each, click **GitHub Actions** under "Trusted Publisher", confirm the auto-filled fields (`<owner>` / `<repo>` / `release.yml`), and tap your security key.
117
+ 6. Revoke the bootstrap token at `https://www.npmjs.com/settings/<your-user>/tokens`.
118
+
119
+ From the next changeset bump onward, that package publishes through the normal `release.yml` flow with full OIDC provenance — same as the rest.
120
+
94
121
  ## Code Style
95
122
 
96
123
  - **TypeScript strict mode** — no `any` unless absolutely necessary
package/README.md CHANGED
@@ -50,6 +50,27 @@ MUSHI_API_KEY=key_xxx MUSHI_PROJECT_ID=proj_xxx npx -y @mushi-mushi/mcp@latest
50
50
 
51
51
  The server speaks stdio MCP transport by default — your client launches it as a subprocess.
52
52
 
53
+ ### 4. Hosted Streamable HTTP (no subprocess) — 2026-05-09 release
54
+
55
+ The Mushi backend now exposes the same tool catalog over the **Streamable HTTP** transport from the MCP 2025-03-26 spec at `/functions/v1/mcp`. Use this when you want to skip the local subprocess — typical for OpenAI Agents SDK, ChatGPT Agent, hosted CrewAI, or any orchestrator that talks remote MCP:
56
+
57
+ ```jsonc
58
+ // .cursor/mcp.json — example
59
+ {
60
+ "mcpServers": {
61
+ "mushi-mushi-hosted": {
62
+ "url": "https://api.mushimushi.dev/functions/v1/mcp",
63
+ "headers": {
64
+ "X-Mushi-Api-Key": "mushi_live_…",
65
+ "X-Mushi-Project-Id": "proj_…"
66
+ }
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ The endpoint accepts JSON-RPC 2.0 over POST (returns `application/json` or `text/event-stream` per content negotiation), opens an SSE stream on GET for server-pushed notifications, and accepts DELETE for session termination. Auth is the same dual-mode API-key / JWT used everywhere else on `/v1/admin/*`.
73
+
53
74
  ## Tools
54
75
 
55
76
  ### Read
@@ -188,9 +209,10 @@ Honest scorecard against the MCP 2025-10 spec:
188
209
  - ✅ **Progress notifications** — wired on `dispatch_fix` (the one genuinely long-running call). Sends `notifications/progress` the moment the orchestrator accepts the job.
189
210
  - ✅ **Scope-aware errors** — `[INSUFFICIENT_SCOPE]` surfaces verbatim so agents don't silently retry.
190
211
  - ✅ **Stdio transport** — default for local editor integration.
212
+ - ✅ **Streamable HTTP transport (2025-03-26 spec)** — hosted at `/functions/v1/mcp` on the Mushi backend; same tool catalog, no local subprocess. The previous "⏳" entry shipped in the 2026-05-09 release.
213
+ - ✅ **Spec traceability for `dispatch_fix`** — the `dispatch_fix` tool input schema now accepts `inventoryActionNodeId`, and `get_fix_context` surfaces the inventory `Action` (with `expected_outcome`) the report was classified against. External orchestrators can read the contract before drafting a fix and pass the anchor back at dispatch time.
191
214
  - ⏳ **Resource subscriptions / `notifications/resources/list_changed`** — the spec supports live-updating resources (e.g. dashboard numbers that push rather than poll). Worth adding once Cursor + Claude Desktop both ship client support (currently patchy).
192
215
  - ⏳ **Sampling / elicitation** — letting the server ask the client to run an LLM call (e.g. to draft a commit message from the fix context). Not yet wired; would let us move some orchestrator LLM spend from server-side to the user's own subscription.
193
- - ⏳ **Streamable HTTP transport** — the spec's alternative to stdio for remote hosting. Relevant if we ever host the MCP server on behalf of customers; irrelevant for the local-install path that 95% of users want.
194
216
 
195
217
  If you want a feature from the "⏳" column, open an issue — we're holding them back on "MCP client support has shipped in ≥2 major clients", not on implementation effort.
196
218
 
package/SECURITY.md CHANGED
@@ -19,15 +19,59 @@ If you discover a security vulnerability, please report it responsibly.
19
19
 
20
20
  **Do NOT open a public GitHub issue.**
21
21
 
22
- Instead, email: **kensaurus@gmail.com**
22
+ Use either channel below:
23
+
24
+ 1. **GitHub Private Vulnerability Reporting** — strongly preferred.
25
+ <https://github.com/kensaurus/mushi-mushi/security/advisories/new>
26
+ Routes the report into a private advisory with built-in CVE issuance,
27
+ patch coordination, and contributor-credit workflow.
28
+ 2. **Email** — `kensaurus@gmail.com`, subject prefix `[mushi-security]`.
29
+ PGP welcome but not required.
23
30
 
24
31
  Include:
25
32
  - Description of the vulnerability
26
- - Steps to reproduce
27
- - Impact assessment
33
+ - Steps to reproduce (smallest reproducer wins)
34
+ - Impact assessment (what an attacker gains)
28
35
  - Suggested fix (if any)
36
+ - Whether you want public credit (and how to spell your name)
37
+
38
+ ### Coordinated-disclosure timeline
39
+
40
+ | Day | Action |
41
+ |-----|--------|
42
+ | 0 | Report received |
43
+ | ≤ 2 | Acknowledgment + assigned a tracking ID |
44
+ | ≤ 7 | Triage complete: severity assigned (CVSS 3.1) and target patch date communicated |
45
+ | ≤ 30 | Patch released for critical / high (CVSS ≥ 7.0); ≤ 60 days for medium; best-effort for low |
46
+ | Patch + 7 | Public advisory + CVE published; reporter credited unless they declined |
47
+ | Patch + 90 | Embargo expires regardless; if upstream is unresponsive, the reporter is free to publish |
48
+
49
+ ### Safe harbor
50
+
51
+ Good-faith security research on Mushi Mushi is welcome. If you stay
52
+ within the rules below, we will not pursue legal action, will not ask
53
+ your hosting provider to take you offline, and will publicly credit your
54
+ work:
55
+
56
+ - Test only against your own self-hosted instance, the public demo at
57
+ <https://kensaur.us/mushi-mushi/admin/>, or accounts you own.
58
+ - Do not access, exfiltrate, or modify data belonging to other users.
59
+ - Do not run automated scanning that affects availability for others
60
+ (rate-limit your tooling, exclude `/health`).
61
+ - Disclose privately first (channels above); do not publish before the
62
+ embargo above expires.
63
+ - Do not intentionally exploit a finding to escalate beyond proving it
64
+ exists.
65
+
66
+ If a finding requires touching production data to confirm, **stop and
67
+ ask first** — describe what you'd need to do and we'll spin up a sandbox.
68
+
69
+ ### Hall of fame
29
70
 
30
- We will acknowledge receipt within 48 hours and aim to release a patch within 7 days for critical issues.
71
+ Researchers who report a confirmed vulnerability are credited in the
72
+ release notes for the patched version and added to
73
+ [`docs/SECURITY_HALL_OF_FAME.md`](./docs/SECURITY_HALL_OF_FAME.md) (with
74
+ permission).
31
75
 
32
76
  ## Scope
33
77
 
@@ -48,6 +92,125 @@ We will acknowledge receipt within 48 hours and aim to release a patch within 7
48
92
  - **Rotate API keys** regularly via the admin console
49
93
  - **Enable SSO** for team projects (Enterprise tier)
50
94
  - **Review audit logs** periodically for suspicious activity
95
+ - **Verify SDK integrity** with `npm audit signatures` after install
96
+ - **Set `Content-Security-Policy`** on any page embedding the Mushi widget;
97
+ the widget itself ships with `script-src 'self'` and does not load
98
+ remote scripts.
99
+
100
+ ## Threat model
101
+
102
+ What we treat as in-scope attacker capabilities, and what we don't.
103
+
104
+ | Capability | In scope | Notes |
105
+ |-----------|----------|-------|
106
+ | Unauthenticated network attacker hitting public endpoints | ✅ | Rate-limit + HMAC + replay protection on every webhook endpoint (`packages/server/supabase/functions/_shared/webhook-middleware.ts`). |
107
+ | Authenticated user trying to read another tenant's data | ✅ | Postgres RLS on every `public.*` table; advisor lints reviewed monthly. |
108
+ | Authenticated user trying to escalate to super-admin | ✅ | Role lives in `auth.users.raw_app_meta_data.role`; cannot be self-edited via PostgREST. |
109
+ | Compromised dependency (npm supply-chain attack) | ✅ | 7-day cooldown + provenance + Harden-Runner + pinned SHAs (see "Supply-chain hardening" below). |
110
+ | Stolen API key | ✅ | Per-key scopes (`api_key_has_scope`), revocation via admin console, audit log of every use. |
111
+ | User pasting a Stripe / OpenAI / GitHub PAT into a bug report | ✅ | PII scrubber redacts ~15 vendor token formats client-side before the report leaves the device. Mirrors `packages/core/src/pii-scrubber.ts` across iOS, Android, Flutter, React Native. |
112
+ | Stolen end-user device with the SDK installed | ⚠️ partial | Offline queue is AsyncStorage / Keychain / SharedPreferences — no app-level encryption beyond the OS default. Reports waiting to flush are vulnerable to a forensic attacker. |
113
+ | Compromised Supabase service-role key | ❌ | Treated as a tier-0 incident; would require key rotation and audit-log forensics. Not defendable in software. |
114
+ | Compromise of `kensaurus@gmail.com` | ❌ | Treated as a project-fork event; downstream consumers should pin to the last known-good version and follow the new release channel. |
115
+ | Physical / OS-level attacker on an end-user device | ❌ | Out of scope. |
116
+ | Malicious fork using the Mushi name to ship malware | ❌ (technical) ✅ (legal) | The MIT/BSL grant lets the fork exist; the trademark policy (`TRADEMARK.md`) makes shipping it under the Mushi name an infringement we will pursue. |
117
+
118
+ ## Data handling and PII
119
+
120
+ ### What the SDK collects by default
121
+
122
+ | Field | Scope | PII risk |
123
+ |-------|-------|----------|
124
+ | URL / route the user was on | Always | Low — strip query strings if your routes encode user IDs. |
125
+ | Browser / OS / device | Always | None |
126
+ | Console errors (last 50) | Opt-in via `captureConsole: true` | Medium — can include user data your code logs. |
127
+ | Network failures (URL + status) | Opt-in via `captureNetwork: true` | Medium — query params logged as-is unless you redact in-app. |
128
+ | User id / email / role | Only if you call `setUser()` | High — only set what you need; we do not auto-discover. |
129
+ | Session replay frames | Off by default | High — handled by the masking layer; passwords / cards / opted-out elements never leave the page. |
130
+ | Free-text bug description | Always | Medium — passed through the PII scrubber (see below). |
131
+
132
+ ### What the PII scrubber redacts before send
133
+
134
+ Implemented identically across `@mushi-mushi/core`, the iOS, Android,
135
+ Flutter, and React Native SDKs. Defaults are below — every category can
136
+ be toggled off, but `secretTokens` is on by default and we recommend
137
+ keeping it that way.
138
+
139
+ | Category | Default | Patterns |
140
+ |----------|---------|----------|
141
+ | `ssns` | on | `123-45-6789` |
142
+ | `creditCards` | on | 12–19 digit Luhn-shaped sequences with optional separators |
143
+ | `secretTokens` | on | AWS access key (`AKIA…` / `ASIA…`), AWS secret (`aws_secret_access_key=…`), Stripe (`sk_live_…`, `sk_test_…`, `rk_…`, `pk_…`), Slack (`xox[abpor]-…`), GitHub PAT (`ghp_…`, `github_pat_…`), OpenAI (`sk-…`, `sk-proj-…`), Anthropic (`sk-ant-…`), Google API (`AIza…`), JWT (`eyJ…` 3-segment) |
144
+ | `emails` | on | RFC-5322 lite |
145
+ | `phones` | on | E.164 with optional country code |
146
+ | `ipAddresses` | off | IPv4 (off because internal IPs are usually not PII and noise hurts triage) |
147
+ | `ipv6` | off | Same |
148
+
149
+ The fields scrubbed are:
150
+
151
+ - `description` — primary free-text field of every bug report
152
+ - `summary` — short summary, in the same composer
153
+ - `breadcrumbs[].message` — auto-captured user-action trail (clicks, route changes, console messages)
154
+
155
+ Structured fields you set explicitly (`metadata.userEmail`,
156
+ `metadata.userId`, etc.) are intentionally **not** scrubbed — those are
157
+ opt-in attribution data, and silently rewriting them would break
158
+ support workflows.
159
+
160
+ ### Where data lives
161
+
162
+ - **Reports & telemetry** — Supabase Postgres in the `us-west-1` region.
163
+ - **Session replays** — Supabase Storage, same region. Lifecycle policy
164
+ trims replays older than 30 days unless explicitly retained from the
165
+ admin console.
166
+ - **Inbound webhook bodies** — only a SHA-256 hash + `delivery_id` of
167
+ the body is persisted (`webhook_audit_log`). The full body is
168
+ processed in memory and discarded.
169
+ - **Outbound integrations** (Slack, Jira, …) — Mushi is a sender only;
170
+ the receiving system's retention applies.
171
+
172
+ ### Encryption
173
+
174
+ | Surface | At rest | In transit |
175
+ |---------|---------|------------|
176
+ | Postgres (Supabase) | AES-256 (Supabase default) | TLS 1.2+ |
177
+ | Supabase Storage (replays) | AES-256 | TLS 1.2+ |
178
+ | Edge Function ↔ Postgres | — | TLS via the Supavisor pooler |
179
+ | SDK ↔ ingest endpoint | — | TLS 1.2+ enforced; HSTS preload on `kensaur.us` |
180
+ | Inbound webhooks | — | TLS terminated at CloudFront / Supabase edge |
181
+ | Audit log integrity | append-only by RLS; no in-row signing | — |
182
+
183
+ ### Cryptographic primitives
184
+
185
+ | Use | Algorithm | Implementation |
186
+ |-----|-----------|---------------|
187
+ | Webhook HMAC verification (Sentry, GitHub, Datadog, Honeycomb, Grafana, Bugsnag, Rollbar, Crashlytics) | HMAC-SHA256, constant-time compare | Web Crypto in Deno; `crypto.subtle.timingSafeEqual` analogue |
188
+ | AWS SNS subscription confirmation | RSA-SHA1 / RSA-SHA256 | Deno `crypto.subtle.verify` with the cert from `SigningCertURL` (URL allow-listed to `*.sns.*.amazonaws.com`) |
189
+ | Opsgenie JWT shared-token | HS256 with `aud` claim verification | `jose` (Deno-compatible) |
190
+ | API-key hashing (database) | SHA-256 prefix + bcrypt secret half | `pgcrypto` |
191
+ | Provenance attestations (npm) | Sigstore (Fulcio + Rekor) | `npm publish --provenance` |
192
+
193
+ We deliberately do not roll our own crypto. If you find an algorithm or
194
+ library above that has been deprecated, please file a security advisory.
195
+
196
+ ### Operator security checklist
197
+
198
+ When you provision a new self-hosted Mushi instance:
199
+
200
+ - [ ] Set `auth_leaked_password_protection = true` in Supabase Auth
201
+ (HaveIBeenPwned blocklist; flagged as `auth_leaked_password_protection`
202
+ in the security advisor).
203
+ - [ ] Enable at least two MFA factors in Supabase Auth (`auth_insufficient_mfa_options`).
204
+ - [ ] Rotate the service-role key on day 1, then quarterly.
205
+ - [ ] Restrict Postgres direct connections to your CI / migration runners
206
+ via Supabase network restrictions.
207
+ - [ ] Run `pnpm dlx supabase advisors --project-ref <ref>` after every
208
+ migration; aim for zero ERROR-level findings.
209
+ - [ ] Configure a Supabase log drain to your SIEM if you are subject to
210
+ SOC 2 / ISO 27001.
211
+ - [ ] Set CSP `frame-ancestors` on the host page if you embed the Mushi
212
+ widget (the widget is iframe-friendly but does not enforce
213
+ framing constraints itself).
51
214
 
52
215
  ## Supply-chain hardening (how this package is protected)
53
216
 
package/dist/index.js CHANGED
@@ -176,6 +176,31 @@ var TOOL_CATALOG = [
176
176
  // client prompts the user on every call.
177
177
  hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
178
178
  useCase: "Dismiss this duplicate / mark it fixed."
179
+ },
180
+ // --- Rewards (P3) -------------------------------------------------------
181
+ {
182
+ name: "list_top_contributors",
183
+ title: "Top contributors leaderboard",
184
+ description: "Return the top N contributors for the organization, ranked by points in a time window (30d | 90d | all). Each row includes display name, tier, total points, report count, and anti-fraud flag. Use this to identify your most engaged power users, write them a thank-you message, or decide who deserves a bonus.",
185
+ scope: "mcp:read",
186
+ hints: { readOnly: true, idempotent: true, openWorld: true },
187
+ useCase: "Who are my top 10 contributors this month?"
188
+ },
189
+ {
190
+ name: "award_bonus_points",
191
+ title: "Award bonus points",
192
+ description: "Award ad-hoc bonus points to a contributor by their external user id (as passed to Mushi.identify()). Points are applied server-side, audit-logged, and trigger tier re-evaluation. Requires mcp:write scope. Use this to thank a contributor who found a critical bug or to run a one-off promotional campaign.",
193
+ scope: "mcp:write",
194
+ hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
195
+ useCase: "Give this contributor 500 bonus points for the critical bug they found."
196
+ },
197
+ {
198
+ name: "set_tier",
199
+ title: "Override contributor tier",
200
+ description: `Override a contributor's tier by tier slug (e.g. "champion"). This is an admin escape hatch for manual promotions \u2014 normal tier transitions happen automatically via point thresholds. The override is logged in end_user_activity with action=tier_override_manual. Requires mcp:write scope.`,
201
+ scope: "mcp:write",
202
+ hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
203
+ useCase: "Manually promote this user to Champion tier as a thank-you."
179
204
  }
180
205
  ];
181
206
 
@@ -570,7 +595,9 @@ function createMushiServer(config) {
570
595
  annotations: annotationsFor("dispatch_fix"),
571
596
  inputSchema: {
572
597
  reportId: z.string().describe("Report UUID to fix"),
573
- agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter")
598
+ agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
599
+ idempotencyKey: z.string().uuid().optional().describe("Optional RFC 4122 UUID. Resend the same key to safely retry without dispatching a duplicate fix job (Idempotency-Key IETF draft)."),
600
+ 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.")
574
601
  }
575
602
  },
576
603
  async (args, extra) => {
@@ -590,9 +617,11 @@ function createMushiServer(config) {
590
617
  }
591
618
  const data = await apiCall("/v1/admin/fixes/dispatch", {
592
619
  method: "POST",
620
+ headers: args.idempotencyKey ? { "Idempotency-Key": args.idempotencyKey } : void 0,
593
621
  body: JSON.stringify({
594
622
  reportId: args.reportId,
595
623
  agent: args.agent,
624
+ inventoryActionNodeId: args.inventoryActionNodeId,
596
625
  ...projectId ? { projectId } : {}
597
626
  })
598
627
  });
@@ -686,6 +715,113 @@ function createMushiServer(config) {
686
715
  contents: [{ uri: "project://dashboard", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2) }]
687
716
  })
688
717
  );
718
+ server.registerTool(
719
+ "list_top_contributors",
720
+ {
721
+ title: titleOf("list_top_contributors"),
722
+ description: descOf("list_top_contributors"),
723
+ inputSchema: {
724
+ limit: z.number().int().min(1).max(100).optional().default(10).describe("Max rows to return (default 10, max 100)"),
725
+ range: z.enum(["30d", "90d", "all"]).optional().default("30d").describe("Time window for points calculation")
726
+ },
727
+ annotations: annotationsFor("list_top_contributors")
728
+ },
729
+ async ({ limit, range }) => ({
730
+ content: [{
731
+ type: "text",
732
+ text: JSON.stringify(
733
+ await apiCall(`/v1/admin/rewards/leaderboard?range=${range}&limit=${limit}`),
734
+ null,
735
+ 2
736
+ )
737
+ }]
738
+ })
739
+ );
740
+ server.registerTool(
741
+ "award_bonus_points",
742
+ {
743
+ title: titleOf("award_bonus_points"),
744
+ description: descOf("award_bonus_points"),
745
+ inputSchema: {
746
+ external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
747
+ points: z.number().int().min(1).max(5e4).describe("Bonus points to award (max 50,000 per call)"),
748
+ reason: z.string().max(200).describe("Human-readable reason, logged to end_user_activity")
749
+ },
750
+ annotations: annotationsFor("award_bonus_points")
751
+ },
752
+ async ({ external_user_id, points, reason }) => ({
753
+ content: [{
754
+ type: "text",
755
+ text: JSON.stringify(
756
+ await apiCall("/v1/admin/rewards/bonus-points", {
757
+ method: "POST",
758
+ headers: { "Content-Type": "application/json" },
759
+ body: JSON.stringify({ external_user_id, points, reason })
760
+ }),
761
+ null,
762
+ 2
763
+ )
764
+ }]
765
+ })
766
+ );
767
+ server.registerTool(
768
+ "set_tier",
769
+ {
770
+ title: titleOf("set_tier"),
771
+ description: descOf("set_tier"),
772
+ inputSchema: {
773
+ external_user_id: z.string().describe("The host-app user id as passed to Mushi.identify()"),
774
+ tier_slug: z.string().describe('Tier slug to assign, e.g. "champion", "contributor", "explorer"'),
775
+ reason: z.string().max(200).optional().describe("Optional reason for manual override")
776
+ },
777
+ annotations: annotationsFor("set_tier")
778
+ },
779
+ async ({ external_user_id, tier_slug, reason }) => ({
780
+ content: [{
781
+ type: "text",
782
+ text: JSON.stringify(
783
+ await apiCall("/v1/admin/rewards/set-tier", {
784
+ method: "POST",
785
+ headers: { "Content-Type": "application/json" },
786
+ body: JSON.stringify({ external_user_id, tier_slug, reason })
787
+ }),
788
+ null,
789
+ 2
790
+ )
791
+ }]
792
+ })
793
+ );
794
+ server.resource(
795
+ "project_integration_health",
796
+ "project://integration-health",
797
+ {
798
+ 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."
799
+ },
800
+ async () => ({
801
+ contents: [{
802
+ uri: "project://integration-health",
803
+ mimeType: "application/json",
804
+ text: JSON.stringify(await apiCall("/v1/admin/integrations/health"), null, 2)
805
+ }]
806
+ })
807
+ );
808
+ server.resource(
809
+ "inventory_current",
810
+ "inventory://current",
811
+ {
812
+ description: "Current inventory snapshot for the active project \u2014 all Action nodes with their spec contract (expected_outcome), build-gate status, linked reports, and fix attempts. Subscribe to this resource to receive notifications/resources/updated when the inventory is re-crawled (e.g. after a PR merge or manual trigger). Orchestrators can use this to enumerate work items and pick the next Action to fix."
813
+ },
814
+ async () => {
815
+ const path = projectId ? `/v1/admin/inventory/${projectId}` : "/v1/admin/inventory";
816
+ return {
817
+ contents: [{
818
+ uri: "inventory://current",
819
+ mimeType: "application/json",
820
+ text: JSON.stringify(await apiCall(path), null, 2)
821
+ }]
822
+ };
823
+ }
824
+ );
689
825
  server.prompt(
690
826
  "summarize_report_for_fix",
691
827
  "Turn a Mushi report into a one-line root cause, smallest file set, repro steps, and blast-radius warnings. Use before asking an agent to write the patch.",
@@ -762,7 +898,7 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
762
898
  var require2 = createRequire(import.meta.url);
763
899
  var VERSION = require2("../package.json").version;
764
900
  var log = createLogger({ scope: "mushi:mcp", level: "info" });
765
- var API_ENDPOINT = process.env.MUSHI_API_ENDPOINT ?? "https://api.mushimushi.dev";
901
+ var API_ENDPOINT = process.env.MUSHI_API_ENDPOINT ?? "";
766
902
  var API_KEY = process.env.MUSHI_API_KEY ?? "";
767
903
  var PROJECT_ID = process.env.MUSHI_PROJECT_ID ?? "";
768
904
  async function main() {
@@ -770,7 +906,17 @@ async function main() {
770
906
  log.fatal("MUSHI_API_KEY environment variable is required");
771
907
  process.exit(1);
772
908
  }
773
- log.info("Starting Mushi MCP server", { version: VERSION, endpoint: API_ENDPOINT, hasProjectId: !!PROJECT_ID });
909
+ if (!API_ENDPOINT) {
910
+ console.error(
911
+ "[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"
912
+ );
913
+ }
914
+ if (!PROJECT_ID) {
915
+ console.error(
916
+ "[mushi-mcp] MUSHI_PROJECT_ID is not set.\n\nTools that scope to a project (get_recent_reports, get_report_detail,\nsearch_reports, etc.) will require you to pass projectId explicitly on\nevery call. To set it once and never pass it again:\n\n 1. Open the Mushi admin console \u2192 Projects\n (https://your-admin-url/projects)\n 2. Find your project \u2014 the UUID chip below the name is the value to use.\n 3. Copy it and set:\n MUSHI_PROJECT_ID=<paste-uuid-here>\n\nYou can also visit Admin \u2192 MCP for a pre-filled .env.local snippet."
917
+ );
918
+ }
919
+ log.info("Starting Mushi MCP server", { version: VERSION, endpoint: API_ENDPOINT || "(unset)", hasProjectId: !!PROJECT_ID });
774
920
  const server = createMushiServer({
775
921
  version: VERSION,
776
922
  apiEndpoint: API_ENDPOINT,
@@ -779,6 +925,36 @@ async function main() {
779
925
  });
780
926
  const transport = new StdioServerTransport();
781
927
  await server.connect(transport);
928
+ if (PROJECT_ID && API_ENDPOINT) {
929
+ let lastInventoryAt = null;
930
+ const POLL_INTERVAL_MS = 6e4;
931
+ const pollInventory = async () => {
932
+ try {
933
+ const res = await fetch(`${API_ENDPOINT}/v1/admin/inventory/${PROJECT_ID}`, {
934
+ headers: {
935
+ "X-Mushi-Api-Key": API_KEY,
936
+ "X-Mushi-Project": PROJECT_ID
937
+ },
938
+ signal: AbortSignal.timeout(1e4)
939
+ });
940
+ if (!res.ok) return;
941
+ const data = await res.json();
942
+ const updatedAt = data?.data?.updatedAt ?? null;
943
+ if (updatedAt && updatedAt !== lastInventoryAt) {
944
+ if (lastInventoryAt !== null) {
945
+ await server.server.sendResourceUpdated({ uri: "inventory://current" });
946
+ log.info("inventory://current updated \u2014 notified subscribers", { updatedAt });
947
+ }
948
+ lastInventoryAt = updatedAt;
949
+ }
950
+ } catch {
951
+ }
952
+ };
953
+ void pollInventory();
954
+ setInterval(() => {
955
+ void pollInventory();
956
+ }, POLL_INTERVAL_MS);
957
+ }
782
958
  }
783
959
  main().catch((err) => {
784
960
  log.fatal("MCP server crashed", { err: String(err) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mushi-mushi/mcp",
3
- "version": "0.3.8",
3
+ "version": "0.5.0",
4
4
  "license": "MIT",
5
5
  "description": "MCP server exposing Mushi Mushi reports to coding agents",
6
6
  "type": "module",
@@ -24,16 +24,16 @@
24
24
  "SECURITY.md"
25
25
  ],
26
26
  "dependencies": {
27
- "@modelcontextprotocol/sdk": "^1.12.1",
28
- "zod": "^4.3.6",
29
- "@mushi-mushi/core": "^0.9.0"
27
+ "@modelcontextprotocol/sdk": "^1.29.0",
28
+ "zod": "^4.4.2",
29
+ "@mushi-mushi/core": "^1.1.0"
30
30
  },
31
31
  "devDependencies": {
32
- "@types/node": "^22.0.0",
33
- "eslint": "^10.2.0",
34
- "tsup": "^8.4.0",
35
- "typescript": "^6.0.2",
36
- "vitest": "^4.1.4",
32
+ "@types/node": "^22.19.17",
33
+ "eslint": "^10.3.0",
34
+ "tsup": "^8.5.1",
35
+ "typescript": "^6.0.3",
36
+ "vitest": "^4.1.5",
37
37
  "@mushi-mushi/eslint-config": "0.0.0"
38
38
  },
39
39
  "repository": {