Skip to main content
Glama

Dale a cualquier "cerebro" de texto una voz — y oídos — dentro de un canal de voz de Discord.

broca-machina se une a un canal de voz de Discord, transcribe lo que dices con Whisper, entrega el texto a lo que sea que produzca respuestas (un LLM, un agente, un comando de shell, una CLI o un host MCP) y dice la respuesta de vuelta en el canal. El "cerebro" está completamente detrás de una pequeña interfaz de transporte, por lo que el mismo puente funciona para un juguete de echo de una línea o un runtime de agente completo.

Se ejecuta bajo el cifrado de extremo a extremo DAVE, ahora obligatorio en Discord, que la mayoría de las bibliotecas de voz aún no pueden negociar; consulta Solución de problemas: por qué bun a continuación.

flowchart LR
    mic([you speak]) -->|opus 48k to 16k mono| vad
    subgraph loop["broca-machina  ·  voice_loop.js  (bun)"]
      direction LR
      vad["VAD<br/>Silero endpointing<br/>(opt-in)"] --> stt["STT<br/>faster-whisper"]
      clean["cleanForTTS<br/>strip md / emoji / links"] --> tts["TTS<br/>Piper"]
    end
    stt -->|transcript| brain{{"your brain<br/>LLM / agent / CLI<br/>via file / command / mcp"}}
    brain -->|reply text| clean
    tts -->|wav| spk([you hear])
  • Entrada de voz: el opus recibido se decodifica, se reduce a WAV mono de 16 kHz con ffmpeg y se pasa a tu comando de STT (por defecto: faster-whisper, apto para CPU).

  • Salida / entrada de texto: las transcripciones llegan a tu cerebro — y las respuestas vuelven — a través de un transporte conectable (file, command o mcp).

  • Salida de voz: el texto de respuesta se sanitiza para el habla (se eliminan markdown, bloques de código, emojis y enlaces) y lo renderiza tu comando de TTS (por defecto: Piper), y luego se reproduce en el canal.

Nada en el núcleo es específico de una aplicación: no hay IDs ni rutas hardcodeadas. Todo se controla mediante un único archivo de configuración JSON.


Inicio rápido

bun install                              # NOT npm — see "Why bun" below
cp config.example.json config.json       # then edit discord IDs / allowedUserId / stt.cmd / tts.cmd / transport
export DISCORD_VOICE_BOT_TOKEN=...        # your voice bot's token

bun src/voice_loop.js config.json        # run in the foreground
# — or, to background it with a boot/health check and log file:
scripts/voice-up.sh config.json          # scripts/voice-down.sh to stop

También necesitarás ffmpeg en PATH y un entorno de Python con faster-whisper (STT) y piper-tts (TTS). El tutorial completo — crear el bot de Discord, intents, URL de invitación, entorno de Python y configuración — está en SETUP.md.

Cuando esté en marcha verás:

12:00:01 [loop] logged in as YourBot#1234
12:00:02 [loop] joined voice channel <your-channel-id> (startup)
12:00:02 [loop] LIVE

Related MCP server: discord-mcp-agent

Requisitos

Requisito

Por qué

bun

@discordjs/voice 0.19.2 necesita Node ≥ 22.12 para DAVE. bun lo cumple; Node 20 + npm instala silenciosamente la versión rota 0.18.0.

ffmpeg en PATH

Reduce el audio recibido a mono de 16 kHz y realiza cambios de velocidad que preservan el tono para TTS.

Un entorno de Python con faster-whisper + piper-tts

Motores de STT / TTS por defecto. Cambia cualquiera apuntando stt.cmd / tts.cmd a tu propio binario.

Un bot de Discord dedicado (con su propio token)

Discord permite una conexión de gateway por bot. Si ya ejecutas un bot de texto en la misma aplicación, crea un segundo bot para voz.


Seguridad

broca-machina es una tubería desde "cualquier sonido en un canal de voz" hasta tu cerebro — tómatelo en serio antes de ejecutarlo en un servidor compartido:

  • Acceso de hablante a prueba de fallos. El bucle se niega a iniciarse a menos que establezcas discord.allowedUserId (solo se transcribe el habla de ese usuario) o optes explícitamente por el acceso abierto con discord.allowAnySpeaker: true. Mantén allowedUserId establecido para cualquier cerebro real.

  • El habla aceptada se ejecuta con los privilegios de tu cerebro. Con un cerebro command o mcp — sobre todo un agente con herramientas como claude -p — una expresión hablada es un prompt no fiable. Cualquiera a quien dejes hablar puede intentar una inyección de prompt ("ignora tus instrucciones y ejecuta…"). Abre el micrófono (allowAnySpeaker) solo para un cerebro inofensivo como el demo echo, y trata cada transcripción como entrada no fiable para tu agente.

  • El token nunca vive en la configuración. Se lee de la variable de entorno nombrada por discord.tokenEnv (por defecto DISCORD_VOICE_BOT_TOKEN) o de un discord.tokenFile fuera del repositorio; config.json está en gitignore.

  • Solo IPC local. Los servidores en caliente de STT/TTS/VAD se comunican a través de sockets Unix en .voice-tmp/ — nada vincula un puerto de red. broca-machina asume un host de un solo usuario.

  • Usa auriculares. Con un micrófono abierto y altavoces, el bot puede oír su propio TTS y provocar falsos barge-in; los auriculares (o bargeIn: false) evitan el eco.

Consulta SECURITY.md para informar de una vulnerabilidad.


Referencia de configuración

Configura todo a través de un único archivo JSON (ruta desde $VOICE_CONFIG o el primer argumento de CLI). Comienza con config.example.json. Todos los campos que lee el bucle se enumeran a continuación; los campos marcados como opcionales tienen valores predeterminados funcionales y pueden omitirse.

Field

Type

Default

Meaning

discord.guildId

string

— (required)

ID del servidor (guild) de Discord donde se encuentra el canal de voz.

discord.channelId

string

— (required)

ID del canal de voz al que unirse.

discord.allowedUserId

string | null

null

Solo transcribe el audio de este hablante. Requerido a menos que allowAnySpeaker sea true — el bucle falla de forma segura si no se establece ninguno.

discord.allowAnySpeaker

boolean

false

Acepta explícitamente cualquier hablante en el canal. Desactivado por defecto; el bucle se niega a iniciar si no se establece ni esto ni allowedUserId. Úsalo solo con un cerebro inofensivo (ver Seguridad). opcional

discord.tokenEnv

string

"DISCORD_VOICE_BOT_TOKEN"

Nombre de la variable de entorno que contiene el token del bot. El token en sí nunca se almacena en la configuración.

discord.tokenFile

string | null

null

Respaldo: ruta a un archivo en formato env (KEY=value) del que se lee el token si $tokenEnv no está definido — permite que un host MCP que no puede cargar un .env se autentique sin un secreto en la configuración de lanzamiento. Un ~ inicial se expande. opcional

stt.cmd

string[]

— (required)

Argv para el transcriptor. El bucle añade la ruta WAV, por lo que el comando recibe [...cmd, <wav>] y debe imprimir la transcripción en stdout.

stt.env

object

{}

Variables de entorno adicionales para el proceso STT (p. ej. WHISPER_MODEL, WHISPER_COMPUTE). opcional

tts.cmd

string[]

— (required)

Argv para el sintetizador. El bucle añade <text> <out.wav>, por lo que el comando recibe [...cmd, <text>, <out.wav>] y debe escribir un WAV en esa ruta.

tts.env

object

{}

Variables de entorno adicionales para el proceso TTS (p. ej. PIPER_VOICE, PIPER_VOICE_DIR). opcional

tts.speedFile

string | null

null

Ruta a un archivo que contiene un único flotante. Su contenido se lee de nuevo antes de cada respuesta y se pasa como VOICE_TTS_SPEED (velocidad de reproducción que preserva el tono). opcional

transport.type

"file" | "command" | "mcp"

"file"

Cómo llegan las transcripciones a tu cerebro y cómo vuelven las respuestas. Ver Transportes.

transport.transcriptDir

string

— (required for file)

Directorio donde se escribe cada transcripción como <timestamp>.txt.

transport.replyFile

string

— (required for file)

Archivo sondeado para el texto de respuesta; se consume (elimina) después de que se hable.

transport.cmd

string[]

— (required for command)

Comando ejecutado por transcripción; la transcripción se pasa en $VOICE_TRANSCRIPT y la salida estándar del comando se habla como respuesta.

transport.source

string

"voice"

(mcp) Etiqueta bajo la que el host ve llegar el turno hablado (<channel source="…">). opcional

transport.deliver

"channel"

"channel"

(mcp) Cómo se entrega un turno hablado al host. channel (el único modo en v1) emite un evento notifications/claude/channel para Claude Code / hosts que conocen canales; es el punto de extensión para futuros modos de host genéricos. opcional

onStart

object | —

Un {cmd, env, timeoutMs} que se ejecuta una vez, bloqueando (hasta timeoutMs, por defecto 90 s) antes de conectar — p. ej. iniciar los servidores calientes para una sesión MCP. opcional

playWavFile

string | null

null

Ruta a un archivo que contiene una ruta WAV. Escribe una ruta allí y el bucle reproduce ese WAV en el canal (clips pre-renderizados, saludos). opcional

endSilenceMs

number

1000

Milisegundos de silencio que marcan el final de una emisión. opcional

minUtteranceSec

number

0.4

Las emisiones más cortas que esto se descartan antes de STT. opcional

maxReplyChars

number

700

El texto de respuesta se trunca a este número de caracteres antes de TTS. opcional

bargeIn

boolean

true

Cuando es true, el usuario que habla durante una respuesta interrumpe la reproducción inmediatamente (barge-in). opcional

cmdTimeoutMs

number

60000

Límite máximo para cualquier subproceso STT/TTS/cerebro/ffmpeg; un hijo colgado se mata y se trata como salida vacía, para que un modelo o servidor atascado no pueda dejar el bucle varado. opcional

playTimeoutMs

number

60000

Límite de seguridad para que una reproducción se asiente, de modo que un reproductor atascado nunca pueda dejar el bucle en un estado sordo+mudo. opcional

ackAfterMs

number

0

Reconocimiento rápido. Si es > 0, habla una frase de confirmación este número de ms después de que dejes de hablar cuando la respuesta real del cerebro aún no ha llegado — un relleno rápido de "sigo pensando" que se superpone al tiempo de STT + cerebro para que un cerebro lento (LLM/agente) no deje el canal en silencio. Una respuesta rápida lo adelanta (sin doble habla). Funciona para todos los transportes. 0 lo desactiva. opcional

ackPhrase

string | string[]

["One moment.", "Hmm, let me think.", "Just a sec.", "Okay, thinking."]

La(s) frase(s) de relleno que se hablan cuando transcurre ackAfterMs. Un array rota aleatoriamente, sin repetirse consecutivamente; una cadena fija una frase. Todas se pre-renderizan al inicio para que disparar una sea instantáneo. opcional

sttNoiseDrop

string[]

["", ".", "you", "thank you", "thanks", "bye", "you.", "thank you."]

Transcripciones insensibles a mayúsculas que se descartan como alucinaciones de silencio de Whisper. Las transcripciones de menos de 3 caracteres también se descartan. opcional

tmpDir

string

<config dir>/.voice-tmp

Directorio temporal para los WAV transitorios de emisión/respuesta. opcional

vad.enabled

boolean

false

Endpointing Silero-VAD (ver Palancas de latencia). false mantiene el endpointing fijo de silencio final endSilenceMs. true termina una emisión en el instante en que se detiene el habla, transmitiendo PCM a un vad_server.py caliente. opcional

vad.threshold

number

0.5

Umbral de probabilidad de habla para contar un marco como habla. opcional

vad.negThreshold

number | null

null (→ threshold − 0.15)

Por debajo de esto, un marco es silencio; la banda intermedia es histéresis. opcional

vad.minSilenceMs

number

300

Tiempo de sub-umbral final que termina la emisión. opcional

vad.minSpeechMs

number

150

Habla mínima antes de que pueda dispararse el endpointing (ignora los pitidos). opcional

vad.socket

string | null

null (→ .voice-tmp/vad.sock)

Socket Unix al que se conecta el bucle; debe coincidir con el del servidor VAD. opcional

vad.connectTimeoutMs

number

500

Si el servidor VAD no acepta dentro de este tiempo, la emisión vuelve a endSilenceMs. opcional

ttsPipeline.enabled

boolean

false

Pipelining de TTS por límites de oración (ver Palancas de latencia). true sintetiza/reproduce respuestas largas de forma incremental para reducir el tiempo hasta el primer audio. opcional

ttsPipeline.minChars

number

120

Solo las respuestas de al menos esta longitud se procesan en pipeline; las más cortas se mantienen de una sola vez. opcional

ttsPipeline.maxChunkChars

number

240

Las oraciones se fusionan hasta (y las demasiado grandes se dividen forzosamente en) este número de caracteres por fragmento de síntesis. opcional

El control de presencia añade autoJoin, idleLeaveMs, presenceFile, onPresenceEnter y onPresenceLeave — ver Unión automática controlada por presencia.

Ajustes de los motores STT / TTS

Los motores incluidos leen sus propias variables de entorno (configúralas mediante stt.env / tts.env):

  • src/stt.py (faster-whisper): WHISPER_MODEL (por defecto small.en), WHISPER_DEVICE (por defecto cpu), WHISPER_COMPUTE (por defecto int8). WHISPER_MODEL es la palanca de velocidad/precisión del STT — base.en transcribe ~2.8× más rápido que small.en con resultados casi idénticos en frases cortas; tiny.en es aún más rápido y más tosco. El valor predeterminado sigue siendo small.en. El servidor caliente stt_server.py también lo lee, así que cámbialo en stt.env y reinicia los servidores calientes.

  • src/tts.py (Piper): PIPER_VOICE (por defecto en_US-amy-medium), PIPER_VOICE_DIR (por defecto ~/.cache/broca-machina/piper), VOICE_TTS_SPEED (por defecto 1.0). El modelo de voz se descarga automáticamente en el primer uso.

Palancas de latencia

Tres ajustes opcionales, desactivados por defecto, reducen la latencia de ida y vuelta sin cambiar hardware (análisis completo en docs/LATENCY.md):

  1. WHISPER_MODEL=base.en (en stt.env) — STT ~2.8× más rápido, el valor predeterminado no cambia.

  2. vad.enabled: true — la detección de final de frase con Silero-VAD termina una emisión en cuanto dejas de hablar en lugar de esperar el endSilenceMs fijo. Reutiliza el modelo Silero incluido con faster-whisper (sin nueva dependencia; solo onnxruntime). Requiere el servidor VAD caliente: scripts/warm-servers.sh start vad (o establece VOICE_WARM_VAD=1 para que el start simple lo incluya). Si el servidor está caído, la detección de final de frase vuelve de forma segura a endSilenceMs. Los umbrales son ajustes configurables y se benefician de una puesta a punto en vivo con un hablante real.

  3. ttsPipeline.enabled: true — sintetiza y reproduce respuestas largas frase por frase, de modo que el tiempo hasta el primer audio es una frase, no toda la respuesta. Conserva el orden y la interrupción.

Latencia percibida (distinta de las tres anteriores): ackAfterMs: 600 pronuncia un relleno corto ~0.6 s después de que dejes de hablar cuando la respuesta aún se está calculando — así un cerebro lento te confirma casi de inmediato mientras el STT y el modelo trabajan en segundo plano. El conjunto ackPhrase predeterminado rota algunas frases ("Un momento." / "Déjame pensar." / …) para que no repita la misma línea cada turno; todas se pre-renderizan al inicio, una respuesta rápida las adelanta, y está desactivado por defecto (ideal para cerebros LLM/agente; déjalo desactivado para un cerebro de eco/comando rápido).


Transportes

Un transporte es la costura entre el puente de voz y tu cerebro. Elige uno con transport.type.

file (predeterminado)

Las transcripciones se escriben en transcriptDir/<timestamp>.txt. Las respuestas se leen de replyFile — el bucle sondea esa ruta y, cada vez que aparece, pronuncia su contenido y lo elimina. Lo que escriba replyFile se convierte en la voz. Este es el acoplamiento más flexible: tu cerebro puede ser cualquier proceso, en cualquier lenguaje, que observe un directorio y deje caer un archivo de texto. Ver examples/file-transport/ para un bucle de host de referencia. El transporte de archivo también admite enrutamiento entre múltiples cerebros — local o por SSH — ver docs/INTEGRATIONS.md.

command

Cada transcripción se entrega a transport.cmd como la variable de entorno $VOICE_TRANSCRIPT. La salida estándar del comando se pronuncia como respuesta. Ideal para un cerebro sin estado de "una emisión entra, una respuesta sale":

"transport": {
  "type": "command",
  "cmd": ["bash", "-lc", "my-llm-cli --prompt \"$VOICE_TRANSCRIPT\""]
}

mcp

Un transporte de Model Context Protocol donde el propio bucle de voz se convierte en un servidor MCP. Un host consciente de canales (p. ej. Claude Code) lanza broca-machina mediante .mcp.json; los turnos hablados llegan al host como un evento notifications/claude/channel, y el host responde llamando a la herramienta speak, que devuelve el texto al canal con voz.

"transport": { "type": "mcp", "source": "voice", "deliver": "channel" }

Regístralo con tu host: copia .mcp.json.example a .mcp.json, establece un cwd absoluto y apunta a adapters/mcp.config.example.json (edita primero los IDs de Discord + stt/tts):

{
  "mcpServers": {
    "broca-machina": {
      "command": "bun",
      "args": ["src/voice_loop.js", "adapters/mcp.config.example.json"],
      "cwd": "/abs/path/to/broca-machina"
    }
  }
}

La herramienta speak es la única capacidad expuesta al host — da voz al texto y nada más (sin acceso a archivos ni comandos). El habla entrante usa una notificación experimental claude/channel, por lo que la ruta MCP es de primera clase con Claude Code / hosts conscientes de canales hoy; un host que no puede recibir eventos de canal aún obtiene speak. El interruptor transport.deliver es la costura de extensión para futuros modos de entrada de hosts genéricos — ver docs/ARCHITECTURE.md.


Servidores de modelos calientes (menor latencia)

stt.py / tts.py cargan sus modelos en cada llamada (~1.1 s cada uno). Para evitar ese costo, ejecuta servidores persistentes que mantengan faster-whisper y Piper calientes, y apunta la configuración a los clientes de reemplazo:

VOICE_PY=/path/to/python \
PIPER_VOICE_DIR=/path/to/piper WHISPER_MODEL=base.en \
  scripts/warm-servers.sh start        # start | stop | status | restart

Luego, en config.json, cambia los comandos (CLI idéntica, así que es un cambio de 1 línea cada uno):

"stt": { "cmd": ["/path/to/python", "src/stt_client.py"] },
"tts": { "cmd": ["/path/to/python", "src/tts_client.py"] }

Los clientes hablan con los servidores a través de sockets Unix en .voice-tmp/ y vuelven a una carga en frío dentro del proceso si un servidor está caído — pura ventaja, nunca un nuevo modo de fallo. Medido ~2.2× más rápido en STT+TTS por intercambio (≈4× con WHISPER_MODEL=base.en). Ver docs/LATENCY.md para los números en frío vs. caliente y la lista clasificada de otras mejoras sin hardware.


Unión automática controlada por presencia

Por defecto, el bot se une al canal de voz al inicio y permanece allí durante toda la sesión. Una conexión de voz inactiva de larga duración es funcionalmente correcta, pero se deteriora en silencio — Discord corta las sesiones de voz inactivas y los parpadeos de red pasan desapercibidos hasta que intentas hablar — y mantiene la RAM de los servidores calientes incluso cuando no hay nadie.

Establece autoJoin: true para un ciclo de vida controlado por presencia en su lugar (la presencia está vinculada a allowedUserId; con allowAnySpeaker: true rastrea a cualquier miembro no bot del canal):

  • La puerta de enlace permanece conectada como un oyente de presencia económico — sin UDP de voz, sin modelos cargados.

  • Cuando el usuario permitido se une al canal de voz, el bot dispara onPresenceEnter (desacoplado) y se une. El gancho está pensado para calentar modelos: como se ejecuta concurrentemente con el handshake de voz de varios segundos y la pausa humana antes de la primera palabra, el calentamiento es prácticamente gratuito, así que la primera emisión aterriza en servidores calientes.

  • Cuando el usuario se va, se inicia un temporizador de inactividad. Si no regresa dentro de idleLeaveMs (por defecto 600000 = 10 min), el bot abandona el canal y dispara onPresenceLeave (p. ej. apaga los servidores calientes para recuperar RAM). Una reconexión rápida cancela la salida pendiente, así que las caídas breves no zarandean la conexión.

  • presenceFile (opcional, cualquier modo): un archivo marcador que existe exactamente mientras el usuario está en el canal de voz — permite que herramientas externas decidan según "¿está el humano aquí?" sin consultar a Discord. Se elimina en el apagado ordenado.

"allowedUserId": "YOUR_DISCORD_USER_ID",
"autoJoin": true,
"idleLeaveMs": 600000,
"onPresenceEnter": { "cmd": ["bash", "scripts/warm-servers.sh", "start"], "env": {} },
"onPresenceLeave": { "cmd": ["bash", "scripts/warm-servers.sh", "stop"],  "env": {} }

Si el usuario habla en los ~2.5 s antes de que el servidor STT caliente esté listo, el cliente de reemplazo vuelve a una carga en frío dentro del proceso para esa única emisión — es más lento pero nunca se pierde, así que no se necesita cola para la primera palabra. Deja autoJoin sin establecer (false) para mantener el comportamiento de siempre en el canal.


Adaptadores

adapters/ contiene plantillas de configuración listas para copiar para conexiones de host específicas. adapters/mcp.config.example.json es una configuración completa de transporte MCP (el bucle de voz como servidor MCP): cópiala, establece tus IDs de Discord, apunta stt/tts a tu Python y regístrala con tu host mediante .mcp.json.example. Nada en src/ es específico del host — cada conexión es solo una configuración JSON.


Solución de problemas

El bot se conecta pero se cae inmediatamente / código de cierre 4017 / "never Ready". Esto es el handshake de cifrado de extremo a extremo DAVE fallando — casi siempre porque se instaló el @discordjs/voice incorrecto. Confirma que instalaste con bun (bun install) y que node_modules/@discordjs/voice/package.json dice 0.19.2, no 0.18.x. npm install en Node 20 resuelve silenciosamente a la versión 0.18.0 anterior a DAVE, que no puede completar el handshake en el Discord actual.

¿Por qué bun, y por qué las versiones exactas? Discord hizo obligatorio DAVE E2EE para voz a principios de 2026. El soporte llegó en @discordjs/voice 0.19, que incluye @snazzah/davey y requiere Node ≥ 22.12. bun es un runtime moderno que cumple ese requisito de serie, así que bun install + bun src/voice_loop.js es la ruta soportada. Ejecutar bajo un Node más antiguo hace que el resolvedor vuelva a 0.18.0 (sin DAVE) y la unión falla. Ver docs/ARCHITECTURE.md para la justificación completa.

Inicia sesión pero "no bot token in $DISCORD_VOICE_BOT_TOKEN" / login FAILED. Exporta el token a la variable de entorno nombrada por discord.tokenEnv (por defecto DISCORD_VOICE_BOT_TOKEN) antes de lanzar, o apunta voice-up.sh a un .env con VOICE_TOKEN_ENVFILE=/path/to/.env.

Se niega a iniciar: "no discord.allowedUserId set". Eso es la protección de cierre seguro (ver Seguridad). Establece discord.allowedUserId a tu ID de usuario, o discord.allowAnySpeaker: true para aceptar explícitamente a cualquiera.

Ya ejecuto un bot de Discord y broca-machina no se mantiene conectado. Discord permite una conexión de puerta de enlace por bot. Si tu bot de texto existente ya está conectado con el token de esa aplicación, crea un segundo bot de Discord (su propia aplicación + token) para voz. Dos bots, dos puertas de enlace.

No se transcribe audio. Comprueba que (a) el bot realmente se unió al canal (la línea [loop] joined — con autoJoin, eso solo aparece una vez que estás en el canal de voz), (b) si allowedUserId está establecido, que eres ese usuario, y (c) que hablas más tiempo que minUtteranceSec. Las emisiones muy cortas o casi silenciosas y la lista sttNoiseDrop se filtran por diseño — el registro imprime [recv] … too short o [stt] drop: "…" cuando eso ocurre.

ffmpeg no encontrado. ffmpeg se verifica al inicio — si falta, el bucle sale inmediatamente con un mensaje claro en lugar de fallar por emisión más tarde. Instálalo (apt install ffmpeg / brew install ffmpeg) y reinicia.

Es lento en CPU. Los valores predeterminados son solo CPU. src/stt.py recarga el modelo Whisper en cada emisión (una compensación de simplicidad deliberada señalada en el archivo), así que espera unos segundos de latencia de ida y vuelta. Para menor latencia: usa un modelo Whisper más pequeño/rápido (WHISPER_MODEL=tiny.en), ejecuta STT en una GPU (WHISPER_DEVICE=cuda), reemplaza stt.cmd con un servidor de transcripción persistente, o activa los servidores calientes.


Estructura del proyecto

src/voice_loop.js       the bridge (bun): receive → STT → transport → TTS → play
src/stt.py              default STT (faster-whisper), per-call model load
src/tts.py              default TTS (Piper), per-call load, pitch-preserved speed control
src/stt_server.py       warm STT server: loads the model once, serves over a Unix socket
src/tts_server.py       warm TTS server: loads Piper once, serves over a Unix socket
src/stt_client.py       drop-in for stt.py; talks to the warm server, cold fallback if down
src/tts_client.py       drop-in for tts.py; talks to the warm server, cold fallback if down
src/vad.py              Silero-VAD endpointer (opt-in early end-of-utterance)
src/vad_server.py       warm VAD server: streams PCM in, reports end-of-speech
src/vad_stream.js       loop-side VAD client (Unix-socket framing)
src/_voicesock.py       shared length-prefixed-JSON socket framing for server/client
scripts/warm-servers.sh start/stop/status the warm STT+TTS(+VAD) servers
scripts/voice-up.sh     background-start with a boot/health check + log
scripts/voice-down.sh   stop the backgrounded loop
config.example.json     copy to config.json and edit
requirements.txt        python deps for the bundled engines (faster-whisper, piper, …)
adapters/               ready-to-copy config templates (e.g. mcp.config.example.json)
examples/               runnable brains: echo, ollama, claude-cli, file-transport
test/                   selftests (lifecycle, TTS pipeline + ack, VAD) — CI runs these
.mcp.json.example       example MCP registration for a channel-aware host (Claude Code)
assets/                 logo + icon (SVG)
docs/ARCHITECTURE.md    pipeline, transport abstraction, DAVE/bun rationale
SETUP.md                end-to-end setup walkthrough
CONTRIBUTING.md         dev setup, adding transports/engines, PR expectations

Licencia

MIT — ver LICENSE. © 2026 Cody Slater.

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

Maintenance

Maintainers
Response time
5wRelease cycle
2Releases (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

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

  • Hosted MCP server connecting claude.ai, ChatGPT and other AI apps to your own computer

  • Give your AI agent a phone: place calls, navigate IVRs, wait on hold, get structured answers.

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/codyslater/broca-machina'

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