A Model Context Protocol (MCP) server for WhatsApp, enabling Claude to read and send WhatsApp messages.
Originally created by Luke Harries. Maintained by Very Good Plugins.
- Message Management: Search and read personal WhatsApp messages (text, images, videos, documents, audio)
- Contact Search: Search contacts by name or phone number with
sender_displayformat ("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
- Go 1.24+
- Python 3.11+
- uv package manager
- Claude Desktop or Cursor
- FFmpeg (optional, for voice message conversion)
-
Clone the repository
git clone https://github.com/verygoodplugins/whatsapp-mcp.git cd whatsapp-mcp -
Start the WhatsApp bridge
cd whatsapp-bridge go run .
Scan the QR code with WhatsApp on your phone to authenticate.
-
Configure Claude Desktop
Add to
~/Library/Application Support/Claude/claude_desktop_config.json:{ "mcpServers": { "whatsapp": { "command": "uv", "args": [ "--directory", "/path/to/whatsapp-mcp/whatsapp-mcp-server", "run", "main.py" ] } } }Replace
/path/to/whatsapp-mcpwith your actual path. -
Restart Claude Desktop
Pull the latest changes, then refresh whichever components moved:
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 (whatsapp-mcp-server/*.py, pyproject.toml, uv.lock) |
Restart Claude Desktop / Cursor — uv re-resolves from the lockfile on next launch. Force a sync with cd whatsapp-mcp-server && uv sync if needed. |
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).
Add to your Cursor MCP settings (~/.cursor/mcp.json):
{
"mcp": {
"servers": {
"whatsapp": {
"command": "uv",
"args": [
"--directory",
"/path/to/whatsapp-mcp/whatsapp-mcp-server",
"run",
"main.py"
]
}
}
}
}Messages include sender_display showing "Name (phone)" format for easy identification by agents.
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?"
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
- Examples:
Natural Language Examples:
- "What's the name for phone number 5551234567?"
- "Look up who owns this number"
- "Who is 184125298348272@lid?"
Get messages with filters, date ranges, and sorting.
Parameters:
chat_jid(optional): Filter by specific chat JIDlimit(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 a text message to a contact or group.
Parameters:
recipient(required): Phone number or group JIDmessage(required): Text content to send
Natural Language Examples:
- "Send 'Hello!' to +1234567890"
- "Message the team group saying 'Meeting at 3pm'"
Send a media file (image, video, document).
Parameters:
recipient(required): Phone number or group JIDfile_path(required): Path to the filecaption(optional): Caption for the media
Send a voice message (automatically converts to Opus .ogg format).
Parameters:
recipient(required): Phone number or group JIDfile_path(required): Path to audio file
Download media from a received message.
Parameters:
message_id(required): ID of the message with mediachat_jid(required): JID of the chat containing the message
List all chats with metadata.
Parameters:
limit(optional): Number of chats (default 50, max 200)
Get specific chat metadata by JID.
Parameters:
jid(required): Chat JID
Find a direct message chat with a contact.
Parameters:
phone(required): Phone number of the contact
List all chats involving a specific contact.
Parameters:
phone(required): Phone number of the contact
Get the last message exchanged with a contact.
Parameters:
phone(required): Phone number of the contact
Get messages around a specific message for context.
Parameters:
message_id(required): ID of the target messagechat_jid(required): JID of the chatbefore(optional): Number of messages before (default 5)after(optional): Number of messages after (default 5)
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 |
| 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 below. |
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:
# 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 managerCaveats:
- 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.dbalready 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.
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.
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)
);- 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'.CallOfferevents don't expose media type directly (it's buried in the binary call data). Group calls viaCallOfferNoticeinclude aMediafield and are recorded accurately as voice or video.
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
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
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"
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
cd whatsapp-mcp-server
uv pip install -e ".[dev]"
uv run pytest -v# Python
cd whatsapp-mcp-server
uv run ruff check .
uv run ruff format .
# Go
cd whatsapp-bridge
golangci-lint run# Go bridge
cd whatsapp-bridge
go build -o whatsapp-bridge
# Run the binary
./whatsapp-bridge
# During development (avoids stale binaries)
go run .Releases use Release Please automation; maintainer steps and fallback procedures are documented in docs/RELEASING.md.
- 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/*.dbfiles and re-authenticate.
Windows requires CGO for go-sqlite3. Install MSYS2 and enable CGO:
go env -w CGO_ENABLED=1
go run .Caution: As with many MCP servers, this is subject to the lethal trifecta. Prompt injection could lead to private data exfiltration. Use with awareness.
MIT License - see LICENSE for details.
This project is a maintained fork of lharries/whatsapp-mcp, originally created by Luke Harries.
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_contacttool,sender_displayfield, and LID ↔ phone resolution via the whatsmeow store- Live capture of incoming voice/video calls into a
callstable --full-history-pairflag to request extended history at pair time- Resilience: recovers from
StreamReplacedsession conflicts; pinnedanyioto 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.
Recent contributors (huge thanks):
- @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 —
StreamReplacedrecovery (#27) - @davidggphy — LID → phone JID consistency (#12)
- @maikol-solis — bridge run command fix (#23)
- @DeetBot —
anyiocancel-scope pin (#44)
And to Luke for creating the original project. See CONTRIBUTING.md if you'd like to join in.
- Very Good Plugins
- MCP Specification
- whatsmeow - WhatsApp Web API library for Go
- FastMCP - Fast Model Context Protocol implementation