Drive your Claude Code session from WhatsApp — your personal number, no bots, no API keys.
The plugin connects to WhatsApp as a linked device (the same protocol as WhatsApp Web, via Baileys) and exposes it to Claude Code as an MCP channel. Incoming messages reach your session in real time; Claude replies from your own number, so recipients see a normal chat. Everything runs locally on your machine — messages travel directly between WhatsApp and your session, with no third-party servers in between. Once paired, it keeps working while your phone is off; only the Claude Code session needs to stay open, and reconnects never require re-pairing.
Published on the Anthropic Official Plugin Marketplace — the first community-built WhatsApp channel plugin reviewed and published by Anthropic.
claude plugin marketplace add Rich627/whatsapp-claude-plugin
claude plugin install whatsapp-channel@whatsapp-claude-plugin
claude --dangerously-load-development-channels plugin:whatsapp-channel@whatsapp-claude-pluginThe --dangerously-load-development-channels flag matters: it registers the plugin as a channel, so an inbound WhatsApp message wakes your session immediately. Without it the tools still load, but nothing wakes the session when messages arrive — they sit unanswered until you (or a watchdog) prompt Claude to check. --channels does not accept this plugin yet (it is not on the research-preview allowlist), so the development flag is currently the only way.
Inside the session, set your number and pair:
/whatsapp-channel:configure <phone> # country code + number, no +
A pairing code is printed on first launch. On your phone: WhatsApp → Settings → Linked Devices → Link a Device → Link with phone number instead → enter the code. No WhatsApp Business API, Meta developer account, or API key is involved — it links to your regular account.
The server is a plain stdio MCP server, so any MCP client can run it. Two things are Claude Code specific and worth knowing before you start:
- Inbound messages are not pushed. Waking a session on an incoming message uses
notifications/claude/channel, a Claude Code extension. MCP has no standard equivalent that reaches the model, and other clients drop unknown notifications silently. Elsewhere the plugin is poll-based: callwait_for_messages(waits up to 40s for the next message) orcatch_up/unreplied. Every tool result also carries a count of unreplied messages, so a client finds out there is traffic on its next call whatever that call was. - Setup is done from a terminal, not a slash command.
/whatsapp-channel:accessand friends are Claude Code skills. Usebun scripts/access.tsinstead (see Access control from a terminal).
Register the server with an absolute path — ${CLAUDE_PLUGIN_ROOT} is substituted by Claude Code only:
Codex CLI (~/.codex/config.toml)
[mcp_servers.whatsapp]
command = "bun"
args = ["run", "--cwd", "/absolute/path/to/whatsapp-channel", "start"]
startup_timeout_sec = 30 # default 10 is tight for a first Baileys connect
tool_timeout_sec = 120 # default 60; wait_for_messages parks for up to 40sGemini CLI (~/.gemini/settings.json)
{
"mcpServers": {
"whatsapp": {
"command": "bun",
"args": ["run", "--cwd", "/absolute/path/to/whatsapp-channel", "start"],
"timeout": 600000
}
}
}Cursor (~/.cursor/mcp.json for all projects, .cursor/mcp.json for one)
{
"mcpServers": {
"whatsapp": {
"type": "stdio",
"command": "bun",
"args": ["run", "--cwd", "/absolute/path/to/whatsapp-channel", "start"]
}
}
}Only one client at a time can hold the WhatsApp connection: WhatsApp allows one linked-device session per account, and two servers would kick each other off. A second server does not fail silently — it stays up and serves a single whatsapp_unavailable tool naming the process that holds the connection.
Everything the access skill does, without Claude Code:
bun scripts/access.ts status # policy, allowlist, pending codes, groups
bun scripts/access.ts policy pairing # open the door
bun scripts/access.ts pair <code> # approve someone who messaged you
bun scripts/access.ts allow <jid> # add directly
bun scripts/access.ts remove <jid>
bun scripts/access.ts group add <groupJid> [--mention] [--allow jid1,jid2]
bun scripts/access.ts set replyToMode first # ackReaction, textChunkLimit, chunkMode, mentionPatternsApproving always needs the specific code, even when only one pairing is waiting: anyone can create a pending entry just by messaging the account, so "approve the pending one" is exactly what a prompt-injected request looks like. For the same reason this is a terminal command and deliberately not an MCP tool, so nothing arriving over WhatsApp can reach it.
- Bidirectional messaging. Send and receive from the session; long replies are chunked to WhatsApp's limits or sent as a document attachment past a configurable threshold.
- @-mentions.
replycan tag people so they actually get notified — ids are accepted as phone, LID, or full JID, and mentions attach only to the chunk that names them. - Full media support. Photos, voice notes, video, documents, and stickers, in both directions.
- Voice transcription. Incoming voice notes are transcribed locally via mlx-whisper (see setup); without the script they arrive as plain attachments.
- Access control. Pairing codes, allowlists, and per-group policies gate every inbound message — strangers never reach your session. Managed via
/whatsapp-channel:accessin Claude Code, orbun scripts/access.tsanywhere. - Per-group personalities. Each group gets its own
config.mdwith a custom personality and conversation memory. - Permission relay. Approve or deny Claude's tool requests from WhatsApp with an emoji reaction (👍 / 👎).
- Cron tasks. A
## Cron Jobssection in a group'sconfig.mdschedules recurring server-side tasks. - Context recovery. After a restart, the
catch_uptool replays recent two-way conversation per chat, unreplied counts, and open tasks fromtasks.md, so a fresh session resumes mid-flight work. - Dual accounts. Run personal and business numbers side by side with separate state and behaviors.
- Self-diagnosis.
/whatsapp-channel:doctorchecks the server process, device link, singleton lock, and config, then walks you through the fixes — no more guessing why replies stopped.
WhatsApp (phone) <──Baileys──> MCP Server <──stdio──> Claude Code
The server (a single Bun process) holds the linked-device connection and forwards inbound messages to the session as channel notifications after they pass the access gate. Claude acts through MCP tools — reply, react, edit_message, download_attachment, status, unreplied, catch_up, list_groups. Runtime state (auth, allowlists, group configs, inbox) lives in ~/.whatsapp-channel/, never in the repo.
Messages sent by Claude appear as coming from your phone number. Use a dedicated number if you want a distinct bot identity.
One-time setup (Apple Silicon, mlx-whisper):
brew install ffmpeg # mlx-whisper uses it to decode audio
python3 -m venv ~/whisper-env
source ~/whisper-env/bin/activate
pip install mlx-whisper
cp scripts/whisper-transcribe.sh ~/whisper-transcribe.sh
chmod +x ~/whisper-transcribe.sh
~/whisper-transcribe.sh path/to/sample.ogg # optional: testThe reference script uses mlx-community/whisper-large-v3-turbo — accurate, fast, multilingual. Swap the model in the script if you prefer a smaller one.
| Issue | Solution |
|---|---|
| Pairing code not showing | Run /whatsapp-channel:configure <phone> first, then relaunch |
| 440 disconnect error | Only one connection per auth state allowed. Kill stale processes: pkill -f "whatsapp.*server" |
| Session not waking on new messages | Most common cause: launched without --dangerously-load-development-channels plugin:whatsapp-channel@whatsapp-claude-plugin. Tools work but inbound pushes are dropped (Channel notifications skipped in the MCP debug log) — relaunch with the flag. |
| Messages not arriving | Known Claude Code client bug (#37933). Server-side is correct, awaiting client fix. |
| Replies still send, nothing arrives | Send a DM to the connected number as well as a group message — a DM that lands while groups stay silent means the group sender-key path, not the connection. ~/.whatsapp-channel/diag.log records an inbound upsert line for every batch WhatsApp delivers, so it distinguishes "never arrived" from "arrived and was dropped". Set WHATSAPP_DIAG_DEBUG=1 for Baileys' full debug stream. |
| Auth expired | Run /whatsapp-channel:configure reset-auth and re-pair |
Full documentation lives in USAGE.md: access control, the tools exposed to the assistant, dual-account setup, session conflicts, and resetting auth.
Issues and pull requests are welcome — read CONTRIBUTING.md before opening one. Report security issues privately per SECURITY.md.
Apache 2.0 — Copyright 2025 Richie Liu
