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
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
@lidpara 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 portastore/.lockimpede que dois processosservedisputem 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_messagequando a entrada não for.oggOpus. Sem ele, usesend_filepara 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:
- Envio —
send_fileesend_audio_messageaceitam um argumentomedia_pathapontando para o arquivo a ser enviado. O caminho deve estar sob a raiz permitida. - Recebimento —
download_mediagrava mídia descriptografada no cache do daemon em<store>/<chat_jid>/. Passar o argumento opcionaloutput_pathadicionalmente coloca o arquivo em um local escolhido pelo chamador, que também deve estar sob a raiz permitida. Se um arquivo já existir emoutput_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_mediado MCP do WhatsApp, sempre passeoutput_pathdefinido 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_pathdeve estar sobWHATSAPP_MCP_MEDIA_ROOTno 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 doserve.
Fluxo de dados
- O cliente envia uma solicitação JSON-RPC
tools/callparaservevia HTTP. - A camada MCP despacha para um handler interno.
- O handler consulta o store SQLite local ou chama o whatsmeow diretamente (envio, download, reações, etc.).
- 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(envWHATSAPP_MCP_ADDR, padrão127.0.0.1:8765).-allow-remote(opt-in explícito para vincular um endereço não-loopback; requerWHATSAPP_MCP_TOKEN).WHATSAPP_MCP_TOKEN— token bearer para/mcpe/pair/*quando-allow-remoteestiver definido. Obrigatório;servesai se estiver ausente.WHATSAPP_MCP_MEDIA_ROOT— raiz permitida parasend_file/send_audio_messagemedia_pathedownload_mediaoutput_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
| Ferramenta | Propósito |
|---|---|
search_contacts | Busca por substring em nomes de contatos e números de telefone em cache |
list_messages | Consulta + filtro de mensagens; retorna texto formatado com janelas de contexto |
list_chats | Lista chats com prévia da última mensagem; ordena por atividade ou nome |
get_chat | Metadados do chat por JID |
get_message_context | Janela antes/depois em torno de uma mensagem específica |
download_media | Baixa mídia persistida para um caminho local |
request_sync | Pede ao WhatsApp para fazer backfill do histórico de um chat |
Envio
| Ferramenta | Propósito |
|---|---|
send_message | Envia uma mensagem de texto para um número de telefone ou JID |
send_file | Envia 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_message | Envia uma nota de voz (converte automaticamente via ffmpeg se não for .ogg Opus); suporta view_once: bool |
send_poll | Envia 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_vote | Vota em uma enquete vista anteriormente; options deve corresponder exatamente aos nomes das opções |
get_poll_results | Retorna a apuração de uma enquete que temos em cache (inclui opções com 0 votos) |
send_contact_card | Envia 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
| Ferramenta | Propósito |
|---|---|
mark_read | Marca IDs de mensagem específicos como lidos |
mark_chat_read | Confirma as mensagens recebidas mais recentes em um chat para limpar o selo de não lidas |
send_reaction | Reage a uma mensagem (emoji vazio limpa uma reação existente) |
send_reply | Resposta de texto que cita uma mensagem anterior |
edit_message | Edita uma mensagem enviada anteriormente |
delete_message | Revoga (exclui para todos) uma mensagem |
send_typing | Define presença de digitando / gravando por chat |
Grupos
| Ferramenta | Finalidade |
|---|---|
create_group | Criar um grupo com um nome e participantes iniciais |
leave_group | Sair de um grupo |
list_groups | Listar todos os grupos dos quais o usuário é membro |
get_group_info | Metadados completos do grupo (participantes, configurações, configuração de convite) |
update_group_participants | Adicionar / remover / promover / rebaixar participantes (action: add|remove|promote|demote) |
set_group_name | Alterar o assunto do grupo |
set_group_topic | Alterar a descrição do grupo; string vazia a limpa |
set_group_announce | Alternar o modo somente-anúncio (apenas administradores podem enviar) |
set_group_locked | Alternar o modo bloqueado (apenas administradores podem editar metadados do grupo) |
get_group_invite_link | Obter o link de convite; reset: true revoga o link anterior primeiro |
join_group_with_link | Entrar em um grupo via URL chat.whatsapp.com ou código de convite simples |
Lista de bloqueio
| Ferramenta | Finalidade |
|---|---|
get_blocklist | Retornar a lista de bloqueio atual |
block_contact | Bloquear um contato por número de telefone ou JID |
unblock_contact | Desbloquear um contato |
Privacidade / presença / status
| Ferramenta | Finalidade |
|---|---|
send_presence | Definir a própria disponibilidade (available ou unavailable) — distinto do send_typing por chat |
get_privacy_settings | Configurações de privacidade atuais como JSON |
set_privacy_setting | Alterar uma configuração de privacidade por name + value (validação estrita de enum; combinações inválidas são rejeitadas) |
set_status_message | Atualizar o texto "Sobre" do perfil; string vazia o limpa |
Administração
| Ferramenta | Finalidade |
|---|---|
is_on_whatsapp | Verificação em lote de quais números de telefone estão registrados no WhatsApp |
get_status | Informar se a ponte está conectada e com qual conta está pareada |
pairing_status | Informar 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 loginquando isso acontecer. - Lacunas de mensagens quando
servenã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, userequest_syncpor chat, ou aceite a perda. - Instância única por armazenamento: apenas um
whatsapp-mcp servepode manter o bloqueio do armazenamento. Clientes MCP paralelos devem apontar para diretórios-storediferentes (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 …emserve— o daemon não está pareado. Abrahttp://127.0.0.1:8765/pairem um navegador e escaneie o QR. Alternativamente, execute./bin/whatsapp-mcp loginem um terminal.another whatsapp-mcp instance is already running— apenas umservepode 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_syncpara direcionar um chat específico. - WhatsApp fora de sincronia — exclua ambos os arquivos de banco de dados (
store/messages.dbestore/whatsapp.db) e execute novamentelogin. ffmpeg not found—send_audio_messageprecisa de ffmpeg emPATHpara converter áudio não-Opus. Usesend_filepara á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.