WhatsApp MCP Server
README.md
# WhatsApp MCP Server
[](https://github.com/verygoodplugins/whatsapp-mcp/actions/workflows/ci.yml)
[](https://opensource.org/licenses/MIT)
[](https://www.python.org/downloads/)
[](https://go.dev/)
A Model Context Protocol (MCP) server for WhatsApp, enabling Claude to read and send WhatsApp messages.
> Originally created by [Luke Harries](https://github.com/lharries/whatsapp-mcp). Maintained by [Very Good Plugins](https://verygoodplugins.com/?utm_source=github).
## Features
- **Message Management**: Search and read personal WhatsApp messages (text, images, videos, documents, audio)
- **Contact Search**: Search contacts by name or phone number with `sender_display` format ("Name (phone)")
- **Send Messages**: Send text messages to individuals or groups
- **Media Support**: Send and download images, videos, documents, and voice messages
- **Call History**: Capture incoming voice/video calls into a local SQLite table (live, 1:1 and group)
- **Webhook Integration**: Forward incoming messages to external services
- **Local Storage**: All messages stored locally in SQLite - only sent to Claude when you allow it
## Installation
### Prerequisites
- Go 1.24+
- Python 3.11+
- [uv](https://docs.astral.sh/uv/) package manager
- Claude Desktop or Cursor
- FFmpeg (optional, for voice message conversion)
### Quick Start
1. **Clone the repository**
```bash
git clone https://github.com/verygoodplugins/whatsapp-mcp.git
cd whatsapp-mcp
```
2. **Start the WhatsApp bridge**
```bash
cd whatsapp-bridge
go run .
```
Scan the QR code with WhatsApp on your phone to authenticate.
3. **Configure Claude Desktop**
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"whatsapp": {
"command": "whatsapp-mcp"
}
}
}
```
The `whatsapp-mcp` console script is installed by `pip install -e .` (or
`pipx install .`) from the repo root. If you'd rather not install editable,
you can call the module directly:
```json
{
"mcpServers": {
"whatsapp": {
"command": "python",
"args": ["-m", "whatsapp_mcp"],
"cwd": "/path/to/whatsapp-mcp"
}
}
}
```
Replace `/path/to/whatsapp-mcp` with your actual repo path.
4. **Restart Claude Desktop**
### Updating
Pull the latest changes, then refresh whichever components moved:
```bash
git pull
```
| You changed | What to do |
| ------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
| **Bridge code** (`whatsapp-bridge/*.go`) and you run `go run .` | Nothing — `go run` recompiles each launch. Just restart the bridge. |
| **Bridge code** and you run a built binary | `cd whatsapp-bridge && go build -o whatsapp-bridge && ./whatsapp-bridge` |
| **MCP server** (`src/whatsapp_mcp/*.py`, `pyproject.toml`) | Restart Claude Desktop / Cursor. If you installed with `-e`, edits are picked up immediately; otherwise re-run `pip install -e .` to refresh the entry point. |
Updates do **not** require re-pairing or deleting `whatsapp.db` — your session and message history are preserved. Re-pairing is only needed when explicitly requesting full history (see [Requesting full history](#requesting-full-history)).
### Cursor IDE Configuration
Add to your Cursor MCP settings (`~/.cursor/mcp.json`):
```json
{
"mcp": {
"servers": {
"whatsapp": {
"command": "whatsapp-mcp"
}
}
}
}
```
## Tools
Messages include `sender_display` showing "Name (phone)" format for easy identification by agents.
### Contact Operations
#### `search_contacts`
Search contacts by name or phone number.
**Parameters:**
- `query` (required): Name or phone number to search
**Natural Language Examples:**
- "Find contacts named John"
- "Search for phone number 555-1234"
- "Who has the phone number starting with +1?"
#### `get_contact`
Resolve a WhatsApp contact name from a phone number, LID, or full JID.
**Parameters:**
- `identifier` (required): Phone number, LID, or full JID (aliases: `phone_number`, `phone`)
- Examples: `12025551234`, `184125298348272`, `12025551234@s.whatsapp.net`, `184125298348272@lid`
**Natural Language Examples:**
- "What's the name for phone number 5551234567?"
- "Look up who owns this number"
- "Who is 184125298348272@lid?"
### Message Operations
#### `list_messages`
Get messages with filters, date ranges, and sorting.
**Parameters:**
- `chat_jid` (optional): Filter by specific chat JID
- `limit` (optional): Number of messages (default 50, max 500)
- `before_date` (optional): Messages before this date (YYYY-MM-DD)
- `after_date` (optional): Messages after this date (YYYY-MM-DD)
- `sort_by` (optional): "newest" or "oldest" (default "newest")
**Natural Language Examples:**
- "Show me the last 100 messages from today"
- "Get messages from the family group chat"
- "Find messages from last week"
#### `send_message`
Send a text message to a contact or group.
**Parameters:**
- `recipient` (required): Phone number or group JID
- `message` (required): Text content to send
**Natural Language Examples:**
- "Send 'Hello!' to +1234567890"
- "Message the team group saying 'Meeting at 3pm'"
#### `send_file`
Send a media file (image, video, document).
**Parameters:**
- `recipient` (required): Phone number or group JID
- `file_path` (required): Path to the file
- `caption` (optional): Caption for the media
#### `send_audio_message`
Send a voice message (automatically converts to Opus .ogg format).
**Parameters:**
- `recipient` (required): Phone number or group JID
- `file_path` (required): Path to audio file
#### `download_media`
Download media from a received message.
**Parameters:**
- `message_id` (required): ID of the message with media
- `chat_jid` (required): JID of the chat containing the message
### Chat Operations
#### `list_chats`
List all chats with metadata.
**Parameters:**
- `limit` (optional): Number of chats (default 50, max 200)
#### `get_chat`
Get specific chat metadata by JID.
**Parameters:**
- `jid` (required): Chat JID
#### `get_direct_chat_by_contact`
Find a direct message chat with a contact.
**Parameters:**
- `phone` (required): Phone number of the contact
#### `get_contact_chats`
List all chats involving a specific contact.
**Parameters:**
- `phone` (required): Phone number of the contact
#### `get_last_interaction`
Get the last message exchanged with a contact.
**Parameters:**
- `phone` (required): Phone number of the contact
#### `get_message_context`
Get messages around a specific message for context.
**Parameters:**
- `message_id` (required): ID of the target message
- `chat_jid` (required): JID of the chat
- `before` (optional): Number of messages before (default 5)
- `after` (optional): Number of messages after (default 5)
## Configuration
Copy `.env.example` to `.env` and configure as needed:
| Variable | Default | Description |
| ---------------------- | ---------------------------------------- | -------------------------------------------- |
| `WHATSAPP_BRIDGE_PORT` | `8080` | Port for Go bridge REST API |
| `WEBHOOK_URL` | `http://localhost:8769/whatsapp/webhook` | Webhook for incoming messages |
| `FORWARD_SELF` | `false` | Forward messages sent by self |
| `WHATSAPP_DB_PATH` | `../whatsapp-bridge/store/messages.db` | Path to SQLite database |
| `WHATSMEOW_DB_PATH` | `../whatsapp-bridge/store/whatsapp.db` | whatsmeow DB used for LID ↔ phone resolution |
| `WHATSAPP_API_URL` | `http://localhost:8080/api` | Go bridge REST API URL |
### CLI flags (Go bridge)
| Flag | Default | Description |
| --------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--full-history-pair` | `false` | Request full history at pair time. Only takes effect on a fresh pair (no existing `whatsapp.db`); no-op for already-paired sessions. The phone ultimately decides the actual history window sent — see [Requesting full history](#requesting-full-history) below. |
### Requesting full history
whatsmeow's default pairing asks for "recent sync" — roughly the last 3 months, with the exact window decided by the phone. If you want to pull more history at pair time:
```bash
# Stop the bridge
launchctl bootout gui/$UID/com.whatsapp-bridge # or however you manage it
# Back up, then remove the auth session (keeps messages.db intact)
cp whatsapp-bridge/store/whatsapp.db{,.bak}
rm whatsapp-bridge/store/whatsapp.db
# Re-pair with the flag
cd whatsapp-bridge
./whatsapp-bridge --full-history-pair
# Scan the QR with WhatsApp → Settings → Linked Devices → Link a Device
# Wait for "History sync complete" in the logs (can take 10-30 minutes)
# Ctrl+C when sync has quiesced, then restart under your normal process manager
```
Caveats:
- **The phone decides the actual cap.** The flag requests up to 10 years / 100 GB, but WhatsApp's iOS primary device enforces its own retention policy. iPad companion is documented at ~1 year max; other linked devices appear to follow similar logic.
- **Only effective on a fresh pair.** With `whatsapp.db` already present, no new pair handshake fires and the flag is a no-op.
- **Messages the phone has deleted are not recoverable** — auto-expire, low-storage cleanup, and manual delete all leave no trace for the phone to share.
## Call History
The bridge captures incoming WhatsApp voice and video calls live into a
dedicated `calls` table in `messages.db`. When a 1:1 call arrives
(`CallOffer`) or a group call is announced (`CallOfferNotice`), a row is
inserted with `result='in_progress'`. Subsequent `CallAccept` /
`CallReject` / `CallTerminate` events update the row — final result becomes
`answered`, `rejected`, `missed`, or `ended` depending on the event
sequence. See the state-machine comment above `StoreCallOffer` in `main.go`
for the exact transitions.
### Schema
```sql
CREATE TABLE calls (
call_id TEXT,
chat_jid TEXT, -- group JID for group calls, call creator JID for 1:1
from_jid TEXT, -- JID of whoever started the call
timestamp TIMESTAMP, -- call start time
is_from_me BOOLEAN,
call_type TEXT, -- 'voice' or 'video'
is_group BOOLEAN,
result TEXT, -- 'in_progress' | 'answered' | 'ended' |
-- 'missed' | 'rejected'
duration_sec INTEGER, -- computed when the call terminates
ended_at TIMESTAMP,
reason TEXT, -- terminate reason string from whatsmeow
PRIMARY KEY (call_id, chat_jid)
);
```
### Caveats
- **Outbound calls are not captured.** WhatsApp's primary device handles
calls it initiates without notifying linked devices, so the bridge never
sees an event for them.
- **Call results only reflect what the bridge saw.** If the bridge is
offline when a call happens, the events are lost.
- **1:1 calls default to `call_type='voice'`.** `CallOffer` events don't
expose media type directly (it's buried in the binary call data). Group
calls via `CallOfferNotice` include a `Media` field and are recorded
accurately as voice or video.
## Architecture
```mermaid
flowchart TB
subgraph Clients["AI Clients"]
CD[Claude Desktop]
CU[Cursor IDE]
CC[Claude Code]
end
subgraph MCP["MCP Layer"]
PY[Python MCP Server<br/>FastMCP]
end
subgraph Bridge["WhatsApp Bridge"]
GO[Go Bridge<br/>whatsmeow]
DB[(SQLite<br/>messages.db)]
WH[Webhook Handler]
end
subgraph External["External Services"]
WA[WhatsApp Web API]
EXT[External Webhook<br/>Receiver]
end
CD & CU & CC -->|MCP Protocol| PY
PY -->|REST API| GO
PY -->|Read| DB
GO -->|Store| DB
GO <-->|WebSocket| WA
GO -->|Forward Messages| WH
WH -->|POST| EXT
```
### Component Details
```mermaid
flowchart LR
subgraph GoAPI["Go Bridge REST API"]
direction TB
SEND["/api/send"]
DOWN["/api/download"]
TYPE["/api/typing"]
HEALTH["/api/health"]
end
subgraph MCPTools["MCP Tools (14 total)"]
direction TB
CONT["Contact Tools<br/>search_contacts, get_contact"]
MSG["Message Tools<br/>list_messages, send_message, etc."]
CHAT["Chat Tools<br/>list_chats, get_chat, etc."]
MEDIA["Media Tools<br/>send_file, download_media, etc."]
end
MCPTools -->|HTTP Requests| GoAPI
```
### Data Flow
```mermaid
sequenceDiagram
participant User as User
participant Claude as Claude Desktop
participant MCP as Python MCP Server
participant Bridge as Go Bridge
participant WA as WhatsApp
User->>Claude: "Send 'Hello' to Mom"
Claude->>MCP: send_message(recipient, message)
MCP->>Bridge: POST /api/send
Bridge->>WA: Send via WebSocket
WA-->>Bridge: Delivery confirmation
Bridge-->>MCP: Success response
MCP-->>Claude: Message sent
Claude-->>User: "Message sent to Mom"
```
### Incoming Message Flow
```mermaid
sequenceDiagram
participant WA as WhatsApp
participant Bridge as Go Bridge
participant DB as SQLite
participant WH as Webhook
participant EXT as External Service
WA->>Bridge: New message
Bridge->>DB: Store message
Bridge->>Bridge: Auto-download media
Bridge->>WH: Forward to webhook
WH->>EXT: POST with message data
Note over EXT: Process incoming message
```
## Development
### Running Tests
```bash
pip install -e ".[dev]"
pytest -m "not integration"
```
### Linting
```bash
# Python
ruff check .
ruff format .
mypy src
# Go
cd whatsapp-bridge
golangci-lint run
```
### Building
```bash
# Go bridge
cd whatsapp-bridge
go build -o whatsapp-bridge
# Run the binary
./whatsapp-bridge
# During development (avoids stale binaries)
go run .
```
### Releasing (Maintainers)
Releases use Release Please automation; maintainer steps and fallback procedures
are documented in [docs/RELEASING.md](docs/RELEASING.md).
## Troubleshooting
### Authentication Issues
- **QR Code Not Displaying**: Restart the bridge. Check terminal QR code support.
- **Device Limit Reached**: Remove a linked device from WhatsApp Settings > Linked Devices.
- **No Messages Loading**: Initial sync can take several minutes for large chat histories.
- **Out of Sync**: Delete `whatsapp-bridge/store/*.db` files and re-authenticate.
### Windows
Windows requires CGO for go-sqlite3. Install [MSYS2](https://www.msys2.org/) and enable CGO:
```bash
go env -w CGO_ENABLED=1
go run .
```
## Security Notice
> **Caution**: As with many MCP servers, this is subject to [the lethal trifecta](https://simonwillison.net/2025/Jun/16/the-lethal-trifecta/). Prompt injection could lead to private data exfiltration. Use with awareness.
## License
MIT License - see [LICENSE](LICENSE) for details.
## Credits & History
This project is a maintained fork of [lharries/whatsapp-mcp](https://github.com/lharries/whatsapp-mcp), originally created by [Luke Harries](https://github.com/lharries).
**Why we forked:** The original repository hasn't been updated since April 2025. We needed continued maintenance, bug fixes, and new features for production use.
**Highlights since the fork:**
- `/api/typing`, `/api/health`, and webhook forwarding (with reply context + image media)
- Auto-download of incoming media with collision-safe filenames
- `get_contact` tool, `sender_display` field, and LID ↔ phone resolution via the whatsmeow store
- Live capture of incoming voice/video calls into a `calls` table
- `--full-history-pair` flag to request extended history at pair time
- Resilience: recovers from `StreamReplaced` session conflicts; pinned `anyio` to dodge a cancel-scope regression
- CI/CD with GitHub Actions, Release Please for automated versioning, and Dependabot
The full release-by-release list lives in [CHANGELOG.md](CHANGELOG.md).
**Recent contributors** (huge thanks):
- [@edmenendez](https://github.com/edmenendez) — call capture (#39), full-history flag (#37), caption surfacing (#42), media filename collisions (#40), download race fix (#41), LID matching (#43), contact resolution via whatsmeow store (#30)
- [@davidsimoes](https://github.com/davidsimoes) — `StreamReplaced` recovery (#27)
- [@davidggphy](https://github.com/davidggphy) — LID → phone JID consistency (#12)
- [@maikol-solis](https://github.com/maikol-solis) — bridge run command fix (#23)
- [@DeetBot](https://github.com/DeetBot) — `anyio` cancel-scope pin (#44)
And to Luke for creating the original project. See [CONTRIBUTING.md](CONTRIBUTING.md) if you'd like to join in.
## Links
- [Very Good Plugins](https://verygoodplugins.com/?utm_source=github)
- [MCP Specification](https://modelcontextprotocol.io/)
- [whatsmeow](https://github.com/tulir/whatsmeow) - WhatsApp Web API library for Go
- [FastMCP](https://github.com/jlowin/fastmcp) - Fast Model Context Protocol implementation
This server cannot be deployed
Maintenance
ActivityInactive
ResponsivenessNo issues