Muse MCP
README.md
# Muse MCP
Stdio MCP server that lets any MCP client spawn and drive Muse Code agents on this machine. The server speaks MSP (Muse Session Protocol): newline-delimited JSON-RPC 2.0 to a long-lived `muse serve` host. Sessions outlive turns. Approvals and user-input prompts arrive as server requests while a turn is live, and the client answers them through tools.
Muse Code stays the billing client. This server never reads, stores, prints, or forwards Muse OAuth tokens, API keys, or `~/.config/muse/auth.json`.
## Default approval mode is yolo
**Spawned sessions default to `approval_mode = "yolo"`.** On the wire that is permission profile `:unrestricted` and `approvalMode: allowAll`. Tool calls are not held for a person. The shipped `~/.muse-mcp/config.toml` has an empty `auto_approve` list, so nothing is auto-approved by policy unless you add a rule. The sandbox stays on: yolo does not pass `--disable-sandbox`.
This is the standing posture for agents on your own machine. It is the wrong default for a shared or untrusted workspace. Pass `approval_mode: "on-request"` (`:ask-me`) or `"judge"` (`:auto-review`) on `muse_spawn`, or set `approval_mode` in the config file, when you want a gate.
## Why TypeScript
Phase 0 matched the installed CLI (Muse Code 1.4.14) to `@muse-code/sdk@1.4.14` on schema fingerprint `sha256:c94c3d1f2f51e6424074b9ec43eac3eedcf532d3dcd77351ec1383a87d3049c6`. The SDK is the official MSP client, has no runtime dependencies, and already implements framing, `initialize`, session folds, and the approval handshake. Node on this machine is v24, which runs the sources with `--experimental-strip-types`, so the server is TypeScript with no compile step. `MuseClient` is not the seam: `session/start.worktree` is experimental and only the raw `Connection` can send it.
## Run
Requires Node 20+ and `muse` on `PATH` (override with `MUSE_MCP_BIN`).
```bash
npm install
./bin/muse-mcp
```
The process speaks MCP on stdin/stdout and opens no port. State lives in `~/.muse-mcp/` (`MUSE_MCP_HOME`):
| File | Role |
| --- | --- |
| `config.toml` | Written on first start if missing. |
| `state.json` | Sessions this server started. After a restart, idle and running rows load as `orphaned` and resume on next use. |
| `audit.log` | Append-only JSONL of requests, decisions, answers, expiries, and refusals. |
One `muse serve` host is kept per workspace + contributor mode + preset + trust. A crashed host restarts on the next call. Its sessions are marked `orphaned` and resumed with `session/resume` when the protocol allows. `sessionInUse` is returned to the caller and not retried: this server does not attach to a session an interactive TUI already owns, and it only drives sessions it started.
## Tools
| Tool | What it does |
| --- | --- |
| `muse_spawn` | Start a session and optionally its first turn. Returns `{session_id, turn_id, status}` immediately unless `wait` is true. |
| `muse_turn` | Follow-up turn on a session this server started. |
| `muse_steer` | Mid-turn input. Requires the running turn id. Idle sessions error and send no frame. |
| `muse_pending` | Pending approvals and questions: request id, session, kind, payload, age. |
| `muse_answer` | Answer a pending question (`request_id`, `answer`). |
| `muse_decide` | `approve` or `deny` a pending approval. Deny can include `reason`. |
| `muse_status` | One session or all: state, turn, model, workspace, token usage, pending counts. |
| `muse_result` | Latest completed turn, final answer text, and a short in-memory event tail. |
| `muse_cancel` | Cancel the current turn. |
| `muse_close` | End a session. |
| `muse_raw` | Send any MSP method with raw params. Credential methods are refused before a frame is written. |
`muse_answer` and `muse_decide` take an optional `actor` of `user` or `orchestrator` (default `orchestrator`). Policy decisions are logged as `policy`. Unanswered approvals expire to deny and unanswered questions are cancelled after `pending_timeout_sec` (default 1800). If the MCP client advertises elicitation, pending items are also offered that way. The tools still work when it does not.
`muse_spawn` arguments: `workspace` (required, absolute, must exist), `prompt` or `prompt_file`, `model`, `reasoning_effort` (`none|minimal|low|medium|high|xhigh|max|ultra`), `preset`, `worktree` (`off|create|existing`) with `worktree_base` or `worktree_path`, `approval_mode` (`yolo|on-request|judge`), `contributor_mode` (default false), `wait` (default false). There is no `output_schema` argument. MSP has no field for it.
Tool arguments win over environment variables, which win over `config.toml`.
| Env | Effect |
| --- | --- |
| `MUSE_MCP_BIN` | Muse binary. Default `muse`. |
| `MUSE_MCP_HOME` | State directory. Default `~/.muse-mcp`. |
| `MUSE_MCP_MODEL` | Model when a spawn omits `model`. |
| `MUSE_MCP_REASONING_EFFORT` | Effort when a spawn omits `reasoning_effort`. |
| `MUSE_MCP_APPROVAL_MODE` | `yolo`, `on-request`, or `judge`. |
| `MUSE_MCP_PENDING_TIMEOUT_SEC` | Pending-request timeout. |
`auto_approve` rules are `{ match = "<regex>", decision = "approve" }`. `match` is tested against one line of tool name, subject kind, command, host, path, and raw args. A hit approves with the once-only choice and is audit-logged as `policy`.
## Streaming
Rich clients can watch turns live instead of polling `muse_result`. Streaming is opt-in: a client declares the experimental capability `muse.stream` in its initialize capabilities. A client that does not declare it receives no stream traffic and its tool results are unchanged.
```json
{ "capabilities": { "experimental": { "muse.stream": {} } } }
```
The server sends one notification method, `notifications/muse/event`, with a plain JSON event object as params:
| Field | Meaning |
| --- | --- |
| `type` | `turn-started`, `item-started`, `item-delta`, `item-completed`, `turn-completed`, `usage`, or `pending`. |
| `sessionId` | The session the event belongs to. Events cover every session on the connection. |
| `turnId` | Present when the host frame carries one. |
| `viewCursor` | The host view cursor, verbatim, on item, usage, and turn-completed events (`turn-started` carries none). Strictly increasing per session; a jump means a gap. |
| `item` | The full item object on `item-started` and `item-completed`. Passed through uninterpreted. |
| `itemId`, `field`, `delta` | On `item-delta`: the item, the dotted field path (`"text"` when the host omits it), and the appended text. |
| `terminal`, `answer` | On `turn-completed`: the turn's terminal word and its final answer text. |
| `droppedDeltas` | On `turn-completed`: delta events whose send failed during that turn. `muse_status` reports the same count cumulatively as `stream_dropped_deltas`. |
| `usage`, `usageKind` | On `usage`: the host's raw token-usage params, plus which frame they came from: `tokenUsage` (a cumulative snapshot) or `changed` (a change notice). |
| `requestId`, `kind`, `phase` | On `pending`: a hint that an approval or user-input request was `requested` or `settled`. |
Client guidance:
- Text deltas for one item field are coalesced over 30 ms, so a delta event may carry several host deltas merged. Concatenating a field's delta events in order rebuilds the field, with one exception: if the host surface saturated, the concatenation can fall short. `item-completed` carries the full item and is authoritative. Reconcile from it.
- A `pending` event is a signal to call `muse_pending`, which stays the source of truth for approval and question payloads. Approvals and questions still arrive through elicitation and the broker exactly as before.
- A client that cannot keep up loses delta events, never the turn. Sends never block the host, failures are counted in `droppedDeltas`, and the first failure per turn is written to the audit log. The `turn-completed` event waits, up to 250 ms, for the turn's in-flight delta sends to settle before its count is stamped; sends that outlive the wait, and the count on a `turn-completed` whose own send fails, still land in the cumulative `stream_dropped_deltas` in `muse_status`, which persists across restarts.
## Register with Grok Build
User scope writes `~/.grok/config.toml`:
```bash
grok mcp add muse-mcp -- /path/to/muse-code-mcp/bin/muse-mcp
```
## Generic MCP client
```json
{
"mcpServers": {
"muse-mcp": {
"command": "/path/to/muse-code-mcp/bin/muse-mcp",
"args": []
}
}
}
```
The server makes no Grok-specific calls. The same command works in Cursor or any other stdio MCP client.
## Protocol notes
`docs/PROTOCOL.md` is the source of truth where it disagrees with any summary. Short version:
- `yolo` / `on-request` / `judge` are public names. The wire values are `:unrestricted`+`allowAll`, `:ask-me`+`onRequest`, and `:auto-review`+`onRequest`. Both the profile and the approval mode are always sent. Omitting the profile on this machine produced no approval prompts.
- Approve selects the once-only `approved` choice (`allow_once`). Deny selects `abort` on this host and passes `reason` when that choice accepts feedback.
- `worktree` is the experimental `session/start.worktree` field. `off` omits it. On this machine, create and attach against a repository Muse has not marked trusted fail with `-32029` `workspaceNotTrusted`. `--trust-workspace` loads skills and rules and does not grant that trust. The server returns the host error and does not run `git worktree`.
- `--preset` and `--contributor-mode` are host process flags, not session fields. Contributor mode is sent as `off` unless the caller sets it.
- No MSP equivalent, so these `muse exec` knobs are not exposed: `--output-schema`, `--max-model-steps`, `--user-input-auto-resolve`.
- `muse session-message list --json` returned `external_agent_ingress_closed`. `muse_message` is not implemented.
`muse_raw` refuses methods under `account/`, `mcpServer/oauth`, and any method whose name contains `login` or `logout`, and writes an audit line with the method name only.
## Tests
```bash
npm test
npm run typecheck
```
`npm test` uses a scripted fake `muse serve`. It does not launch the real CLI.
Live checks against the installed `muse` are off unless you opt in. They spend model calls:
```bash
MUSE_MCP_LIVE=1 node --experimental-strip-types --test --test-timeout=300000 test/live.test.ts
```
## License
Licensed under the 0BSD license (see `LICENSE`).
This is an independent project. It is not affiliated with or
endorsed by Meta. Muse and Muse Code are trademarks of Meta
Platforms, Inc.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues