Ryan Malloy d054caa43c Rename project to mcspeak, add README
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.
2026-03-04 19:46:25 -07:00
2026-03-04 19:46:25 -07: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

# 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-play audio 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

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