mcp-whatsapp

Servidor MCP local para uma conta pessoal do WhatsApp. Binário único em Go que encapsula o whatsmeow. Adiciona resolução de LID, armazenamento de mensagens enviadas, temporizadores de mensagens que desaparecem e sincronização direcionada de histórico. Uso pessoal; os Termos de Serviço do Meta se aplicam.

Documentação

Servidor MCP do WhatsApp

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

Um servidor MCP em Go de binário único que encapsula o whatsmeow para expor uma conta pessoal do WhatsApp a LLMs. O whatsapp-mcp serve roda como um daemon HTTP leve na porta 127.0.0.1:8765; clientes MCP (Claude Desktop, Cursor, Claude Code, etc.) conectam-se a ele via HTTP — sem spawn de processos, sem manipulação de stdin/stdout. As mensagens são armazenadas em cache no SQLite local e só chegam ao modelo quando o agente chama uma ferramenta.

Sem afiliação. Este é um projeto open-source independente. Não é afiliado, endossado ou associado à Meta Platforms, Inc., WhatsApp ou whatsmeow. "WhatsApp" é uma marca registrada da Meta Platforms, Inc., usada aqui de forma nominativa para descrever interoperabilidade.

Este projeto começou como um fork do lharries/whatsapp-mcp e desde então foi reescrito como um binário Go único. O que ele adiciona em relação ao original:

  • Resolução de LID — normaliza JIDs @lid para números de telefone reais, garantindo correspondência precisa de contatos.
  • Armazenamento de mensagens enviadas — mensagens de saída são persistidas localmente para que o histórico da conversa permaneça completo.
  • Temporizadores de mensagens efêmeras — mensagens de saída herdam automaticamente o temporizador efêmero do chat em grupo.
  • Sincronização de histórico direcionada — backfill sob demanda por chat via a ferramenta request_sync.
  • Superfície de ferramentas estendida — 42 ferramentas (veja abaixo): reações, respostas, edições, revogação, marcar como lida, digitando, está-no-whatsapp, administração completa de grupos, lista de bloqueio, enquetes (criar + votar + apurar), cartões de contato, sinalizador de visualização única, presença, configurações de privacidade e o texto "Recado" do perfil.
  • Imposição de instância única — um flock(2) na porta store/.lock impede que dois processos serve disputem os mesmos arquivos SQLite.

Configuração

Pré-requisitos

  • Go 1.25+ (apenas em tempo de build; em tempo de execução basta o binário compilado).
  • Um cliente MCP que fale HTTP (Claude Desktop, Cursor, Claude Code, etc.).
  • FFmpeg (opcional) — necessário apenas para send_audio_message quando a entrada não for .ogg Opus. Sem ele, use send_file para enviar áudio bruto.
  • Windows: CGO deve estar habilitado — veja docs/windows.md.

Instalação

git clone https://github.com/Sealjay/mcp-whatsapp.git
cd mcp-whatsapp
make build    # writes ./bin/whatsapp-mcp

Vincule seu telefone (apenas na primeira execução)

Inicie o daemon e abra a página de vinculação no 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

Escaneie o QR code com o WhatsApp no seu telefone (Configurações → Aparelhos conectados → Conectar um aparelho). A vinculação persiste em ./store/whatsapp.db. Quando o WhatsApp invalidar a sessão (aproximadamente a cada 20 dias), visite /pair novamente e reescaneie.

Alternativa (headless / CI): ./bin/whatsapp-mcp login renderiza o QR no terminal. Use isso quando não houver navegador disponível.

Conecte seu cliente MCP

whatsapp-mcp serve é um daemon HTTP na porta 127.0.0.1:8765 (ou $WHATSAPP_MCP_ADDR). Clientes MCP conectam-se a ele via 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" }
  }
}

Reinicie o cliente. O WhatsApp aparece como uma integração disponível. Fechar e reabrir o cliente reconecta ao daemon — sem spawn de processo, sem handshake por sessão, sem manipulação de stdin/stdout.

Enviando e recebendo arquivos

WHATSAPP_MCP_MEDIA_ROOT controla o movimento de arquivos em ambas as direções:

  • Enviosend_file e send_audio_message aceitam um argumento media_path apontando para o arquivo a ser enviado. O caminho deve estar sob a raiz permitida.
  • Recebimentodownload_media grava mídia descriptografada no cache do daemon em <store>/<chat_jid>/. Passar o argumento opcional output_path adicionalmente coloca o arquivo em um local escolhido pelo chamador, que também deve estar sob a raiz permitida. Se um arquivo já existir em output_path, a chamada é um no-op.

Por padrão, a raiz permitida é ./store/uploads/ (resolvida em relação ao seu diretório -store). Na primeira execução, serve a cria automaticamente; coloque os arquivos que deseja enviar nela e aponte output_path para cá se quiser ler mídia recebida do mesmo local.

Para permitir um diretório diferente, defina WHATSAPP_MCP_MEDIA_ROOT (caminho absoluto) ao iniciar o daemon:

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

Ou adicione-o ao seu plist do launchd / unit do systemd / perfil do shell para que persista entre reinicializações.

Caminhos fora da raiz permitida são rejeitados com um erro claro para que o Claude possa pedir que você mova o arquivo ou atualize a variável de ambiente. Symlinks dentro da raiz são resolvidos antes da verificação, então um symlink que aponte para fora da raiz também é rejeitado. Não coloque segredos dentro da raiz permitida — a allowlist delimita o que a ferramenta pode ler ou gravar, mas qualquer coisa dentro dela está acessível.

Clientes em sandbox (Claude.ai com Cowork, etc.)

Clientes MCP em sandbox não conseguem ler o cache local do daemon. Para tornar a mídia baixada visível para eles, aponte WHATSAPP_MCP_MEDIA_ROOT para um diretório que o sandbox do cliente também possa ler (um mount do workspace do Cowork, um volume compartilhado, etc.) e diga ao cliente para passar output_path em toda chamada download_media para essa raiz. Uma instrução de sistema copiável e colável:

Ao chamar o download_media do MCP do WhatsApp, sempre passe output_path definido para um caminho sob seu workspace compartilhado. Sem isso, o arquivo descriptografado cai apenas no cache local do daemon, que está fora do seu sandbox e é ilegível. output_path deve estar sob WHATSAPP_MCP_MEDIA_ROOT no lado do daemon; o nome base é de sua escolha.

Arquitetura

Um binário, sete pacotes 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 do processo

serve roda como um daemon HTTP de longa duração. Clientes MCP conectam-se e desconectam-se livremente; o daemon permanece ativo e continua recebendo eventos do WhatsApp. Um flock(2) na porta store/.lock impede que duas instâncias disputem o mesmo store (o WhatsApp expulsaria uma das duas conexões de dispositivo vinculado de qualquer forma).

A troca: eventos são persistidos no SQLite apenas enquanto serve estiver rodando. Se o daemon parar, a conexão com o WhatsApp é encerrada. Na próxima inicialização, o whatsmeow emite eventos events.HistorySync que fazem backfill das conversas no SQLite, mas a janela de recuperação é governada pela retenção do lado do servidor do WhatsApp para clientes multidispositivo — não por este código. Mensagens que chegam durante uma lacuna longa o suficiente para exceder a retenção do WhatsApp não são recuperáveis. Para lacunas conhecidas mais curtas, a ferramenta request_sync aciona um backfill por chat sob demanda.

Armazenamento de dados

Tudo vive sob ./store/ (substituível com -store DIR):

  • store/messages.db — cache local de chats/mensagens, indexado para busca.
  • store/whatsapp.db — estado de dispositivo/sessão do próprio whatsmeow.
  • store/.lock — lock consultivo efêmero para instância única do serve.

Fluxo de dados

  1. O cliente envia uma solicitação JSON-RPC tools/call para serve via HTTP.
  2. A camada MCP despacha para um handler interno.
  3. O handler consulta o store SQLite local ou chama o whatsmeow diretamente (envio, download, reações, etc.).
  4. Eventos recebidos do WhatsApp são persistidos no store em uma goroutine em segundo plano dentro do mesmo processo, então as ferramentas de consulta sempre veem o estado atual.

Executando o daemon

O daemon é projetado para rodar independentemente de qualquer cliente MCP. Três modelos de ciclo de vida suportados:

macOS — launchd. Modelo em docs/launchd/com.sealjay.whatsapp-mcp.plist. Copie para ~/Library/LaunchAgents/, substitua os placeholders {{PATH_TO_REPO}} / {{STORE_DIR}}, launchctl load. O daemon roda desde o login.

Linux — unit do systemd por usuário. Modelo em docs/systemd/whatsapp-mcp.service. Copie para ~/.config/systemd/user/, substitua os placeholders, systemctl --user enable --now whatsapp-mcp.

Hook SessionStart do Claude Code. Para ciclos de vida com escopo de projeto, coloque docs/hooks/setup.sh no .claude/hooks/ do seu projeto e configure settings.json para invocá-lo. O hook é idempotente — seguro para rodar junto com launchd/systemd.

Manual. ./bin/whatsapp-mcp serve -addr 127.0.0.1:8765 em qualquer terminal. Ctrl-C para parar.

A vinculação inicial acontece no navegador: inicie o daemon, abra http://127.0.0.1:8765/pair, escaneie o QR com seu telefone. Nenhum terminal é necessário. O protocolo multidispositivo do WhatsApp rotaciona a sessão do dispositivo vinculado aproximadamente a cada 20 dias; quando isso acontece, a página /pair serve um novo QR automaticamente — visite-a novamente e revincule. Os endpoints /pair/* têm limite de taxa (5 GET/min, 1 POST/min em /pair/reset) e são protegidos contra CSRF.

Flags e variáveis de ambiente para serve:

  • -addr host:port (env WHATSAPP_MCP_ADDR, padrão 127.0.0.1:8765).
  • -allow-remote (opt-in explícito para vincular um endereço não-loopback; requer WHATSAPP_MCP_TOKEN).
  • WHATSAPP_MCP_TOKEN — token bearer para /mcp e /pair/* quando -allow-remote estiver definido. Obrigatório; serve sai se estiver ausente.
  • WHATSAPP_MCP_MEDIA_ROOT — raiz permitida para send_file / send_audio_message media_path e download_media output_path.
  • WHATSAPP_MCP_DEBUG=1 — habilita log detalhado com redação parcial de números de telefone (últimos 5 dígitos visíveis).

Ferramentas

42 ferramentas, agrupadas por propósito.

Leitura / consulta

FerramentaPropósito
search_contactsBusca por substring em nomes de contatos e números de telefone em cache
list_messagesConsulta + filtro de mensagens; retorna texto formatado com janelas de contexto
list_chatsLista chats com prévia da última mensagem; ordena por atividade ou nome
get_chatMetadados do chat por JID
get_message_contextJanela antes/depois em torno de uma mensagem específica
download_mediaBaixa mídia persistida para um caminho local
request_syncPede ao WhatsApp para fazer backfill do histórico de um chat

Envio

FerramentaPropósito
send_messageEnvia uma mensagem de texto para um número de telefone ou JID
send_fileEnvia imagem/vídeo/documento/áudio bruto com legenda opcional; view_once: bool marca submensagens de imagem/vídeo/áudio como visualização única (ignorado para documentos)
send_audio_messageEnvia uma nota de voz (converte automaticamente via ffmpeg se não for .ogg Opus); suporta view_once: bool
send_pollEnvia uma enquete com uma pergunta e 2+ opções; selectable_count controla quantas opções um eleitor pode escolher. Gera o MessageSecret de 32 bytes necessário para que os votos sejam descriptografados
send_poll_voteVota em uma enquete vista anteriormente; options deve corresponder exatamente aos nomes das opções
get_poll_resultsRetorna a apuração de uma enquete que temos em cache (inclui opções com 0 votos)
send_contact_cardEnvia um cartão de contato; sintetiza um vCard 3.0 a partir de name + phone, ou passe um vcard bruto para pular a síntese

Ações de mensagem

FerramentaPropósito
mark_readMarca IDs de mensagem específicos como lidos
mark_chat_readConfirma as mensagens recebidas mais recentes em um chat para limpar o selo de não lidas
send_reactionReage a uma mensagem (emoji vazio limpa uma reação existente)
send_replyResposta de texto que cita uma mensagem anterior
edit_messageEdita uma mensagem enviada anteriormente
delete_messageRevoga (exclui para todos) uma mensagem
send_typingDefine presença de digitando / gravando por chat

Grupos

FerramentaFinalidade
create_groupCriar um grupo com um nome e participantes iniciais
leave_groupSair de um grupo
list_groupsListar todos os grupos dos quais o usuário é membro
get_group_infoMetadados completos do grupo (participantes, configurações, configuração de convite)
update_group_participantsAdicionar / remover / promover / rebaixar participantes (action: add|remove|promote|demote)
set_group_nameAlterar o assunto do grupo
set_group_topicAlterar a descrição do grupo; string vazia a limpa
set_group_announceAlternar o modo somente-anúncio (apenas administradores podem enviar)
set_group_lockedAlternar o modo bloqueado (apenas administradores podem editar metadados do grupo)
get_group_invite_linkObter o link de convite; reset: true revoga o link anterior primeiro
join_group_with_linkEntrar em um grupo via URL chat.whatsapp.com ou código de convite simples

Lista de bloqueio

FerramentaFinalidade
get_blocklistRetornar a lista de bloqueio atual
block_contactBloquear um contato por número de telefone ou JID
unblock_contactDesbloquear um contato

Privacidade / presença / status

FerramentaFinalidade
send_presenceDefinir a própria disponibilidade (available ou unavailable) — distinto do send_typing por chat
get_privacy_settingsConfigurações de privacidade atuais como JSON
set_privacy_settingAlterar uma configuração de privacidade por name + value (validação estrita de enum; combinações inválidas são rejeitadas)
set_status_messageAtualizar o texto "Sobre" do perfil; string vazia o limpa

Administração

FerramentaFinalidade
is_on_whatsappVerificação em lote de quais números de telefone estão registrados no WhatsApp
get_statusInformar se a ponte está conectada e com qual conta está pareada
pairing_statusInformar o estado de pareamento do dispositivo como um envelope estruturado setup_state (ready / awaiting_qr + qr_payload / error) para supervisores programáticos que exibem o QR de vinculação

Adiado

Intencionalmente ainda não exposto:

  • subscribe_presence — sem camada de persistência para eventos de presença, ignorado para evitar uma ferramenta pendente.
  • Definidor de foto de perfil — o whatsmeow upstream não expõe um definidor em nível de usuário.
  • Participantes em modo de aprovação, comunidades, newsletters — superfície de baixo uso, adiado.

Limitações

  • Risco de injeção de prompt: como muitos servidores MCP, este está sujeito à tríade letal. Injeção de prompt em mensagens recebidas pode levar à exfiltração de dados privados — trate a superfície da ferramenta de acordo.
  • Reautenticação: o WhatsApp pode invalidar a sessão do dispositivo vinculado periodicamente; execute novamente ./bin/whatsapp-mcp login quando isso acontecer.
  • Lacunas de mensagens quando serve não está em execução: os eventos só fluem para o SQLite enquanto o binário está ativo. Mensagens enviadas durante uma janela offline são recuperadas na próxima reconexão apenas se a retenção de multidispositivo do WhatsApp ainda as mantiver; para lacunas maiores, use request_sync por chat, ou aceite a perda.
  • Instância única por armazenamento: apenas um whatsapp-mcp serve pode manter o bloqueio do armazenamento. Clientes MCP paralelos devem apontar para diretórios -store diferentes (e, portanto, sessões pareadas diferentes).
  • Windows: requer CGO e um compilador C — veja docs/windows.md.
  • Limites upstream: busca/envio de mensagens é limitado pelo que whatsmeow suporta contra a API multidispositivo web do WhatsApp.
  • Redação de logs é ofuscação, não anonimização. Conhecimento parcial dos seus contatos permite correlação a partir dos últimos 5 dígitos visíveis. Symlinks dentro de ./store/uploads/ são resolvidos antes da verificação de caminho para que não possam escapar, mas a raiz em si é um limite de confiança — coloque apenas arquivos que você pretende enviar dentro dela.

Desenvolvimento

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

Atualizando o whatsmeow

O CI semanal executa uma sonda de atualização upstream. Para fazer manualmente:

make upgrade-check

Isso incrementa go.mau.fi/whatsmeow@main, reorganiza, compila e testa. Se estiver verde, faça commit das alterações de go.mod / go.sum.

scripts/mdtest-parity.sh no CI falha a compilação cedo se o upstream remover ou renomear qualquer método do whatsmeow que chamamos — é o canário para deriva de API.

Solução de problemas

  • connect failed … em serve — o daemon não está pareado. Abra http://127.0.0.1:8765/pair em um navegador e escaneie o QR. Alternativamente, execute ./bin/whatsapp-mcp login em um terminal.
  • another whatsapp-mcp instance is already running — apenas um serve pode manter o bloqueio do armazenamento. Verifique se há um processo solto (ps aux | grep whatsapp-mcp) ou outro cliente MCP apontando para o mesmo diretório -store.
  • QR não exibe — o terminal não renderiza Unicode de meio-bloco. Tente iTerm2, Windows Terminal ou similar.
  • Limite de dispositivos atingido — o WhatsApp limita dispositivos vinculados. Remova um em Configurações → Dispositivos vinculados no seu telefone.
  • Nenhuma mensagem carregando — após a autenticação inicial, pode levar vários minutos para o histórico ser preenchido. Use request_sync para direcionar um chat específico.
  • WhatsApp fora de sincronia — exclua ambos os arquivos de banco de dados (store/messages.db e store/whatsapp.db) e execute novamente login.
  • ffmpeg not foundsend_audio_message precisa de ffmpeg em PATH para converter áudio não-Opus. Use send_file para áudio bruto em vez disso.

Log de depuração

Por padrão, JIDs em logs de stderr são redigidos para …<last-4-chars-of-user-part> e corpos de mensagens são resumidos como [<length>B: text|url|command]. URLs de CDN de mídia são colapsadas para <scheme>://<host>/…. Para ver o conteúdo das mensagens durante depuração ativa:

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

  • Como variável de ambiente na configuração do seu cliente MCP:

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

Mesmo com o modo de depuração ativado, sequências de dígitos em formato de número de telefone em corpos e JIDs são parcialmente mascaradas — apenas os últimos 5 dígitos são visíveis (ex.: +15551234567****34567). Isso significa que logs de depuração são seguros para compartilhar em relatórios de bugs sem vazar números de telefone completos.

Aviso de honestidade. O esquema de redação parcial é ofuscação para conveniência de leitura de logs, não anonimização. Alguém com conhecimento independente dos seus contatos ainda pode correlacionar os últimos 5 dígitos com um número de telefone específico. Trate logs redigidos como "provavelmente seguros para colar em uma issue do GitHub", não como "anonimizados".

Para problemas de integração com Claude Desktop, veja a documentação do MCP.

Contribuindo

Contribuições são bem-vindas via pull request. Veja CONTRIBUTING.md.

Licença

Licença MIT — veja LICENSE.