Ryan Malloy fa7a4d51bd listen(): fire the ready tone non-blocking so you can talk over it
The "go" tone was awaited before the VAD read loop started, adding ~1.1s of
dead time before capture effectively began. Now the tone fires as a background
task right after the mic warmup and the read loop starts immediately, so the
person can talk over the dial tone and the turn also ends sooner on silence.
The tone still bleeds into the captured head; VAD skips it (pure tone !=
speech) and the min_speech_ms guard is unchanged. Applied to all three
recorder paths (VAD, fixed-duration Linux, macOS stream); the tone task is
cancelled in the finally so a short recording is never outlived by the tone.
2026-07-04 21:39:43 -06:00

McSpeak

Multi-engine text-to-speech server exposed as MCP tools via FastMCP 3.0 Streamable HTTP. Plays audio through the host's speakers using PipeWire. Built for giving AI agents a voice -- literally.

PyPI Python 3.12+ License: MIT

Quick Start

McSpeak runs a Streamable HTTP MCP server on :8371. Start the server, then point any MCP client at it.

1. Get the Kokoro model files (~340 MB, one-time):

mkdir -p models/kokoro && cd models/kokoro
curl -LO https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/kokoro-v1.0.onnx
curl -LO https://github.com/thewh1teagle/kokoro-onnx/releases/download/model-files-v1.0/voices-v1.0.bin
cd -

The defaults look for models/kokoro/kokoro-v1.0.onnx and models/kokoro/voices-v1.0.bin relative to the working directory. Override with TTS_KOKORO_MODEL / TTS_KOKORO_VOICES if you put them elsewhere.

2. Start the server:

# Run directly from PyPI (no install needed)
uvx mcspeak

# Or install and run
pip install mcspeak
mcspeak

Wait for McSpeak ready on 0.0.0.0:8371 in the logs. Piper and Orpheus will report unhealthy unless their backends are running -- that's expected; Kokoro alone is enough to start.

3. Wire it into Claude Code (HTTP transport, server must be running):

claude mcp add --transport http mcspeak http://127.0.0.1:8371/mcp

mcspeak ships as a Streamable HTTP server, not a stdio one, so --transport http is required. The older form claude mcp add mcspeak -- uvx mcspeak registers stdio and will fail to connect.

For multi-engine Docker deployment with Piper and GPU-accelerated Orpheus, see Docker Setup below.

MCP Tools

Tool What it does
speak Synthesize text and play through speakers. Blocks with live progress.
generate_audio Synthesize to WAV file without playing. Bypasses the queue.
list_voices List available voices for an engine.
list_engines Show all engines and their health status.
speech_status Check status of a queued/playing speech item.
cancel_speech Kill playback immediately. Plays a cancel tone.

There's also an audio://recent resource listing the last 20 generated WAV files.

Engines

Engine Backend Speed Voices Notes
kokoro ONNX (local CPU) ~4x realtime ~50 Default. Fast, good quality.
piper Wyoming protocol (Docker) ~2x realtime Depends on model Connects to any Wyoming-compatible TTS server.
orpheus llama-server + SNAC ~1x realtime 6 LLM-based. Supports <laugh>, <sigh>, emotion tags. Needs GPU.

Features

Radio-Style Alert Tones

Speech is bookended by short tones inspired by two-way radio systems. An entry tone fires immediately when speak() is called (before synthesis starts), acting as an audible "I heard you" that covers the 1-3s synthesis latency. An exit tone plays when the queue empties.

Available tones: chirp (Nextel iDEN), apollo (NASA quindar key-up), roger (CB radio descending beep), quindar-out (NASA unkey), scratch (vinyl record scratch for cancellation), and more. All generated programmatically at startup -- no bundled audio files.

Voice Identity

When multiple projects connect simultaneously, each one automatically gets a distinct voice via round-robin assignment from a curated pool. Voices are interleaved across gender and accent (American, British, European) for maximum contrast. Assignments persist across restarts.

Pass project="my-project" in speak() calls, or let it auto-detect from MCP Roots.

Media Ducking

External audio (YouTube, Spotify, etc.) automatically fades down before speech and fades back in after the exit tone, like a radio broadcast interruption. Uses pactl under the hood. Fails silently if PulseAudio isn't available.

Chunked Synthesis Pipeline

Long texts (20+ words with sentence boundaries) are split into chunks and pipelined -- synthesis of chunk N+1 overlaps playback of chunk N. First audio plays within ~1-2s instead of waiting for the full text to synthesize.

Graceful Shutdown

docker compose down lets the current speech finish instead of cutting mid-sentence. Two-phase: 3s handler drain, then up to 30s for the consumer to finish. Configurable via TTS_SHUTDOWN_TIMEOUT.

Configuration

All settings use the TTS_ prefix and can be set via environment variables or .env file.

Variable Default Description
TTS_HOST 0.0.0.0 Server bind address
TTS_PORT 8371 Server port
TTS_ENTRY_TONE chirp Tone before speech (chirp, apollo, none, or WAV path)
TTS_EXIT_TONE roger Tone after speech (roger, quindar-out, none, or WAV path)
TTS_CANCEL_TONE scratch Tone on cancel (scratch, reverse-roger, none, or WAV path)
TTS_DUCK_MEDIA true Fade external audio during speech
TTS_DUCK_FADE_OUT_MS 500 Media fade-out duration (ms)
TTS_DUCK_FADE_IN_MS 1000 Media fade-in duration (ms)
TTS_VOICE_IDENTITY true Auto-assign distinct voices per project
TTS_VOICE_IDENTITY_FILE (empty) JSON file to persist voice assignments
TTS_VOICE_IDENTITY_EXCLUDE af_nicole Voices excluded from auto-assign pool
TTS_ANNOUNCE_PROJECT false Prefix speech with project name
TTS_VOICE_BLACKLIST am_adam,af_jessica Voices hidden from listings
TTS_SHUTDOWN_TIMEOUT 30 Max seconds to wait for speech on shutdown
TTS_KOKORO_MODEL models/kokoro/kokoro-v1.0.onnx Path to Kokoro ONNX model
TTS_KOKORO_VOICES models/kokoro/voices-v1.0.bin Path to Kokoro voice pack
TTS_PIPER_HOST 172.26.0.3 Piper Wyoming server host
TTS_PIPER_PORT 10200 Piper Wyoming server port
TTS_ORPHEUS_URL http://127.0.0.1:8081 Orpheus llama-server URL

Docker Setup

The compose file defaults to a kokoro-only stack — no GPU required. Orpheus (GPU-accelerated, llama-server backed) is opt-in via the with-orpheus profile. Piper integrates via its host/port settings; you supply your own Wyoming server.

Prerequisites (base, kokoro-only):

  • PipeWire on the host (the container plays audio via the host's pw-play socket)
  • An external Docker network named caddy (create with docker network create caddy if it doesn't exist). The private mcspeak-internal network is auto-created per-stack.
  • Kokoro ONNX model files in ./models/kokoro/ (see Quick Start for the download)

Additional prerequisites for with-orpheus:

  • NVIDIA GPU + nvidia-container-toolkit
  • Orpheus GGUF model on disk — path goes in .env as ORPHEUS_GGUF_PATH

Bring it up (kokoro-only):

make up        # build + start kokoro-only
make logs      # follow logs
make status    # health check

Bring it up with Orpheus:

cp .env.example .env
# Edit .env: set ORPHEUS_GGUF_PATH to your Orpheus GGUF file
make up-with-orpheus

Once make status shows the container as healthy, register it with Claude Code. The compose file applies a caddy-docker-proxy label exposing the server at mctalkbox.l.supported.systems -- if you're not using that hostname, point at the container's mapped port directly:

# Direct connection (replace with your hostname/port if different)
claude mcp add --transport http mcspeak http://127.0.0.1:8371/mcp

The container mounts the host PipeWire socket (/run/user/1000/pipewire-0) for audio playback and the PulseAudio compat socket (/run/user/1000/pulse) for media ducking.

stop_grace_period in the compose file is set to 38s (3s drain + 30s shutdown timeout + 5s safety). Adjust if you change TTS_SHUTDOWN_TIMEOUT.

Architecture

src/tts_mcp/
  server.py          FastMCP lifespan, tool definitions, chunked synthesis
  queue.py           Producer-consumer speech queue with priority + outcome tracking
  tones.py           Procedural tone WAV generation (numpy, 48kHz 16-bit PCM)
  audio.py           WAV writing and async pw-play wrapper
  media_duck.py      Async pactl volume control for media ducking
  voice_identity.py  Project-to-voice mapping with round-robin assignment
  settings.py        Pydantic settings from env vars
  engines/
    base.py          TTSEngine abstract base
    kokoro.py        Kokoro ONNX engine
    piper.py         Wyoming protocol client
    orpheus.py       llama-server + SNAC decoder

See CLAUDE.md for the full technical reference -- design decisions, progress lifecycle, cancellation semantics, and the Python 3.13 asyncio.wait_for pitfall that cost a few hours of debugging.

License

MIT

Description
No description provided
Readme 946 KiB
Languages
Python 95.9%
Dockerfile 2%
Swift 1.4%
Makefile 0.7%