# MCP — driving FaustWave from external clients

The **Model Context Protocol (MCP)** is the open standard for "LLMs talking to applications." FaustWave exposes its action registry as an MCP server. That means you can hook FaustWave up to **any MCP client** — Claude Desktop, a custom script, a CI job, another AI tool — and drive the IDE from outside.

The same surface the in-app AI Assistant uses. Same actions, same input schemas, same trace into the Activity log. Different transport, that's all.

## What it's for

Three real use cases:

1. **Claude Desktop driving FaustWave** — pull up Claude, ask it to build a DSP, it drives FaustWave via MCP. Same as the in-app Assistant, but you're outside the FaustWave window.
2. **Automation scripts** — render N variations of a DSP with different param sweeps, batch-import a sample folder, regenerate sequencer patterns from an external composer. Headless-friendly via daemon mode.
3. **Authoring tutorials + content** — the very manual you're reading is being written + published via MCP. The loop: open a markdown editor module via `documents.add`, `editor.source.set` the content, `documents.save`, build the pack, `hub.version.upload`. See § *Authoring this manual via MCP* below.

## Connecting Claude Desktop

The Claude Desktop config file lives at:

| Platform | Path |
|---|---|
| **macOS** | `~/Library/Application Support/Claude/claude_desktop_config.json` |
| **Windows** | `%APPDATA%\Claude\claude_desktop_config.json` |
| **Linux** | `~/.config/Claude/claude_desktop_config.json` |

Add a FaustWave entry under `mcpServers`:

```json
{
  "mcpServers": {
    "faustwave": {
      "command": "<path-to-faustwave>",
      "args": ["--mcp"],
      "env": {}
    }
  }
}
```

Restart Claude Desktop. You should see a "🛠 faustwave" indicator in Claude's tool palette next to your message bar; clicking it reveals the action catalog.

> 🟦 **HOW THE TRANSPORT WORKS**: Claude Desktop spawns FaustWave as a child process and talks to it over stdin/stdout JSON-RPC. Each tool call is a JSON message; the response is a JSON message back. FaustWave's MCP server is the same Electron process — it boots into a headless renderer (no visible window in `--mcp` mode), registers the action catalog, and serves requests until Claude disconnects.

## The tool catalog

When the MCP client connects, FaustWave publishes its actions as tools. Roughly:

- **~130 actions** total in the registry.
- **Hot-path tools** (a curated subset of the highest-frequency ones) are exposed directly with their full schemas so clients see them at first glance: `dsp.compile`, `dsp.run`, `dsp.stop`, `dsp.node.*`, `builder.graph.connect/disconnect`, `midi.note.play`, `sequencer.play/stop/step.set`, `master.volume.set`, `transport.play/stop`, `recorder.start/stop/toggle`, `routing.list/connect/disconnect`, etc.
- **`palette.run`** is the meta-action — exposes the FULL catalog. To call any action that isn't a hot-path tool, you call `palette.run { action_id, input }`.

The split exists because of the **MCP 128-tool soft cap** — Anthropic's API rejects more than 128 published tools in a single context. Hot-path tools are the high-frequency ones that benefit from inline schemas; everything else lives behind `palette.run`. Current publishing decision: ~22 hot-path tools + `palette.run` + `palette.list` + `palette.describe` = 25 published, well under the cap.

## Discovery — find what you need

Three meta-actions FaustWave always publishes:

| Action | Purpose |
|---|---|
| **`palette.list`** | Browse the full catalog. Defaults to category summaries. Pass `category` for a focused list. Pass `search` for substring matches. Pass `all: true` for everything. |
| **`palette.describe`** | Full input_schema + description + examples for up to 10 action ids at a time. |
| **`palette.run`** | Invoke any action by id with structured input. Validates against the schema; rejects unknown ids with a friendly error. |

**Pattern**: when you want to do something — say, add reverb:

```
1. palette.list     { search: "reverb" }                     → candidate actions
2. palette.describe { action_ids: ["master.fx.slot.add"] }   → input_schema + examples
3. palette.run      { action_id: "master.fx.slot.add", input: { … } }
```

This is the dance the in-app Assistant does internally on every turn. External clients (Claude Desktop included) do the same.

## Hard-required `module_id` on DSP-Builder actions

A discipline rule: **every DSP-Builder action requires an explicit `module_id`** input. No implicit fallback to "the currently active Builder."

The reason is a time-of-check / time-of-use race. Before the rule, an AI mid-task could call `builder.graph.node.add` and FaustWave would silently target whatever Builder was active in the user's UI at the moment of dispatch. User switches tabs mid-AI-task → next AI action lands on the WRONG Builder. The symptom (AI's work appearing in a different file) is hard for the user to spot because the AI's reply still reads coherent.

Closed by the rule: `module_id` is required, the schema rejects the call without it, and the error path lists the available Builder ids so the AI self-corrects:

```json
{
  "error": "Module id is required. Available Builders: builder-mq2dtphb-8ygb (\"test-slice-c\"), builder-mq2du5g5-coyr (\"untitled\")"
}
```

A pre-existing **sequencer exception** is grandfathered — sequencer actions take an optional `instance_id` with active-fallback. Lower-frequency surface; sequencers are typically created once per project and switched between rarely, so the race window is empirically tiny.

## Activity surfaces every call

`activity.recent { limit?, surface?, outcome?, text? }` returns the most-recent action invocations across MCP, palette, and AI surfaces. Filter to see only what YOU did via MCP:

```json
{ "action_id": "activity.recent", "input": { "surface": "mcp", "limit": 20 } }
```

For a session-history view, this is the source of truth. The in-app Activity rail panel reads from the same store, so you and the user see the same thing.

## Daemon mode — headless FaustWave

For server-side use or scripted batch work, boot FaustWave with the `--daemon` flag (or set `FAUSTWAVE_DAEMON=1`). Behaviour:

- No visible window. Tray icon for stop/quit control.
- Full audio + MCP run.
- MCP force-starts.
- Persistence + project state work as normal.

Use case: a batch job that opens a project, sweeps a Builder's params, records 20 takes, and exits.

Daemon mode is a deliberate "FaustWave-as-service" surface — not for everyday use, but the door is open for the socket-API track and the "open alternative to controller second-tracks" use case.

## Building your own MCP client

If Claude Desktop is too heavyweight (or you want CI integration), write a thin client. The protocol is open + documented at [modelcontextprotocol.io](https://modelcontextprotocol.io). FaustWave's server is a standard MCP implementation — no FaustWave-specific extensions.

Roughly:

1. Spawn FaustWave as a child process with `--mcp` (or connect to a running daemon).
2. Send a `tools/list` request to discover the published tools.
3. Send `tools/call` requests with `{ name, arguments }` to invoke.
4. Process the response stream.

Languages with established MCP SDKs include TypeScript / JavaScript (official), Python (official), and a growing community list. Pick whichever fits your stack.

## Authoring this manual via MCP — the live loop

The book you're reading is the canonical live-loop demo:

1. **Open a markdown editor module in the IDE** via `documents.add { ref: "asset:assets/<chapter file>.md" }` (or `file_path` with an absolute OS path to import an external file).
2. **Edit** — `editor.source.set { module_id, source: "<new content>" }` (or just type in the editor surface).
3. **Save** — `documents.save { module_id }` writes to the file path.
4. **Build** the pack — `node docs/books/build-books.mjs` concatenates the pack's `.md` files into `_build/<slug>.md` with H1 → H2 demotion so each chapter becomes one Book section.
5. **Publish a new version** — `hub.version.upload { item_id, kind: "book", file_path: "<built bundle>", changelog }`.
6. **Reinstall locally** — `hub.install { item_id }` to pull v(n+1) into the local KB.

Four to six MCP calls per chapter update. The dance is fast enough that "write a paragraph, see it rendered" is a 10-second loop. The manual writes itself via the actions it documents.

## Authoring chains — not just one-shot

The Book Reader supports **chained tutorial actions** in section YAML blocks. Each step's return value can be saved with `save_as: <var>`; downstream steps reference `{{var.path}}` to thread results through. Every playable chain in this manual uses the same primitive — launched from the Reader, the chain runs through `api.actions.run` with status badges, inline result chips, and a Reset button. See **FaustWave — Hub** § 1 for the book content shape and any section's `actions:` block in this manual for examples.

## Where to go from here

- **§ 1 (The AI Assistant)** — the in-app equivalent of what you do from Claude Desktop.
- **§ 2 (The Command Palette)** — the human-side surface to the same registry.
- **FaustWave — Hub** § 3 — the publish + install workflows that the live-loop hits over MCP.
- **FaustWave — Reference** § *MCP actions* — the full hot-path catalog.
