mcp-whatsapp
Servidor MCP local para una cuenta personal de WhatsApp. Binario único en Go que envuelve whatsmeow. Añade resolución de LID, almacenamiento de mensajes enviados, temporizadores de mensajes que desaparecen y sincronización de historial dirigida. Uso personal; se aplican los Términos de Servicio de Meta.
Documentación
Servidor MCP de WhatsApp
Un servidor MCP de un solo binario en Go que envuelve whatsmeow para exponer una cuenta personal de WhatsApp a los LLMs. whatsapp-mcp serve se ejecuta como un daemon HTTP ligero en 127.0.0.1:8765; los clientes MCP (Claude Desktop, Cursor, Claude Code, etc.) se conectan a él vía HTTP — sin creación de procesos, sin manejo de stdin/stdout. Los mensajes se almacenan en caché en SQLite local y solo viajan al modelo cuando el agente llama a una herramienta.
Sin afiliación. Este es un proyecto independiente de código abierto. No está afiliado, respaldado ni asociado de ninguna manera con Meta Platforms, Inc., WhatsApp o whatsmeow. "WhatsApp" es una marca comercial de Meta Platforms, Inc., utilizada aquí de forma nominativa para describir interoperabilidad.
Esto comenzó como un fork de lharries/whatsapp-mcp y desde entonces se ha reescrito como un único binario en Go. Lo que añade sobre el original:
- Resolución de LID — normaliza los JIDs de
@lida números de teléfono reales para una coincidencia precisa de contactos. - Almacenamiento de mensajes enviados — los mensajes salientes se persisten localmente para que el historial de conversaciones permanezca completo.
- Temporizadores de mensajes efímeros — los mensajes salientes heredan automáticamente el temporizador efímero del chat grupal.
- Sincronización de historial dirigida — relleno bajo demanda por chat mediante la herramienta
request_sync. - Superficie de herramientas ampliada — 42 herramientas (ver más abajo): reacciones, respuestas, ediciones, revocar, marcar como leído, escribiendo, está-en-whatsapp, administración completa de grupos, lista de bloqueados, encuestas (crear + votar + recuento), tarjetas de contacto, indicador de ver-una-vez, presencia, ajustes de privacidad y el texto "Acerca de" del perfil.
- Aplicación de instancia única — un
flock(2)enstore/.lockevita que dos procesos deservecompitan por los mismos archivos SQLite.
Configuración
Requisitos previos
- Go 1.26+ (solo en tiempo de compilación; el tiempo de ejecución solo necesita el binario compilado).
- Un cliente MCP que hable HTTP (Claude Desktop, Cursor, Claude Code, etc.).
- FFmpeg (opcional) — requerido solo para
send_audio_messagecuando la entrada no es ya.oggOpus. Sin él, usasend_filepara enviar audio sin procesar. - Windows: CGO debe estar habilitado — ver docs/windows.md.
Instalación
git clone https://github.com/Sealjay/mcp-whatsapp.git
cd mcp-whatsapp
make build # writes ./bin/whatsapp-mcp
Vincular tu teléfono (solo la primera ejecución)
Inicia el daemon y luego abre la página de vinculación en un navegador:
./bin/whatsapp-mcp serve # starts on 127.0.0.1:8765
open http://127.0.0.1:8765/pair # macOS; or visit the URL manually
Escanea el código QR con WhatsApp (Configuración → Dispositivos vinculados → Vincular un dispositivo). La sesión persiste en ./store/whatsapp.db; WhatsApp la rota aproximadamente cada 20 días, cuando /pair muestra un nuevo código QR.
- Sin navegador:
./bin/whatsapp-mcp loginrenderiza el código QR en la terminal. - Sin escaneo de QR: usa el formulario de número de teléfono en
/pair(ologin -phone +<international number>), luego escribe el código en Vincular un dispositivo → Vincular con número de teléfono en su lugar. - Los endpoints de
/pair/*están protegidos contra CSRF y limitados en velocidad (15 GET/min, 1 restablecimiento/min, 3 solicitudes de código/min).
Conecta tu cliente MCP
serve escucha en 127.0.0.1:8765 (o $WHATSAPP_MCP_ADDR) y sirve MCP en /mcp. Es sin estado (sin Mcp-Session-Id) y habla MCP 2026-07-28 así como revisiones anteriores.
// Claude Code (.claude/mcp.json or ~/.claude/mcp.json) and Cursor (~/.cursor/mcp.json)
{ "mcpServers": { "whatsapp": { "type": "http", "url": "http://127.0.0.1:8765/mcp" } } }
// Claude Desktop only launches stdio servers, so bridge with mcp-remote
// (~/Library/Application Support/Claude/claude_desktop_config.json)
{ "mcpServers": { "whatsapp": { "command": "npx", "args": ["-y", "mcp-remote", "http://127.0.0.1:8765/mcp"] } } }
Reinicia el cliente. El daemon sobrevive a los reinicios del cliente.
Envío y recepción de archivos
WHATSAPP_MCP_MEDIA_ROOT (por defecto <store>/uploads/, creado en la primera ejecución) es el único directorio que las herramientas de archivos pueden tocar:
- Envío:
send_fileysend_audio_messagetoman unmedia_pathbajo la raíz. - Recepción:
download_mediaalmacena en caché en<store>/<chat_jid>/<hex(message_id)>/<safe_filename>(verificado contra el tamaño almacenado y SHA-256). Unoutput_pathopcional también coloca el archivo bajo la raíz; un archivo existente allí hace que la llamada sea una operación sin efecto.
Las rutas fuera de la raíz, incluidos los enlaces simbólicos que resuelven fuera de ella, se rechazan. Cualquier cosa dentro de la raíz es legible y escribible por las herramientas, así que mantén los secretos fuera.
Clientes en sandbox (Claude Cowork y similares) no pueden leer la caché del daemon. Apunta WHATSAPP_MCP_MEDIA_ROOT a un directorio que el sandbox pueda leer y dale al cliente esta instrucción:
Al llamar al
download_mediadel MCP de WhatsApp, pasa siempreoutput_pathestablecido en una ruta bajo tu espacio de trabajo compartido. Sin él, el archivo descifrado aterriza solo en la caché local del daemon, que está fuera de tu sandbox y no es legible.output_pathdebe vivir bajoWHATSAPP_MCP_MEDIA_ROOTen el lado del daemon; el nombre base es tuyo para elegir.
Banderas de servicio y entorno
-addr host:port(envWHATSAPP_MCP_ADDR, por defecto127.0.0.1:8765).-allow-remote: vincula una dirección que no sea de loopback. RequiereWHATSAPP_MCP_TOKEN, un token portador para/mcpy/pair/*;servesale sin él.WHATSAPP_MCP_MEDIA_ROOT: la raíz de la lista blanca de archivos anterior.WHATSAPP_MCP_DEBUG=1o-debug: registros verbosos (ver Registro de depuración).
Ejecución del daemon
Los eventos llegan a SQLite solo mientras serve se ejecuta. Después de un tiempo de inactividad, whatsmeow rellena mediante HistorySync dentro de la ventana de retención propia de WhatsApp; cualquier cosa más antigua se pierde, y request_sync rellena un chat bajo demanda. Un flock(2) en store/.lock permite un serve (o login) por almacén.
| Opción | Cómo |
|---|---|
| macOS launchd | Copia docs/launchd/com.sealjay.whatsapp-mcp.plist a ~/Library/LaunchAgents/, completa {{PATH_TO_REPO}} / {{STORE_DIR}}, launchctl load |
| Linux systemd | Copia docs/systemd/whatsapp-mcp.service a ~/.config/systemd/user/, completa los marcadores de posición, systemctl --user enable --now whatsapp-mcp |
| Gancho de Claude Code | Pon docs/hooks/setup.sh en .claude/hooks/ y llámalo desde settings.json (idempotente) |
| Manual | ./bin/whatsapp-mcp serve |
Docker. La imagen (debian:bookworm-slim más ffmpeg, unos cientos de MB) ejecuta -store /home/app/store; persiste ese volumen. Vincula primero con un contenedor único de login, porque login y un serve en ejecución no pueden compartir el bloqueo del almacén:
docker build -t whatsapp-mcp --build-arg VERSION=$(git describe --tags --always) .
docker run --rm -it -v whatsapp-mcp-store:/home/app/store whatsapp-mcp login
TOKEN=$(openssl rand -hex 32); echo "$TOKEN"
docker run -d --name whatsapp-mcp -v whatsapp-mcp-store:/home/app/store \
-p 127.0.0.1:8765:8765 -e WHATSAPP_MCP_TOKEN="$TOKEN" \
whatsapp-mcp serve -addr 0.0.0.0:8765 -allow-remote
Los clientes luego envían Authorization: Bearer $TOKEN. El contenedor vincula loopback por defecto, así que -allow-remote más un token es lo que hace que -p funcione. Para re-vincular después de que WhatsApp rote la sesión (aproximadamente cada 20 días), detén el contenedor, vuelve a ejecutar el contenedor de login e inícialo de nuevo.
Arquitectura
cmd/whatsapp-mcp/ login / serve / smoke subcommands
internal/client/ whatsmeow client wrapper (send, download, events, history, features)
internal/daemon/ HTTP server, pairing state machine, /pair endpoints
internal/mcp/ mark3labs/mcp-go v1 server + tool registrations
internal/media/ ogg parsing, waveform synthesis, ffmpeg shell-out
internal/security/ path allowlisting, filename sanitisation, log redaction
internal/store/ SQLite cache, LID resolution, query layer
Los datos viven bajo ./store/ (-store DIR): messages.db (caché de mensajes), whatsapp.db (sesión de whatsmeow) y .lock. Las herramientas de consulta leen SQLite; las herramientas de envío y acción llaman a whatsmeow; los eventos entrantes se escriben en SQLite por una goroutine en el mismo proceso.
Herramientas
42 herramientas, agrupadas por propósito.
Lectura / consulta
| Herramienta | Propósito |
|---|---|
search_contacts | Búsqueda de subcadenas en nombres de contactos y números de teléfono en caché |
list_messages | Consulta + filtro de mensajes; devuelve texto formateado con ventanas de contexto |
list_chats | Lista de chats con vista previa del último mensaje; ordena por actividad o nombre |
get_chat | Metadatos del chat por JID |
get_message_context | Ventana antes/después alrededor de un mensaje específico |
download_media | Descarga medios persistidos a una ruta local |
request_sync | Pide a WhatsApp que rellene el historial de un chat |
Envío
| Herramienta | Propósito |
|---|---|
send_message | Envía un mensaje de texto a un número de teléfono o JID |
send_file | Envía imagen/video/documento/audio sin procesar con subtítulo opcional; view_once: bool marca los submensajes de imagen/video/audio como ver-una-vez (ignorado para documentos) |
send_audio_message | Envía una nota de voz (auto-convierte vía ffmpeg si no es .ogg Opus); soporta view_once: bool |
send_poll | Envía una encuesta con una pregunta y 2+ opciones; selectable_count controla cuántas opciones puede elegir un votante. Genera el MessageSecret de 32 bytes requerido para que los votos se descifren |
send_poll_vote | Emite un voto en una encuesta vista previamente; options debe coincidir exactamente con los nombres de las opciones |
get_poll_results | Devuelve el recuento de una encuesta que tenemos en caché (incluye opciones con 0 votos) |
send_contact_card | Envía una tarjeta de contacto; sintetiza una vCard 3.0 desde name + phone, o pasa un vcard sin procesar para omitir la síntesis |
Acciones de mensajes
| Herramienta | Propósito |
|---|---|
mark_read | Marca IDs de mensajes específicos como leídos |
mark_chat_read | Confirma los mensajes entrantes más recientes en un chat para limpiar la insignia de no leídos |
send_reaction | Reacciona a un mensaje (emoji vacío limpia una reacción existente) |
send_reply | Respuesta de texto que cita un mensaje anterior |
edit_message | Edita un mensaje enviado previamente |
delete_message | Revoca (elimina para todos) un mensaje |
send_typing | Establece presencia de escribiendo / grabando por chat |
Grupos
| Herramienta | Propósito |
|---|---|
create_group | Crea un grupo con un nombre y participantes iniciales |
leave_group | Abandona un grupo |
list_groups | Lista todos los grupos de los que el usuario es miembro |
get_group_info | Metadatos completos del grupo (participantes, ajustes, configuración de invitación) |
update_group_participants | Añadir / eliminar / ascender / degradar participantes (action: add|remove|promote|demote). Comprueba primero que eres administrador; las personas cuyos ajustes de privacidad bloquean una adición directa reciben una tarjeta de invitación al grupo en su chat 1:1 (send_invite, por defecto true; invite_message de subtítulo opcional) |
set_group_name | Cambia el asunto del grupo |
set_group_topic | Cambia la descripción del grupo; cadena vacía la limpia |
set_group_announce | Alterna el modo solo-anuncios (solo los administradores pueden enviar) |
set_group_locked | Alterna el modo bloqueado (solo los administradores pueden editar los metadatos del grupo) |
get_group_invite_link | Obtiene el enlace de invitación; reset: true revoca el enlace anterior primero |
join_group_with_link | Únete a un grupo mediante una URL de chat.whatsapp.com o código de invitación simple |
Lista de bloqueados
| Herramienta | Propósito |
|---|---|
get_blocklist | Devuelve la lista de bloqueados actual (JIDs, usualmente @lid, más números de teléfono donde se conocen) |
block_contact | Bloquea un contacto por número de teléfono o JID |
unblock_contact | Desbloquea un contacto |
Privacidad / presencia / estado
| Herramienta | Propósito |
|---|---|
send_presence | Establece la disponibilidad propia (available o unavailable) — distinta del send_typing por chat |
get_privacy_settings | Ajustes de privacidad actuales como JSON |
set_privacy_setting | Cambia un ajuste de privacidad por name + value (validación estricta de enumeraciones; las combinaciones inválidas se rechazan) |
set_status_message | Actualiza el texto "Acerca de" del perfil; cadena vacía lo limpia |
Administración
| Herramienta | Propósito |
|---|---|
is_on_whatsapp | Verificación por lotes de qué números de teléfono están registrados en WhatsApp |
get_status | Informa si el puente está conectado y con qué cuenta está vinculado |
pairing_status | Informa el estado de vinculación del dispositivo como un sobre setup_state estructurado (ready / awaiting_qr + qr_payload / error) para supervisores programáticos que muestran el QR de vinculación |
Diferido
Intencionalmente aún no expuesto:
subscribe_presence— sin capa de persistencia para eventos de presencia, omitido para evitar una herramienta colgante.- Configurador de foto de perfil — whatsmeow aguas arriba no expone un configurador a nivel de usuario.
- Participantes en modo aprobación, comunidades, boletines — superficie de bajo uso, diferido.
Limitaciones
- Riesgo de inyección de prompts: como muchos servidores MCP, este está sujeto a la tríada letal. La inyección de prompts en mensajes entrantes podría provocar la exfiltración de datos privados: trate la superficie de herramientas en consecuencia.
- Lapsos de mensajes: consulte Ejecutando el daemon.
- Instancia única por almacén: solo un
whatsapp-mcp servepuede mantener el bloqueo del almacén. Los clientes MCP paralelos deben apuntar a directorios-storediferentes (y, por lo tanto, a sesiones emparejadas diferentes). - Windows: requiere CGO y un compilador de C — consulte docs/windows.md.
- Límites ascendentes: la obtención/envío de mensajes está limitado por lo que whatsmeow soporta contra la API multidispositivo web de WhatsApp.
Desarrollo
make test # unit tests
make test-race # with -race
make vet # go vet
make e2e # build + JSON-RPC smoke over HTTP (requires -tags=e2e)
make smoke # boot-test the server without connecting to WhatsApp
Actualización de whatsmeow
El CI semanal ejecuta una sonda de actualización ascendente. Para hacerlo manualmente:
make upgrade-check
Esto actualiza go.mau.fi/whatsmeow@main, reordena, compila y prueba. Si todo está en verde, confirme los cambios de go.mod / go.sum.
scripts/mdtest-parity.sh en CI falla la compilación temprano si el upstream elimina o renombra cualquier método de whatsmeow que llamamos: es el canario para la deriva de la API.
Solución de problemas
connect failed …enserve— el daemon no está emparejado. Abrahttp://127.0.0.1:8765/pairen un navegador y escanee el código QR. Alternativamente, ejecute./bin/whatsapp-mcp loginen una terminal.another whatsapp-mcp instance is already running— solo unservepuede mantener el bloqueo del almacén. Verifique si hay un proceso suelto (ps aux | grep whatsapp-mcp) u otro cliente MCP apuntando al mismo directorio-store.- El QR no se muestra — la terminal no renderiza Unicode de medio bloque. Pruebe con iTerm2, Windows Terminal o similar.
- Límite de dispositivos alcanzado — WhatsApp limita los dispositivos vinculados. Elimine uno desde Configuración → Dispositivos vinculados en su teléfono.
- No se cargan mensajes — después de la autenticación inicial, puede tomar varios minutos que el historial se rellene. Use
request_syncpara apuntar a un chat específico. - WhatsApp desincronizado — elimine ambos archivos de base de datos (
store/messages.dbystore/whatsapp.db) y vuelva a ejecutarlogin. ffmpeg not found—send_audio_messagenecesita ffmpeg enPATHpara convertir audio que no sea Opus. Usesend_filepara audio sin procesar en su lugar.
Registro de depuración
Por defecto, los registros de stderr redactan los JID a …<last-4>, resumen los cuerpos como [<length>B: text|url|command] y colapsan las URL de medios a <scheme>://<host>/…. Para ver el contenido mientras depura, ejecute ./bin/whatsapp-mcp -debug serve o establezca WHATSAPP_MCP_DEBUG=1. Incluso entonces, los dígitos con forma de número de teléfono muestran solo sus últimos 5 (+15551234567 → ****34567). Eso es ofuscación, no anonimización: alguien que conozca sus contactos aún puede correlacionarlos.
Para problemas de integración con Claude Desktop, consulte la documentación de MCP.
Contribuciones
Las contribuciones son bienvenidas mediante solicitudes de extracción. Consulte CONTRIBUTING.md.
Licencia
Licencia MIT — consulte LICENSE.