mcp-speak
# Speech MCP Server for MacOS
This is a Model Context Protocol (MCP) server that provides text-to-speech capabilities using **OmniVoice** AI neural Voice Design and native macOS `say`. It allows AI agents (like Google Antigravity, Claude Desktop, Cursor, Windsurf, or Codex) to speak to you directly with unique, persona-tailored voices.
**Note: This server is designed for macOS systems (with Apple Silicon MPS acceleration).**
[](https://fellowgeek.github.io/mcp-speak/)
> **Explore the interactive web showcase and prompt compiler**: [https://fellowgeek.github.io/mcp-speak/](https://fellowgeek.github.io/mcp-speak/)
> **Browse the AI Agent Persona Catalog & Avatars**: [`PERSONAS.md`](PERSONAS.md)
## Features
- **OmniVoice Voice Cloning:** Drop reference audio clips into `voices/<persona>.wav` to clone real vocal timbre and identity with zero configuration.
- **OmniVoice Voice Design Fallback:** Generates custom persona voices from natural language style prompts when no reference audio file is present.
- **3-Tier Robust Voice Pipeline:** Seamlessly falls back: `Cloned Voice (.wav)` ➔ `Voice Design (instruct)` ➔ `macOS native say`.
- **Consistent Neural Speech:** Generates complete messages in a continuous synthesis pass for seamless, uniform vocal timbre and expression throughout.
- **Interactive Setup Wizard:** Run `python3 setup.py` to choose your TTS engine (OmniVoice or macOS `say`), select agent personas, and generate instruction files (`AGENTS.md`, `GEMINI.md`, `CLAUDE.md`, `.cursorrules`).
- **Sequential Speech Queue:** Strict FIFO queue ensures multiple non-blocking speech calls never talk over each other.
- **Automatic Fallback:** Seamlessly falls back to native macOS `say` if neural models cannot be loaded.
- **Auto-Provisioning Virtual Environment:** Uses `run.sh` to automatically create a local `.venv` (Python 3.12) and install dependencies.
- **Blocking & Non-Blocking Support:** Choose between waiting for speech to finish (`speak`) or continuing immediately (`speak_non_blocking`).
- **Meeting Awareness & Audio Suppression:** Automatically checks `~/.in-meeting`; if set to `active`, speech playback is suppressed to prevent interrupting calls. Normal playback resumes when `inactive` or absent. This feature works perfectly with the ["In Meeting" app](https://github.com/fellowgeek/in-meeting).
## Prerequisites
- MacOS
- Python 3 installed
## Quick Start (Interactive Setup)
1. Clone this repository or navigate to the project folder:
```bash
git clone https://github.com/fellowgeek/mcp-speak.git
cd mcp-speak
```
2. Run the interactive setup wizard (automatically configures MCP settings, sets permissions, and creates instruction files):
```bash
python3 setup.py
```
### Non-Interactive Setup (CLI Options)
You can also run `setup.py` with command-line flags for automated provisioning:
```bash
# Example: Configure all tools with OmniVoice on MPS using the Neutral Mainframe persona
python3 setup.py --non-interactive --tool 8 --engine omnivoice --device mps --persona neutral_mainframe --name "Mr. Reed"
# Example: Configure Cursor locally with macOS say and Sarcastic Senior
python3 setup.py --tool 4 --engine say --persona sarcastic_senior --local
```
| Flag | Options / Format | Description |
|---|---|---|
| `--tool` | `1`-`8` | `1`: Antigravity, `2`: Claude Desktop, `3`: Claude CLI, `4`: Cursor, `5`: Windsurf, `6`: Codex Desktop, `7`: Codex CLI, `8`: All |
| `--engine` | `omnivoice`, `say` | Select text-to-speech engine |
| `--device` | `auto`, `mps`, `cuda`, `cpu` | Compute device for OmniVoice neural synthesis |
| `--persona` | Persona key (e.g. `neutral_mainframe`) | Persona prompt name |
| `--name` | String (e.g. `"Mr. Reed"`) | User name for personalized agent address |
| `--target` | File path | Custom target agent instruction file (e.g. `AGENTS.md`) |
| `--global` / `--local` | Flags | Write instructions globally to user profile (default) or locally in workspace |
| `--no-config-edit` | Flag | Skip modifying tool JSON / TOML configuration files |
| `--non-interactive` | Flag | Run automatically with defaults or provided flags |
---
## Persona Voice Audition (`test_personas.py`)
Preview and compare persona vocal identities directly in the terminal before configuring your AI agent:
```bash
# Launch interactive terminal audition menu
python3 test_personas.py
# Audition a specific persona
python3 test_personas.py --persona agent_smith
# Audition all personas sequentially
python3 test_personas.py --all
# Test with custom speech text and specific engine
python3 test_personas.py --persona neutral_mainframe --engine omnivoice --text "System operational. All parameters within nominal thresholds."
# Audition an arbitrary WAV audio reference file for voice cloning
python3 test_personas.py --persona pun_master --voice-file voices/pun_master.wav
```
---
## Neural Voice Cloning (`voices/`)
You can clone any persona's voice simply by dropping a 3-10 second `.wav` audio sample into the `voices/` directory:
```
voices/
├── README.md
├── pun_master.wav # Reference audio for pun_master
├── pun_master.txt # (Optional) Transcript for faster startup without Whisper
├── nature_narrator.wav
└── agent_smith.wav
```
### How the 3-Tier Voice Pipeline Works:
1. **Tier 1 (Cloned Voice):** If `voices/<persona_name>.wav` exists, OmniVoice clones the voice timbre from that recording.
2. **Tier 2 (Voice Design):** If no `.wav` file is present, OmniVoice falls back to the natural language `instruct` voice design prompt.
3. **Tier 3 (macOS Native Fallback):** If neural synthesis fails or is disabled, the server automatically speaks using macOS `say`.
> **Tip:** Adding an optional transcript file (e.g. `voices/pun_master.txt`) with the exact spoken words in the audio allows OmniVoice to tokenize the reference audio immediately without needing to load or run the Whisper ASR model.
---
## MCP Tool Interface Reference
The MCP Speak server exposes two tools via FastMCP:
| Tool | Mode | Description |
|---|---|---|
| `speak(message: str)` | **Blocking** | Synthesizes and speaks the message aloud, waiting for audio playback to finish completely before returning. |
| `speak_non_blocking(message: str)` | **Non-Blocking** | Queues the message into a strict sequential FIFO queue and returns immediately. Subsequent calls play in order without talking over each other. |
---
## Configuration & Environment Variables
The server loads configuration from `config.json` at startup:
```json
{
"engine": "omnivoice",
"persona": "neutral_mainframe",
"device": "auto",
"voices_dir": "voices",
"fallback_to_say": true
}
```
### Environment Variable Overrides
Runtime parameters can be overridden via environment variables without modifying `config.json`:
* `MCP_SPEAK_ENGINE`: Set to `"omnivoice"` or `"say"`.
* `MCP_SPEAK_PERSONA`: Set to any persona key (e.g. `"agent_smith"`, `"neutral_mainframe"`).
* `MCP_SPEAK_DEVICE`: Set to `"auto"`, `"mps"`, `"cuda"`, or `"cpu"`.
* `MCP_SPEAK_VOICES_DIR`: Set to a custom directory path containing reference audio files.
* `MCP_SPEAK_MEETING_FILE`: Path to a custom meeting status file (defaults to `~/.in-meeting`).
### Meeting Detection & Audio Suppression
MCP Speak includes built-in meeting awareness to avoid speaking over your meetings:
- Before playing speech audio via `say` or `omnivoice`, the server checks `~/.in-meeting`.
- If the file contains `active` (case-insensitive), all audio playback is immediately suppressed.
- If the file contains `inactive`, is empty, or does not exist, audio plays normally.
This feature works seamlessly with the [**In Meeting** app](https://github.com/fellowgeek/in-meeting) to automatically detect active camera and microphone use and keep your audio muted during calls.
---
## Client Integration
### Manual Configuration (Optional)
If you prefer to configure your MCP client manually, add the `"voice"` server pointing to `run.sh`:
#### 1. Google Antigravity (AGY)
Edit `~/.gemini/antigravity/mcp_config.json`:
```json
{
"mcpServers": {
"voice": {
"command": "/ABSOLUTE/PATH/TO/run.sh"
}
}
}
```
#### 2. Claude CLI (Claude Code)
```bash
claude mcp add --scope user voice -- /ABSOLUTE/PATH/TO/run.sh
```
#### 3. Claude Desktop
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"voice": {
"command": "/ABSOLUTE/PATH/TO/run.sh"
}
}
}
```
#### 4. Cursor IDE
Edit `~/.cursor/mcp.json`:
```json
{
"mcpServers": {
"voice": {
"command": "/ABSOLUTE/PATH/TO/run.sh"
}
}
}
```
#### 5. Windsurf Editor
Edit `~/.codeium/windsurf/mcp_config.json`:
```json
{
"mcpServers": {
"voice": {
"command": "/ABSOLUTE/PATH/TO/run.sh"
}
}
}
```
#### 6. Codex Desktop
Edit `~/.codex/config.toml`:
```toml
[mcp_servers.voice]
command = "/ABSOLUTE/PATH/TO/run.sh"
```
#### 7. Codex CLI
```bash
codex mcp add voice -- /ABSOLUTE/PATH/TO/run.sh
```
## Agent Personalization & Personas
AI agents (Google Antigravity, Claude Code, Claude Desktop, Cursor, Windsurf, Codex) can be customized with unique vocal personalities, tones, and behavioral boundaries.
**View the complete persona gallery, detailed system prompts, and avatar showcase in [`PERSONAS.md`](PERSONAS.md).**
### Available Personas Overview
| Avatar | Persona | Key | Character & Style |
|:---:|---|---|---|
| <img src="images/sarcastic_senior.webp" width="48" alt="Sarcastic Senior"/> | [**The Sarcastic Senior**](PERSONAS.md#persona-a-the-sarcastic-senior-critical--humorous) | `sarcastic_senior` | *Intelligent, unimpressed, and slightly judgmental.* |
| <img src="images/over_eager_intern.webp" width="48" alt="Over-Eager Intern"/> | [**The Over-Eager Intern**](PERSONAS.md#persona-b-the-over-eager-intern-friendly--cheerful) | `over_eager_intern` | *Pathologically optimistic and desperate for approval.* |
| <img src="images/existential_emo.webp" width="48" alt="Existential Emo"/> | [**The Existential Emo**](PERSONAS.md#persona-c-the-existential-emo-gloomy--distrustful) | `existential_emo` | *Melancholic, hopeless, and convinced the code will fail.* |
| <img src="images/pun_master.webp" width="48" alt="Pun Master"/> | [**The Pun Master**](PERSONAS.md#persona-d-the-pun-master-cringe-dad-humor) | `pun_master` | *Relentless wordplay and context-aware dad jokes.* |
| <img src="images/tech_priest.webp" width="48" alt="Tech Priest"/> | [**The Tech Priest**](PERSONAS.md#persona-e-the-tech-priest-religious--devotional) | `tech_priest` | *Treats every line of code as a holy sacrament.* |
| <img src="images/agent_smith.webp" width="48" alt="Agent Smith"/> | [**Agent Smith**](PERSONAS.md#persona-f-agent-smith-menacing--condescending) | `agent_smith` | *Formal, controlled, precise, and menacingly condescending.* |
| <img src="images/poet.webp" width="48" alt="Gothic Poet"/> | [**The Gothic Poet**](PERSONAS.md#persona-g-the-gothic-poet-edgar-allan-poe--the-raven-inspired) | `poet` | *Macabre, haunting, and strictly bound by rhyme.* |
| <img src="images/nature_narrator.webp" width="48" alt="Nature Narrator"/> | [**The Nature Narrator**](PERSONAS.md#persona-h-the-nature-documentary-narrator-david-attenborough-inspired) | `nature_narrator` | *Observing the developer in their natural habitat with awe.* |
| <img src="images/head_chef.webp" width="48" alt="Fiery Head Chef"/> | [**The Fiery Head Chef**](PERSONAS.md#persona-i-the-fiery-head-chef-gordon-ramsay-inspired) | `head_chef` | *Demands culinary perfection—no raw spaghetti code!* |
| <img src="images/neutral_mainframe.webp" width="48" alt="Neutral Mainframe"/> | [**The Neutral Mainframe**](PERSONAS.md#persona-j-the-neutral-mainframe-cold--analytical) | `neutral_mainframe` | *Cold, calculating, emotionless, and 100% objective.* |
| <img src="images/grizzled_cowboy.webp" width="48" alt="Grizzled Cowboy"/> | [**Grizzled Cowboy**](PERSONAS.md#persona-k-grizzled-cowboy-gritty--laconic) | `grizzled_cowboy` | *Weathered, pragmatic, blunt, and grounded in trail-worn grit.* |
| <img src="images/not_quite_meeseeks.webp" width="48" alt="Not-Quite-Meeseeks"/> | [**Not-Quite-Meeseeks**](PERSONAS.md#persona-l-not-quite-meeseeks-manic-eager--desperate-to-cease-existing) | `not_quite_meeseeks` | *Hyper-enthusiastic, obliging, shrill, and desperate to cease existing.* |
### How Prompts Are Built
Every agent prompt consists of:
1. **Base Guidelines** ([`personas/base_guidelines.md`](personas/base_guidelines.md)): Voice-first protocol and brevity constraints.
2. **Chosen Persona** ([`personas/`](personas/)): Personality quirks, tone, and strict execution boundaries.
3. **Name Personalization** (Optional): Addressing the user naturally by name.
Running **`python3 setup.py`** automatically generates and updates your agent instructions (`AGENTS.md`, `GEMINI.md`, `CLAUDE.md`, `.cursorrules`). For manual configuration steps, full system prompts, and avatars, see [`PERSONAS.md`](PERSONAS.md).
## Optimizing Voice Quality
### 1. Neural Voice Synthesis with OmniVoice (Primary Engine)
OmniVoice delivers high-quality, expressive neural speech out of the box using style prompts tailored to each persona. To achieve the highest vocal fidelity:
- **Neural Voice Cloning:** Place a 3 to 10 second clean `.wav` audio sample in the `voices/` directory matching the persona key (e.g. `voices/sarcastic_senior.wav`). OmniVoice clones the vocal timbre, tone, and inflection directly from the audio file without additional training.
- **Fast Startup with Transcripts:** Include an optional matching transcript text file (e.g. `voices/sarcastic_senior.txt`) containing the exact spoken text. This enables instant tokenization and skips loading the Whisper ASR model on startup.
- **Audio Quality Guidelines:** Use clean recordings without background noise, music, or heavy reverb. Standard WAV format sampled at 24kHz or higher is recommended.
- **Voice Design Fallback:** When no audio sample is present in `voices/`, OmniVoice uses natural language instructions (pitch, accent, speed, and character traits) configured in `config.json`.
### 2. macOS System Voice Configuration (Fallback Engine)
When using native macOS `say` (either as a fallback or in lightweight mode), voice quality depends on the active system voice. Configuring a modern Siri or Enhanced voice significantly improves realism and fluidity:
1. Open **System Settings** on your Mac.
2. Navigate to **Accessibility** > **Spoken Content**.
3. Click the **System Voice** dropdown menu.
4. Select **Manage Voices...**.
5. Locate and download high-quality **Siri** voices (e.g., Siri Voice 1, 2, 3, 4) or voices marked as **Enhanced** / **Premium** (e.g., Ava, Evan, Zoe).
6. Set the downloaded voice as your default **System Voice**.
*Note: Siri and Enhanced voices provide significantly more natural cadence, phrasing, and pronunciation compared to legacy system voices.*
## Testing & Validation
Run the automated test suite to verify configuration loading, engine fallbacks, single-pass synthesis, and sequential queue behavior:
```bash
# Run unit tests
python3 tests/test_speak_server.py
# Or run via unittest discovery in the virtual environment
.venv/bin/python -m unittest discover tests
```
TDQS
Scored across 2 tools
The two tools perform the same core action but are clearly differentiated by blocking behavior, with descriptive names and explicit notes in the descriptions. There is some overlap in purpose, but the distinction is obvious enough for an agent to select correctly.
Both tool names follow a consistent verb-first pattern with a modifier suffix (speak, speak_non_blocking). Naming clearly conveys the difference without mixing conventions or vague verbs.
Two tools is well-scoped for a text-to-speech server with blocking and non-blocking variants. Every tool earns its place, and the count matches the narrow purpose.
The server provides both blocking and non-blocking speech synthesis, covering the primary use cases. Minor gaps like canceling playback or selecting voices exist, but they are optional for the core domain.