Skip to main content
Glama
jorgemovitext

Voice Brain MCP Server

Voice Brain MCP — Prototipo Voz (NL Pearl v2) + Brain (MCP) + Canales

Prototipo de gateway de voz sobre NL Pearl v2 con un "Brain" de contexto unificado por contacto (independiente del canal), expuesto también como servidor MCP, y una consola Angular 22 para operar el flujo.

Corre end-to-end en modo mock sin credenciales. Los adaptadores reales de NL Pearl v2 están listos para conectar.

Qué hace

  • Voz: NL Pearl v2 como motor detrás de nuestro propio gateway (no se usa su consola ni sus canales de texto). La llamada saliente se dispara con addLead; antes de hablar, el nodo PreCallAPI pide contexto a POST /precall; al terminar, el webhook POST /webhooks/nlpearl trae el aviso y el gateway recupera transcripción/resumen/sentimiento/datos.

  • Brain: contexto unificado por contacto (identidad, timeline cross-channel, señales tipo promesa de pago). Expuesto por REST para la consola y como servidor MCP (stdio) con tools brain_*.

  • Canales propios: WhatsApp/SMS (stubs con hueco para WABA/proveedor SMS) leen y escriben el mismo contexto → el seguimiento continúa el mismo hilo.

Related MCP server: Customer Support MCP Server

Diagrama del flujo (demo)

 consola /demo ──POST /api/demo/run──▶ DemoService
   1. siembra contacto (promesa activa + WhatsApp previo)
   2. addLead (VoiceEnginePort → mock | NL Pearl v2)
        │
        ▼  (ciclo de llamada)
   3. POST /precall  ◀── nodo PreCallAPI      → variables (nombre, promesa, saldo, último resumen)
   4. ... conversación ...
   5. POST /webhooks/nlpearl (HMAC guard)     → getCall → Brain.recordCallContext
        │                                        · interacción voice + señal promesa
        ▼
   6. FollowupService → brain_suggest_followup → WhatsApp propio (stub)
        │                                        · interacción whatsapp outbound
        ▼
   7. consola: timeline del contacto con voz + WhatsApp en el mismo hilo

Estructura

voice-brain-mcp/
├─ apps/
│  ├─ api/        # NestJS 11 + Fastify: Brain, NL Pearl, canales, MCP, demo
│  └─ console/    # Angular 22 (signals + zoneless). Vistas:
│                 #   /home           inicio con avatar de voz
│                 #   /contacts       directorio de contactos
│                 #   /contacts/:id   chat + contexto en vivo (2 columnas)
│                 #   /conversations  módulo de conversaciones: lista de hilos
│                 #                   + chat + contexto en vivo (3 columnas)
│                 #   /demo           flujos end-to-end paso a paso
├─ scripts/run-demo.mjs
├─ data/brain.json   # respaldo de persistencia (se crea al correr)
└─ .env              # copiar de .env.example

Cómo correr

Requisitos: Node 20+ (probado con Node 24).

cp .env.example .env     # MOCK=true por defecto
npm install

npm run dev              # api (3000) + consola (4200) juntos
# o por separado:
npm run dev:api
npm run dev:console
  • Consola: http://localhost:4200 → pestaña Demo del flujo → “Correr flujo end-to-end”. Al final hay link al contexto del contacto (voz + WhatsApp en el mismo timeline).

  • Demo por CLI (con la api levantada): npm run demo

  • Tests: npm test · Build: npm run build

Desplegado

https://voice-brain-mcp.vercel.app — corre en modo mock, sin credenciales.

Desplegar en Vercel (desde GitHub)

El repo ya trae vercel.json y la función serverless en api/index.js.

git init && git add -A && git commit -m "Prototipo voz + Brain MCP"
git remote add origin git@github.com:<usuario>/<repo>.git
git push -u origin main

En Vercel: Add New → Project → Import ese repo y Deploy. vercel.json define el build, publica la consola Angular como estático y rutea /api/*, /precall y /webhooks/* a la función Nest.

Un solo proyecto, con Root Directory en la raíz del repo. El build tolera que Vercel arranque dentro de un workspace, pero la función serverless vive en api/index.js (raíz) y Vercel solo la detecta si el Root Directory es esa raíz. Con proyectos separados por workspace, la consola se despliega pero /api/* responde 404.

Por qué el build es un script y no npm run --workspace (dos trampas de Vercel con monorepos npm, ambas ya resueltas en scripts/vercel-build.sh):

  • Un script llamado vercel-build en el package.json raíz no sirve: Vercel le da trato especial y npm lo propaga a cada workspace, que no lo define → Missing script: "vercel-build".

  • npm run build --workspace apps/api falla con No workspaces found si Vercel ejecuta el build desde un subdirectorio. El script localiza la raíz del monorepo por su cuenta y llama a nest/ng con npx, así funciona desde cualquier ubicación.

Si el deploy vuelve a fallar, mirá las primeras líneas del log: el script imprime cwd inicial y raíz del monorepo, que dicen exactamente desde dónde arrancó Vercel.

Variables de entorno (Project → Settings → Environment Variables): ninguna es obligatoria — sin nada, el deploy corre en modo mock. Para conectar NL Pearl real, cargá MOCK=false, NLPEARL_ACCOUNT_ID, NLPEARL_API_KEY, NLPEARL_PEARL_ID y NLPEARL_WEBHOOK_SECRET, y apuntá el webhook del Pearl a https://<tu-deploy>.vercel.app/webhooks/nlpearl y el nodo PreCallAPI a https://<tu-deploy>.vercel.app/precall.

Qué cambia en serverless (y por qué)

Vercel congela el proceso al responder y solo /tmp es escribible, así que el código se adapta solo (detecta VERCEL):

  • Persistencia: con un Vercel Blob store conectado (Storage → Create → Blob), el Brain guarda un único JSON compartido por todas las instancias y el estado deja de perderse. Vercel inyecta BLOB_READ_WRITE_TOKEN solo y el código lo detecta. Sin store, cae al archivo en /tmp: cada lambda tiene su propia copia, así que un contacto creado en una instancia no existe en la siguiente y los mensajes que entran por webhook no aparecen en la consola.

  • Sembrado en frío: si el Brain arranca vacío se siembra el directorio de demo con IDs fijos, para que los enlaces /contacts/:id sigan valiendo entre instancias.

  • Flujo demo: se completa dentro del request (no hay timers de fondo) y los pasos viajan en la respuesta, porque el polling podría caer en otra instancia.

  • Mock: usa los servicios in-process en vez de llamarse por HTTP a sí mismo (la protección de deployments bloquearía ese self-request). En local sigue usando HTTP real contra /precall y /webhooks/nlpearl.

  • MCP: el servidor stdio no aplica en Vercel; corre local con npm run mcp.

Brain como servidor MCP

npm run mcp                                    # servidor stdio
npx @modelcontextprotocol/inspector npm run mcp  # probarlo con el inspector

Tools: brain_resolve_identity, brain_get_context, brain_upsert_contact, brain_append_interaction, brain_set_signal, brain_get_signals, brain_record_call_context, brain_suggest_followup.

Comparte persistencia (archivo JSON) con el gateway HTTP.

Conectar NL Pearl real

  1. En .env: MOCK=false, NLPEARL_ACCOUNT_ID, NLPEARL_API_KEY y NLPEARL_PEARL_ID (Pearl outbound de voz). Auth confirmada en docs: Authorization: Bearer {AccountId}:{SecretKey}.

  2. En NL Pearl: configurar el flujo del Pearl con nodo PreCallAPI apuntando a https://tu-host/precall, y activar el webhook de llamada apuntando a https://tu-host/webhooks/nlpearl.

    Dónde está el webhook (no es workspace-wide, es por Pearl): dashboard (salí de Settings con Go Back) → abrí tu Pearl → editor de flujo PearlVibe → pestaña Outbound Settings (o Inbound Settings) → grupo Campaign Settings → bajá hasta el final, sección Webhooks → activá el toggle (los campos de URL solo aparecen al habilitarlo) → Call Webhook URL. El Lead Webhook no lo usamos. Ojo: en Settings del workspace, Agent(s) es capacidad de llamadas simultáneas, no los Pearls; y Text Channels no se usa (WhatsApp/SMS son nuestros).

  3. NLPEARL_WEBHOOK_SECRET: NL Pearl no firma webhooks con HMAC — al configurar el webhook podés adjuntar un Credential (un token que creás vos) que viaja en cada entrega. Poné ese mismo valor en NLPEARL_WEBHOOK_SECRET y el guard lo verificará; vacío = no se exige.

  4. Los paths confirmados contra la doc v2 están en apps/api/src/nlpearl/nlpearl.client.ts; los no verificados quedaron marcados // TODO: confirmar con NL Pearl (igual que el shape exacto del webhook y del PreCallAPI en webhook.controller.ts / precall.controller.ts).

Espejo NL Pearl (todos los canales) + Postgres

Desde el pivote de 2026-08, la plataforma consume todos los canales de NL Pearl (voz y texto: SMS "Línea 100 AMDC TEXT", WhatsApp, etc.) y almacena la atención a detalle en DB propia.

  • Sync multi-pearl: POST /api/nlpearl/sync recorre todas las pearls de la cuenta, trae la actividad con Calls/Bulk (paginado, límite 100 del API) y guarda el raw completo + la interacción normalizada en el Brain. La consola lo dispara sola cada ~30 s (?soft=true, con rate-limit). GET /api/nlpearl/activity y GET /api/nlpearl/pearls exponen lo espejado.

  • Canal por pearl: nombre con "Whatsapp" ⇒ whatsapp, "TEXT/SMS/Chat" ⇒ sms, resto ⇒ voice. Se puede forzar con NLPEARL_TEXT_PEARL_IDS (ids separados por coma ⇒ sms).

  • Postgres (Neon): en Vercel → Storage → Create Database → Neon (Postgres) → conectar al proyecto. Vercel inyecta DATABASE_URL sola y el Brain migra a Postgres en el próximo deploy (prioridad: Postgres > Blob > archivo JSON). El esquema se crea solo al primer uso; tablas: contacts, interactions, signals, nlpearl_pearls, nlpearl_activity (raw).

Qué Pearl usa cada canal (sin variables de entorno)

NLPEARL_PEARL_ID obligaba a un redeploy para cambiar de Pearl. Ahora la asignación vive en la DB y se cambia desde la app:

  • En Obreros, cada Pearl trae el botón "Usar para <canal>". El canal sale de su agentType (voz / WhatsApp / SMS), así que cada Pearl solo se puede asignar al canal que realmente atiende.

  • El backend resuelve en este orden: pearlId de la petición (elección puntual, p. ej. POST /api/calls/trigger con {contactId, pearlId}) → Pearl asignada al canalNLPEARL_PEARL_ID como respaldo heredado.

  • API: GET /api/workers/routing y PUT /api/workers/routing ({channel, pearlId}; pearlId: null libera el canal).

  • NLPEARL_PEARL_ID pasa a ser opcional: solo hacen falta NLPEARL_ACCOUNT_ID y NLPEARL_API_KEY.

Autenticación de la consola

Toda la plataforma exige sesión: sin login, cualquier URL de la consola cae en /login y toda la API responde 401 (guard global deny-by-default; solo son públicos los webhooks de proveedores y /precall, que llevan su propia verificación).

  • Registro / login: teléfono E.164 + contraseña (scrypt) y OTP de 6 dígitos por WhatsApp como segundo factor (vía el canal Gupshup propio).

  • Hardening: OTP hasheado con vencimiento (5 min), 5 intentos y un solo uso; cooldown de reenvío (60 s); bloqueo de cuenta 15 min tras 5 contraseñas fallidas; mensajes de error genéricos (sin enumeración de usuarios); sesión JWT en cookie httpOnly + Secure + SameSite=Lax (12 h).

  • Variables: AUTH_JWT_SECRET (obligatoria en prod — ya cargada en Vercel), AUTH_SESSION_HOURS, AUTH_OTP_TTL_MIN.

  • En MOCK=true (desarrollo) el OTP se imprime en el log del server en vez de enviarse por WhatsApp.

  • Usuarios en Postgres (tabla users); sin DATABASE_URL caen a un archivo local solo apto para desarrollo.

Decisiones / notas

  • Puertos como injection tokens (VoiceEnginePort, ChannelPort, BrainRepository): el binding mock/real vive en cada módulo adaptador según MOCK; el Brain nunca importa clientes concretos.

  • El mock ejercita los endpoints HTTP reales del gateway (self-HTTP a /precall y /webhooks/nlpearl, con firma HMAC si hay secreto), no atajos internos.

  • Persistencia: memoria + respaldo JSON detrás de BrainRepository (cambiable a SQLite/Postgres vía provider).

  • No se usan los canales de texto de NL Pearl: WhatsApp/SMS son adaptadores propios (stubs con log).

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

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

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    B
    maintenance
    Provides AI agents with operational customer context, including typed revenue objects, persistent state, scoped tools, and human-in-the-loop handoffs through MCP, REST, and CLI.
    15
    12
    Apache 2.0
  • A
    license
    Not graded
    quality
    D
    maintenance
    Provides phone intelligence for AI voice agents, enabling inbound caller identification, outbound risk assessment, line type detection, and do-not-contact checks via four read-only MCP tools.
    MIT

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/jorgemovitext/voice-brain-mcp'

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