Skip to main content
Glama
moonslayers
by moonslayers

whatsapp-mcp

Servidor MCP que permite a opencode (o cualquier cliente MCP) controlar WhatsApp Desktop vía Chrome DevTools Protocol (CDP), usando la sesión real del usuario (misma cuenta, misma ventana, mismos datos) en lugar de una API no oficial. El control se hace sobre el DOM de web.whatsapp.com que renderiza el propio WhatsApp Desktop, con medidas anti-ban (rate limiting y typing simulado) para minimizar el riesgo de bloqueo de la cuenta.

Arquitectura

┌────────────┐   JSON-RPC 2.0 (stdio)   ┌──────────────────────────────┐
│  opencode  │ ◀──────────────────────▶ │        MCP server             │
│ (cliente)  │                          │        src/server.ts          │
└────────────┘                          │  (10 tools expuestas)         │
                                        └───────────────┬───────────────┘
                                                        │ CDP (WebSocket)
                                                        ▼
                                      ┌──────────────────────────────────┐
                                      │  WhatsApp Desktop (Electron)     │
                                      │  --remote-debugging-port=9222    │
                                      │        web.whatsapp.com          │
                                      └──────────────────────────────────┘

Capas del proyecto:

Capa

Archivos

Responsabilidad

Config

src/config.ts

Configuración con defaults y override por env (WA_MCP_*); objeto inmutable (Object.freeze).

CDP

src/cdp/

Cliente Chrome DevTools Protocol: descubrimiento de targets (HTTP /json), conexión WebSocket al target de WhatsApp, Runtime.evaluate y captura de pantalla. Implementado sobre el WebSocket nativo de Node (sin dependencias de runtime extra).

WhatsApp

src/whatsapp/

Operaciones de negocio sobre el DOM de WhatsApp: chats.ts (listar, no-leídos, buscar), messages.ts (leer mensajes), send.ts (enviar con typing simulado), draft.ts (leer/limpiar texto sin enviar del input), media.ts (descargar media de un mensaje a disco), dom.ts (selectores y extractores del DOM) y errors.ts (errores tipados).

Rate limit

src/ratelimit.ts

RateLimiter anti-ban en memoria con ventana deslizante (3 políticas). Desacoplado del CDP para poder probarse sin WhatsApp; cubierto por tests unitarios (npm test, test/ratelimit.test.ts).

Server

src/server.ts

Registra las 10 tools MCP, traduce errores a resultados estructurados { ok:false, error, message, retryAfterMs?, detail? } y gestiona el ciclo de vida (conexión on-demand, cierre limpio en SIGINT/SIGTERM).

Flujo de una llamada típica: el server recibe tools/call por stdio → la tool correspondiente conecta (si no está cacheada) al target de WhatsApp vía CDP → evalúa JavaScript en la página → devuelve el resultado estructurado al cliente.

Related MCP server: WhatsApp MCP Server

Requisitos

  • Node.js >= 23 (se usa el type stripping nativo de TypeScript, sin build step; habilitado por defecto desde Node 23.6 — se probó con Node 24).

  • WhatsApp Desktop instalado (ver Compatibilidad).

  • Cliente MCP (p. ej. opencode) para consumir las tools.

Compatibilidad (¿cuál WhatsApp funciona?)

El server no usa una API no oficial: controla el DOM de web.whatsapp.com que renderiza la app de escritorio de WhatsApp. Por lo tanto, lo que necesita es una app de escritorio basada en Electron que muestre la web de WhatsApp.

  • Probado y verificado en vivo: paquete AUR whatsapp-linux-desktop-bin (la app no oficial de WhatsApp para Linux), versión 1.0.1-1, con binario en /opt/WhatsApp Desktop/whatsapp-linux-desktop. Es la que el wrapper scripts/launch-whatsapp.sh lanza por defecto.

  • Los selectores del DOM (src/whatsapp/dom.ts) y los mecanismos CDP fueron verificados contra el build 2026 de esa app (Electron 32). Si WhatsApp actualiza su web y cambia el DOM, hay que actualizar los selectores (ver Notas / limitaciones).

  • ⚠️ Puede funcionar con otras apps de escritorio de WhatsApp (WebCord, Ferdium, etc.) siempre que: (1) sean Electron y expongan --remote-debugging-port, y (2) rendericen web.whatsapp.com con el mismo DOM. No están soportadas ni probadas: ajusta BIN en scripts/launch-whatsapp.sh y verifica los selectores antes de usarlas.

  • No funciona con WhatsApp Web en un navegador normal (necesitas el flag de CDP de un runtime controlable) ni con el cliente móvil.

Para saber qué tienes instalado:

pacman -Q | grep -i whatsapp            # paquete + versión (p. ej. whatsapp-linux-desktop-bin 1.0.1-1)
ls /opt/ | grep -i whatsapp            # binario (p. ej. "WhatsApp Desktop")

Instalación

npm install

No hay step de compilación: node src/server.ts ejecuta el TypeScript directamente.

Configuración

Todas las variables son opcionales y se leen del entorno con prefijo WA_MCP_. Ver .env.example.

Variable

Default

Descripción

WA_MCP_CDP_PORT

9222

Puerto TCP donde Chrome/Electron expone el endpoint CDP.

WA_MCP_CDP_HOST

127.0.0.1

Interfaz donde escucha el endpoint CDP.

WA_MCP_MEDIA_DIR

/tmp/opencode

Directorio donde se escriben los medios descargados (imágenes, documentos, etc.).

WA_MCP_RATE_MIN_INTERVAL_MS

3000

Delay mínimo entre dos send_message cualquiera (global).

WA_MCP_RATE_COOLDOWN_CHAT_MS

15000

Delay mínimo entre dos mensajes al mismo chat.

WA_MCP_RATE_MAX_PER_MINUTE

10

Tope de mensajes por ventana deslizante de 60s (todos los chats).

WA_MCP_TYPING_ENABLED

true

Toggle del typing simulado.

WA_MCP_TYPING_MIN_DELAY_MS

40

Delay mínimo entre caracteres al teclear.

WA_MCP_TYPING_MAX_DELAY_MS

120

Delay máximo entre caracteres al teclear.

WA_MCP_TYPING_PUNCTUATION_PAUSE_MS

350

Pausa extra tras puntuación (. , ; : ! ? y salto de línea), con jitter 60–140%.

WA_MCP_TYPING_THINK_BEFORE_SEND_MS

600

Delay aleatorio (jitter 50–150%) entre terminar de teclear y pulsar enviar.

WA_MCP_TYPING_MAX_MESSAGE_CHARS

400

Mensajes más largos que esto omiten el typing simulado (inserción directa).

Uso con WhatsApp

Lanzar WhatsApp con CDP

El server solo puede controlar WhatsApp si la app corre con el flag de debugging de CDP. El wrapper scripts/launch-whatsapp.sh lo garantiza de forma idempotente:

scripts/launch-whatsapp.sh          # lanza (o reutiliza) WhatsApp con CDP en 127.0.0.1:9222
scripts/launch-whatsapp.sh --check  # dry-run: solo informa qué haría, sin tocar nada

Tres casos que maneja el wrapper:

  1. Ya corriendo con el flag (--remote-debugging-port=9222) → no hace nada; solo verifica que el endpoint CDP responda.

  2. Corriendo sin el flag → termina esa instancia (SIGTERM → SIGKILL si es necesario), espera a que liberen los procesos hijos y relanza con el flag.

  3. No corriendo → lo lanza directamente con el flag.

El wrapper usa --no-sandbox (la app no tiene chrome-sandbox setuid-root) y comprueba el endpoint CDP durante 15s tras lanzar. El login/sesión persiste en el user-data-dir, así que relanzar es seguro.

IMPORTANTE: el server solo funciona con WhatsApp abierto y con la sesión iniciada. Si WhatsApp no está corriendo, las tools no crashean: devuelven un error estructurado accionable (whatsapp_not_running) indicando cómo lanzarlo.

Override del .desktop de usuario

Para que WhatsApp siempre arranque con CDP (aunque se lance desde el menú, no solo desde el wrapper), el lanzador de aplicaciones del usuario (~/.local/share/applications/whatsapp-linux-desktop.desktop) apunta al wrapper:

Exec=/home/junior/Projects/whatsapp-mcp/scripts/launch-whatsapp.sh %U

El archivo fuente está en desktop/whatsapp-linux-desktop.desktop. Así cualquier apertura de WhatsApp pasa por el wrapper y garantiza el puerto CDP.

Integración con opencode

Registra el server como MCP local (stdio) en ~/.config/opencode/opencode.json:

{
  "mcp": {
    "whatsapp": {
      "type": "local",
      "command": ["node", "/home/junior/Projects/whatsapp-mcp/src/server.ts"],
      "enabled": true
    }
  }
}

Después de editar el archivo hay que reiniciar opencode. Y ojo: el server MCP es un proceso stdio long-running, así que cualquier cambio bajo src/*.ts (no solo la config) tampoco se recarga en vivo — npm run verify y los tests spawnean instancias frescas y pasan con el código nuevo, pero el cliente opencode en ejecución conserva el código viejo hasta el reinicio. Para verificar que quedó registrado, revisa que las tools whatsapp_status, list_chats, read_messages, get_unread, search_contacts, read_draft, clear_draft, send_message, take_screenshot y download_media estén disponibles para el agente.

Leer adjuntos de WhatsApp desde opencode: read_messages reporta para cada mensaje su id y type. Para leer el contenido de un adjunto, llama a download_media con ese id (la media se escribe en /tmp/opencode, o el directorio de WA_MCP_MEDIA_DIR) y luego lee el path devuelto con las herramientas de archivo de opencode (o el agente file-analyser) para analizar la imagen, PDF, documento, etc.

Nota: send_message envía mensajes reales. Configura los permisos de opencode (o el flujo de aprobación de tools) si quieres que cada envío requiera confirmación.

Tools MCP

Tool

Argumentos

Descripción

whatsapp_status

Estado de WhatsApp: running, targetUrl, loggedIn y mensaje accionable. Nunca falla (es el health check).

list_chats

limit (default 20)

Lista los chats renderizados en el panel (nombre, último mensaje, hora, no-leídos).

read_messages

chat (obligatorio), limit (default 20)

Abre el chat indicado y lee sus últimos mensajes (autor, texto, hora/fecha, dirección, tipo).

get_unread

Chats con al menos un mensaje sin leer (nombre, último mensaje, contador).

search_contacts

query (obligatorio)

Busca chats usando el buscador real de WhatsApp (no un filtro local).

read_draft

chat (obligatorio)

Lee el draft (texto a medio escribir) del input del chat. Devuelve draft (texto) o null si el input está vacío. Solo lectura: no modifica nada.

clear_draft

chat (obligatorio)

Limpia el draft del input. DESTRUCTIVO: elimina el texto sin enviar del usuario; devuelve el texto eliminado (previousDraft). Usar solo con aprobación explícita.

send_message

chat, text (obligatorios), clearDraft (opcional, default false)

Envía un mensaje con typing simulado y pasando por el rate limiter. Si el chat tiene un draft, aborta con draft_conflict (sin tocar el draft) salvo que se pase clearDraft: true. Devuelve delivered, sentAt, chatId, preview.

download_media

chat, messageId (obligatorios), destDir (opcional)

Descarga la media (imagen, video, audio, documento) del mensaje messageId (el data-id que reporta read_messages) y la escribe en destDir (default WA_MCP_MEDIA_DIR). Solo lectura: no envía mensajes ni pasa por el rate limiter. Devuelve path absoluto, filename, mimeType, sizeBytes, mediaType. Requiere que el chat esté abierto y el mensaje renderizado (la media puede no estar cargada si salió del viewport).

take_screenshot

Captura PNG de la ventana de WhatsApp vía CDP y la devuelve como data URL base64.

Errores: todas las tools (salvo whatsapp_status) devuelven { ok:false, error, message, ... } con isError: true ante fallos, con claves estables (whatsapp_not_running, not_logged_in, chat_not_found, rate_limited con retryAfterMs, draft_conflict, send_not_confirmed, message_not_found, media_unsupported, media_not_loaded, cdp_error, unexpected).

Seguridad anti-ban

  • Rate limiting (src/ratelimit.ts): ventana deslizante de 60s con tres políticas evaluadas en orden antes de tocar el DOM — intervalo mínimo global (minIntervalMs), cooldown por chat (cooldownPerChatMs) y tope por minuto (maxPerMinute). Un envío bloqueado devuelve rate_limited con retryAfterMs. Nada se envía sin pasar por checkSend.

  • Typing simulado: el texto se ingresa carácter a carácter con delays aleatorios configurables y pausas tras puntuación, más un delay de "pensar" antes de pulsar enviar. El indicador "escribiendo…" aparece para el receptor. Se omite (inserción directa, con console.warn) cuando el mensaje supera WA_MCP_TYPING_MAX_MESSAGE_CHARS o el toggle está apagado.

  • Manejo de drafts (texto sin enviar): si el chat tiene un draft en el input, send_message aborta con draft_conflict (incluye el texto del draft en el mensaje) en lugar de borrarlo o concatenarlo con el mensaje. El draft nunca se sobreescribe sin consentimiento: solo se limpia con clear_draft o pasando clearDraft: true explícitamente a send_message.

  • Sin broadcasts ni reenvíos automáticos: no hay código que haga envíos masivos.

  • Confirmación de permisos: send_message es una tool como las demás; puede exigirse aprobación manual desde el cliente MCP (permissions de opencode).

Validación

npm run typecheck    # tsc --noEmit (validación de tipos)
npm test             # node:test (44 tests: RateLimiter, confirmación de envío, open-chat, media)
node scripts/verify.mjs   # batería completa de verificación
npm run verify       # alias del anterior

scripts/verify.mjs ejecuta y reporta PASS/FAIL/SKIP por sección:

  • ENV: versión de Node (>= 23) y alcance del endpoint CDP.

  • TYPECHECK: npx tsc --noEmit.

  • MCP: spawn del server, handshake (initialize + notifications/initialized), tools/list (las 10 tools), whatsapp_status, list_chats, get_unread, take_screenshot y cierre limpio con SIGTERM. send_message nunca se invoca (solo se comprueba que esté registrada); lo mismo para download_media (presencia en tools/list es suficiente).

Exit code 0 si no hay FAILs, 1 si algo falla. Si WhatsApp no está corriendo, los checks dependientes de CDP se reportan como SKIP (no FAIL) con un mensaje claro:

WA_MCP_CDP_PORT=9299 node scripts/verify.mjs   # simula "WhatsApp caído"

Troubleshooting

"Sesión nueva / QR al lanzar"

WhatsApp Desktop no tiene single-instance lock: si arranca una segunda instancia (p. ej. la abres del menú estando ya abierta, o el wrapper relanza), dos procesos compiten por los mismos LevelDB y la segunda cae a un estado vacío (pantalla de QR/sesión nueva).

Solución: cerrar WhatsApp por completo y lanzarlo solo con el wrapper (una única instancia con el flag CDP):

scripts/launch-whatsapp.sh

El wrapper ya contempla el caso b (instancia sin flag → la termina y relanza). Evita abrir WhatsApp de cualquier otra forma mientras uses este server.

"Puerto 9222 no responde"

Las tools devuelven whatsapp_not_running. Causa casi siempre: WhatsApp no está corriendo con el flag de CDP. Lánzalo con el wrapper y verifica:

scripts/launch-whatsapp.sh
curl http://127.0.0.1:9222/json/version   # debe responder con el "Browser" de WhatsApp

Errores comunes de las tools

Error

Significado / solución

whatsapp_not_running

WhatsApp no está con CDP. Lanza con scripts/launch-whatsapp.sh.

not_logged_in

WhatsApp abierto pero en pantalla de QR/login. Completa el login en la ventana y reintenta.

chat_not_found

El nombre no coincide con el del chat list. Usa list_chats para ver los nombres exactos (incluyen emojis). Ten en cuenta que los emojis del nombre pueden CAMBIAR con el tiempo — matchea por el nombre base; list_chats/search_contacts devuelven el render actual.

rate_limited

Envío bloqueado por el rate limiter; respeta retryAfterMs.

draft_conflict

El chat tiene un draft (texto sin enviar) en el input. Usa read_draft para verlo, clear_draft o send_message con clearDraft: true para sobreescribirlo.

input_not_found / send_not_confirmed

El chat no cargó o el mensaje no se confirmó dentro del timeout. Reintenta.

message_not_found

download_media: el messageId no está en el DOM — el mensaje salió del viewport o el id es incorrecto. Re-pasa read_messages o scrollea el mensaje a la vista y reintenta.

media_not_loaded

download_media: se detectó media pero el blob no está disponible (blob URL revocado por virtualización) o el fetch falló. Reintenta con el chat abierto y el mensaje en el viewport.

media_unsupported

download_media: el mensaje no tiene media descargable (texto/unknown, o un documento que en este build no expone URL del archivo en el DOM).

cdp_error / unexpected

Problema de conexión o error inesperado; revisa los logs y verifica que WhatsApp sigue corriendo.

Notas / limitaciones

  • Selectores del DOM: las tools dependen de la estructura del DOM de WhatsApp Web, que cambia con las actualizaciones. Los selectores actuales fueron verificados contra el build 2026 (Electron 32); si WhatsApp cambia su DOM, habrá que actualizar src/whatsapp/dom.ts, src/whatsapp/send.ts y src/whatsapp/media.ts.

  • Descarga de media: download_media necesita que el mensaje esté renderizado en el DOM (WhatsApp virtualiza #main). El blob URL de la media se revoca si el mensaje sale del viewport, así que extrae con el chat abierto y, si falla con media_not_loaded/message_not_found, scrollea el mensaje a la vista y reintenta. Los documentos no exponen URL en el DOM en este build, así que no son descargables (media_unsupported/sin fuente).

  • Identificador de chat: la clave es el nombre del chat tal como aparece en el chat list (el DOM no expone un JID estable para todas las operaciones). send_message intenta leer el JID del header cuando está disponible, pero la identificación sigue siendo por nombre. Las variantes de emoji del mismo nombre se tratan como el mismo chat: la verificación del header (headerMatchesName, emoji-strip + case-insensitive) hace que un nombre cuya emoji difiera del header abra correctamente el chat.

  • Typing simulado omitido en mensajes largos: por encima de WA_MCP_TYPING_MAX_MESSAGE_CHARS el texto se inserta de golpe (con aviso en stderr), para no bloquear el envío con un tecleo interminable.

  • Conexión on-demand: el server arranca sin tocar CDP y cada tool conecta/reutiliza la conexión WebSocket cacheada; se re-conecta si WhatsApp se relanza.

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

  • A
    license
    B
    quality
    C
    maintenance
    An MCP server that enables interaction with WhatsApp using the Baileys library and Streamable HTTP transport. It supports managing contacts, chats, and messages, while providing a web admin UI for QR code authentication and media handling.
    28
    5
    MIT
  • A
    license
    B
    quality
    D
    maintenance
    An MCP server that enables AI to control WhatsApp Web via Puppeteer Stealth for reading and sending messages. It features human-like interaction patterns and anti-ban protections to securely manage chats and communications through natural language.
    3
    68
    MIT
  • A
    license
    -
    quality
    B
    maintenance
    A WhatsApp Web MCP server that enables reading chats, contacts, and messages, as well as sending, replying, reacting, and managing WhatsApp messages via stdio.
    49
    3
    MIT
  • A
    license
    -
    quality
    C
    maintenance
    MCP server that connects AI agents to WhatsApp using the multi-device API, enabling messaging, group management, and more as a regular user.
    8
    MIT

View all related MCP servers

Related MCP Connectors

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/moonslayers/whatsapp-cdp-mcp'

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