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

License: MIT CI Go Report Card Go 1.25+ MCP 42 tools whatsmeow Sealjay/mcp-whatsapp MCP server GitHub issues

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 @lid a 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, revocación, marcar como leído, escritura, is-on-whatsapp, administración completa de grupos, lista de bloqueados, encuestas (crear + votar + recuento), tarjetas de contacto, indicador de vista única, presencia, ajustes de privacidad y el texto "Acerca de" del perfil.
  • Aplicación de instancia única — un flock(2) en store/.lock evita que dos procesos serve compitan por los mismos archivos SQLite.

Configuración

Requisitos previos

  • Go 1.25+ (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_message cuando la entrada no es ya .ogg Opus. Sin él, usa send_file para 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 en tu teléfono (Ajustes → Dispositivos vinculados → Vincular un dispositivo). La vinculación persiste en ./store/whatsapp.db. Cuando WhatsApp invalida la sesión (aproximadamente cada 20 días), visita /pair de nuevo y vuelve a escanear.

Alternativa (sin interfaz gráfica / CI): ./bin/whatsapp-mcp login renderiza el QR en la terminal. Úsalo cuando no haya un navegador disponible.

Conecta tu cliente MCP

whatsapp-mcp serve es un daemon HTTP en 127.0.0.1:8765 (o $WHATSAPP_MCP_ADDR). Los clientes MCP se conectan a él por HTTP:

// Claude Desktop — ~/Library/Application Support/Claude/claude_desktop_config.json
{
  "mcpServers": {
    "whatsapp": { "url": "http://127.0.0.1:8765/mcp" }
  }
}
// Claude Code — .claude/mcp.json (project) or ~/.claude/mcp.json (user)
{
  "mcpServers": {
    "whatsapp": { "type": "http", "url": "http://127.0.0.1:8765/mcp" }
  }
}
// Cursor — ~/.cursor/mcp.json
{
  "mcpServers": {
    "whatsapp": { "type": "http", "url": "http://127.0.0.1:8765/mcp" }
  }
}

Reinicia el cliente. WhatsApp aparece como una integración disponible. Cerrar y reabrir el cliente se reconecta al daemon — sin creación de procesos, sin handshake por sesión, sin manejo de stdin/stdout.

Envío y recepción de archivos

WHATSAPP_MCP_MEDIA_ROOT controla ambas direcciones del movimiento de archivos:

  • Envíosend_file y send_audio_message aceptan un argumento media_path que apunta al archivo a enviar. La ruta debe estar bajo la raíz permitida.
  • Recepcióndownload_media escribe los medios descifrados en la caché del daemon en <store>/<chat_jid>/. Pasar el argumento opcional output_path además coloca el archivo en una ubicación elegida por el llamante, que también debe estar bajo la raíz permitida. Si ya existe un archivo en output_path, la llamada es un no-op.

Por defecto, la raíz permitida es ./store/uploads/ (resuelta en relación con tu directorio -store). En la primera ejecución, serve la crea automáticamente; coloca los archivos que quieras enviar en ella y apunta output_path aquí si quieres leer los medios entrantes desde el mismo lugar.

Para permitir un directorio diferente, establece WHATSAPP_MCP_MEDIA_ROOT (ruta absoluta) al iniciar el daemon:

WHATSAPP_MCP_MEDIA_ROOT=/Users/me/whatsapp-shared ./bin/whatsapp-mcp serve

O añádelo a tu plist de launchd / unidad de systemd / perfil de shell para que persista entre reinicios.

Las rutas fuera de la raíz permitida se rechazan con un error claro para que Claude pueda pedirte que muevas el archivo o actualices la variable de entorno. Los enlaces simbólicos dentro de la raíz se resuelven antes de la comprobación, por lo que un enlace simbólico que apunte fuera de la raíz también se rechaza. No coloques secretos dentro de la raíz permitida — la lista de permitidos limita lo que la herramienta puede leer o escribir, pero cualquier cosa dentro está en juego.

Clientes en sandbox (Claude.ai con Cowork, etc.)

Los clientes MCP en sandbox no pueden leer la caché local del daemon. Para que los medios descargados sean visibles para ellos, apunta WHATSAPP_MCP_MEDIA_ROOT a un directorio que el sandbox del cliente también pueda leer (un montaje del espacio de trabajo de Cowork, un volumen compartido, etc.), y dile al cliente que pase output_path en cada llamada a download_media dentro de esa raíz. Una instrucción de sistema copiable y pegable:

Al llamar al download_media del MCP de WhatsApp, pasa siempre output_path establecido 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_path debe estar bajo WHATSAPP_MCP_MEDIA_ROOT en el lado del daemon; el nombre base es tuyo para elegir.

Arquitectura

Un binario, siete paquetes internos:

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 endpoint
internal/mcp/           mark3labs/mcp-go 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

Ciclo de vida del proceso

serve se ejecuta como un daemon HTTP de larga duración. Los clientes MCP se conectan y desconectan libremente; el daemon permanece activo y continúa recibiendo eventos de WhatsApp. Un flock(2) en store/.lock evita que dos instancias compitan por el mismo almacén (WhatsApp expulsaría de todos modos una de las dos conexiones de dispositivo vinculado).

La compensación: los eventos se persisten en SQLite solo mientras serve está en ejecución. Si el daemon se detiene, la conexión de WhatsApp se cierra. En el siguiente inicio, whatsmeow emite eventos events.HistorySync que rellenan las conversaciones en SQLite, pero la ventana de recuperación está gobernada por la retención del lado del servidor de WhatsApp para clientes multidispositivo — no por este código. Los mensajes que llegan durante una brecha lo suficientemente larga como para superar la retención de WhatsApp no son recuperables. Para brechas conocidas más cortas, la herramienta request_sync activa un relleno por chat bajo demanda.

Almacenamiento de datos

Todo vive bajo ./store/ (anula con -store DIR):

  • store/messages.db — caché local de chats/mensajes, indexada para búsqueda.
  • store/whatsapp.db — estado de dispositivo/sesión propio de whatsmeow.
  • store/.lock — bloqueo de asesoramiento efímero para instancia única de serve.

Flujo de datos

  1. El cliente envía un tools/call JSON-RPC a serve por HTTP.
  2. La capa MCP despacha a un manejador interno.
  3. El manejador consulta el almacén SQLite local o llama a whatsmeow directamente (enviar, descargar, reacciones, etc.).
  4. Los eventos entrantes de WhatsApp se persisten en el almacén en una goroutine en segundo plano dentro del mismo proceso, por lo que las herramientas de consulta siempre ven el estado actual.

Ejecución del daemon

El daemon está diseñado para ejecutarse independientemente de cualquier cliente MCP. Tres modelos de ciclo de vida compatibles:

macOS — launchd. Plantilla en docs/launchd/com.sealjay.whatsapp-mcp.plist. Copia a ~/Library/LaunchAgents/, reemplaza los marcadores de posición {{PATH_TO_REPO}} / {{STORE_DIR}}, launchctl load. El daemon se ejecuta desde el inicio de sesión en adelante.

Linux — unidad de usuario de systemd. Plantilla en docs/systemd/whatsapp-mcp.service. Copia a ~/.config/systemd/user/, reemplaza los marcadores de posición, systemctl --user enable --now whatsapp-mcp.

Hook de SessionStart de Claude Code. Para ciclos de vida con alcance de proyecto, coloca docs/hooks/setup.sh en el .claude/hooks/ de tu proyecto y configura settings.json para invocarlo. El hook es idempotente — seguro de ejecutar junto con launchd/systemd.

Manual. ./bin/whatsapp-mcp serve -addr 127.0.0.1:8765 en cualquier terminal. Ctrl-C para detener.

La vinculación inicial ocurre en un navegador: inicia el daemon, abre http://127.0.0.1:8765/pair, escanea el QR con tu teléfono. No se requiere terminal. El protocolo multidispositivo de WhatsApp rota la sesión del dispositivo vinculado aproximadamente cada 20 días; cuando eso ocurre, la página /pair sirve un QR nuevo automáticamente — visítala de nuevo y vuelve a vincular. Los endpoints /pair/* tienen límite de velocidad (5 GET/min, 1 POST/min en /pair/reset) y están protegidos contra CSRF.

Banderas y variables de entorno para serve:

  • -addr host:port (env WHATSAPP_MCP_ADDR, predeterminado 127.0.0.1:8765).
  • -allow-remote (opt-in explícito para vincular una dirección que no sea de loopback; requiere WHATSAPP_MCP_TOKEN).
  • WHATSAPP_MCP_TOKEN — token de portador para /mcp y /pair/* cuando -allow-remote está establecido. Requerido; serve sale si falta.
  • WHATSAPP_MCP_MEDIA_ROOT — raíz permitida para send_file / send_audio_message media_path y download_media output_path.
  • WHATSAPP_MCP_DEBUG=1 — habilita el registro verboso con redacción parcial de números de teléfono (últimos 5 dígitos visibles).

Herramientas

42 herramientas, agrupadas por propósito.

Leer / consultar

HerramientaPropósito
search_contactsBúsqueda de subcadenas en nombres de contactos y números de teléfono en caché
list_messagesConsultar + filtrar mensajes; devuelve texto formateado con ventanas de contexto
list_chatsListar chats con vista previa del último mensaje; ordenar por actividad o nombre
get_chatMetadatos del chat por JID
get_message_contextVentana antes/después alrededor de un mensaje específico
download_mediaDescargar medios persistidos a una ruta local
request_syncPedir a WhatsApp que rellene el historial de un chat

Enviar

HerramientaPropósito
send_messageEnviar un mensaje de texto a un número de teléfono o JID
send_fileEnviar imagen/video/documento/audio sin procesar con subtítulo opcional; view_once: bool marca los submensajes de imagen/video/audio como de vista única (ignorado para documentos)
send_audio_messageEnviar una nota de voz (auto-convierte vía ffmpeg si no es .ogg Opus); admite view_once: bool
send_pollEnviar 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_voteEmitir un voto en una encuesta vista previamente; options debe coincidir exactamente con los nombres de las opciones
get_poll_resultsDevolver el recuento de una encuesta que tenemos en caché (incluye opciones con 0 votos)
send_contact_cardEnviar una tarjeta de contacto; sintetiza una vCard 3.0 a partir de name + phone, o pasa un vcard sin procesar para omitir la síntesis

Acciones de mensajes

HerramientaPropósito
mark_readMarcar IDs de mensajes específicos como leídos
mark_chat_readConfirmar los mensajes entrantes más recientes en un chat para limpiar la insignia de no leídos
send_reactionReaccionar a un mensaje (emoji vacío limpia una reacción existente)
send_replyRespuesta de texto que cita un mensaje anterior
edit_messageEditar un mensaje enviado previamente
delete_messageRevocar (eliminar para todos) un mensaje
send_typingEstablecer presencia de redacción / grabación por chat

Grupos

HerramientaPropósito
create_groupCrear un grupo con un nombre y participantes iniciales
leave_groupAbandonar un grupo
list_groupsListar todos los grupos de los que el usuario es miembro
get_group_infoMetadatos completos del grupo (participantes, ajustes, configuración de invitación)
update_group_participantsAñadir / eliminar / ascender / degradar participantes (action: add|remove|promote|demote)
set_group_nameCambiar el asunto del grupo
set_group_topicCambiar la descripción del grupo; una cadena vacía la borra
set_group_announceAlternar el modo solo anuncios (solo los administradores pueden enviar)
set_group_lockedAlternar el modo bloqueado (solo los administradores pueden editar los metadatos del grupo)
get_group_invite_linkObtener el enlace de invitación; reset: true revoca el enlace anterior primero
join_group_with_linkUnirse a un grupo mediante una URL chat.whatsapp.com o un código de invitación simple

Lista de bloqueados

HerramientaPropósito
get_blocklistDevolver la lista de bloqueados actual
block_contactBloquear un contacto por número de teléfono o JID
unblock_contactDesbloquear un contacto

Privacidad / presencia / estado

HerramientaPropósito
send_presenceEstablecer la propia disponibilidad (available o unavailable) — distinto del send_typing por chat
get_privacy_settingsAjustes de privacidad actuales como JSON
set_privacy_settingCambiar un ajuste de privacidad mediante name + value (validación estricta de enumerados; las combinaciones inválidas se rechazan)
set_status_messageActualizar el texto "Acerca de" del perfil; una cadena vacía lo borra

Administración

HerramientaPropósito
is_on_whatsappComprobar por lotes qué números de teléfono están registrados en WhatsApp
get_statusInformar si el puente está conectado y con qué cuenta está emparejado
pairing_statusInformar del estado de emparejamiento 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

Intencionadamente aún no expuesto:

  • subscribe_presence — no hay capa de persistencia para eventos de presencia, se omite para evitar una herramienta colgante.
  • Establecedor de foto de perfil — whatsmeow aguas arriba no expone un establecedor a nivel de usuario.
  • Participantes en modo aprobación, comunidades, boletines — superficie de bajo uso, diferido.

Limitaciones

  • Riesgo de inyección de instrucciones: como en muchos servidores MCP, este está sujeto a la tríada letal. La inyección de instrucciones en mensajes entrantes podría provocar la exfiltración de datos privados — trate la superficie de herramientas en consecuencia.
  • Reautenticación: WhatsApp puede invalidar la sesión del dispositivo vinculado periódicamente; vuelva a ejecutar ./bin/whatsapp-mcp login cuando ocurra.
  • Lagunas de mensajes cuando serve no está en ejecución: los eventos solo fluyen a SQLite mientras el binario está activo. Los mensajes enviados durante una ventana sin conexión se recuperan en la siguiente reconexión solo si la retención de multidispositivo de WhatsApp aún los conserva; para lagunas más largas use request_sync por chat, o acepte la pérdida.
  • Instancia única por almacén: solo un whatsapp-mcp serve puede mantener el bloqueo del almacén. Los clientes MCP paralelos deben apuntar a directorios -store diferentes (y por tanto a sesiones emparejadas diferentes).
  • Windows: requiere CGO y un compilador de C — consulte docs/windows.md.
  • Límites aguas arriba: la obtención/envío de mensajes está limitado por lo que whatsmeow soporta contra la API de multidispositivo web de WhatsApp.
  • La redacción de registros es ofuscación, no anonimización. El conocimiento parcial de sus contactos permite la correlación a partir de los últimos 5 dígitos visibles. Los enlaces simbólicos dentro de ./store/uploads/ se resuelven antes de la comprobación de ruta para que no puedan escapar, pero la raíz misma es un límite de confianza — solo coloque archivos que tenga intención de enviar dentro de ella.

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

Actualizar whatsmeow

El CI semanal ejecuta una sonda de actualización aguas arriba. 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 aguas arriba 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 … en serve — el daemon no está emparejado. Abra http://127.0.0.1:8765/pair en un navegador y escanee el QR. Alternativamente, ejecute ./bin/whatsapp-mcp login en una terminal.
  • another whatsapp-mcp instance is already running — solo un serve puede mantener el bloqueo del almacén. Compruebe 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 Ajustes → Dispositivos vinculados en su teléfono.
  • No se cargan mensajes — tras la autenticación inicial, puede tardar varios minutos en rellenarse el historial. Use request_sync para apuntar a un chat específico.
  • WhatsApp desincronizado — elimine ambos archivos de base de datos (store/messages.db y store/whatsapp.db) y vuelva a ejecutar login.
  • ffmpeg not foundsend_audio_message necesita ffmpeg en PATH para convertir audio no Opus. Use send_file para audio sin procesar en su lugar.

Registro de depuración

Por defecto, los JID en los registros de stderr se redactan a …<last-4-chars-of-user-part> y los cuerpos de mensajes se resumen como [<length>B: text|url|command]. Las URL de CDN de medios se colapsan a <scheme>://<host>/…. Para ver el contenido de los mensajes mientras depura activamente:

  • Como bandera: ./bin/whatsapp-mcp -debug serve

  • Como variable de entorno en la configuración de su cliente MCP:

    "env": { "WHATSAPP_MCP_DEBUG": "1" }
    

Incluso con el modo de depuración activado, las secuencias de dígitos con forma de número de teléfono en cuerpos y JID están parcialmente enmascaradas — solo los últimos 5 dígitos son visibles (p. ej. +15551234567****34567). Esto significa que los registros de depuración son seguros para compartir en informes de errores sin filtrar números de teléfono completos.

Aviso de honestidad. El esquema de redacción parcial es ofuscación para la comodidad del lector de registros, no anonimización. Alguien con conocimiento independiente de sus contactos aún puede correlacionar los últimos 5 dígitos con un número de teléfono específico. Trate los registros redactados como "probablemente seguros para pegar en un issue de GitHub", no como "anonimizados".

Para problemas de integración con Claude Desktop, consulte la documentación de MCP.

Contribuciones

Las contribuciones son bienvenidas mediante pull request. Consulte CONTRIBUTING.md.

Licencia

Licencia MIT — consulte LICENSE.