WhatsApp MCP Server
Allows reading, searching, and sending WhatsApp messages (text, images, videos, documents, audio), managing contacts and chats, and handling call history, all through the WhatsApp platform.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@WhatsApp MCP Server send a message to John: meeting at 3pm"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
WhatsApp MCP Server
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.
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_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
Related MCP server: whatsapp-local-mcp
Installation
Prerequisites
Go 1.24+
Python 3.11+
uv package manager
Claude Desktop or Cursor
FFmpeg (optional, for voice message conversion)
The Python server currently uses the MCP SDK v1 FastMCP API. The dependency
is bounded below v2 so a fresh install does not select an incompatible major
release. CI selects its Go toolchain from whatsapp-bridge/go.mod.
Quick Start
Clone the repository
git clone https://github.com/verygoodplugins/whatsapp-mcp.git cd whatsapp-mcpStart 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": "whatsapp-mcp" } } }The
whatsapp-mcpconsole script is installed bypip install -e .(orpipx install .) from the repo root. If you'd rather not install editable, you can call the module directly:{ "mcpServers": { "whatsapp": { "command": "python", "args": ["-m", "whatsapp_mcp"], "cwd": "/path/to/whatsapp-mcp" } } }Replace
/path/to/whatsapp-mcpwith your actual repo path.Restart Claude Desktop
Updating
Pull the latest changes, then refresh whichever components moved:
git pullYou changed | What to do |
Bridge code ( | Nothing — |
Bridge code and you run a built binary |
|
MCP server ( | Restart Claude Desktop / Cursor. If you installed with |
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).
Cursor IDE Configuration
Add to your Cursor MCP settings (~/.cursor/mcp.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 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_message
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_file
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_audio_message
Send a voice message (automatically converts to Opus .ogg format).
Parameters:
recipient(required): Phone number or group JIDfile_path(required): Path to audio file
Relative upload paths are resolved by the MCP process before forwarding them to the bridge. Temporary audio conversions are removed after the request; original files are retained. Bridge requests use a 30-second timeout. A send timeout means delivery is unknown, so check the chat before retrying to avoid sending the same message twice.
download_media
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
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 messagechat_jid(optional): JID of the chat; required if the message ID occurs in multiple chatsbefore(optional): Number of messages before (default 5)after(optional): Number of messages after (default 5)
Message context preserves the requested sort order and does not collapse equal message IDs from different chats. Chat listings return one row per chat even when several messages share the latest timestamp.
Configuration
Copy .env.example to .env and configure as needed:
Set these variables in the MCP process environment (or load .env through your
process manager). A source checkout resolves its default database location to
whatsapp-bridge/store, even before first pairing. Explicit WHATSAPP_DB_PATH,
WHATSMEOW_DB_PATH, and WHATSAPP_API_URL values override those defaults.
Startup probes the configured API's /health route; a disconnected bridge is
recognized as running rather than launching another instance.
Variable | Default | Description |
|
| Port for Go bridge REST API |
|
| Webhook for incoming messages |
|
| Forward messages sent by self |
|
| Path to SQLite database |
|
| whatsmeow DB used for LID ↔ phone resolution |
|
| Go bridge REST API URL |
Set WEBHOOK_URL to an explicitly empty value to disable forwarding. Leaving
the variable unset preserves the localhost default. Webhook receivers may
return any successful 2xx response, including 201 and 204.
CLI flags (Go bridge)
Flag | Default | Description |
|
| Request full history at pair time. Only takes effect on a fresh pair (no existing |
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:
# 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.
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
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'.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.
Architecture
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| EXTComponent Details
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| GoAPIData Flow
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
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 messageDevelopment
AnyIO requires version 4.14.2 or later within major version 4. This replaces the older cap that excluded security and cancellation fixes. The offline suite includes a real MCP stdio initialization/list-tools/shutdown check with bridge startup disabled, so lifecycle regressions can be detected without an account.
Running Tests
pip install -e ".[dev]"
pytest -m "not integration"Linting
# Python
ruff check .
ruff format .
mypy src
# Go
cd whatsapp-bridge
golangci-lint runBuilding
# 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.
Troubleshooting
The optional launch.py --bridge-only launcher waits for the bridge process it
starts; it does not immediately stop it on returning from startup. Normal MCP
launches preserve the child server's exit status so supervisors can detect
failures. Launcher health probes honor WHATSAPP_API_URL and the configured
bridge host/port.
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/*.dbfiles and re-authenticate.
Windows
Windows requires CGO for go-sqlite3. Install MSYS2 and enable CGO:
go env -w CGO_ENABLED=1
go run .Security Notice
Caution: As with many MCP servers, this is subject to the lethal trifecta. Prompt injection could lead to private data exfiltration. Use with awareness.
License
MIT License - see LICENSE for details.
Credits & History
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 storeLive capture of incoming voice/video calls into a
callstable--full-history-pairflag to request extended history at pair timeResilience: recovers from
StreamReplacedsession conflicts; pinnedanyioto dodge a cancel-scope regressionCI/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.
Links
Available Tools
13 toolsdownload_mediaA
Download media from a WhatsApp message and get the local file path.
Args: message_id: The ID of the message containing the media chat_jid: The JID of the chat containing the message
Returns: A dictionary containing success status, a status message, and the file path if successful
Inputs: chat_jid (required), message_id (required). Returns: object
get_chatC
Get WhatsApp chat metadata by JID.
Args: chat_jid: The JID of the chat to retrieve include_last_message: Whether to include the last message (default True)
Inputs: chat_jid (required), include_last_message. Returns: result
get_contactA
Look up a WhatsApp contact by phone number, LID, or full JID.
Automatically detects the identifier type and queries appropriately.
Args:
identifier: Phone number, LID, or full JID. Examples:
- "12025551234" (phone number)
- "184125298348272" (LID - long numeric)
- "12025551234@s.whatsapp.net" (phone JID)
- "184125298348272@lid" (LID JID)
phone_number: Backward-compatible alias for identifier.
phone: Backward-compatible alias for identifier (matches README parameter name).
Returns: Dictionary with jid, name, display_name, is_lid, and resolved status
Inputs: phone, identifier, phone_number. Returns: object
get_contact_chatsB
Get all WhatsApp chats involving the contact.
Args: jid: The contact's JID to search for limit: Maximum number of chats to return (default 20) page: Page number for pagination (default 0)
Inputs: jid (required), page, limit. Returns: result
get_direct_chat_by_contactB
Get WhatsApp chat metadata by sender phone number.
Args: sender_phone_number: The phone number to search for
Inputs: sender_phone_number (required). Returns: result
get_last_interactionB
Get the most recent WhatsApp message involving the contact.
Args: jid: The JID of the contact to search for
Returns: Message dictionary with id, timestamp, sender, content, etc. or empty dict if not found.
Inputs: jid (required). Returns: object
get_message_contextA
Get context around a specific WhatsApp message.
Args: message_id: The ID of the message to get context for before: Number of messages to include before the target message (default 5) after: Number of messages to include after the target message (default 5) chat_jid: Chat containing the message; required when its ID occurs in multiple chats
Inputs: message_id (required), after, before, chat_jid. Returns: result
list_chatsB
Get WhatsApp chats matching specified criteria.
Args: query: Search term to filter chats by name or JID limit: Max chats to return (default 50, max 200) page: Page number for pagination (default 0) include_last_message: Include the last message in each chat (default True) sort_by: "last_active" (default, most recent first) or "name" (alphabetical)
Inputs: page, limit, query, sort_by, +1 more. Returns: result
list_messagesA
Get WhatsApp messages matching specified criteria with optional context.
Each message includes sender_display showing "Name (phone)" for easy identification.
Args: after: ISO-8601 date string (e.g., "2026-01-01" or "2026-01-01T09:00:00") before: ISO-8601 date string (e.g., "2026-01-09" or "2026-01-09T18:00:00") sender_phone_number: Phone number to filter by sender (e.g., "12025551234") chat_jid: Chat JID to filter by (e.g., "12025551234@s.whatsapp.net" or group JID) query: Search term to filter messages by content limit: Max messages to return (default 50, max 500) page: Page number for pagination (default 0) include_context: Include surrounding messages for context (default True) context_before: Messages to include before each match (default 1) context_after: Messages to include after each match (default 1) sort_by: "newest" (default, most recent first) or "oldest" (chronological)
Inputs: page, after, limit, query, +7 more. Returns: result
search_contactsB
Search WhatsApp contacts by name or phone number.
Args: query: Search term to match against contact names or phone numbers
Inputs: query (required). Returns: result
send_audio_messageA
Send any audio file as a WhatsApp audio message. For group messages use the JID.
If ffmpeg isn't installed, use send_file instead.
Args: recipient: The recipient — either a phone number with country code but no + or other symbols, or a JID (e.g., "123456789@s.whatsapp.net" or a group JID like "123456789@g.us") media_path: The absolute path to the audio file (will be converted to Opus .ogg if needed)
Returns: A dictionary containing success status and a status message
Inputs: recipient (required), media_path (required). Returns: object
send_fileA
Send a file (image / raw audio / video / document) via WhatsApp. For group messages use the JID.
Args: recipient: The recipient — either a phone number with country code but no + or other symbols, or a JID (e.g., "123456789@s.whatsapp.net" or a group JID like "123456789@g.us") media_path: The absolute path to the media file to send (image, video, document)
Returns: A dictionary containing success status and a status message
Inputs: recipient (required), media_path (required). Returns: object
send_messageA
Send a WhatsApp message to a person or group. For group chats use the JID.
Args: recipient: The recipient — either a phone number with country code but no + or other symbols, or a JID (e.g., "123456789@s.whatsapp.net" or a group JID like "123456789@g.us") message: The message text to send
Returns: A dictionary containing success status and a status message
Inputs: message (required), recipient (required). Returns: object
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 13 tools
Most tools have clear, distinct purposes, but the chat-retrieval set has some overlap: list_chats, get_chat, get_direct_chat_by_contact, and get_contact_chats can be confusing depending on whether the agent has a contact, phone number, or JID. send_audio_message and send_file also overlap for audio files, though the description clarifies the distinction.
All tools use consistent snake_case verb_noun naming (search_contacts, get_chat, list_messages, send_message, download_media, etc.). The pattern is predictable throughout despite a few longer names.
At 13 tools, the set is well-scoped for a WhatsApp MCP server. Each tool maps to a meaningful contact, chat, message, send, or media operation without excessive surface area.
The surface covers the core WhatsApp workflows: contact lookup, chat listing, message reading/context, sending text/files/audio, and media download. Minor gaps remain around message editing/deletion, reactions, and group administration, but the main agent workflows are supported.
Maintenance
Related MCP Connectors
Your own WhatsApp in Claude and ChatGPT: read chats, draft replies, send messages you approve.
WhatsApp for Claude, ChatGPT or any AI agent: search, reply and send. Personal or Business API.
WhatsMCP connects Claude and other MCP-compatible AI agents directly to WhatsApp. Send and receive text, images, documents, and voice notes; manage groups (create, add/remove members, promote admins); look up contacts and profiles; follow channels; and read call and message history — all through a standard MCP interface. For voice use cases, WhatsMCP offers SIP-based calling plans (inbound-only, or full inbound/outbound) so AI voice agents can answer and place WhatsApp calls, plus low-latency WebSocket integrations with voice agent providers like ElevenLabs. Multiple WhatsApp accounts can be paired and managed per workspace, with webhook support for real-time inbound message delivery to your own infrastructure.
Free WhatsApp MCP server for ChatGPT and Claude: read and search chats, every send you approve.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables Claude Code to interact with WhatsApp, allowing reading messages, sending messages, and daily summaries using a local WhatsApp Web session.-
- AlicenseAqualityDmaintenanceEnables Claude to read and search WhatsApp messages, transcribe voice notes, and analyze images locally through a read-only bridge.19MIT
- AlicenseNot gradedqualityDmaintenanceEnables Claude to read and send WhatsApp messages through a self-hosted REST API, supporting multi-user sessions, media, groups, contacts, and webhooks.4 npmMIT
- AlicenseNot gradedqualityBmaintenanceEnables Claude to interact with WhatsApp: read chats, search messages, send messages with a mandatory confirmation step, and transcribe voice notes locally, all with encrypted storage and prompt-injection scrubbing.7MIT