WhatsApp MCP Server (Docker Edition)

ABrauser/whatsapp-mcp-docker
0 starsCommunity

Install to Claude Code

This server doesn't publish a one-line install command. Follow the setup in the source repository.

Summary

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.

README.md

WhatsApp MCP Server (Docker Edition)

A WhatsApp MCP (Model Context Protocol) server that runs as a Docker container and is accessible over HTTP/SSE. Based on whatsapp-mcp-ts with the transport layer changed from stdio to SSE for remote access.

Features

  • 8 MCP Tools: search_contacts, list_messages, list_recent_messages, list_chats, get_chat, get_message_context, send_message, search_messages
  • Docker-first: Runs as a container, accessible via IP
  • Streamable HTTP Transport: stateless MCP over HTTP POST
  • Bearer-token auth for /sse (set MCP_AUTH_TOKEN)
  • Persistent Data: SQLite DB (WAL + FTS5 search) and WhatsApp auth in Docker volumes
  • Health Check: /health returns 503 when WhatsApp is disconnected
  • Multi-Arch: Builds for both amd64 and arm64

Quick Start

1. Configure secrets

cp .env.example .env
# Generate a strong token and write it to .env:
echo "MCP_AUTH_TOKEN=$(openssl rand -hex 32)" >> .env

2. Pull and Run

# Pull the image
docker pull ghcr.io/YOUR-USERNAME/whatsapp-mcp-docker:latest

# Run with docker compose
docker compose up -d

# Watch logs for QR code
docker logs -f whatsapp-mcp

3. Scan QR Code

On first start, a QR code URL will appear in the logs. Open it in your browser and scan with WhatsApp (Settings → Linked Devices → Link a Device).

4. Configure MCP Client

This server speaks Streamable HTTP MCP (POST /sse), not legacy SSE. The exact config key depends on your client:

Gemini CLI (~/.gemini/settings.json — the key is httpUrl, not url):

{
  "mcpServers": {
    "whatsapp": {
      "httpUrl": "http://192.168.0.101:3010/sse",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN_FROM_DOTENV"
      }
    }
  }
}

⚠️ Common pitfall: Using "url" in Gemini CLI selects the legacy SSE transport, which this server does not implement. You will get 401 errors that look like an auth problem but are actually a transport mismatch. Use "httpUrl".

Claude Desktop / Cline / Continue / Cursor (most use url for Streamable HTTP):

{
  "mcpServers": {
    "whatsapp": {
      "url": "http://192.168.0.101:3010/sse",
      "headers": {
        "Authorization": "Bearer YOUR_TOKEN_FROM_DOTENV"
      }
    }
  }
}

Contact Name Resolution

WhatsApp Web (Baileys) does not give us your phone-side address book directly. To still show your saved names — not the contact's self-chosen push names — the server uses three layers, in this order:

  1. Manual override (contact_overrides.json, see below) — wins everything.
  2. Auto-linking — every incoming message's pushName is captured and stored as the notify of the corresponding @s.whatsapp.net contact. This lets the existing fuzzy-match in queries automatically connect the @lid form (used in groups & status updates) to the @s.whatsapp.net row that holds your saved address-book name. After enough message activity, most contacts resolve themselves.
  3. Fallbacknamenotify → phone digits → JID.

Manual overrides (contact_overrides.json)

Use this only for contacts that auto-linking can't fix (e.g. a contact who has never written you directly, or whose push name and saved name are completely different and the fuzzy match doesn't catch it).

Path: <data dir>/contact_overrides.json (typically /opt/docker/whatsapp-mcp/data/contact_overrides.json on the host)

{
  "210444891463794@lid": "Tammy",
  "11390320418995@lid": "Amelie",
  "4917697335710@s.whatsapp.net": "Tammy"
}
  • The override wins over the DB-resolved name in every MCP response (chat_name, sender_name, last_sender_name).
  • File is hot-reloaded on save — no container restart required.
  • Each entry is a JID → display name mapping. Find a contact's JID via search_contacts or list_chats.
  • See contact_overrides.example.json in the repo for the format.

Security

  • Always set MCP_AUTH_TOKEN unless you bind the container to 127.0.0.1 only. Without a token, anyone on your network can read your chats and send messages from your account.
  • Without TLS, the token is sent in plaintext. For LAN-only deployments this is usually acceptable; for anything wider, terminate TLS in a reverse proxy (Caddy, Traefik, nginx).
  • Logs in /app/data/wa-logs.txt and /app/data/mcp-logs.txt may contain message snippets. Treat the data volume as sensitive.
  • The QR code from initial pairing is logged at info level once — it grants full session access for a few seconds. Don’t share that log.

Endpoints

| Endpoint | Method | Auth | Description | |----------|--------|------|-------------| | /sse | POST | Bearer | Streamable HTTP endpoint for MCP (stateless) | | /health | GET | none | 200 when WhatsApp connected, 503 otherwise |

MCP Tools

| Tool | Description | |------|-------------| | search_contacts | Search contacts by name or phone number | | list_messages | Get message history for a chat (paginated) | | list_chats | List chats sorted by activity or name | | get_chat | Get details of a specific chat | | get_message_context | Get messages around a specific message | | list_recent_messages | Cross-chat time-window query (e.g. "last 8 hours") with optional ISO since/until | | send_message | Send a text message to a user or group | | search_messages | Full-text search across messages |

Environment Variables

| Variable | Default | Description | |----------|---------|-------------| | MCP_PORT | 3010 | HTTP server port | | MCP_AUTH_TOKEN | _(unset)_ | Bearer token required on /sse. If unset, endpoint is open. | | LOG_LEVEL | info | Log level (debug, info, warn, error) | | WHATSAPP_MCP_DATA_DIR | /app/data | SQLite database directory | | WHATSAPP_AUTH_DIR | /app/auth_info | WhatsApp auth credentials directory | | TZ | Europe/Berlin | Timezone used for human-readable timestamps | | WHATSAPP_DEBUG | _(unset)_ | If true, prints @lid mapping diagnostics on startup |

Development

Requires Node.js >= 24 (stable node:sqlite and on-by-default TypeScript stripping). On Node 22/23, run with --experimental-sqlite --experimental-strip-types.

# Install dependencies
npm install

# Run locally (Node 24+)
node src/main.ts

# Run locally (Node 22/23)
node --experimental-sqlite --experimental-strip-types src/main.ts

# Type-check only
npx tsc --noEmit

# Build Docker image locally
docker build -t whatsapp-mcp-docker .

Architecture

AI Client (Gemini CLI, Claude, etc.)
    │
    │ HTTP POST (Streamable)
    ▼
┌─────────────────────┐
│  Express Server     │  Port 3010
│  ├── POST /sse      │  Stateless MCP
│  └── GET /health    │  Health check
├─────────────────────┤
│  MCP Server         │  7 Tools
├─────────────────────┤
│  Baileys            │  WhatsApp Web API (Reconnects + Backoff)
├─────────────────────┤
│  SQLite             │  Messages & Contacts (Batch Sync)
└─────────────────────┘

Credits

Based on whatsapp-mcp-ts by jlucaso1.

Related MCP servers

Browse all →