Skip to main content
Glama
eusoubrasileiro

whatsapp-mcp

WhatsApp MCP Server

WhatsApp as an MCP server. Runs as a long-lived Docker daemon, accessed over HTTPS with Bearer auth, and pushes ntfy notifications when your session needs attention (QR expired, connection dropped). Built on Baileys via the @amiticia/baileys-client wrapper, which is vendored in this repository as a workspace package under packages/baileys-client/ — there is no private dependency to resolve.

▶ Start here: src/stream/follow.ts — the smallest file that shows what this server is actually for: turning an inbound WhatsApp chat into a stream an agent can be woken by, instead of a mailbox it has to poll.

Look at first

  1. src/stream/follow.ts — the follow_chat tool. An agent that must "be present" in a conversation for hours cannot hold a turn open and cannot afford to poll; this hands it a stream URL its harness subscribes to, and the agent is woken per message. The three-way choice between a bounded block, a session-length stream and a deployment-length webhook is the whole design question for a reactive messaging tool, and this file is where it is answered.

  2. docs/spec-agent-presence-stream.md — the spec behind that file: why presence is a distinct lifetime from request/response, and what the stream must guarantee so a woken agent never silently drops a message.

  3. docs/account-restrictions.md — the unglamorous one, and the one worth reading. An unofficial WhatsApp client can get a real account banned. This documents the constraints that shaped the send path — pacing, typing simulation, a send blocklist, a cold-contact guard — which is why src/send-pacer.ts, src/send-guard.ts, src/send-blocklist.ts and src/cold-contact.ts exist at all.

Related MCP server: WhatsApp MCP Server (Docker Edition)

Who this is for

You want your personal WhatsApp account reachable as a set of tools from Claude Code, Claude Desktop, Cursor, or any custom MCP/HTTP client — from any machine — with the WhatsApp connection surviving client restarts, multiple clients sharing one socket, and a push telling you on your phone when you need to re-scan a QR.

Features

  • HTTP MCP endpoint (httpStream transport) — connect from anywhere, share the session across multiple clients without Baileys fighting for the socket

  • Bearer-token auth on the MCP endpoint

  • Public QR web page (protected by paired-number check) — tap the ntfy push and scan directly from your phone browser

  • ntfy push on: first QR after disconnect, every 2min while still waiting, connection drop, reconnect after drop, and unexpected pairings

  • Bad-pairing protection — if someone else scans the public QR, the app auto-logs out and purges credentials (EXPECTED_WA_NUMBER)

  • 23 MCP tools — search contacts/messages, list chats, send text/media, react, delete, mark read, download media, plus webhook subscriptions for real-time inbound push (see table below)

  • Persistent SQLite (chats/messages/contacts) and Baileys multi-file auth stored in a Docker volume

Architecture

                    https (Bearer)
  MCP clients  ───────────────────▶  Traefik  ──▶  :39001 (FastMCP httpStream)
  (Claude Code, Desktop,                                │
   Cursor, custom agents)                               │
                                                        ▼
                    https (public)                 whatsapp-mcp
  Phone browser ───────────────▶  Traefik  ──▶  :39002 (QR page)
                                                        │
                                                        ▼
                                                    Baileys
                                                        │ WA Web API
                                                        ▼
                                                  WhatsApp servers

                                                   outbound only ▲
                                                        │
                                                       POST
                                                        │
                                                     ntfy.sh
                                                        │ push
                                                        ▼
                                                     Your phone

One container exposes two HTTP servers on different ports. Traefik terminates TLS (Let's Encrypt) and routes by hostname.

Quick start for MCP clients

The canonical deployment exposes:

  • https://mcp.example.com/mcp — MCP endpoint, requires Authorization: Bearer <MCP_AUTH_TOKEN>

  • https://wa.example.com/ — QR web page

If you run your own instance, replace hostnames accordingly.

Claude Code

Edit ~/.claude.json, in the top-level mcpServers object:

"whatsapp": {
  "type": "http",
  "url": "https://mcp.example.com/mcp",
  "headers": {
    "Authorization": "Bearer ${MCP_AUTH_TOKEN}"
  }
}

Export MCP_AUTH_TOKEN in your shell (or put the token literally — ~/.claude.json is 0600). Restart Claude Code, run /mcp — should show whatsapp: ✓ Connected.

Claude Desktop

~/.config/Claude/claude_desktop_config.json (Linux) / ~/Library/Application Support/Claude/claude_desktop_config.json (macOS):

{
  "mcpServers": {
    "whatsapp": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer your-token-here" }
    }
  }
}

Cursor

~/.cursor/mcp.json — same shape as Claude Desktop above.

Other clients / custom code

See examples/ for a raw HTTPS JSON-RPC transcript (curl), a Python client, and a custom TypeScript agent using StreamableHTTPClientTransport.

MCP tools

The server exposes 23 tools. Full details are in CLAUDE.md.

Category

Tools

Connection / Auth

get_connection_status, logout

Contacts

search_contacts, list_contacts

Messages

list_messages, get_messages_today, search_messages, get_message_context

Reactive monitoring

get_new_messages (cursor delta), wait_for_messages (long-poll — block until a reply lands), follow_chat (session-length stream: returns a URL your harness subscribes to, so the agent is woken per message instead of occupying a turn)

Chats

list_chats, get_chat

Groups

get_group_info

Sending

send_message, send_file

Actions

react_to_message, delete_message, mark_chat_read

Media

download_media (audio → transcription by default; image → opt-in description)

Webhooks

register_webhook, deregister_webhook, list_webhooks (real-time inbound push to a reactive agent; allow-listed chats; HMAC-signed)

Deployment

deploy/README.md describes the reference topology: the two HTTP servers, the four Traefik routes, the volumes and the auth surface.

What is deliberately not here. The host-specific operations runbook — SSH targets, DNS records, backup and restore procedures, credential rotation — is not part of this public repository, and neither is the private container registry the image is normally published to. deploy/docker-compose.yaml is a sanitised topology reference, not a deployable manifest for anyone else's host.

The image builds from this repository alone; packages/baileys-client is a workspace package, so no sibling checkout and no build context are needed:

docker build -t whatsapp-mcp:latest .

Local development

For development without Docker, keep the default stdio transport:

pnpm install
pnpm test       # vitest — 687 pass, 4 skipped; must stay green before commits (extreme TDD)
pnpm typecheck
pnpm start      # node --experimental-strip-types src/main.ts

Requires Node.js >= 23.10.0 for --experimental-strip-types and native better-sqlite3.

For local HTTP mode (same as production minus Traefik):

MCP_TRANSPORT=httpstream MCP_AUTH_TOKEN=dev pnpm start
# then: curl -H 'Authorization: Bearer dev' http://127.0.0.1:39001/mcp ...

See scripts/smoke-test.sh for the auth matrix.

Environment variables

See the full table in CLAUDE.md#environment-variables. Highlights:

Variable

Purpose

MCP_AUTH_TOKEN

Bearer token required by the HTTP MCP endpoint (mandatory in production)

NTFY_TOPIC_URL

Unset = no push notifications; set to enable

EXPECTED_WA_NUMBER

Prefix allowed to pair; wrong scan → auto-logout + purge (strongly recommended when wa.example.com is public)

WHATSAPP_MCP_DATA_DIR

Base dir for auth_info/, data/, and logs (defaults to ., Docker uses /data)

OPENROUTER_API_KEY

Whisper provider for download_media audio transcription

AUDIO_PROVIDER

Transcription route: openrouter (default) | groq | openai. Rollback lanes only — the route is chosen by this var, never by which key happens to be set

GEMINI_API_KEY

Required for download_media({ describe: true }) image captioning via Gemini 2.5 Flash

Data storage & privacy

  • Credentials: WHATSAPP_MCP_DATA_DIR/auth_info/ (Baileys multi-file auth state)

  • Messages / chats / contacts: WHATSAPP_MCP_DATA_DIR/data/whatsapp.db (SQLite via Drizzle + better-sqlite3)

  • Media: served from a RustFS sidecar on the same VPS, behind Traefik at https://mcp.example.com/media/<key>. The download_media tool returns an MCP resource_link pointing at that URL (publicly fetchable, no Bearer needed) plus inline imageContent/audioContent on the first call. Cache hits return the URL only.

  • Audio → text: by default, download_media on an audio/ptt message transcribes via OpenRouter Whisper (openai/whisper-large-v3) after preprocessing to 16 kHz mono FLAC. The response is wrapped in an <transcription> XML block. Pass transcribe: false to get raw audio bytes instead. Requires OPENROUTER_API_KEY. Groq and OpenAI remain as rollback routes via AUDIO_PROVIDER, but both accounts are scheduled for closure — once they are, those routes stop working and this line should say so.

  • Image → text: opt-in via download_media({ ..., describe: true }). Sends bytes to Gemini 2.5 Flash; response wrapped in an <image_description> XML block. Requires GEMINI_API_KEY.

  • Logs: WHATSAPP_MCP_DATA_DIR/{wa,mcp}-logs.txt (pino JSON lines)

Everything stays on the VPS (Docker bind mount in production, filesystem in dev). Data leaves the VPS only when an MCP client explicitly invokes a tool.

All data directories are .gitignored. Treat them as sensitive — anyone with auth_info/ can impersonate your WhatsApp session.

About this public export

This repository is published as a single squashed commit rather than with its development history, and some content was removed rather than redacted:

  • No session credentials, no message database, no logs. auth_info/, data/whatsapp.db and the *-logs.txt files were never in the tree or in any commit, and remain gitignored. Anyone holding auth_info/ can impersonate the WhatsApp session it belongs to.

  • No host-specific deployment runbook — see the Deployment section above.

  • Phone numbers, group ids and @lid identifiers in tests, fixtures and examples are synthetic placeholders. They keep the real wire format so the identifier-canonicalisation tests still parse them, but they identify nobody.

  • deploy/.env.example is a template: every variable is empty, with the openssl rand command that generates it. No secret material.

Credits

  • Conceptual origin: lharries/whatsapp-mcp (Go + Python).

  • Fork history: started from jlucaso1/whatsapp-mcp-ts, then heavily rewritten.

License

MIT — see LICENSE, matching package.json.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Enables AI clients to interact with WhatsApp via MCP tools for searching contacts, sending/receiving messages, and managing chats, running in a Docker container with HTTP/SSE transport.
    -
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables sending WhatsApp messages from MCP clients using a personal WhatsApp account via WebSocket protocol, without needing the Business API or browser automation.
    13 npm
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    A self-hosted WhatsApp gateway that exposes messaging capabilities via MCP, allowing AI agents to send, receive, and manage WhatsApp messages through a single-command setup with SQLite storage.
    1
    MIT