WhatsApp Channel
by naokikoo
README.md
# WhatsApp Channel for Claude Code
Drive your Claude Code session from WhatsApp. Choose a **linked device** for a
personal account or the **official WhatsApp Cloud API** for business DMs.
The default Baileys provider connects like WhatsApp Web: pair your phone, then
Claude replies from your own number. Existing installations keep this provider.
The optional Cloud API provider uses Meta's business API and a public HTTPS
webhook, with its own credentials, messaging policies and applicable fees.
Both providers feed the same MCP channel and access gate.
[](https://code.claude.com/docs/en/plugins)
[](https://modelcontextprotocol.io)
[](https://opensource.org/licenses/Apache-2.0)
> **Community project.** This plugin is not made or endorsed by Anthropic or Meta.
> Its Baileys provider uses an unofficial linked-device protocol, which may
> conflict with WhatsApp's terms and put the number at risk. The optional Cloud
> provider uses official API transport; confirm your use is permitted under
> Meta's business and AI-provider rules before enabling it.
## Installation
```sh
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-plugin
```
The `--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](./scripts/watchdog.sh)) 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.
> **Know what this flag does.** Claude Code warns against loading downloaded channels this way: a loaded channel can put text from other people straight into your session. Read this plugin's code before you use it, keep the [allowlist](./ACCESS.md) to people you trust, and keep permission prompts on. Never combine it with `--dangerously-skip-permissions`: with prompts off, anyone in an allowlisted chat could get Claude to run commands on your computer.
Inside the session, choose your connection:
```text
/whatsapp-channel:setup
/whatsapp-channel:configure provider
```
The wizard runs in your own terminal so Cloud credentials never pass through
chat. Or run `bun scripts/provider.ts choose` from the plugin directory.
| Connection | Setup | Main limits |
| ---------------------------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| Linked device (`baileys`, default) | Pair your phone; no Meta API credentials | Unofficial protocol; existing personal/group behavior |
| Official (`cloud-api`) | Meta business number, token, app secret, verify token, public HTTPS webhook | DMs only in this implementation; service window/templates and Meta fees/policies apply |
For linked-device pairing, save your number with
`/whatsapp-channel:configure <phone>` (country code + number, no `+`). Enter the
printed pairing code in WhatsApp → Settings → Linked Devices → Link a Device →
**Link with phone number instead**, or scan the QR.
For official business DMs, follow [Cloud API setup](./docs/cloud-api.md). This
provider does not yet support groups, message editing, phone history/address-book
import or coexistence synchronization. Add an explicitly allowed personal owner;
the business number is never auto-added, and the owner must be set explicitly with
`/whatsapp-channel:access set owner <jid>`. A non-empty key saved in
`~/.whatsapp-channel/.env` by the terminal tools wins over plugin `/config` values
for that key; remove it from `.env` to return it to plugin management.
Configuration readiness is not proof
that the public webhook works. Rates vary; consult
[Meta pricing](https://business.whatsapp.com/products/platform-pricing) and
[FAQ](https://whatsappbusiness.com/resources/faq/).
## Other MCP clients (Codex CLI, Gemini CLI, Cursor)
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: call `wait_for_messages` (parks up to 40s; the first call on a connection returns whatever is already unreplied, later calls only what arrived since) or `catch_up` / `unreplied`. Most tool results also carry a count of unreplied messages, so a client finds out there is traffic on its next call. Three do not: `unreplied` and `wait_for_messages`, which just returned those very messages, and `catch_up` with no arguments, which is that count already.
- **Setup is done from a terminal, not a slash command.** `/whatsapp-channel:access` and friends are Claude Code skills. Use `bun scripts/access.ts` instead (see [Access control from a terminal](#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`)
```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 40s
```
**Gemini CLI** (`~/.gemini/settings.json`)
```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)
```json
{
"mcpServers": {
"whatsapp": {
"type": "stdio",
"command": "bun",
"args": ["run", "--cwd", "/absolute/path/to/whatsapp-channel", "start"]
}
}
}
```
Only one client at a time can hold a state directory’s singleton lock. With Baileys, two connections using the same linked-device auth would kick each other off. Use separate state directories for separate numbers. A second server does not fail silently — it stays up and serves a single `whatsapp_unavailable` tool naming the process that holds the connection.
## Access control from a terminal
Everything the access skill does, without Claude Code:
```sh
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, mentionPatterns
```
Approving 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.
## Features
The list below describes the default linked-device provider. See the
[provider capability comparison](./docs/cloud-api.md#capabilities-in-this-plugins-first-cloud-provider)
for the initial Cloud API scope. Shared DM access, attachment processing,
transcription and context recovery also work with Cloud API.
- **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.** `reply` can 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](#voice-transcription-optional)); 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:access` in Claude Code, or `bun scripts/access.ts` anywhere.
- **Per-group personalities.** Each group gets its own `config.md` with 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 Jobs` section in a group's `config.md` schedules recurring server-side tasks.
- **Context recovery.** After a restart, `catch_up` with no arguments says how many messages are waiting per chat - counts only, no message text - plus open tasks from `tasks.md`. Name a chat and it replays that room's recent two-way conversation, so a fresh session resumes mid-flight work without reading every chat it has.
- **Dual accounts.** Run personal and business numbers side by side with separate state and behaviors.
- **Self-diagnosis.** `/whatsapp-channel:doctor` checks the server process, device link, singleton lock, and config, then walks you through the fixes — no more guessing why replies stopped.
## How it works
```text
WhatsApp linked device <──Baileys────────> MCP Server <──stdio──> Claude Code
WhatsApp business DMs <──Cloud API/HTTPS─> MCP Server
```
The server (a single Bun process) holds either the linked-device connection or the Cloud API webhook listener and forwards inbound messages after they pass the access gate. Claude acts through shared MCP tools such as `reply`, `react`, `download_attachment`, `status`, `unreplied` and `catch_up`. Provider-specific tools include linked-device group/edit operations and Cloud `send_template`. Runtime state lives in `~/.whatsapp-channel/` (or `WHATSAPP_STATE_DIR`), never in the repo.
Messages sent by Claude appear from your linked personal number or configured business number. Use a dedicated number if you want a distinct assistant identity.
## Voice transcription (optional)
One-time setup (Apple Silicon, mlx-whisper):
```bash
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: test
```
The reference script uses `mlx-community/whisper-large-v3-turbo` — accurate, fast, multilingual. Swap the model in the script if you prefer a smaller one.
**Cloud alternative.** To skip the local setup, open `/plugin`, pick this plugin, and set **Voice transcription provider** to `groq` or `openai` along with that provider's API key. Keys are kept in your OS keychain, not in `settings.json`. Voice notes are then uploaded to `api.groq.com` or `api.openai.com` for transcription. The older `TRANSCRIPTION_PROVIDER`, `GROQ_API_KEY` and `OPENAI_API_KEY` environment variables still work.
## Troubleshooting
| 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](https://github.com/anthropics/claude-code/issues/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 |
## Documentation
Full documentation lives in [USAGE.md](./USAGE.md): [access control](./USAGE.md#access-control), the [tools exposed to the assistant](./USAGE.md#tools-exposed-to-the-assistant), [dual-account setup](./USAGE.md#dual-account-setup), [session conflicts](./USAGE.md#session-conflicts), and [resetting auth](./USAGE.md#resetting-auth).
## Contributing
Issues and pull requests are welcome — read [CONTRIBUTING.md](./CONTRIBUTING.md) before opening one. Report security issues privately per [SECURITY.md](./SECURITY.md).
## Star History
<a href="https://www.star-history.com/?type=date&repos=Rich627%2Fwhatsapp-claude-plugin">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/chart?repos=Rich627/whatsapp-claude-plugin&type=date&theme=dark&legend=top-left&sealed_token=NrfP1Fv0z7ipQM961lFZJbXE76GS7paukclIhr6km37t0lJAzivyX0JUNQTkRaxa5lSpRCYmef3xvHaiUKCgBS0KbwpeIohfMOqur0ULPiTt2h2DWcUui1YJ2nux4W9Ug8u8D6CNl91ZYInSZCrrdNi5hydWjSLy89XtzYYM83F-mhgJI44lLZoxj7Na" />
<source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/chart?repos=Rich627/whatsapp-claude-plugin&type=date&legend=top-left&sealed_token=NrfP1Fv0z7ipQM961lFZJbXE76GS7paukclIhr6km37t0lJAzivyX0JUNQTkRaxa5lSpRCYmef3xvHaiUKCgBS0KbwpeIohfMOqur0ULPiTt2h2DWcUui1YJ2nux4W9Ug8u8D6CNl91ZYInSZCrrdNi5hydWjSLy89XtzYYM83F-mhgJI44lLZoxj7Na" />
<img alt="Star History Chart" src="https://api.star-history.com/chart?repos=Rich627/whatsapp-claude-plugin&type=date&legend=top-left&sealed_token=NrfP1Fv0z7ipQM961lFZJbXE76GS7paukclIhr6km37t0lJAzivyX0JUNQTkRaxa5lSpRCYmef3xvHaiUKCgBS0KbwpeIohfMOqur0ULPiTt2h2DWcUui1YJ2nux4W9Ug8u8D6CNl91ZYInSZCrrdNi5hydWjSLy89XtzYYM83F-mhgJI44lLZoxj7Na" />
</picture>
</a>
## License
[Apache 2.0](./LICENSE) — Copyright 2025 Richie Liu
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues