Build a speaking agent

Two ways to give an agent a voice: let it call the MCP voice_speak tool (simplest — Claude does the talking), or consume the streaming API directly for real-time playback.

1. MCP: the agent speaks by calling a tool

Connect the Era Voice MCP server (full setup on the MCP page):

claude mcp add --transport http era-voice https://voice.erasmuslabs.ai/mcp \
  --header "Authorization: Bearer ev_your_key"

The agent then lists voices and speaks; the audio is stored and fetched with the same API key:

# Inside a Claude session with the era-voice MCP server connected:
# 1. list what you can sound like
voice_list {}

# 2. speak — the tool stores the audio and returns an authed download URL
voice_speak {"text": "Deploy finished. All twelve checks green.", "voice_id": "VOICE_ID"}
# -> Generated 3.2s of wav audio from 41 characters.
#    Download (requires your same Bearer API key): GET .../v1/audio/<generation_id>

2. Streaming API: real-time playback

For live agents (assistants, NPCs, call flows), consume POST /v1/tts/stream and play chunks as they land:

"""Minimal speaking agent: stream Era Voice TTS and play chunks as they arrive."""
import base64, json, subprocess, requests

API = "https://voice.erasmuslabs.ai"
KEY = "ev_your_key"

def speak(text: str, voice_id: str, language: str = "english"):
    resp = requests.post(
        f"{API}/v1/tts/stream",
        headers={"Authorization": f"Bearer {KEY}"},
        json={"text": text, "voice_id": voice_id, "language": language},
        stream=True,
    )
    resp.raise_for_status()
    for line in resp.iter_lines(decode_unicode=True):
        if not line or not line.startswith("data: "):
            continue  # SSE frames are separated by blank lines
        event = json.loads(line[len("data: "):])
        if event.get("error"):
            raise RuntimeError(event["error"])
        if event.get("done"):
            print(f"done: {event['total_duration_ms']}ms from {event['chars']} chars")
            return
        audio = base64.b64decode(event["audio_base64"])
        # First audio lands after ~one sentence; play each chunk back-to-back.
        subprocess.run(["afplay", "-"], input=audio)  # macOS; use aplay/ffplay elsewhere

speak("Hello! I am your voice agent. Ask me anything.", voice_id="VOICE_ID")

Latency notes

Measured September 11, 2026 through the public WebSocket endpoint using a 67-character, two-sentence English sample and the already-loaded Qwen3-TTS 1.7B Base model on NVIDIA GB10: 30 fresh TLS connections reached first audio in 2.50 seconds at p50 and 2.78 seconds at p95; 30 sequential connections with attempted TLS resumption measured 2.44 and 2.73 seconds (29 of those sessions resumed). Connect-to-ready p95 was 169 ms and 159 ms respectively. Receiving the full response took about 1.08 times the output audio duration at the median. These are observed results for this sample and load, not a latency guarantee. Model startup was not measured; longer opening sentences, concurrency, languages and network conditions can change latency.