@mushi-mushi/mcp 0.3.7 → 0.4.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/README.md +23 -1
- package/SECURITY.md +167 -4
- package/dist/index.js +274 -19
- package/package.json +2 -2
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
|
-
|
|
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
|
-
|
|
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
|
@@ -83,6 +83,55 @@ var TOOL_CATALOG = [
|
|
|
83
83
|
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
84
84
|
useCase: "Which components had the most critical bugs this week?"
|
|
85
85
|
},
|
|
86
|
+
// --- Inventory v2 (whitepaper §6.8) -------------------------------------
|
|
87
|
+
{
|
|
88
|
+
name: "inventory_get",
|
|
89
|
+
title: "Inventory snapshot",
|
|
90
|
+
description: "Current inventory.yaml snapshot for a project (latest ingest, validation errors, per-action status summary). Requires inventory_v2 on the project plan.",
|
|
91
|
+
scope: "mcp:read",
|
|
92
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
93
|
+
useCase: "What does the live inventory claim for this repo right now?"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: "inventory_diff",
|
|
97
|
+
title: "Inventory diff",
|
|
98
|
+
description: "Diff two ingested inventory commits (fromSha \u2192 toSha) \u2014 added/removed nodes and edges. Use before merging a PR that touches inventory.yaml.",
|
|
99
|
+
scope: "mcp:read",
|
|
100
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
101
|
+
useCase: "What changed in inventory between these two SHAs?"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: "inventory_findings",
|
|
105
|
+
title: "Gate findings",
|
|
106
|
+
description: "Latest gate runs + findings (dead-handler, mock-leak, crawl, status-claim, \u2026). Filter by gate name or severity.",
|
|
107
|
+
scope: "mcp:read",
|
|
108
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
109
|
+
useCase: "Show me what CI gates failed on the last run."
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
name: "graph_neighborhood",
|
|
113
|
+
title: "Graph neighborhood",
|
|
114
|
+
description: 'BFS neighborhood around a graph node id or label \u2014 nodes + edges within a depth budget (max 4). Same backend as knowledge-graph traversal, tuned for "what touches this action?".',
|
|
115
|
+
scope: "mcp:read",
|
|
116
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
117
|
+
useCase: "What nodes connect to this inventory Action within 2 hops?"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: "graph_node_status",
|
|
121
|
+
title: "Graph node detail",
|
|
122
|
+
description: "Fetch a single graph node row (label, type, metadata \u2014 includes v2 derived status on Action nodes).",
|
|
123
|
+
scope: "mcp:read",
|
|
124
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
125
|
+
useCase: "What status does the graph store on this node id?"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: "fix_suggest",
|
|
129
|
+
title: "Suggested fix (from triage)",
|
|
130
|
+
description: "Read-only slice of a report focused on Stage 2 suggested fix + root cause + reproduction \u2014 faster than pulling the full blob when you only need the human-readable hint.",
|
|
131
|
+
scope: "mcp:read",
|
|
132
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
133
|
+
useCase: "What did Stage 2 say we should try for this report?"
|
|
134
|
+
},
|
|
86
135
|
// --- Write / agentic ----------------------------------------------------
|
|
87
136
|
{
|
|
88
137
|
name: "submit_fix_result",
|
|
@@ -109,6 +158,14 @@ var TOOL_CATALOG = [
|
|
|
109
158
|
hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
|
|
110
159
|
useCase: "Grade the latest batch of fixes before I ship."
|
|
111
160
|
},
|
|
161
|
+
{
|
|
162
|
+
name: "test_gen_from_report",
|
|
163
|
+
title: "Generate Playwright test from report",
|
|
164
|
+
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.",
|
|
165
|
+
scope: "mcp:write",
|
|
166
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
167
|
+
useCase: "Turn this regression report into an E2E test PR."
|
|
168
|
+
},
|
|
112
169
|
{
|
|
113
170
|
name: "transition_status",
|
|
114
171
|
title: "Move report between states",
|
|
@@ -347,6 +404,129 @@ function createMushiServer(config) {
|
|
|
347
404
|
return jsonText(await apiCall(`/v1/admin/graph/traverse?${params}`));
|
|
348
405
|
}
|
|
349
406
|
);
|
|
407
|
+
server.registerTool(
|
|
408
|
+
"graph_neighborhood",
|
|
409
|
+
{
|
|
410
|
+
title: titleOf("graph_neighborhood"),
|
|
411
|
+
description: descOf("graph_neighborhood"),
|
|
412
|
+
annotations: annotationsFor("graph_neighborhood"),
|
|
413
|
+
inputSchema: {
|
|
414
|
+
seed: z.string().describe("Starting node id or label"),
|
|
415
|
+
depth: z.number().optional().describe("Traversal depth (default 2, max 4)")
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
async (args) => {
|
|
419
|
+
const params = new URLSearchParams({
|
|
420
|
+
seed: args.seed,
|
|
421
|
+
depth: String(Math.min(args.depth ?? 2, 4))
|
|
422
|
+
});
|
|
423
|
+
return jsonText(await apiCall(`/v1/admin/graph/traverse?${params}`));
|
|
424
|
+
}
|
|
425
|
+
);
|
|
426
|
+
server.registerTool(
|
|
427
|
+
"graph_node_status",
|
|
428
|
+
{
|
|
429
|
+
title: titleOf("graph_node_status"),
|
|
430
|
+
description: descOf("graph_node_status"),
|
|
431
|
+
annotations: annotationsFor("graph_node_status"),
|
|
432
|
+
inputSchema: { nodeId: z.string().describe("graph_nodes.id") }
|
|
433
|
+
},
|
|
434
|
+
async (args) => jsonText(await apiCall(`/v1/admin/graph/node/${args.nodeId}`))
|
|
435
|
+
);
|
|
436
|
+
server.registerTool(
|
|
437
|
+
"inventory_get",
|
|
438
|
+
{
|
|
439
|
+
title: titleOf("inventory_get"),
|
|
440
|
+
description: descOf("inventory_get"),
|
|
441
|
+
annotations: annotationsFor("inventory_get"),
|
|
442
|
+
inputSchema: {
|
|
443
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to the server-configured project when omitted")
|
|
444
|
+
}
|
|
445
|
+
},
|
|
446
|
+
async (args) => {
|
|
447
|
+
const pid = args.projectId ?? projectId;
|
|
448
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for inventory_get");
|
|
449
|
+
return jsonText(await apiCall(`/v1/admin/inventory/${pid}`));
|
|
450
|
+
}
|
|
451
|
+
);
|
|
452
|
+
server.registerTool(
|
|
453
|
+
"inventory_diff",
|
|
454
|
+
{
|
|
455
|
+
title: titleOf("inventory_diff"),
|
|
456
|
+
description: descOf("inventory_diff"),
|
|
457
|
+
annotations: annotationsFor("inventory_diff"),
|
|
458
|
+
inputSchema: {
|
|
459
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project"),
|
|
460
|
+
fromSha: z.string().describe("Older commit SHA (baseline)"),
|
|
461
|
+
toSha: z.string().describe("Newer commit SHA (candidate)")
|
|
462
|
+
}
|
|
463
|
+
},
|
|
464
|
+
async (args) => {
|
|
465
|
+
const pid = args.projectId ?? projectId;
|
|
466
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for inventory_diff");
|
|
467
|
+
const q = new URLSearchParams({ from: args.fromSha, to: args.toSha });
|
|
468
|
+
return jsonText(await apiCall(`/v1/admin/inventory/${pid}/diff?${q}`));
|
|
469
|
+
}
|
|
470
|
+
);
|
|
471
|
+
server.registerTool(
|
|
472
|
+
"inventory_findings",
|
|
473
|
+
{
|
|
474
|
+
title: titleOf("inventory_findings"),
|
|
475
|
+
description: descOf("inventory_findings"),
|
|
476
|
+
annotations: annotationsFor("inventory_findings"),
|
|
477
|
+
inputSchema: {
|
|
478
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project"),
|
|
479
|
+
gate: z.string().optional().describe("Filter by gate id (e.g. dead_handler, status_claim)"),
|
|
480
|
+
severity: z.string().optional().describe("Filter findings by severity")
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
async (args) => {
|
|
484
|
+
const pid = args.projectId ?? projectId;
|
|
485
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for inventory_findings");
|
|
486
|
+
const q = new URLSearchParams();
|
|
487
|
+
if (args.gate) q.set("gate", args.gate);
|
|
488
|
+
if (args.severity) q.set("severity", args.severity);
|
|
489
|
+
const suffix = q.toString() ? `?${q}` : "";
|
|
490
|
+
return jsonText(await apiCall(`/v1/admin/inventory/${pid}/findings${suffix}`));
|
|
491
|
+
}
|
|
492
|
+
);
|
|
493
|
+
server.registerTool(
|
|
494
|
+
"fix_suggest",
|
|
495
|
+
{
|
|
496
|
+
title: titleOf("fix_suggest"),
|
|
497
|
+
description: descOf("fix_suggest"),
|
|
498
|
+
annotations: annotationsFor("fix_suggest"),
|
|
499
|
+
inputSchema: { reportId: z.string().describe("Report UUID") }
|
|
500
|
+
},
|
|
501
|
+
async (args) => {
|
|
502
|
+
const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
503
|
+
const s2 = report.stage2_analysis;
|
|
504
|
+
return jsonText({
|
|
505
|
+
reportId: args.reportId,
|
|
506
|
+
rootCause: s2?.rootCause ?? null,
|
|
507
|
+
suggestedFix: s2?.suggestedFix ?? null,
|
|
508
|
+
reproductionSteps: report.reproduction_steps ?? [],
|
|
509
|
+
summary: report.summary ?? null,
|
|
510
|
+
component: report.component ?? null
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
);
|
|
514
|
+
server.registerTool(
|
|
515
|
+
"run_nl_query",
|
|
516
|
+
{
|
|
517
|
+
title: titleOf("run_nl_query"),
|
|
518
|
+
description: descOf("run_nl_query"),
|
|
519
|
+
annotations: annotationsFor("run_nl_query"),
|
|
520
|
+
inputSchema: { question: z.string().describe('Question in plain English, e.g. "Which components had the most critical bugs this week?"') }
|
|
521
|
+
},
|
|
522
|
+
async (args) => {
|
|
523
|
+
const data = await apiCall("/v1/admin/query", {
|
|
524
|
+
method: "POST",
|
|
525
|
+
body: JSON.stringify({ question: args.question })
|
|
526
|
+
});
|
|
527
|
+
return jsonText(data);
|
|
528
|
+
}
|
|
529
|
+
);
|
|
350
530
|
server.registerTool(
|
|
351
531
|
"submit_fix_result",
|
|
352
532
|
{
|
|
@@ -390,7 +570,9 @@ function createMushiServer(config) {
|
|
|
390
570
|
annotations: annotationsFor("dispatch_fix"),
|
|
391
571
|
inputSchema: {
|
|
392
572
|
reportId: z.string().describe("Report UUID to fix"),
|
|
393
|
-
agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter")
|
|
573
|
+
agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter"),
|
|
574
|
+
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)."),
|
|
575
|
+
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.")
|
|
394
576
|
}
|
|
395
577
|
},
|
|
396
578
|
async (args, extra) => {
|
|
@@ -410,9 +592,11 @@ function createMushiServer(config) {
|
|
|
410
592
|
}
|
|
411
593
|
const data = await apiCall("/v1/admin/fixes/dispatch", {
|
|
412
594
|
method: "POST",
|
|
595
|
+
headers: args.idempotencyKey ? { "Idempotency-Key": args.idempotencyKey } : void 0,
|
|
413
596
|
body: JSON.stringify({
|
|
414
597
|
reportId: args.reportId,
|
|
415
598
|
agent: args.agent,
|
|
599
|
+
inventoryActionNodeId: args.inventoryActionNodeId,
|
|
416
600
|
...projectId ? { projectId } : {}
|
|
417
601
|
})
|
|
418
602
|
});
|
|
@@ -441,6 +625,27 @@ function createMushiServer(config) {
|
|
|
441
625
|
return jsonText(data);
|
|
442
626
|
}
|
|
443
627
|
);
|
|
628
|
+
server.registerTool(
|
|
629
|
+
"test_gen_from_report",
|
|
630
|
+
{
|
|
631
|
+
title: titleOf("test_gen_from_report"),
|
|
632
|
+
description: descOf("test_gen_from_report"),
|
|
633
|
+
annotations: annotationsFor("test_gen_from_report"),
|
|
634
|
+
inputSchema: {
|
|
635
|
+
reportId: z.string().describe("Report UUID to turn into a Playwright PR"),
|
|
636
|
+
projectId: z.string().optional().describe("Project UUID \u2014 defaults to configured project")
|
|
637
|
+
}
|
|
638
|
+
},
|
|
639
|
+
async (args) => {
|
|
640
|
+
const pid = args.projectId ?? projectId;
|
|
641
|
+
if (!pid) throw new MushiApiError(400, "MISSING_PROJECT", "projectId is required for test_gen_from_report");
|
|
642
|
+
const data = await apiCall(`/v1/admin/inventory/${pid}/test-gen/from-report/${args.reportId}`, {
|
|
643
|
+
method: "POST",
|
|
644
|
+
body: JSON.stringify({})
|
|
645
|
+
});
|
|
646
|
+
return jsonText(data);
|
|
647
|
+
}
|
|
648
|
+
);
|
|
444
649
|
server.registerTool(
|
|
445
650
|
"transition_status",
|
|
446
651
|
{
|
|
@@ -461,22 +666,6 @@ function createMushiServer(config) {
|
|
|
461
666
|
return jsonText(data);
|
|
462
667
|
}
|
|
463
668
|
);
|
|
464
|
-
server.registerTool(
|
|
465
|
-
"run_nl_query",
|
|
466
|
-
{
|
|
467
|
-
title: titleOf("run_nl_query"),
|
|
468
|
-
description: descOf("run_nl_query"),
|
|
469
|
-
annotations: annotationsFor("run_nl_query"),
|
|
470
|
-
inputSchema: { question: z.string().describe('Question in plain English, e.g. "Which components had the most critical bugs this week?"') }
|
|
471
|
-
},
|
|
472
|
-
async (args) => {
|
|
473
|
-
const data = await apiCall("/v1/admin/query", {
|
|
474
|
-
method: "POST",
|
|
475
|
-
body: JSON.stringify({ question: args.question })
|
|
476
|
-
});
|
|
477
|
-
return jsonText(data);
|
|
478
|
-
}
|
|
479
|
-
);
|
|
480
669
|
server.resource(
|
|
481
670
|
"project_stats",
|
|
482
671
|
"project://stats",
|
|
@@ -501,6 +690,37 @@ function createMushiServer(config) {
|
|
|
501
690
|
contents: [{ uri: "project://dashboard", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2) }]
|
|
502
691
|
})
|
|
503
692
|
);
|
|
693
|
+
server.resource(
|
|
694
|
+
"project_integration_health",
|
|
695
|
+
"project://integration-health",
|
|
696
|
+
{
|
|
697
|
+
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."
|
|
698
|
+
},
|
|
699
|
+
async () => ({
|
|
700
|
+
contents: [{
|
|
701
|
+
uri: "project://integration-health",
|
|
702
|
+
mimeType: "application/json",
|
|
703
|
+
text: JSON.stringify(await apiCall("/v1/admin/integrations/health"), null, 2)
|
|
704
|
+
}]
|
|
705
|
+
})
|
|
706
|
+
);
|
|
707
|
+
server.resource(
|
|
708
|
+
"inventory_current",
|
|
709
|
+
"inventory://current",
|
|
710
|
+
{
|
|
711
|
+
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."
|
|
712
|
+
},
|
|
713
|
+
async () => {
|
|
714
|
+
const path = projectId ? `/v1/admin/inventory/${projectId}` : "/v1/admin/inventory";
|
|
715
|
+
return {
|
|
716
|
+
contents: [{
|
|
717
|
+
uri: "inventory://current",
|
|
718
|
+
mimeType: "application/json",
|
|
719
|
+
text: JSON.stringify(await apiCall(path), null, 2)
|
|
720
|
+
}]
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
);
|
|
504
724
|
server.prompt(
|
|
505
725
|
"summarize_report_for_fix",
|
|
506
726
|
"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.",
|
|
@@ -577,7 +797,7 @@ Prefer items that are bottlenecks or critical severity. Skip filler.`
|
|
|
577
797
|
var require2 = createRequire(import.meta.url);
|
|
578
798
|
var VERSION = require2("../package.json").version;
|
|
579
799
|
var log = createLogger({ scope: "mushi:mcp", level: "info" });
|
|
580
|
-
var API_ENDPOINT = process.env.MUSHI_API_ENDPOINT ?? "
|
|
800
|
+
var API_ENDPOINT = process.env.MUSHI_API_ENDPOINT ?? "";
|
|
581
801
|
var API_KEY = process.env.MUSHI_API_KEY ?? "";
|
|
582
802
|
var PROJECT_ID = process.env.MUSHI_PROJECT_ID ?? "";
|
|
583
803
|
async function main() {
|
|
@@ -585,7 +805,12 @@ async function main() {
|
|
|
585
805
|
log.fatal("MUSHI_API_KEY environment variable is required");
|
|
586
806
|
process.exit(1);
|
|
587
807
|
}
|
|
588
|
-
|
|
808
|
+
if (!API_ENDPOINT) {
|
|
809
|
+
console.error(
|
|
810
|
+
"[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"
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
log.info("Starting Mushi MCP server", { version: VERSION, endpoint: API_ENDPOINT || "(unset)", hasProjectId: !!PROJECT_ID });
|
|
589
814
|
const server = createMushiServer({
|
|
590
815
|
version: VERSION,
|
|
591
816
|
apiEndpoint: API_ENDPOINT,
|
|
@@ -594,6 +819,36 @@ async function main() {
|
|
|
594
819
|
});
|
|
595
820
|
const transport = new StdioServerTransport();
|
|
596
821
|
await server.connect(transport);
|
|
822
|
+
if (PROJECT_ID && API_ENDPOINT) {
|
|
823
|
+
let lastInventoryAt = null;
|
|
824
|
+
const POLL_INTERVAL_MS = 6e4;
|
|
825
|
+
const pollInventory = async () => {
|
|
826
|
+
try {
|
|
827
|
+
const res = await fetch(`${API_ENDPOINT}/v1/admin/inventory/${PROJECT_ID}`, {
|
|
828
|
+
headers: {
|
|
829
|
+
"X-Mushi-Api-Key": API_KEY,
|
|
830
|
+
"X-Mushi-Project": PROJECT_ID
|
|
831
|
+
},
|
|
832
|
+
signal: AbortSignal.timeout(1e4)
|
|
833
|
+
});
|
|
834
|
+
if (!res.ok) return;
|
|
835
|
+
const data = await res.json();
|
|
836
|
+
const updatedAt = data?.data?.updatedAt ?? null;
|
|
837
|
+
if (updatedAt && updatedAt !== lastInventoryAt) {
|
|
838
|
+
if (lastInventoryAt !== null) {
|
|
839
|
+
await server.server.sendResourceUpdated({ uri: "inventory://current" });
|
|
840
|
+
log.info("inventory://current updated \u2014 notified subscribers", { updatedAt });
|
|
841
|
+
}
|
|
842
|
+
lastInventoryAt = updatedAt;
|
|
843
|
+
}
|
|
844
|
+
} catch {
|
|
845
|
+
}
|
|
846
|
+
};
|
|
847
|
+
void pollInventory();
|
|
848
|
+
setInterval(() => {
|
|
849
|
+
void pollInventory();
|
|
850
|
+
}, POLL_INTERVAL_MS);
|
|
851
|
+
}
|
|
597
852
|
}
|
|
598
853
|
main().catch((err) => {
|
|
599
854
|
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
|
+
"version": "0.4.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "MCP server exposing Mushi Mushi reports to coding agents",
|
|
6
6
|
"type": "module",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"dependencies": {
|
|
27
27
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
28
28
|
"zod": "^4.3.6",
|
|
29
|
-
"@mushi-mushi/core": "^0.
|
|
29
|
+
"@mushi-mushi/core": "^1.0.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/node": "^22.0.0",
|