Skip to main content
Glama

voice-service

A self-hosted Voice API (speech-to-text + text-to-speech) with real auth and a small admin website, plus a thin MCP stdio proxy that lets MCP clients (bots, tools) call the API as native tools.

Monorepo, two deployables:

voice-service/
├── voice-api/     # FastAPI server: Voice REST API + admin web (dockerized, runs on the VPS)
├── mcp-proxy/     # MCP stdio proxy (client shim; pip-installable / vendored into bot images)
├── docs/          # design spec
├── docker-compose.yml
└── README.md

Architecture

 browser (admin) ──session cookie──▶ Admin Web ┐
                                                ├─(same FastAPI app)─▶ Voice API ─▶ SQLite + audio cache + Whisper
 bots / tools ──stdio──▶ MCP proxy ──Bearer─────┘

One container runs the FastAPI app serving both the Voice API and the admin web against one SQLite DB. The MCP proxy is a separate artifact that runs locally next to each client and translates tool calls into authenticated REST calls.

Related MCP server: voice-audio-mcp

Voice API

All API endpoints except /audio, /healthz require Authorization: Bearer <api-key>.

Method

Path

Scope

Body / notes

POST

/transcribe

transcribe

{audio_base64, language?}{text, language} (Whisper, lazy-loaded, threadpool)

POST

/speak

speak

{text, lang?, engine?, voice?, style?}{url, cached, engine, voice, format}

GET

/voices

any key

Gemini voices + current default

GET

/audio/<sha>.ogg

none

serves a cached clip (opaque hashed name)

GET

/healthz

none

liveness + engine/model info

TTS engines. gTTS is the free default (one voice per language, no key). Gemini TTS is optional — enable it by pasting a Gemini key in the admin settings, then choose the engine mode: gtts, gemini_primary (Gemini first, gTTS fallback), or gemini_fallback (gTTS first, Gemini on failure). voice and style apply to Gemini only (30 prebuilt voices + natural-language style); gTTS ignores them. All output is transcoded by ffmpeg to Ogg/Opus so it renders as a Telegram voice bubble. /speak returns a URL, never base64.

Audio cache. Content-addressed: sha256(text + lang + engine + voice + style). A cache hit returns instantly with no engine/ffmpeg work. gTTS clips are keyed with empty voice/style so they are shared across requests.

Admin website

Session-cookie login (Starlette SessionMiddleware), CSRF on all POST forms, login rate-limit, forced password change on first login.

  • First boot seeds an admin user with a random password, written to /data/admin-password.txt (0600) and logged once; must_change=1.

  • /keys — create (full key shown once), enable/disable, delete API keys per bot.

  • /settings — Gemini key (write-only), engine mode, Whisper model, default voice + style, with a preview button.

  • /dashboard — per-key usage (requests, STT seconds, TTS chars) over 7 / 30 days.

Run locally

cd voice-service
python3.12 -m venv .venv && .venv/bin/pip install -r voice-api/requirements.txt

# ffmpeg must be on PATH (brew install ffmpeg / apt install ffmpeg)
mkdir -p /tmp/voice/{data,models,audio}
cd voice-api
DATA_DIR=/tmp/voice/data MODELS_DIR=/tmp/voice/models AUDIO_DIR=/tmp/voice/audio \
  WHISPER_MODEL=tiny PUBLIC_BASE_URL=http://localhost:8770 PORT=8770 HOST=127.0.0.1 \
  VOICE_COOKIE_SECURE=0 \
  ../.venv/bin/python -m voice_api

The first-boot admin password is printed to the log and written to /tmp/voice/data/admin-password.txt. Open http://localhost:8770/login, change the password, then create an API key under API keys (or use the CLI):

DATA_DIR=/tmp/voice/data ../.venv/bin/python -m voice_api.seed --name mybot --scopes transcribe,speak

Smoke test:

KEY=vsk_...   # the key you created
curl -s -X POST localhost:8770/speak -H "Authorization: Bearer $KEY" \
  -H 'Content-Type: application/json' -d '{"text":"xin chào","lang":"vi"}'

Run the tests:

.venv/bin/python -m pytest voice-api/tests -v

MCP proxy

pip install ./mcp-proxy        # or vendor voice_mcp_proxy into the bot image

# register with a client (env carries the URL + per-bot key):
VOICE_API_URL=https://voice.veasy.vn VOICE_API_KEY=vsk_... \
  claude mcp add --transport stdio voice -- python -m voice_mcp_proxy

Tools: transcribe, speak(text, lang?, engine?, voice?, style?), list_voices, voice_info.

Deploy (VPS behind a reverse proxy)

cp .env.example .env      # set PUBLIC_BASE_URL=https://voice.veasy.vn, WHISPER_MODEL, etc.
docker compose up -d --build
docker compose logs voice-api | grep 'First-boot admin'   # grab the password once

Point a reverse proxy (nginx/Caddy/Traefik) with HTTPS at 127.0.0.1:8770. Keep VOICE_COOKIE_SECURE=1 in production. Gemini key + engine mode are configured in the admin web (stored in the DB), not in env.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

View all related MCP servers

Related MCP Connectors

  • AI-manageable audio CDN: upload, transcode, normalize, stream & deliver audio, plus grounded docs.

  • Hosted pay-per-use TTS: 54 neural voices, 9 languages incl. Brazilian Portuguese. $10 free credits.

  • Voice and chat for AI agents — Discord, Teams, Meet, Slack, Zoom, Telegram, WhatsApp, NC Talk, SIP

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/trongnguyenbinh/voice-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server