---
title: "MCP Server"
description: "Let Claude Code, Cursor, Cline, or any MCP-aware agent speak in one of your cloned voices — locally, with no cloud."
---

## Overview

Voicebox ships a built-in **Model Context Protocol** server so local AI
agents can call your Voicebox install directly: speak text in a voice
profile, transcribe audio, and list captures or profiles. The server runs
inside the same process as the rest of Voicebox and is mounted at `/mcp`
over Streamable HTTP.

Agent asks to speak → Voicebox plays audio on your speakers → an on-screen
pill surfaces the voice name for the whole duration so you always see what's
coming out of your machine.

<Callout type="info">
  MCP shipped in **0.5.0** alongside [Dictation](/overview/dictation) and
  [Voice Personalities](/overview/voice-personalities). The design goal is
  "local voice layer for every agent on your machine" — the same app that
  captures your voice can generate a response in any voice profile you've
  cloned.
</Callout>

## Quick install

### Claude Code

```
claude mcp add voicebox \
  --transport http \
  --url http://127.0.0.1:17493/mcp \
  --header "X-Voicebox-Client-Id: claude-code"
```

### Cursor / Windsurf / VS Code MCP / any HTTP MCP client

Drop this into the client's MCP config (usually `.mcp.json` or a Settings UI):

```json
{
  "mcpServers": {
    "voicebox": {
      "url": "http://127.0.0.1:17493/mcp",
      "headers": { "X-Voicebox-Client-Id": "cursor" }
    }
  }
}
```

Change `cursor` to whatever name you want the binding to show up as in
Voicebox → Settings → MCP. The value is just an identifier for the
per-client voice binding — not a secret, not a credential.

### Clients that only speak stdio

A stdio shim binary `voicebox-mcp` is bundled with the desktop app. Point
the client at that binary's absolute path:

<Tabs items={["macOS", "Windows", "Linux"]}>
  <Tab value="macOS">
  ```json
  {
    "mcpServers": {
      "voicebox": {
        "command": "/Applications/Voicebox.app/Contents/MacOS/voicebox-mcp",
        "env": { "VOICEBOX_CLIENT_ID": "claude-desktop" }
      }
    }
  }
  ```
  </Tab>
  <Tab value="Windows">
  ```json
  {
    "mcpServers": {
      "voicebox": {
        "command": "C:\\Program Files\\Voicebox\\voicebox-mcp.exe",
        "env": { "VOICEBOX_CLIENT_ID": "claude-desktop" }
      }
    }
  }
  ```
  </Tab>
  <Tab value="Linux">
  ```json
  {
    "mcpServers": {
      "voicebox": {
        "command": "/opt/voicebox/voicebox-mcp",
        "env": { "VOICEBOX_CLIENT_ID": "claude-desktop" }
      }
    }
  }
  ```
  </Tab>
</Tabs>

The shim waits up to 30 seconds for the Voicebox backend to come up, then
proxies JSON-RPC from stdio over Streamable HTTP. Voicebox must be running
for the shim to connect.

## Tools

| Tool | Use |
|---|---|
| `voicebox.speak` | Speak text in a voice profile. Returns a `generation_id` to poll. |
| `voicebox.transcribe` | Whisper transcription of base64 audio or an absolute local path. |
| `voicebox.list_captures` | Recent captures with transcripts, paginated. |
| `voicebox.list_profiles` | Available voice profiles (cloned + preset). |

### `voicebox.speak`

```ts
voicebox.speak({
  text: "Deploy complete.",
  profile?: "Morgan",            // name or id; falls back to per-client binding, then default
  engine?: "qwen",               // qwen | qwen_custom_voice | luxtts | chatterbox | chatterbox_turbo | tada | kokoro
  personality?: true,            // rewrite via the profile's personality LLM before TTS; default comes from the per-client binding
  language?: "en",
})
```

Returns:

```json
{
  "generation_id": "…",
  "status": "generating",
  "profile": "Morgan",
  "source": "mcp",
  "poll_url": "/generate/<id>/status"
}
```

- **Plain TTS** — `personality: false` (or omitted + binding default is false). Text is spoken as-is.
- **Persona mode** — `personality: true` and the profile must have a personality prompt set.
  The LLM rewrites the text in character before TTS. See [Voice Personalities](/overview/voice-personalities).

### `voicebox.transcribe`

```ts
voicebox.transcribe({
  audio_base64?: "<base64>",    // exactly one of these two
  audio_path?: "/absolute/path/to/file.wav",
  language?: "en",
  model?: "turbo",              // base | small | medium | large | turbo
})
```

Returns `{ text, duration, language, model }`. 200 MB ceiling on either path.

### `voicebox.list_captures`

`{ limit?: 20, offset?: 0 }` → `{ captures: [...], total }`. `limit` is
clamped to `1..=200`.

### `voicebox.list_profiles`

No args → `{ profiles: [{ id, name, voice_type, language, has_personality }] }`.

## Voice resolution

Every call to `voicebox.speak` (and `POST /speak`) resolves the voice profile
in this order:

<Steps>
  <Step title="Explicit `profile` arg">
    Passed as a name (case-insensitive) or id. If the name/id doesn't match,
    the call errors — the server doesn't silently fall back.
  </Step>
  <Step title="Per-client binding">
    Looked up by the `X-Voicebox-Client-Id` header. Managed in
    **Voicebox → Settings → MCP**. Lets you pin Claude Code to Morgan,
    Cursor to Scarlett, etc.
  </Step>
  <Step title="Global default">
    `capture_settings.default_playback_voice_id` — same default voice the
    Captures tab's "Play as voice" action uses.
  </Step>
</Steps>

If none of the three produce a profile the tool returns a helpful error
pointing at Settings.

## Per-client bindings

Voicebox → Settings → MCP shows one row per `client_id` Voicebox has heard
from, plus the config snippets you can copy into each agent. Each row
carries:

| Field | Purpose |
|---|---|
| `label` | Display name in the Settings UI (e.g. "Claude Code"). |
| `profile_id` | The voice this client uses when `profile` isn't passed. |
| `default_engine` | Override the TTS engine for this client. |
| `default_personality` | When true, `voicebox.speak` routes through the profile's personality LLM (rewrite) by default. |
| `last_seen_at` | Last time the server saw a request from this client. |

`last_seen_at` is stamped automatically by middleware on every `/mcp/*`
request — useful when you're not sure whether your config took.

## The speaking pill

Every agent-initiated speak surfaces the floating pill the same way
[Dictation](/overview/dictation) does, in a new `Speaking` state showing the
profile name and an elapsed timer. The pill is intentionally unmissable —
silent background TTS is a trust hazard, so Voicebox always shows what's
being spoken and in what voice.

Behind the scenes, the backend broadcasts `speak-start` and `speak-end`
events on `GET /events/speak`, which `DictateWindow` subscribes to via SSE.
The pill overrides the capture session when both would render — you can't
hear two pills at once.

## Non-MCP REST surface

`POST /speak` is a thin wrapper on the same code path for callers that
don't speak MCP — shell scripts, ACP, A2A, GitHub Actions, whatever.

```bash
curl -X POST http://127.0.0.1:17493/speak \
  -H 'Content-Type: application/json' \
  -H 'X-Voicebox-Client-Id: ci' \
  -d '{"text":"Build complete.","profile":"Morgan"}'
```

Body fields match the MCP tool: `text`, optional `profile`, `engine`,
`personality`, `language`. Returns a `GenerationResponse` — the same shape as
`POST /generate`.

## Debugging

Use the MCP Inspector to poke tools directly without plumbing through an
agent:

```
npx @modelcontextprotocol/inspector http://127.0.0.1:17493/mcp
```

Start with `voicebox.list_profiles` to confirm wiring, then
`voicebox.speak` for end-to-end — you should hear audio and see the
generation land in the Captures tab.

<Callout type="info">
  If an agent can't reach the server, the first thing to check is that
  Voicebox is running — the backend only listens while the desktop app is
  open. The stdio shim surfaces this as a JSON-RPC error on the client
  side after its 30-second health-wait window elapses.
</Callout>

## Security

- **Localhost only.** The server binds to `127.0.0.1`. If you ever point
  Voicebox at a non-loopback interface (e.g. remote-mode over a trusted
  network), add a bearer token — it's on the roadmap but not in 0.5.0.
- **No auth today.** Any process that can connect to your loopback can
  call MCP. That's the same trust boundary as the rest of Voicebox's REST
  API and is appropriate for a single-user local tool.
- **`audio_path` reads are unrestricted** against the same trust
  boundary. If you're scripting against a shared host, prefer
  `audio_base64` so you don't have to think about path sandboxing.
- **Voice cloning consent applies.** See [Voice Cloning](/overview/voice-cloning#limitations)
  — an agent being able to call `voicebox.speak` in someone's voice
  doesn't change the ethics of whose voices you clone.

## Implementation notes

- **Transport:** Streamable HTTP (Nov-2025 MCP spec, post-SSE). Claude
  Code, Cursor, Windsurf, and VS Code MCP extensions all support it.
- **Package naming:** the backend package is `backend/mcp_server/`, not
  `mcp`, to avoid shadowing the PyPI `mcp` package FastMCP imports
  internally.
- **Dependencies:** `fastmcp>=3.0,<4.0`, `sse-starlette>=2.0`.
- **Lifespan:** mounting FastMCP requires the `lifespan=` kwarg on
  `FastAPI()` — the startup/shutdown event decorators are incompatible
  with FastMCP's Streamable HTTP session manager. The Voicebox app.py
  composes both into one async context manager.

For the full developer-facing tour of the code layout, see
`backend/mcp_server/README.md` in the repo.

## Next steps

<Cards>
  <Card title="Voice Personalities" href="/overview/voice-personalities">
    Persona mode (`personality: true`) for agents that should
    transform text in-character before speaking.
  </Card>
  <Card title="Dictation" href="/overview/dictation">
    The pill that surfaces agent speech is the same one that surfaces
    your dictations — one mental model for both directions of the loop.
  </Card>
  <Card title="Captures" href="/overview/captures">
    Every agent-initiated speak lands in the Captures tab with its
    generated audio — replay, download, repurpose.
  </Card>
</Cards>
