chatterbox-tts-cli/mcp_adapter.py
dschlueter fe74b84360 Audio-Download-Endpunkt GET /audio/{job_id} hinzufügen
- SpeakRequest: keep_audio=true speichert WAV in ~/.cache/chatterbox-tts/
- SpeakJob: audio_path-Feld für gespeicherte WAV-Datei
- GET /audio/{job_id}: liefert WAV als FileResponse, löscht Datei danach
- mcp_adapter: keep_audio-Parameter in speak() weitergereicht
- Docstring: neuen Endpunkt dokumentiert

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-03 21:09:06 +02:00

164 lines
5.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env python3
"""
Chatterbox TTS MCP-Adapter
Setzt einen laufenden tts_service.py voraus (Standard: http://127.0.0.1:9999).
Start (streamable-http, Port 8001 für beliebige MCP-Clients):
python mcp_adapter.py
Start (stdio für Claude Code / Claude Desktop):
python mcp_adapter.py --stdio
Claude Code (bereits konfiguriert via `claude mcp add --scope user`):
claude mcp add --scope user chatterbox-tts \
/home/dschlueter/miniforge3/envs/chatterbox/bin/python \
/home/dschlueter/chatterbox-tts-cli/mcp_adapter.py --stdio
Umgebungsvariable TTS_URL überschreibt die Service-Adresse:
TTS_URL=http://192.168.1.10:9999 python mcp_adapter.py --stdio
"""
from __future__ import annotations
import argparse
import os
import httpx
from mcp.server.fastmcp import FastMCP
TTS_URL = os.environ.get("TTS_URL", "http://127.0.0.1:9999").rstrip("/")
mcp = FastMCP(
"Chatterbox TTS",
instructions=(
"Lokaler Text-to-Speech-Service. Liest Texte auf Deutsch und 20+ weiteren "
"Sprachen vor. Unterstützt Voice Cloning, Geschwindigkeitsanpassung und "
"Aussprache-Wörterbücher."
),
port=8001,
)
# ---------------------------------------------------------------------------
# Tools
# ---------------------------------------------------------------------------
def _raise_for_status(r: httpx.Response) -> None:
"""Wirft einen klaren Fehler bei HTTP-4xx/5xx statt rohem httpx-Fehler."""
try:
r.raise_for_status()
except httpx.HTTPStatusError as exc:
raise RuntimeError(
f"TTS-Service antwortet mit HTTP {exc.response.status_code}: {exc.response.text[:200]}"
) from exc
@mcp.tool()
async def speak(
text: str,
lang: str = "de",
voice: str | None = None,
interrupt: bool = False,
speed: float = 1.0,
session_id: str | None = None,
keep_audio: bool = False,
) -> dict:
"""Text als Sprache ausgeben.
Reiht den Text in die Ausgabewarteschlange ein. Das Modell generiert
satzweise und beginnt sofort mit der Wiedergabe.
Args:
text: Auszugebender Text (max. 4000 Zeichen).
lang: Sprachcode, z. B. 'de', 'en', 'fr'. Standard: 'de'.
voice: Optionaler Pfad zu einer WAV-Referenzdatei (1030s) für
Voice Cloning.
interrupt: True = laufende Ausgabe sofort unterbrechen und diesen
Text vorgezogen abspielen.
speed: Wiedergabegeschwindigkeit (0.52.0). Pitch bleibt gleich.
session_id: Optionale Session-ID für Job-Tracking im TTS-Service.
keep_audio: True = WAV-Datei nach der Synthese im Cache behalten;
abrufbar via GET /audio/{job_id}.
"""
async with httpx.AsyncClient(timeout=30) as client:
r = await client.post(f"{TTS_URL}/speak", json={
"text": text,
"lang": lang,
"voice": voice,
"interrupt": interrupt,
"speed": speed,
"session_id": session_id,
"keep_audio": keep_audio,
})
_raise_for_status(r)
return r.json()
@mcp.tool()
async def stop() -> dict:
"""Laufende Sprachausgabe sofort stoppen und Warteschlange leeren."""
async with httpx.AsyncClient(timeout=10) as client:
r = await client.post(f"{TTS_URL}/stop")
_raise_for_status(r)
return r.json()
@mcp.tool()
async def pause() -> dict:
"""Laufende Sprachausgabe pausieren (ohne Datenverlust). Mit resume() fortsetzen."""
async with httpx.AsyncClient(timeout=10) as client:
r = await client.post(f"{TTS_URL}/pause")
_raise_for_status(r)
return r.json()
@mcp.tool()
async def resume() -> dict:
"""Pausierte Sprachausgabe fortsetzen."""
async with httpx.AsyncClient(timeout=10) as client:
r = await client.post(f"{TTS_URL}/resume")
_raise_for_status(r)
return r.json()
@mcp.tool()
async def get_status() -> dict:
"""Aktuellen Ausgabe-Status abfragen.
Gibt zurück: laufender Job (mit Chunk-Fortschritt), Queue-Länge und
die letzten abgeschlossenen Jobs.
"""
async with httpx.AsyncClient(timeout=10) as client:
r = await client.get(f"{TTS_URL}/status")
_raise_for_status(r)
return r.json()
@mcp.tool()
async def list_voices() -> dict:
"""Unterstützte Sprachen und Hinweise zu Voice Cloning abfragen."""
async with httpx.AsyncClient(timeout=10) as client:
r = await client.get(f"{TTS_URL}/voices")
_raise_for_status(r)
return r.json()
# ---------------------------------------------------------------------------
# Einstiegspunkt
# ---------------------------------------------------------------------------
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Chatterbox TTS MCP-Adapter")
parser.add_argument(
"--stdio", action="store_true",
help="stdio-Transport (für Claude Code / Claude Desktop)",
)
parser.add_argument("--host", default="127.0.0.1",
help="Host für streamable-http (Standard: 127.0.0.1)")
parser.add_argument("--port", type=int, default=8001,
help="Port für streamable-http (Standard: 8001)")
args = parser.parse_args()
if args.stdio:
mcp.run() # stdio ist der Default-Transport
else:
mcp.run(transport="streamable-http", host=args.host, port=args.port)