Rename all user-facing references from tts-mcp to mcspeak: service name, container, compose project, Dockerfile CMD, FastMCP instance, MCP config, startup logs, temp dir, and CLAUDE.md header. Python module stays tts_mcp (import name != package name is standard practice). README covers quick start, MCP tools, engines, features, config, Docker setup, and architecture.
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.
Quick Start
# Run directly from PyPI (no install needed)
uvx mcspeak
# Or install and run
pip install mcspeak
mcspeak
The server starts on http://0.0.0.0:8371 and exposes MCP tools over Streamable HTTP. Point any MCP client at it.
Add to Claude Code:
claude mcp add mcspeak -- uvx mcspeak
For Docker deployment (recommended -- handles PipeWire, Piper, and Orpheus GPU inference), 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
Docker is the recommended deployment. The compose file runs McSpeak alongside a GPU-accelerated llama-server for Orpheus.
Prerequisites:
- PipeWire running on the host (for
pw-playaudio output) - NVIDIA GPU + nvidia-container-toolkit (for Orpheus only)
- Kokoro ONNX model files in
./models/kokoro/
# Copy and edit .env
cp .env.example .env
# Set ORPHEUS_GGUF_PATH to your Orpheus GGUF model location
# Build and start
make up
# Follow logs
make logs
# Check health
make status
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