@mushi-mushi/mcp 0.3.0 → 0.3.2
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/CODE_OF_CONDUCT.md +51 -0
- package/CONTRIBUTING.md +122 -0
- package/README.md +186 -4
- package/SECURITY.md +50 -0
- package/dist/index.js +555 -225
- package/package.json +12 -4
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-SYNCED from repo root by scripts/sync-community-files.mjs.
|
|
3
|
+
Do not edit here — edit the canonical file at the repository root and
|
|
4
|
+
re-run `node scripts/sync-community-files.mjs` (pre-commit hook does this
|
|
5
|
+
automatically).
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Contributor Covenant Code of Conduct
|
|
9
|
+
|
|
10
|
+
## Our Pledge
|
|
11
|
+
|
|
12
|
+
We as members, contributors, and leaders pledge to make participation in our
|
|
13
|
+
community a harassment-free experience for everyone, regardless of age, body
|
|
14
|
+
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
15
|
+
identity and expression, level of experience, education, socio-economic status,
|
|
16
|
+
nationality, personal appearance, race, caste, color, religion, or sexual
|
|
17
|
+
identity and orientation.
|
|
18
|
+
|
|
19
|
+
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
20
|
+
diverse, inclusive, and healthy community.
|
|
21
|
+
|
|
22
|
+
## Our Standards
|
|
23
|
+
|
|
24
|
+
Examples of behavior that contributes to a positive environment:
|
|
25
|
+
|
|
26
|
+
- Using welcoming and inclusive language
|
|
27
|
+
- Being respectful of differing viewpoints and experiences
|
|
28
|
+
- Gracefully accepting constructive criticism
|
|
29
|
+
- Focusing on what is best for the community
|
|
30
|
+
- Showing empathy towards other community members
|
|
31
|
+
|
|
32
|
+
Examples of unacceptable behavior:
|
|
33
|
+
|
|
34
|
+
- The use of sexualized language or imagery, and sexual attention or advances of any kind
|
|
35
|
+
- Trolling, insulting or derogatory comments, and personal or political attacks
|
|
36
|
+
- Public or private harassment
|
|
37
|
+
- Publishing others' private information without explicit permission
|
|
38
|
+
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
39
|
+
|
|
40
|
+
## Enforcement
|
|
41
|
+
|
|
42
|
+
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
43
|
+
reported to the project team at **security@mushimushi.dev**.
|
|
44
|
+
|
|
45
|
+
All complaints will be reviewed and investigated promptly and fairly.
|
|
46
|
+
|
|
47
|
+
## Attribution
|
|
48
|
+
|
|
49
|
+
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org/),
|
|
50
|
+
version 2.1, available at
|
|
51
|
+
https://www.contributor-covenant.org/version/2/1/code_of_conduct.html.
|
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-SYNCED from repo root by scripts/sync-community-files.mjs.
|
|
3
|
+
Do not edit here — edit the canonical file at the repository root and
|
|
4
|
+
re-run `node scripts/sync-community-files.mjs` (pre-commit hook does this
|
|
5
|
+
automatically).
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Contributing to Mushi Mushi
|
|
9
|
+
|
|
10
|
+
Thanks for wanting to help. Here's everything you need to get started.
|
|
11
|
+
|
|
12
|
+
## Prerequisites
|
|
13
|
+
|
|
14
|
+
- **Node.js >= 22** (see `.node-version`)
|
|
15
|
+
- **pnpm >= 10** — install with `corepack enable`
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
git clone https://github.com/kensaurus/mushi-mushi.git
|
|
21
|
+
cd mushi-mushi
|
|
22
|
+
pnpm install
|
|
23
|
+
pnpm build
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Development
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pnpm dev # Start all dev servers (admin on :6464)
|
|
30
|
+
pnpm test # Run Vitest across all packages
|
|
31
|
+
pnpm typecheck # TypeScript checks
|
|
32
|
+
pnpm lint # ESLint
|
|
33
|
+
pnpm format # Prettier
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Working on a single package
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
cd packages/core
|
|
40
|
+
pnpm dev # Watch mode
|
|
41
|
+
pnpm test # Tests for this package only
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Project Structure
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
packages/
|
|
48
|
+
core/ Types, API client, offline queue (MIT)
|
|
49
|
+
web/ Browser SDK — widget, capture (MIT)
|
|
50
|
+
react/ React bindings (MIT)
|
|
51
|
+
vue/ Vue 3 plugin (MIT)
|
|
52
|
+
svelte/ Svelte SDK (MIT)
|
|
53
|
+
angular/ Angular SDK (MIT)
|
|
54
|
+
react-native/ React Native SDK (MIT)
|
|
55
|
+
cli/ CLI tool (MIT)
|
|
56
|
+
mcp/ MCP server for coding agents (MIT)
|
|
57
|
+
server/ Supabase Edge Functions (BSL)
|
|
58
|
+
agents/ Agentic fix pipeline (BSL)
|
|
59
|
+
verify/ Fix verification (BSL)
|
|
60
|
+
apps/
|
|
61
|
+
admin/ Admin dashboard (React + Tailwind)
|
|
62
|
+
docs/ Documentation site (planned)
|
|
63
|
+
tooling/
|
|
64
|
+
eslint-config/ Shared ESLint flat config
|
|
65
|
+
tsconfig/ Shared TypeScript configs
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Making Changes
|
|
69
|
+
|
|
70
|
+
1. Create a feature branch from `master`
|
|
71
|
+
2. Make your changes
|
|
72
|
+
3. Add tests for new functionality
|
|
73
|
+
4. Run `pnpm typecheck && pnpm lint && pnpm test` to verify
|
|
74
|
+
5. Create a changeset if your change affects published packages:
|
|
75
|
+
```bash
|
|
76
|
+
pnpm changeset
|
|
77
|
+
```
|
|
78
|
+
6. Open a pull request
|
|
79
|
+
|
|
80
|
+
## Changesets
|
|
81
|
+
|
|
82
|
+
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:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pnpm changeset
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Select the affected packages, the semver bump type, and write a summary. The changeset file gets committed with your PR.
|
|
89
|
+
|
|
90
|
+
## Code Style
|
|
91
|
+
|
|
92
|
+
- **TypeScript strict mode** — no `any` unless absolutely necessary
|
|
93
|
+
- **Prettier** formats everything — run `pnpm format` before committing
|
|
94
|
+
- **ESLint** catches bugs — `pnpm lint` must pass
|
|
95
|
+
- **No default exports** in library packages — use named exports
|
|
96
|
+
- **Dual ESM/CJS** builds via tsup for all SDK packages
|
|
97
|
+
|
|
98
|
+
## Commit Messages
|
|
99
|
+
|
|
100
|
+
Use conventional commits:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
feat(core): add batch report submission
|
|
104
|
+
fix(web): prevent widget from opening during screenshot
|
|
105
|
+
docs(react): update provider usage example
|
|
106
|
+
chore: bump dependencies
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## Tests
|
|
110
|
+
|
|
111
|
+
- **Framework:** Vitest
|
|
112
|
+
- **Location:** Co-located with source (`src/foo.test.ts`)
|
|
113
|
+
- **Coverage:** Required for `core`, `web`, `react` — encouraged for all packages
|
|
114
|
+
|
|
115
|
+
## License
|
|
116
|
+
|
|
117
|
+
- SDK packages are MIT — your contributions will be MIT-licensed
|
|
118
|
+
- Server/agents/verify are BSL 1.1 — contributions to those packages fall under BSL
|
|
119
|
+
|
|
120
|
+
## Questions?
|
|
121
|
+
|
|
122
|
+
Open an issue or start a discussion. We're happy to help.
|
package/README.md
CHANGED
|
@@ -77,6 +77,23 @@ The server speaks stdio MCP transport by default — your client launches it as
|
|
|
77
77
|
|
|
78
78
|
> Need a tool that isn't here? Open an issue at [github.com/kensaurus/mushi-mushi/issues](https://github.com/kensaurus/mushi-mushi/issues) and tag it `mcp`.
|
|
79
79
|
|
|
80
|
+
### Tool annotations — what MCP clients see before invoking
|
|
81
|
+
|
|
82
|
+
Every tool is registered via `server.registerTool()` with MCP 2025-10 **tool annotations** so clients (Cursor, Claude Desktop, Continue, Cline, Zed) can render a proper "is this safe to auto-invoke?" UI without calling the tool first. The annotations come from a single source of truth — `src/catalog.ts` — mirrored into the admin console (`apps/admin/src/lib/mcpCatalog.ts`) and guarded against drift by `scripts/check-mcp-catalog-sync.mjs`.
|
|
83
|
+
|
|
84
|
+
| Annotation | Meaning | Example |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `readOnlyHint: true` | Safe to loop on — never mutates state | `get_recent_reports`, every `project://` resource |
|
|
87
|
+
| `destructiveHint: true` | Mutates project state; client should confirm | `dispatch_fix`, `transition_status` |
|
|
88
|
+
| `idempotentHint: true` | Repeated calls produce the same effect | `transition_status`, `submit_fix_result` |
|
|
89
|
+
| `openWorldHint: true` | Reaches out to your Mushi deployment (not a pure local function) | Every tool in this server |
|
|
90
|
+
|
|
91
|
+
The same catalog entries power the `/mcp` beginner console in the admin app — so the tool catalog the agent sees and the tool catalog the human operator reads can never disagree.
|
|
92
|
+
|
|
93
|
+
### Progress notifications on long-running tools
|
|
94
|
+
|
|
95
|
+
`dispatch_fix` emits an MCP `notifications/progress` event the moment the orchestrator accepts the request, so clients that support progress (Cursor, Claude Desktop) can render a live "Dispatching fix…" indicator instead of freezing until the HTTP round-trip returns. The notification mirrors the `ProgressToken` the client passed in `_meta.progressToken`; clients that don't pass one get the normal non-streaming response, unchanged.
|
|
96
|
+
|
|
80
97
|
## Resources
|
|
81
98
|
|
|
82
99
|
| URI | Returns |
|
|
@@ -99,16 +116,181 @@ Named templates the MCP client surfaces in its slash-menu. Each one bakes in the
|
|
|
99
116
|
|
|
100
117
|
| Variable | Required | Default | Notes |
|
|
101
118
|
|---|---|---|---|
|
|
102
|
-
| `MUSHI_API_KEY` | yes | — | Project API key.
|
|
103
|
-
| `MUSHI_PROJECT_ID` | yes | — |
|
|
104
|
-
| `MUSHI_API_ENDPOINT` | no | `https://api.mushimushi.dev` | Override only if you self-host. |
|
|
119
|
+
| `MUSHI_API_KEY` | yes | — | Project API key with `mcp:read` or `mcp:write` scope. Mint one in the admin console → **Projects** (the one-time reveal card has a **Copy as `.env.local`** tab). |
|
|
120
|
+
| `MUSHI_PROJECT_ID` | yes | — | UUID from the admin console URL (`/projects/<uuid>/...`) or the reveal card. |
|
|
121
|
+
| `MUSHI_API_ENDPOINT` | no | `https://api.mushimushi.dev` | Override only if you self-host. Localhost value: `http://localhost:54321/functions/v1/api`. |
|
|
122
|
+
|
|
123
|
+
### Storing the key in `.env.local`
|
|
124
|
+
|
|
125
|
+
The MCP binary reads these three vars from `process.env` on spawn — that means **anywhere you normally put env vars works**. The zero-friction path:
|
|
126
|
+
|
|
127
|
+
1. In the admin console, mint a key and pick **Copy as `.env.local`** on the reveal card. You get a pre-formatted block:
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Mushi MCP — drop into .env.local (gitignored). The MCP binary picks these up on spawn.
|
|
131
|
+
MUSHI_API_ENDPOINT=https://api.mushimushi.dev
|
|
132
|
+
MUSHI_PROJECT_ID=<your-uuid>
|
|
133
|
+
MUSHI_API_KEY=mushi_live_…
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
2. Paste it into your repo's `.env.local` (already gitignored by every Vite / Next.js / Node project scaffold). Confirm `.env.local` is in `.gitignore` if you're in an unusual setup.
|
|
137
|
+
|
|
138
|
+
3. Tell your MCP client to inherit the shell env. For Cursor, the simplest form:
|
|
139
|
+
|
|
140
|
+
```json
|
|
141
|
+
{
|
|
142
|
+
"mcpServers": {
|
|
143
|
+
"mushi-mushi": {
|
|
144
|
+
"command": "npx",
|
|
145
|
+
"args": ["-y", "@mushi-mushi/mcp@latest"]
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Cursor spawns the subprocess with the parent shell's env, so as long as you ran Cursor from a terminal that has `.env.local` sourced (or you're using `direnv` / `dotenv-cli`), the three vars are already in place. If you prefer to inline them — Cursor / Claude Desktop both support an `env` block in `mcp.json` — use the **Copy as `.cursor/mcp.json`** tab on the reveal card, which hard-codes the three values into the JSON for you.
|
|
152
|
+
|
|
153
|
+
4. **Never** commit `.env.local`, and **never** paste a key into a repo-tracked `.cursor/mcp.json`. If you accidentally do, rotate the key from the admin console — the denormalised owner binding is rebuilt automatically on rotation.
|
|
154
|
+
|
|
155
|
+
## API key scopes
|
|
156
|
+
|
|
157
|
+
The admin routes the MCP server hits enforce per-key scopes. When you mint a key, pick the smallest scope that works for your agent workflow:
|
|
158
|
+
|
|
159
|
+
| Scope | Grants | Use for |
|
|
160
|
+
|---|---|---|
|
|
161
|
+
| `report:write` | SDK ingest only (`/v1/reports`, `/v1/notifications`). **No admin access.** | Your app's runtime Mushi SDK — never give this to an MCP client. |
|
|
162
|
+
| `mcp:read` | Every MCP read tool (`get_recent_reports`, `search_reports`, `get_fix_context`, `get_fix_timeline`, `get_blast_radius`, `get_knowledge_graph`) and every `project://*` resource. | Safe default for agents that only *read* from Mushi. |
|
|
163
|
+
| `mcp:write` | Everything `mcp:read` grants **plus** mutating tools (`dispatch_fix`, `submit_fix_result`, `trigger_judge`, `transition_status`, `run_nl_query`). | Agents that should act on bugs (open PRs, judge, transition status). |
|
|
164
|
+
|
|
165
|
+
The middleware replies **403 `INSUFFICIENT_SCOPE`** with a human-readable message if your key is missing a required scope — no silent failures.
|
|
166
|
+
|
|
167
|
+
## Is this actually useful? — honest answer
|
|
168
|
+
|
|
169
|
+
The short version: **yes, but only for teams that already fix bugs in an AI-augmented editor.** If your team still opens bugs exclusively in Jira and writes patches longhand, this server is solving a problem you don't have yet. Use the SDK + Discord webhook and come back later.
|
|
170
|
+
|
|
171
|
+
For teams that do live in Cursor / Claude Code / Continue / Cline / Zed / Windsurf, the wins are concrete:
|
|
172
|
+
|
|
173
|
+
| Use case | What it replaces | Why MCP wins |
|
|
174
|
+
|---|---|---|
|
|
175
|
+
| **"What should I triage right now?"** | Flipping to the admin tab, squinting at the dashboard, copying a report URL into chat | `triage_next_steps` prompt reads the live dashboard and gives the agent a five-item plan grounded in today's numbers — zero context-switch |
|
|
176
|
+
| **"Fix this bug"** (from an agent) | Copy-pasting the Sentry issue body, guessing at the blast radius, hoping the agent knows which files to touch | `get_fix_context` returns a pre-baked brief (root cause + smallest file set + repro + ontology tags) over a standardised MCP transport every client supports |
|
|
177
|
+
| **Cross-IDE parity** | Shipping a Cursor plugin AND a VS Code extension AND a JetBrains plugin | Ship **one** MCP server; every MCP-compatible editor picks it up. Cursor, Claude Desktop, Continue, Cline, Zed, Windsurf all already speak the protocol |
|
|
178
|
+
| **Scoped automation** | Giving the agent a full admin token or writing a brittle REST wrapper | `mcp:read` vs `mcp:write` scopes are enforced at the edge function. The agent can safely loop on reads; writes require the stricter key. No bespoke ACLs |
|
|
179
|
+
| **Natural-language data questions** | Opening the admin `/query` page, writing SQL by hand | `run_nl_query` — ask the agent "how many critical reports landed this week by component?" and it goes through the same NL→SQL pipeline the admin UI uses, rate-limited to 60/hour |
|
|
180
|
+
| **Ad-hoc dashboards inside chat** | Refreshing the admin tab every 15 s during a release | `project://dashboard` resource returns the live PDCA snapshot; clients can re-read the URI whenever the conversation needs fresh numbers |
|
|
181
|
+
|
|
182
|
+
### Are we using the full power of MCP?
|
|
183
|
+
|
|
184
|
+
Honest scorecard against the MCP 2025-10 spec:
|
|
185
|
+
|
|
186
|
+
- ✅ **Tools, resources, prompts** — all three primitives advertised.
|
|
187
|
+
- ✅ **Tool annotations** (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`) — every tool, every run.
|
|
188
|
+
- ✅ **Progress notifications** — wired on `dispatch_fix` (the one genuinely long-running call). Sends `notifications/progress` the moment the orchestrator accepts the job.
|
|
189
|
+
- ✅ **Scope-aware errors** — `[INSUFFICIENT_SCOPE]` surfaces verbatim so agents don't silently retry.
|
|
190
|
+
- ✅ **Stdio transport** — default for local editor integration.
|
|
191
|
+
- ⏳ **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
|
+
- ⏳ **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
|
+
|
|
195
|
+
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
|
+
|
|
197
|
+
## Admin console: `/mcp` page
|
|
198
|
+
|
|
199
|
+
The admin app ships a beginner-friendly `/mcp` page (sidebar → **Act → MCP**) that mirrors this README for non-CLI users:
|
|
200
|
+
|
|
201
|
+
- **Connection status strip** — live-reads the active project's keys and tells you whether you have `mcp:read` / `mcp:write`, linking to the mint form if not.
|
|
202
|
+
- **Install block** — toggles between `.cursor/mcp.json` and `.env.local` output, pre-filled with the active project's id and a `MUSHI_API_KEY` placeholder.
|
|
203
|
+
- **Use-cases grid** — the same honest table above, but clickable so you can jump from a use case straight to the relevant tool in the catalog.
|
|
204
|
+
- **Full tool / resource / prompt catalog** — rendered from the same `catalog.ts` the MCP server registers from, so the human doc and the machine-readable server contract cannot drift.
|
|
205
|
+
|
|
206
|
+
The page is the recommended first stop for a new team member — it takes about 60 seconds to go from "I have a Mushi account" to "Cursor is calling `get_recent_reports` for me". The source of truth lives at `apps/admin/src/pages/McpPage.tsx`.
|
|
105
207
|
|
|
106
208
|
## Security
|
|
107
209
|
|
|
108
210
|
- The server runs locally; your API key never leaves your machine except in calls to your configured `MUSHI_API_ENDPOINT`.
|
|
109
|
-
-
|
|
211
|
+
- **Scope keys tightly.** Give MCP the smallest scope that works — `mcp:read` is fine for 90% of agent loops.
|
|
212
|
+
- Never paste a service-role key or a `report:write` SDK key — the former bypasses RLS, the latter is rejected by admin routes anyway.
|
|
213
|
+
- Rotate keys from the admin console if a laptop is lost — the denormalised owner binding is rebuilt automatically on rotation.
|
|
110
214
|
- The server logs to stderr; redirect to a file if you need an audit trail.
|
|
111
215
|
|
|
216
|
+
## Testing locally
|
|
217
|
+
|
|
218
|
+
Three layers of testing, each solving a different problem.
|
|
219
|
+
|
|
220
|
+
### Layer 1 — In-process integration tests (fastest, no subprocess)
|
|
221
|
+
|
|
222
|
+
Real `Client ↔ Server` handshake over `InMemoryTransport` with a mocked `fetch`. Catches protocol regressions in under a second.
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
pnpm --filter @mushi-mushi/mcp test
|
|
226
|
+
# 18/18 pass — handshake, tool contracts, envelope unwrapping, scope errors, annotation contract
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Layer 2 — Stdio smoke test (verifies the built bin boots)
|
|
230
|
+
|
|
231
|
+
Spawns `dist/index.js` with a dummy unreachable endpoint and confirms it advertises the expected tools/resources/prompts over stdio. Good CI gate before publishing.
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
pnpm --filter @mushi-mushi/mcp build
|
|
235
|
+
pnpm --filter @mushi-mushi/mcp test:smoke
|
|
236
|
+
# OK — 13 tools, 3 resources, 3 prompts
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
### Layer 3 — Full localhost E2E (real binary + real backend behaviour)
|
|
240
|
+
|
|
241
|
+
Boots a tiny `node:http` mock of `/v1/admin/*`, spawns the real MCP binary pointed at it, and runs a real `StdioClientTransport` client through every tool + resource + a scope-denial path. This is the closest you can get to a production handshake without running Supabase Edge Functions.
|
|
242
|
+
|
|
243
|
+
```bash
|
|
244
|
+
pnpm --filter @mushi-mushi/mcp build
|
|
245
|
+
node packages/mcp/scripts/localhost-e2e.mjs
|
|
246
|
+
# 27/27 assertions — every tool, every resource, scope denial surfaced correctly
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
The harness is also the quickest way to iterate on new tools: extend the `FIXTURES` + the route switch in `scripts/localhost-e2e.mjs`, rebuild, rerun. No migrations, no Supabase boot time, no DB state.
|
|
250
|
+
|
|
251
|
+
## Configuring against your own localhost
|
|
252
|
+
|
|
253
|
+
When you're ready to wire the MCP into your local Mushi Mushi stack (i.e. `pnpm dev` is running the admin console + local Supabase), use these endpoints:
|
|
254
|
+
|
|
255
|
+
| Env var | Localhost value | Notes |
|
|
256
|
+
|---|---|---|
|
|
257
|
+
| `MUSHI_API_ENDPOINT` | `http://localhost:54321/functions/v1/api` | Default Supabase CLI port; the `/api` suffix is the Hono `basePath`. |
|
|
258
|
+
| `MUSHI_API_KEY` | the key you minted (see below) | Must have `mcp:read` or `mcp:write` scope. |
|
|
259
|
+
| `MUSHI_PROJECT_ID` | the UUID from the admin URL | `/projects/<uuid>` in the admin console. |
|
|
260
|
+
|
|
261
|
+
### Minting a localhost key
|
|
262
|
+
|
|
263
|
+
1. Start the stack: `pnpm dev` (admin on `:6464`, Supabase on `:54321`).
|
|
264
|
+
2. Sign in and open **Settings → API keys**.
|
|
265
|
+
3. Click **New key**, pick **MCP read** or **MCP read-write** (the scope picker on the "New key" form), and copy the plain-text key that appears once — it's not retrievable later.
|
|
266
|
+
4. The admin console URL is `http://localhost:6464/projects/<uuid>/...`; the `<uuid>` is your `MUSHI_PROJECT_ID`.
|
|
267
|
+
|
|
268
|
+
### Pointing Cursor/Claude Desktop at localhost
|
|
269
|
+
|
|
270
|
+
Replace the public endpoint block in your client's MCP config with the three env vars above. For Cursor, this lives in `.cursor/mcp.json` at your repo root:
|
|
271
|
+
|
|
272
|
+
```json
|
|
273
|
+
{
|
|
274
|
+
"mcpServers": {
|
|
275
|
+
"mushi-mushi-local": {
|
|
276
|
+
"command": "node",
|
|
277
|
+
"args": ["/absolute/path/to/mushi-mushi/packages/mcp/dist/index.js"],
|
|
278
|
+
"env": {
|
|
279
|
+
"MUSHI_API_ENDPOINT": "http://localhost:54321/functions/v1/api",
|
|
280
|
+
"MUSHI_API_KEY": "mushi_live_abc…",
|
|
281
|
+
"MUSHI_PROJECT_ID": "00000000-0000-0000-0000-000000000000"
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Rebuild the package (`pnpm --filter @mushi-mushi/mcp build`) after any MCP source change — Cursor re-spawns the subprocess on config reload but doesn't recompile for you.
|
|
289
|
+
|
|
290
|
+
### Sanity check: did it connect?
|
|
291
|
+
|
|
292
|
+
In Cursor chat, type `/` — you should see the Mushi Mushi slash-prompts (`/summarize_report_for_fix`, `/explain_judge_result`, `/triage_next_steps`). Or ask the agent directly: _"Use the Mushi MCP to list my recent reports"_. If scope is wrong you'll see the `[INSUFFICIENT_SCOPE]` error text verbatim — rotate the key with the right scope and retry.
|
|
293
|
+
|
|
112
294
|
## See also
|
|
113
295
|
|
|
114
296
|
- [V5.3 whitepaper §2.10](../../MushiMushi_Whitepaper_V5.md) — the agentic fix architecture this server feeds into.
|
package/SECURITY.md
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
AUTO-SYNCED from repo root by scripts/sync-community-files.mjs.
|
|
3
|
+
Do not edit here — edit the canonical file at the repository root and
|
|
4
|
+
re-run `node scripts/sync-community-files.mjs` (pre-commit hook does this
|
|
5
|
+
automatically).
|
|
6
|
+
-->
|
|
7
|
+
|
|
8
|
+
# Security Policy
|
|
9
|
+
|
|
10
|
+
## Supported Versions
|
|
11
|
+
|
|
12
|
+
| Version | Supported |
|
|
13
|
+
|---------|-----------|
|
|
14
|
+
| 0.x | Yes |
|
|
15
|
+
|
|
16
|
+
## Reporting a Vulnerability
|
|
17
|
+
|
|
18
|
+
If you discover a security vulnerability, please report it responsibly.
|
|
19
|
+
|
|
20
|
+
**Do NOT open a public GitHub issue.**
|
|
21
|
+
|
|
22
|
+
Instead, email: **security@mushimushi.dev**
|
|
23
|
+
|
|
24
|
+
Include:
|
|
25
|
+
- Description of the vulnerability
|
|
26
|
+
- Steps to reproduce
|
|
27
|
+
- Impact assessment
|
|
28
|
+
- Suggested fix (if any)
|
|
29
|
+
|
|
30
|
+
We will acknowledge receipt within 48 hours and aim to release a patch within 7 days for critical issues.
|
|
31
|
+
|
|
32
|
+
## Scope
|
|
33
|
+
|
|
34
|
+
- All `@mushi-mushi/*` npm packages
|
|
35
|
+
- Supabase Edge Functions (server-side)
|
|
36
|
+
- Admin console application
|
|
37
|
+
- CLI tool
|
|
38
|
+
|
|
39
|
+
## Out of Scope
|
|
40
|
+
|
|
41
|
+
- Self-hosted deployments configured by the user
|
|
42
|
+
- Third-party integrations (Jira, Linear, PagerDuty)
|
|
43
|
+
- Vulnerabilities requiring physical access
|
|
44
|
+
|
|
45
|
+
## Security Best Practices for Users
|
|
46
|
+
|
|
47
|
+
- **Never commit your API keys** — use environment variables
|
|
48
|
+
- **Rotate API keys** regularly via the admin console
|
|
49
|
+
- **Enable SSO** for team projects (Enterprise tier)
|
|
50
|
+
- **Review audit logs** periodically for suspicious activity
|
package/dist/index.js
CHANGED
|
@@ -1,267 +1,597 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
4
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
-
import {
|
|
5
|
+
import { createRequire } from "module";
|
|
7
6
|
import { createLogger } from "@mushi-mushi/core";
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
headers: {
|
|
16
|
-
"Content-Type": "application/json",
|
|
17
|
-
"Authorization": `Bearer ${API_KEY}`,
|
|
18
|
-
"X-Mushi-Api-Key": API_KEY,
|
|
19
|
-
"X-Mushi-Project": PROJECT_ID,
|
|
20
|
-
...options?.headers ?? {}
|
|
21
|
-
}
|
|
22
|
-
});
|
|
23
|
-
if (!res.ok) {
|
|
24
|
-
throw new Error(`Mushi API error: ${res.status} ${await res.text()}`);
|
|
25
|
-
}
|
|
26
|
-
return res.json();
|
|
27
|
-
}
|
|
28
|
-
var server = new McpServer({
|
|
29
|
-
name: "mushi-mushi",
|
|
30
|
-
version: "0.0.1"
|
|
31
|
-
});
|
|
32
|
-
server.tool(
|
|
33
|
-
"get_recent_reports",
|
|
34
|
-
"List recent bug reports with optional filters",
|
|
7
|
+
|
|
8
|
+
// src/server.ts
|
|
9
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
10
|
+
import { z } from "zod";
|
|
11
|
+
|
|
12
|
+
// src/catalog.ts
|
|
13
|
+
var TOOL_CATALOG = [
|
|
35
14
|
{
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
15
|
+
name: "get_recent_reports",
|
|
16
|
+
title: "Recent bug reports",
|
|
17
|
+
description: "List recent bug reports with optional filters (status / category / severity). Use this to survey what the triage queue looks like right now.",
|
|
18
|
+
scope: "mcp:read",
|
|
19
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
20
|
+
useCase: "What landed in my triage queue today?"
|
|
40
21
|
},
|
|
41
|
-
async (args) => {
|
|
42
|
-
const params = new URLSearchParams();
|
|
43
|
-
if (args.status) params.set("status", args.status);
|
|
44
|
-
if (args.category) params.set("category", args.category);
|
|
45
|
-
if (args.severity) params.set("severity", args.severity);
|
|
46
|
-
params.set("limit", String(args.limit ?? 20));
|
|
47
|
-
const data = await apiCall(`/v1/admin/reports?${params}`);
|
|
48
|
-
return {
|
|
49
|
-
content: [{ type: "text", text: JSON.stringify(data.data, null, 2) }]
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
);
|
|
53
|
-
server.tool(
|
|
54
|
-
"get_report_detail",
|
|
55
|
-
"Get full details for a single bug report including classification, logs, and environment",
|
|
56
22
|
{
|
|
57
|
-
|
|
23
|
+
name: "get_report_detail",
|
|
24
|
+
title: "Report detail",
|
|
25
|
+
description: "Full payload for a single report \u2014 description, console logs, network requests, screenshot URL, classification, fix history.",
|
|
26
|
+
scope: "mcp:read",
|
|
27
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
28
|
+
useCase: "Show me everything you know about this report."
|
|
58
29
|
},
|
|
59
|
-
async (args) => {
|
|
60
|
-
const data = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
61
|
-
return {
|
|
62
|
-
content: [{ type: "text", text: JSON.stringify(data.data, null, 2) }]
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
);
|
|
66
|
-
server.tool(
|
|
67
|
-
"search_reports",
|
|
68
|
-
"Search reports by keyword in description or summary",
|
|
69
30
|
{
|
|
70
|
-
|
|
71
|
-
|
|
31
|
+
name: "search_reports",
|
|
32
|
+
title: "Search reports",
|
|
33
|
+
description: "Semantic + keyword search over reports. Uses pgvector similarity server-side \u2014 falls back to description/summary substring only if embeddings are unavailable for the project.",
|
|
34
|
+
scope: "mcp:read",
|
|
35
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
36
|
+
useCase: 'Find reports mentioning "checkout flakiness".'
|
|
72
37
|
},
|
|
73
|
-
async (args) => {
|
|
74
|
-
const data = await apiCall(`/v1/admin/reports?limit=${args.limit ?? 10}`);
|
|
75
|
-
const q = args.query.toLowerCase();
|
|
76
|
-
const filtered = data.data.reports.filter((r) => {
|
|
77
|
-
const desc = (r.description ?? "").toLowerCase();
|
|
78
|
-
const summary = (r.summary ?? "").toLowerCase();
|
|
79
|
-
return desc.includes(q) || summary.includes(q);
|
|
80
|
-
});
|
|
81
|
-
return {
|
|
82
|
-
content: [{ type: "text", text: JSON.stringify({ results: filtered, total: filtered.length }, null, 2) }]
|
|
83
|
-
};
|
|
84
|
-
}
|
|
85
|
-
);
|
|
86
|
-
server.tool(
|
|
87
|
-
"get_fix_context",
|
|
88
|
-
"Get all context an agent needs to fix a bug: report, classification, repro steps, relevant code, graph context",
|
|
89
38
|
{
|
|
90
|
-
|
|
39
|
+
name: "get_similar_bugs",
|
|
40
|
+
title: "Similar bugs",
|
|
41
|
+
description: 'Find bugs related to a component, page, or description via pgvector nearest-neighbour search. Same backend as search_reports but tuned for "have we seen this before?".',
|
|
42
|
+
scope: "mcp:read",
|
|
43
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
44
|
+
useCase: "Have we seen a bug like this before?"
|
|
91
45
|
},
|
|
92
|
-
async (args) => {
|
|
93
|
-
const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
94
|
-
return {
|
|
95
|
-
content: [{
|
|
96
|
-
type: "text",
|
|
97
|
-
text: JSON.stringify({
|
|
98
|
-
report: report.data,
|
|
99
|
-
reproductionSteps: report.data.reproduction_steps ?? [],
|
|
100
|
-
component: report.data.component,
|
|
101
|
-
rootCause: report.data.stage2_analysis?.rootCause,
|
|
102
|
-
bugOntologyTags: report.data.bug_ontology_tags
|
|
103
|
-
}, null, 2)
|
|
104
|
-
}]
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
);
|
|
108
|
-
server.tool(
|
|
109
|
-
"submit_fix_result",
|
|
110
|
-
"Agent reports fix outcome \u2014 branch, PR URL, files changed",
|
|
111
46
|
{
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
47
|
+
name: "get_fix_context",
|
|
48
|
+
title: "Fix context bundle",
|
|
49
|
+
description: "Bundle the full context an agent needs to fix a bug: report detail, reproduction steps, component, root cause, ontology tags. One call instead of several.",
|
|
50
|
+
scope: "mcp:read",
|
|
51
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
52
|
+
useCase: "Give me everything I need to fix this in one payload."
|
|
118
53
|
},
|
|
119
|
-
async (args) => {
|
|
120
|
-
const data = await apiCall("/v1/admin/fixes", {
|
|
121
|
-
method: "POST",
|
|
122
|
-
body: JSON.stringify({ reportId: args.reportId, agent: "mcp" })
|
|
123
|
-
});
|
|
124
|
-
await apiCall(`/v1/admin/fixes/${data.data.fixId}`, {
|
|
125
|
-
method: "PATCH",
|
|
126
|
-
body: JSON.stringify({
|
|
127
|
-
status: "completed",
|
|
128
|
-
branch: args.branch,
|
|
129
|
-
pr_url: args.prUrl,
|
|
130
|
-
files_changed: args.filesChanged,
|
|
131
|
-
lines_changed: args.linesChanged,
|
|
132
|
-
summary: args.summary,
|
|
133
|
-
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
134
|
-
})
|
|
135
|
-
});
|
|
136
|
-
return {
|
|
137
|
-
content: [{ type: "text", text: JSON.stringify({ ok: true, fixId: data.data.fixId }) }]
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
);
|
|
141
|
-
server.tool(
|
|
142
|
-
"get_similar_bugs",
|
|
143
|
-
"Find related bugs via knowledge graph or keyword search",
|
|
144
54
|
{
|
|
145
|
-
|
|
146
|
-
|
|
55
|
+
name: "get_fix_timeline",
|
|
56
|
+
title: "Fix timeline",
|
|
57
|
+
description: 'Ordered timeline of a fix attempt \u2014 dispatched \u2192 started \u2192 branch \u2192 commit \u2192 PR opened \u2192 CI \u2192 completed/failed. Use this to debug "why did this fix fail?".',
|
|
58
|
+
scope: "mcp:read",
|
|
59
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
60
|
+
useCase: "Why did this fix attempt fail \u2014 show me every step."
|
|
147
61
|
},
|
|
148
|
-
async (args) => {
|
|
149
|
-
const data = await apiCall(`/v1/admin/reports?limit=${args.limit ?? 5}`);
|
|
150
|
-
const q = args.query.toLowerCase();
|
|
151
|
-
const similar = data.data.reports.filter((r) => {
|
|
152
|
-
const text = `${r.summary ?? ""} ${r.component ?? ""} ${r.description ?? ""}`.toLowerCase();
|
|
153
|
-
return text.includes(q);
|
|
154
|
-
});
|
|
155
|
-
return {
|
|
156
|
-
content: [{ type: "text", text: JSON.stringify({ similar, total: similar.length }, null, 2) }]
|
|
157
|
-
};
|
|
158
|
-
}
|
|
159
|
-
);
|
|
160
|
-
server.tool(
|
|
161
|
-
"get_blast_radius",
|
|
162
|
-
"Graph traversal showing affected areas for a given bug group",
|
|
163
62
|
{
|
|
164
|
-
|
|
63
|
+
name: "get_blast_radius",
|
|
64
|
+
title: "Blast radius",
|
|
65
|
+
description: "Graph traversal showing other components / pages a bug group touches. Use before dispatching a fix so the agent can scope its changes.",
|
|
66
|
+
scope: "mcp:read",
|
|
67
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
68
|
+
useCase: "What else might break if I change this component?"
|
|
165
69
|
},
|
|
166
|
-
async (args) => {
|
|
167
|
-
const data = await apiCall(`/v1/admin/graph/blast-radius/${args.nodeId}`);
|
|
168
|
-
return {
|
|
169
|
-
content: [{ type: "text", text: JSON.stringify(data.data, null, 2) }]
|
|
170
|
-
};
|
|
171
|
-
}
|
|
172
|
-
);
|
|
173
|
-
server.tool(
|
|
174
|
-
"trigger_judge",
|
|
175
|
-
"Run the Sonnet-as-Judge over a batch of classified reports. Returns batch id; results land in judge_results.",
|
|
176
70
|
{
|
|
177
|
-
|
|
178
|
-
|
|
71
|
+
name: "get_knowledge_graph",
|
|
72
|
+
title: "Knowledge graph traversal",
|
|
73
|
+
description: "Traverse the knowledge graph from a seed component or page. Returns nodes + edges within a depth budget (max 4 hops).",
|
|
74
|
+
scope: "mcp:read",
|
|
75
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
76
|
+
useCase: "Show me how this component connects to the rest of the app."
|
|
179
77
|
},
|
|
180
|
-
async (args) => {
|
|
181
|
-
const data = await apiCall("/v1/admin/judge/run", {
|
|
182
|
-
method: "POST",
|
|
183
|
-
body: JSON.stringify({ limit: Math.min(args.limit ?? 25, 100), projectId: args.projectId })
|
|
184
|
-
});
|
|
185
|
-
return { content: [{ type: "text", text: JSON.stringify(data.data, null, 2) }] };
|
|
186
|
-
}
|
|
187
|
-
);
|
|
188
|
-
server.tool(
|
|
189
|
-
"dispatch_fix",
|
|
190
|
-
"Dispatch the agentic fix orchestrator for a classified report. Returns fix_attempt id; stream progress via get_fix_progress.",
|
|
191
78
|
{
|
|
192
|
-
|
|
193
|
-
|
|
79
|
+
name: "run_nl_query",
|
|
80
|
+
title: "Ask your data (NL \u2192 SQL)",
|
|
81
|
+
description: "Natural-language question \u2192 SQL query run against your project data. Read-only, 60/hour rate-limited, no privileged schemas.",
|
|
82
|
+
scope: "mcp:read",
|
|
83
|
+
hints: { readOnly: true, idempotent: true, openWorld: true },
|
|
84
|
+
useCase: "Which components had the most critical bugs this week?"
|
|
194
85
|
},
|
|
195
|
-
|
|
196
|
-
const data = await apiCall("/v1/admin/fixes/dispatch", {
|
|
197
|
-
method: "POST",
|
|
198
|
-
body: JSON.stringify({ reportId: args.reportId, agent: args.agent })
|
|
199
|
-
});
|
|
200
|
-
return { content: [{ type: "text", text: JSON.stringify(data.data, null, 2) }] };
|
|
201
|
-
}
|
|
202
|
-
);
|
|
203
|
-
server.tool(
|
|
204
|
-
"transition_status",
|
|
205
|
-
"Move a report between workflow states. Enforces the same transition rules as the admin UI.",
|
|
86
|
+
// --- Write / agentic ----------------------------------------------------
|
|
206
87
|
{
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
88
|
+
name: "submit_fix_result",
|
|
89
|
+
title: "Record a fix outcome",
|
|
90
|
+
description: "Record a fix outcome (branch, PR, files, lines) from an external agent. Creates a fix_attempt then patches it to completed.",
|
|
91
|
+
scope: "mcp:write",
|
|
92
|
+
// Not idempotent: re-running creates a second fix_attempt row.
|
|
93
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
94
|
+
useCase: "I just opened a PR \u2014 log it against the report."
|
|
210
95
|
},
|
|
211
|
-
async (args) => {
|
|
212
|
-
const data = await apiCall(`/v1/admin/reports/${args.reportId}`, {
|
|
213
|
-
method: "PATCH",
|
|
214
|
-
body: JSON.stringify({ status: args.status, reason: args.reason })
|
|
215
|
-
});
|
|
216
|
-
return { content: [{ type: "text", text: JSON.stringify(data.data, null, 2) }] };
|
|
217
|
-
}
|
|
218
|
-
);
|
|
219
|
-
server.tool(
|
|
220
|
-
"run_nl_query",
|
|
221
|
-
"Natural-language question \u2192 SQL query run against your project data. Read-only, 60/hour rate-limited, no privileged schemas.",
|
|
222
96
|
{
|
|
223
|
-
|
|
97
|
+
name: "dispatch_fix",
|
|
98
|
+
title: "Dispatch Mushi fix agent",
|
|
99
|
+
description: "Dispatch the Mushi agentic fix orchestrator for a classified report. Returns a fix_attempt id; poll get_fix_timeline for progress.",
|
|
100
|
+
scope: "mcp:write",
|
|
101
|
+
hints: { readOnly: false, destructive: false, idempotent: false, openWorld: true },
|
|
102
|
+
useCase: "Let the in-repo agent attempt this fix for me."
|
|
224
103
|
},
|
|
225
|
-
async (args) => {
|
|
226
|
-
const data = await apiCall("/v1/admin/query", {
|
|
227
|
-
method: "POST",
|
|
228
|
-
body: JSON.stringify({ question: args.question })
|
|
229
|
-
});
|
|
230
|
-
return { content: [{ type: "text", text: JSON.stringify(data.data, null, 2) }] };
|
|
231
|
-
}
|
|
232
|
-
);
|
|
233
|
-
server.tool(
|
|
234
|
-
"get_knowledge_graph",
|
|
235
|
-
"Traverse the knowledge graph from a seed component or page. Returns nodes + edges within a depth budget.",
|
|
236
104
|
{
|
|
237
|
-
|
|
238
|
-
|
|
105
|
+
name: "trigger_judge",
|
|
106
|
+
title: "Run Sonnet-as-Judge",
|
|
107
|
+
description: "Run the Sonnet-as-Judge over a batch of classified reports. Returns a batch id; results land in judge_results.",
|
|
108
|
+
scope: "mcp:write",
|
|
109
|
+
hints: { readOnly: false, destructive: false, idempotent: true, openWorld: true },
|
|
110
|
+
useCase: "Grade the latest batch of fixes before I ship."
|
|
239
111
|
},
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
112
|
+
{
|
|
113
|
+
name: "transition_status",
|
|
114
|
+
title: "Move report between states",
|
|
115
|
+
description: "Move a report between workflow states (new \u2192 classified \u2192 grouped \u2192 fixing \u2192 fixed \u2192 dismissed). Enforces the same transition rules as the admin UI.",
|
|
116
|
+
scope: "mcp:write",
|
|
117
|
+
// Transitioning to `dismissed` is destructive by intent — it removes the
|
|
118
|
+
// report from triage queues. Flag the whole tool as destructive so the
|
|
119
|
+
// client prompts the user on every call.
|
|
120
|
+
hints: { readOnly: false, destructive: true, idempotent: true, openWorld: true },
|
|
121
|
+
useCase: "Dismiss this duplicate / mark it fixed."
|
|
122
|
+
}
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
// src/server.ts
|
|
126
|
+
var MushiApiError = class extends Error {
|
|
127
|
+
constructor(status, code, message) {
|
|
128
|
+
super(`[${code}] ${message}`);
|
|
129
|
+
this.status = status;
|
|
130
|
+
this.code = code;
|
|
131
|
+
this.name = "MushiApiError";
|
|
132
|
+
}
|
|
133
|
+
status;
|
|
134
|
+
code;
|
|
135
|
+
};
|
|
136
|
+
function createMushiServer(config) {
|
|
137
|
+
const { version, apiEndpoint, apiKey, projectId } = config;
|
|
138
|
+
const doFetch = config.fetch ?? globalThis.fetch;
|
|
139
|
+
async function apiCall(path, options) {
|
|
140
|
+
const res = await doFetch(`${apiEndpoint}${path}`, {
|
|
141
|
+
...options,
|
|
142
|
+
headers: {
|
|
143
|
+
"Content-Type": "application/json",
|
|
144
|
+
// Authorization is accepted by `adminOrApiKey` only as a JWT, but we
|
|
145
|
+
// still send it for endpoints still behind plain `jwtAuth` (legacy)
|
|
146
|
+
// and for transparent proxies that strip X-Mushi-* headers.
|
|
147
|
+
"Authorization": `Bearer ${apiKey}`,
|
|
148
|
+
"X-Mushi-Api-Key": apiKey,
|
|
149
|
+
...projectId ? { "X-Mushi-Project": projectId } : {},
|
|
150
|
+
...options?.headers ?? {}
|
|
151
|
+
}
|
|
244
152
|
});
|
|
245
|
-
const
|
|
246
|
-
|
|
153
|
+
const text = await res.text();
|
|
154
|
+
let body = null;
|
|
155
|
+
if (text) {
|
|
156
|
+
try {
|
|
157
|
+
body = JSON.parse(text);
|
|
158
|
+
} catch {
|
|
159
|
+
body = { raw: text };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!res.ok) {
|
|
163
|
+
const envelope2 = body;
|
|
164
|
+
const code = envelope2?.error?.code ?? `HTTP_${res.status}`;
|
|
165
|
+
const message = envelope2?.error?.message ?? text.slice(0, 500) ?? `Request failed with ${res.status}`;
|
|
166
|
+
throw new MushiApiError(res.status, code, message);
|
|
167
|
+
}
|
|
168
|
+
const envelope = body;
|
|
169
|
+
if (envelope && typeof envelope === "object" && "ok" in envelope) {
|
|
170
|
+
if (!envelope.ok) {
|
|
171
|
+
const code = envelope.error?.code ?? "API_ERROR";
|
|
172
|
+
const message = envelope.error?.message ?? "API returned ok=false";
|
|
173
|
+
throw new MushiApiError(res.status, code, message);
|
|
174
|
+
}
|
|
175
|
+
return envelope.data ?? {};
|
|
176
|
+
}
|
|
177
|
+
return body;
|
|
247
178
|
}
|
|
248
|
-
)
|
|
249
|
-
server.resource(
|
|
250
|
-
"project_stats",
|
|
251
|
-
"project://stats",
|
|
252
|
-
{ description: "Report counts, category breakdown, severity distribution" },
|
|
253
|
-
async () => {
|
|
254
|
-
const data = await apiCall("/v1/admin/stats");
|
|
179
|
+
function jsonText(value) {
|
|
255
180
|
return {
|
|
256
|
-
|
|
181
|
+
content: [{ type: "text", text: JSON.stringify(value, null, 2) }]
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const server = new McpServer({
|
|
185
|
+
name: "mushi-mushi",
|
|
186
|
+
version
|
|
187
|
+
});
|
|
188
|
+
function annotationsFor(name) {
|
|
189
|
+
const spec = TOOL_CATALOG.find((t) => t.name === name);
|
|
190
|
+
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
191
|
+
const a = {
|
|
192
|
+
title: spec.title,
|
|
193
|
+
readOnlyHint: spec.hints.readOnly
|
|
257
194
|
};
|
|
195
|
+
if (spec.hints.destructive !== void 0) a.destructiveHint = spec.hints.destructive;
|
|
196
|
+
if (spec.hints.idempotent !== void 0) a.idempotentHint = spec.hints.idempotent;
|
|
197
|
+
if (spec.hints.openWorld !== void 0) a.openWorldHint = spec.hints.openWorld;
|
|
198
|
+
return a;
|
|
199
|
+
}
|
|
200
|
+
function descOf(name) {
|
|
201
|
+
const spec = TOOL_CATALOG.find((t) => t.name === name);
|
|
202
|
+
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
203
|
+
return spec.description;
|
|
204
|
+
}
|
|
205
|
+
function titleOf(name) {
|
|
206
|
+
const spec = TOOL_CATALOG.find((t) => t.name === name);
|
|
207
|
+
if (!spec) throw new Error(`[mushi-mcp] tool "${name}" is missing from TOOL_CATALOG`);
|
|
208
|
+
return spec.title;
|
|
258
209
|
}
|
|
259
|
-
|
|
210
|
+
server.registerTool(
|
|
211
|
+
"get_recent_reports",
|
|
212
|
+
{
|
|
213
|
+
title: titleOf("get_recent_reports"),
|
|
214
|
+
description: descOf("get_recent_reports"),
|
|
215
|
+
annotations: annotationsFor("get_recent_reports"),
|
|
216
|
+
inputSchema: {
|
|
217
|
+
status: z.string().optional().describe("Filter by status: new, classified, grouped, fixing, fixed, dismissed"),
|
|
218
|
+
category: z.string().optional().describe("Filter by category: bug, slow, visual, confusing, other"),
|
|
219
|
+
severity: z.string().optional().describe("Filter by severity: critical, high, medium, low"),
|
|
220
|
+
limit: z.number().optional().describe("Max reports to return (default 20, max 100)")
|
|
221
|
+
}
|
|
222
|
+
},
|
|
223
|
+
async (args) => {
|
|
224
|
+
const params = new URLSearchParams();
|
|
225
|
+
if (args.status) params.set("status", args.status);
|
|
226
|
+
if (args.category) params.set("category", args.category);
|
|
227
|
+
if (args.severity) params.set("severity", args.severity);
|
|
228
|
+
params.set("limit", String(Math.min(args.limit ?? 20, 100)));
|
|
229
|
+
const data = await apiCall(`/v1/admin/reports?${params}`);
|
|
230
|
+
return jsonText(data);
|
|
231
|
+
}
|
|
232
|
+
);
|
|
233
|
+
server.registerTool(
|
|
234
|
+
"get_report_detail",
|
|
235
|
+
{
|
|
236
|
+
title: titleOf("get_report_detail"),
|
|
237
|
+
description: descOf("get_report_detail"),
|
|
238
|
+
annotations: annotationsFor("get_report_detail"),
|
|
239
|
+
inputSchema: { reportId: z.string().describe("The report UUID") }
|
|
240
|
+
},
|
|
241
|
+
async (args) => jsonText(await apiCall(`/v1/admin/reports/${args.reportId}`))
|
|
242
|
+
);
|
|
243
|
+
server.registerTool(
|
|
244
|
+
"search_reports",
|
|
245
|
+
{
|
|
246
|
+
title: titleOf("search_reports"),
|
|
247
|
+
description: descOf("search_reports"),
|
|
248
|
+
annotations: annotationsFor("search_reports"),
|
|
249
|
+
inputSchema: {
|
|
250
|
+
query: z.string().describe("Natural-language search text or component path"),
|
|
251
|
+
limit: z.number().optional().describe("Max results (default 10, max 50)"),
|
|
252
|
+
threshold: z.number().optional().describe("Similarity threshold 0..1, default 0.2")
|
|
253
|
+
}
|
|
254
|
+
},
|
|
255
|
+
async (args) => {
|
|
256
|
+
const data = await apiCall("/v1/admin/reports/similarity", {
|
|
257
|
+
method: "POST",
|
|
258
|
+
body: JSON.stringify({
|
|
259
|
+
query: args.query,
|
|
260
|
+
k: Math.min(args.limit ?? 10, 50),
|
|
261
|
+
threshold: args.threshold ?? 0.2,
|
|
262
|
+
...projectId ? { projectId } : {}
|
|
263
|
+
})
|
|
264
|
+
});
|
|
265
|
+
return jsonText(data);
|
|
266
|
+
}
|
|
267
|
+
);
|
|
268
|
+
server.registerTool(
|
|
269
|
+
"get_similar_bugs",
|
|
270
|
+
{
|
|
271
|
+
title: titleOf("get_similar_bugs"),
|
|
272
|
+
description: descOf("get_similar_bugs"),
|
|
273
|
+
annotations: annotationsFor("get_similar_bugs"),
|
|
274
|
+
inputSchema: {
|
|
275
|
+
query: z.string().describe("Component name, page path, or bug description"),
|
|
276
|
+
limit: z.number().optional().describe("Max results (default 5, max 20)")
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
async (args) => {
|
|
280
|
+
const data = await apiCall("/v1/admin/reports/similarity", {
|
|
281
|
+
method: "POST",
|
|
282
|
+
body: JSON.stringify({
|
|
283
|
+
query: args.query,
|
|
284
|
+
k: Math.min(args.limit ?? 5, 20),
|
|
285
|
+
threshold: 0.3,
|
|
286
|
+
...projectId ? { projectId } : {}
|
|
287
|
+
})
|
|
288
|
+
});
|
|
289
|
+
return jsonText(data);
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
server.registerTool(
|
|
293
|
+
"get_fix_context",
|
|
294
|
+
{
|
|
295
|
+
title: titleOf("get_fix_context"),
|
|
296
|
+
description: descOf("get_fix_context"),
|
|
297
|
+
annotations: annotationsFor("get_fix_context"),
|
|
298
|
+
inputSchema: { reportId: z.string().describe("The report UUID to fix") }
|
|
299
|
+
},
|
|
300
|
+
async (args) => {
|
|
301
|
+
const report = await apiCall(`/v1/admin/reports/${args.reportId}`);
|
|
302
|
+
return jsonText({
|
|
303
|
+
report,
|
|
304
|
+
reproductionSteps: report.reproduction_steps ?? [],
|
|
305
|
+
component: report.component,
|
|
306
|
+
rootCause: report.stage2_analysis?.rootCause,
|
|
307
|
+
bugOntologyTags: report.bug_ontology_tags
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
);
|
|
311
|
+
server.registerTool(
|
|
312
|
+
"get_fix_timeline",
|
|
313
|
+
{
|
|
314
|
+
title: titleOf("get_fix_timeline"),
|
|
315
|
+
description: descOf("get_fix_timeline"),
|
|
316
|
+
annotations: annotationsFor("get_fix_timeline"),
|
|
317
|
+
inputSchema: { fixId: z.string().describe("fix_attempt UUID") }
|
|
318
|
+
},
|
|
319
|
+
async (args) => jsonText(await apiCall(`/v1/admin/fixes/${args.fixId}/timeline`))
|
|
320
|
+
);
|
|
321
|
+
server.registerTool(
|
|
322
|
+
"get_blast_radius",
|
|
323
|
+
{
|
|
324
|
+
title: titleOf("get_blast_radius"),
|
|
325
|
+
description: descOf("get_blast_radius"),
|
|
326
|
+
annotations: annotationsFor("get_blast_radius"),
|
|
327
|
+
inputSchema: { nodeId: z.string().describe("Graph node UUID") }
|
|
328
|
+
},
|
|
329
|
+
async (args) => jsonText(await apiCall(`/v1/admin/graph/blast-radius/${args.nodeId}`))
|
|
330
|
+
);
|
|
331
|
+
server.registerTool(
|
|
332
|
+
"get_knowledge_graph",
|
|
333
|
+
{
|
|
334
|
+
title: titleOf("get_knowledge_graph"),
|
|
335
|
+
description: descOf("get_knowledge_graph"),
|
|
336
|
+
annotations: annotationsFor("get_knowledge_graph"),
|
|
337
|
+
inputSchema: {
|
|
338
|
+
seed: z.string().describe("Starting node id or label"),
|
|
339
|
+
depth: z.number().optional().describe("Traversal depth (default 2, max 4)")
|
|
340
|
+
}
|
|
341
|
+
},
|
|
342
|
+
async (args) => {
|
|
343
|
+
const params = new URLSearchParams({
|
|
344
|
+
seed: args.seed,
|
|
345
|
+
depth: String(Math.min(args.depth ?? 2, 4))
|
|
346
|
+
});
|
|
347
|
+
return jsonText(await apiCall(`/v1/admin/graph/traverse?${params}`));
|
|
348
|
+
}
|
|
349
|
+
);
|
|
350
|
+
server.registerTool(
|
|
351
|
+
"submit_fix_result",
|
|
352
|
+
{
|
|
353
|
+
title: titleOf("submit_fix_result"),
|
|
354
|
+
description: descOf("submit_fix_result"),
|
|
355
|
+
annotations: annotationsFor("submit_fix_result"),
|
|
356
|
+
inputSchema: {
|
|
357
|
+
reportId: z.string().describe("The report UUID"),
|
|
358
|
+
branch: z.string().describe("Git branch name"),
|
|
359
|
+
prUrl: z.string().optional().describe("GitHub PR URL"),
|
|
360
|
+
filesChanged: z.array(z.string()).describe("Files modified"),
|
|
361
|
+
linesChanged: z.number().describe("Total lines changed"),
|
|
362
|
+
summary: z.string().describe("Fix summary")
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
async (args) => {
|
|
366
|
+
const created = await apiCall("/v1/admin/fixes", {
|
|
367
|
+
method: "POST",
|
|
368
|
+
body: JSON.stringify({ reportId: args.reportId, agent: "mcp" })
|
|
369
|
+
});
|
|
370
|
+
await apiCall(`/v1/admin/fixes/${created.fixId}`, {
|
|
371
|
+
method: "PATCH",
|
|
372
|
+
body: JSON.stringify({
|
|
373
|
+
status: "completed",
|
|
374
|
+
branch: args.branch,
|
|
375
|
+
pr_url: args.prUrl,
|
|
376
|
+
files_changed: args.filesChanged,
|
|
377
|
+
lines_changed: args.linesChanged,
|
|
378
|
+
summary: args.summary,
|
|
379
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
380
|
+
})
|
|
381
|
+
});
|
|
382
|
+
return jsonText({ ok: true, fixId: created.fixId });
|
|
383
|
+
}
|
|
384
|
+
);
|
|
385
|
+
server.registerTool(
|
|
386
|
+
"dispatch_fix",
|
|
387
|
+
{
|
|
388
|
+
title: titleOf("dispatch_fix"),
|
|
389
|
+
description: descOf("dispatch_fix"),
|
|
390
|
+
annotations: annotationsFor("dispatch_fix"),
|
|
391
|
+
inputSchema: {
|
|
392
|
+
reportId: z.string().describe("Report UUID to fix"),
|
|
393
|
+
agent: z.enum(["claude_code", "codex", "rest_worker", "mcp"]).optional().describe("Override the agent adapter")
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
async (args, extra) => {
|
|
397
|
+
if (extra?.sendNotification && extra?._meta?.progressToken) {
|
|
398
|
+
try {
|
|
399
|
+
await extra.sendNotification({
|
|
400
|
+
method: "notifications/progress",
|
|
401
|
+
params: {
|
|
402
|
+
progressToken: extra._meta.progressToken,
|
|
403
|
+
progress: 0,
|
|
404
|
+
total: 100,
|
|
405
|
+
message: "Dispatching Mushi fix orchestrator\u2026"
|
|
406
|
+
}
|
|
407
|
+
});
|
|
408
|
+
} catch {
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const data = await apiCall("/v1/admin/fixes/dispatch", {
|
|
412
|
+
method: "POST",
|
|
413
|
+
body: JSON.stringify({
|
|
414
|
+
reportId: args.reportId,
|
|
415
|
+
agent: args.agent,
|
|
416
|
+
...projectId ? { projectId } : {}
|
|
417
|
+
})
|
|
418
|
+
});
|
|
419
|
+
return jsonText(data);
|
|
420
|
+
}
|
|
421
|
+
);
|
|
422
|
+
server.registerTool(
|
|
423
|
+
"trigger_judge",
|
|
424
|
+
{
|
|
425
|
+
title: titleOf("trigger_judge"),
|
|
426
|
+
description: descOf("trigger_judge"),
|
|
427
|
+
annotations: annotationsFor("trigger_judge"),
|
|
428
|
+
inputSchema: {
|
|
429
|
+
limit: z.number().optional().describe("Max reports to judge in this batch (default 25, max 100)"),
|
|
430
|
+
projectId: z.string().optional().describe("Restrict to one project when the API key owns multiple")
|
|
431
|
+
}
|
|
432
|
+
},
|
|
433
|
+
async (args) => {
|
|
434
|
+
const data = await apiCall("/v1/admin/judge/run", {
|
|
435
|
+
method: "POST",
|
|
436
|
+
body: JSON.stringify({
|
|
437
|
+
limit: Math.min(args.limit ?? 25, 100),
|
|
438
|
+
projectId: args.projectId ?? projectId ?? void 0
|
|
439
|
+
})
|
|
440
|
+
});
|
|
441
|
+
return jsonText(data);
|
|
442
|
+
}
|
|
443
|
+
);
|
|
444
|
+
server.registerTool(
|
|
445
|
+
"transition_status",
|
|
446
|
+
{
|
|
447
|
+
title: titleOf("transition_status"),
|
|
448
|
+
description: descOf("transition_status"),
|
|
449
|
+
annotations: annotationsFor("transition_status"),
|
|
450
|
+
inputSchema: {
|
|
451
|
+
reportId: z.string().describe("Report UUID"),
|
|
452
|
+
status: z.enum(["pending", "classified", "grouped", "fixing", "fixed", "dismissed"]).describe("Target status"),
|
|
453
|
+
reason: z.string().optional().describe("Reason for the transition (audit trail)")
|
|
454
|
+
}
|
|
455
|
+
},
|
|
456
|
+
async (args) => {
|
|
457
|
+
const data = await apiCall(`/v1/admin/reports/${args.reportId}`, {
|
|
458
|
+
method: "PATCH",
|
|
459
|
+
body: JSON.stringify({ status: args.status, reason: args.reason })
|
|
460
|
+
});
|
|
461
|
+
return jsonText(data);
|
|
462
|
+
}
|
|
463
|
+
);
|
|
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
|
+
server.resource(
|
|
481
|
+
"project_stats",
|
|
482
|
+
"project://stats",
|
|
483
|
+
{ description: "Report counts, category breakdown, severity distribution" },
|
|
484
|
+
async () => ({
|
|
485
|
+
contents: [{ uri: "project://stats", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/stats"), null, 2) }]
|
|
486
|
+
})
|
|
487
|
+
);
|
|
488
|
+
server.resource(
|
|
489
|
+
"project_settings",
|
|
490
|
+
"project://settings",
|
|
491
|
+
{ description: "Project configuration \u2014 autofix agent, plugins enabled, ontology, LLM budgets" },
|
|
492
|
+
async () => ({
|
|
493
|
+
contents: [{ uri: "project://settings", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/settings"), null, 2) }]
|
|
494
|
+
})
|
|
495
|
+
);
|
|
496
|
+
server.resource(
|
|
497
|
+
"project_dashboard",
|
|
498
|
+
"project://dashboard",
|
|
499
|
+
{ description: "PDCA health snapshot \u2014 stage counts, bottleneck, recent activity (same payload the admin console polls)" },
|
|
500
|
+
async () => ({
|
|
501
|
+
contents: [{ uri: "project://dashboard", mimeType: "application/json", text: JSON.stringify(await apiCall("/v1/admin/dashboard"), null, 2) }]
|
|
502
|
+
})
|
|
503
|
+
);
|
|
504
|
+
server.prompt(
|
|
505
|
+
"summarize_report_for_fix",
|
|
506
|
+
"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.",
|
|
507
|
+
{ reportId: z.string().describe("The report UUID to summarize") },
|
|
508
|
+
({ reportId }) => ({
|
|
509
|
+
messages: [{
|
|
510
|
+
role: "user",
|
|
511
|
+
content: {
|
|
512
|
+
type: "text",
|
|
513
|
+
text: `You are a senior engineer preparing a fix plan. Use the Mushi MCP tools to:
|
|
514
|
+
1. Call get_fix_context for reportId "${reportId}".
|
|
515
|
+
2. Call get_blast_radius if the report has a component node id.
|
|
516
|
+
3. Call get_similar_bugs with the component or summary as the query.
|
|
517
|
+
|
|
518
|
+
Then produce a markdown fix plan with exactly these sections:
|
|
519
|
+
- One-line root cause
|
|
520
|
+
- Files likely to change (smallest set that fixes the root cause)
|
|
521
|
+
- Reproduction steps (numbered, \u22645)
|
|
522
|
+
- Blast-radius warnings (what else might break)
|
|
523
|
+
- Confidence (low/medium/high) with a one-line justification
|
|
524
|
+
|
|
525
|
+
Lead with the fix. Skip the preamble.`
|
|
526
|
+
}
|
|
527
|
+
}]
|
|
528
|
+
})
|
|
529
|
+
);
|
|
530
|
+
server.prompt(
|
|
531
|
+
"explain_judge_result",
|
|
532
|
+
"Turn raw Sonnet-as-Judge scores into ship / iterate / dismiss guidance. Use after a fix attempt has been judged.",
|
|
533
|
+
{ fixId: z.string().describe("The fix_attempt UUID to explain") },
|
|
534
|
+
({ fixId }) => ({
|
|
535
|
+
messages: [{
|
|
536
|
+
role: "user",
|
|
537
|
+
content: {
|
|
538
|
+
type: "text",
|
|
539
|
+
text: `You are a release engineer. Use the Mushi MCP tools:
|
|
540
|
+
1. Call get_fix_timeline for fixId "${fixId}" to see the full PDCA journey.
|
|
541
|
+
|
|
542
|
+
Then write a short verdict in this format:
|
|
543
|
+
- **Recommendation:** ship / iterate / dismiss
|
|
544
|
+
- **Why:** 1\u20132 sentences referencing the judge scores and CI signal
|
|
545
|
+
- **If iterate:** bullet list of the smallest next patch
|
|
546
|
+
|
|
547
|
+
No preamble, no score-by-score recap \u2014 the numbers are in the tool output.`
|
|
548
|
+
}
|
|
549
|
+
}]
|
|
550
|
+
})
|
|
551
|
+
);
|
|
552
|
+
server.prompt(
|
|
553
|
+
"triage_next_steps",
|
|
554
|
+
'Answer "what should I focus on right now?" \u2014 five-item markdown list drawn from the dashboard + recent classified queue.',
|
|
555
|
+
{},
|
|
556
|
+
() => ({
|
|
557
|
+
messages: [{
|
|
558
|
+
role: "user",
|
|
559
|
+
content: {
|
|
560
|
+
type: "text",
|
|
561
|
+
text: `You are a tech lead looking at the Mushi cockpit. Use the MCP tools:
|
|
562
|
+
1. Read the project://dashboard resource for the PDCA snapshot.
|
|
563
|
+
2. Call get_recent_reports with status="classified", limit=10.
|
|
564
|
+
|
|
565
|
+
Then output exactly 5 bullets, in priority order, each formatted as:
|
|
566
|
+
\`**Action** \u2014 why it matters \u2014 suggested tool call\`
|
|
567
|
+
|
|
568
|
+
Prefer items that are bottlenecks or critical severity. Skip filler.`
|
|
569
|
+
}
|
|
570
|
+
}]
|
|
571
|
+
})
|
|
572
|
+
);
|
|
573
|
+
return server;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
// src/index.ts
|
|
577
|
+
var require2 = createRequire(import.meta.url);
|
|
578
|
+
var VERSION = require2("../package.json").version;
|
|
579
|
+
var log = createLogger({ scope: "mushi:mcp", level: "info" });
|
|
580
|
+
var API_ENDPOINT = process.env.MUSHI_API_ENDPOINT ?? "https://api.mushimushi.dev";
|
|
581
|
+
var API_KEY = process.env.MUSHI_API_KEY ?? "";
|
|
582
|
+
var PROJECT_ID = process.env.MUSHI_PROJECT_ID ?? "";
|
|
260
583
|
async function main() {
|
|
261
584
|
if (!API_KEY) {
|
|
262
585
|
log.fatal("MUSHI_API_KEY environment variable is required");
|
|
263
586
|
process.exit(1);
|
|
264
587
|
}
|
|
588
|
+
log.info("Starting Mushi MCP server", { version: VERSION, endpoint: API_ENDPOINT, hasProjectId: !!PROJECT_ID });
|
|
589
|
+
const server = createMushiServer({
|
|
590
|
+
version: VERSION,
|
|
591
|
+
apiEndpoint: API_ENDPOINT,
|
|
592
|
+
apiKey: API_KEY,
|
|
593
|
+
projectId: PROJECT_ID || void 0
|
|
594
|
+
});
|
|
265
595
|
const transport = new StdioServerTransport();
|
|
266
596
|
await server.connect(transport);
|
|
267
597
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mushi-mushi/mcp",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "MCP server exposing Mushi Mushi reports to coding agents",
|
|
6
6
|
"type": "module",
|
|
@@ -18,12 +18,15 @@
|
|
|
18
18
|
"files": [
|
|
19
19
|
"dist",
|
|
20
20
|
"README.md",
|
|
21
|
-
"LICENSE"
|
|
21
|
+
"LICENSE",
|
|
22
|
+
"CONTRIBUTING.md",
|
|
23
|
+
"CODE_OF_CONDUCT.md",
|
|
24
|
+
"SECURITY.md"
|
|
22
25
|
],
|
|
23
26
|
"dependencies": {
|
|
24
27
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
25
28
|
"zod": "^4.3.6",
|
|
26
|
-
"@mushi-mushi/core": "^0.3.
|
|
29
|
+
"@mushi-mushi/core": "^0.3.1"
|
|
27
30
|
},
|
|
28
31
|
"devDependencies": {
|
|
29
32
|
"@types/node": "^22.0.0",
|
|
@@ -44,7 +47,8 @@
|
|
|
44
47
|
"url": "https://github.com/kensaurus/mushi-mushi/issues"
|
|
45
48
|
},
|
|
46
49
|
"publishConfig": {
|
|
47
|
-
"access": "public"
|
|
50
|
+
"access": "public",
|
|
51
|
+
"provenance": true
|
|
48
52
|
},
|
|
49
53
|
"sideEffects": false,
|
|
50
54
|
"keywords": [
|
|
@@ -62,9 +66,13 @@
|
|
|
62
66
|
],
|
|
63
67
|
"scripts": {
|
|
64
68
|
"build": "tsup",
|
|
69
|
+
"clean:types": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
65
70
|
"dev": "tsup --watch",
|
|
66
71
|
"lint": "eslint src/",
|
|
67
72
|
"test": "vitest run",
|
|
73
|
+
"test:smoke": "node scripts/smoke-stdio.mjs",
|
|
74
|
+
"test:localhost": "node scripts/localhost-e2e.mjs",
|
|
75
|
+
"demo": "node scripts/demo-terminal-config.mjs",
|
|
68
76
|
"typecheck": "tsc --noEmit"
|
|
69
77
|
}
|
|
70
78
|
}
|