replywizard-whatsapp-mcp
v0.5.0
Published
Local MCP server and command line that expose WhatsApp Web (read + value tools) to AI clients via the Reply Wizard Chrome extension.
Maintainers
Readme
replywizard-whatsapp-mcp
Local Model Context Protocol server that exposes your WhatsApp Web session to AI clients
(Claude Desktop, Claude Code, Cursor, Antigravity, …) through the Reply Wizard Chrome
extension, plus a replywizard command line onto the same bridge.
Read + value only and local-only: there is no path that can send or modify WhatsApp messages, and nothing leaves your machine.
How it works
A Chrome extension cannot itself be an MCP server (no listening socket / stdio). So this is a small companion process that bridges two connections:
AI client ──MCP over stdio──▶ replywizard-whatsapp-mcp ──WebSocket (127.0.0.1)──▶ Reply Wizard extension ──▶ WhatsApp Web- The AI client speaks MCP to this process over stdio.
- This process runs a WebSocket server on
127.0.0.1:7869; the extension dials in as a client. - Each
tools/callis forwarded to the extension, executed against the WhatsApp page, and returned.
This package is intentionally self-contained (only @modelcontextprotocol/sdk and ws) so it can be
lifted into its own repository without touching the extension.
Tools
| Tool | Kind | What it does |
|------|------|--------------|
| ping | health | Confirms the extension is connected and a WhatsApp Web tab is reachable. |
| list_chats | read | Recent chats: id, name, isGroup, unreadCount, timestamp. |
| get_messages | read | Recent (loaded) messages for a chat; media is labelled, voice notes report duration. |
| get_contact | read | Look up one contact by id. |
| list_contacts | read | Your saved (address-book) contacts. |
| summarize_chat | value | Bulleted summary of a chat's recent messages (uses your configured AI provider). |
| suggest_reply | value | Drafts a reply as you, returns text only, never sends. |
| transcribe_voice_note | value | Transcribes a voice note via your configured cloud speech-to-text provider. |
| draft_message | write | Puts text in one chat's message box under a "Drafted by" bar. Refuses a box that already holds text. Never sends: you read it and press Send. |
| create_reminder | write | Saves a Reply Wizard reminder for a chat (dueAt in ISO 8601 with a time zone, optional repeat). Chrome notifies you at that time; nothing is sent. |
| save_template | write | Saves a message template to Reply Wizard's Templates. |
Write tools (0.5.0) are off until you allow them. They need Reply Wizard 3.5.9 or later and the switch "Let AI assistants save and draft" on the extension's WhatsApp MCP tab. Until then every call answers with a sentence saying so and changes nothing. Every attempt, allowed or refused, is listed on that tab, and none of the three can send a message.
Value tools reuse the extension's existing AI/STT configuration; your API keys never leave the extension (the hub only sees tool arguments and results).
Setup
Multi-agent by default. The
npx -y replywizard-whatsapp-mcpconfig below runs the connector (the package's default bin), which auto-starts a shared local daemon, so several assistants (Claude Code + Cursor + Claude Desktop, …) can share one WhatsApp session at once, with no extra setup. See Multiple AI assistants at once for the architecture. Use the single-arg form above (not the older-p <pkg> <bin>form, which some clients fail to launch).
Published to npm as replywizard-whatsapp-mcp.
You don't install anything by hand, your AI client runs it via npx (requires Node.js 18+).
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"replywizard": {
"command": "npx",
"args": ["-y", "replywizard-whatsapp-mcp"],
"env": { "RW_MCP_PORT": "7869" }
}
}
}Claude Code
claude mcp add replywizard -- npx -y replywizard-whatsapp-mcpCursor and VS Code: one click
The extension's WhatsApp MCP tab has Add to Cursor and Add to VS Code buttons that hand the editor exactly the config above, plus a copy button for the Claude Code command. The raw JSON stays on screen next to them: an install deeplink is opaque by nature, so reading it first is always an option.
Antigravity
Antigravity is a VS Code fork and inherits both --add-mcp and the mcp/install URL handler,
but it keeps two MCP lists and they are not the same one:
| List | Path | Key | Reaches the agent? |
|---|---|---|---|
| The editor's | ~/.config/Antigravity IDE/User/mcp.json | servers | No. Written by antigravity-ide --add-mcp |
| The agent's | ~/.gemini/config/mcp_config.json | mcpServers | Yes. Edited by hand, and it is the config above |
Use the agent's file. That ordering was measured rather than assumed: a traced entry was
left in the editor's list, whose command appends a line to a log file before starting the real
server, and it sat there across an IDE restart two and a half hours later without the log ever
being written. Nothing started it. In that same session the agent answered live WhatsApp
questions, configured only in the agent's file. So --add-mcp reports success and changes
nothing the agent reads. The Copy Antigravity config button gives you the JSON above; the
--add-mcp line is still printed on the tab, labelled for what it does, for anyone who wants
the entry in the editor's own MCP panel as well.
The bridge names its own version on the handshake as of 0.3.1, so the extension can tell a
current host from a stale one and say so on its WhatsApp MCP tab. An absent version is itself
the signal: a host old enough to matter is exactly one that does not send the field, which is
why /healthz carrying it since 0.3.0 was not enough on its own. The commonest cause of a
stale host is a global install shadowing npx -- run
npm uninstall -g replywizard-whatsapp-mcp and the next client start fetches the current one.
Configured is not the same as loaded. Antigravity caches each loaded server's tool schemas
under ~/.gemini/antigravity-ide/mcp/<name>/. On that restart six of the seven configured
servers had their schemas rewritten within the minute, and Reply Wizard's directory kept a
two-week-old timestamp, so the agent had no Reply Wizard tools that session. If the agent panel
does not list Reply Wizard after you edit the file, switch it on there. The connector is not
the usual cause: five consecutive stdio launches all came up in about 1.7 seconds.
There is deliberately no deeplink button: the handler ships in the build, but nothing registers
the antigravity-ide: scheme on Linux, so the click would do nothing and say nothing.
The command line
The same package installs a replywizard command. It is a THIRD front end onto the same local
daemon, beside the MCP HTTP endpoint and the stdio connector, so it works while every editor you
have connected is still attached, and it can do exactly what an AI client can do and nothing more.
npx -y -p replywizard-whatsapp-mcp replywizard --help
replywizard status # is the bridge up, is the browser paired
replywizard pair # where to find the 6-digit code
replywizard chats --limit 10
replywizard messages 919110412164 --limit 20
replywizard contacts
replywizard summarize 919110412164 # uses your configured AI provider
replywizard suggest 919110412164 "politely decline"
replywizard transcribe <messageId>--json prints the raw answer for scripting, and a failed call exits non-zero, so a pipeline can
branch on it:
replywizard chats --limit 5 --json | jq -r '.chats[] | "\(.unreadCount)\t\(.name)"'It never sends. There is no send command because there is no send path in the extension,
which is the answer to "will this get my WhatsApp account banned". The commands are generated from
the same tool definitions the MCP surface uses, so the two can never document different arguments.
The first command starts the shared daemon if it is not already running, exactly as an editor's
launch does. It identifies itself as replywizard-cli, so it appears beside your editors under
Connected assistants on the extension's WhatsApp MCP tab.
RW_MCP_PORT is optional (defaults to 7869); set it on both sides if you change it.
From any other computer: --remote (0.4.0)
The local mode needs to run on the computer where Chrome is open. Remote mode runs anywhere, a
second laptop, a server or a cloud coding agent, and reaches the same browser through Reply
Wizard's relay at https://mcp.replywizard.app, the server claude.ai and ChatGPT use. It needs a
paid Reply Wizard plan and the Use from claude.ai and ChatGPT switch on in the extension's
WhatsApp MCP tab.
replywizard login # once: opens your browser to sign in and approve
replywizard chats --remote --limit 10
REPLYWIZARD_REMOTE=1 replywizard summarize 919110412164 # the same, for an agent's shell
replywizard status --remote # signed in, and is your browser reachable
replywizard logoutSign-in is OAuth 2.1 with PKCE over a loopback redirect on 127.0.0.1, the flow Claude Code uses.
With no browser on the machine, open the printed link on any device, approve, and paste the
address the browser lands on back into the terminal. The sign-in lives in
~/.replywizard-mcp/remote.json (0600): a one-hour access token and a 30-day refresh token that
rotates on every use. Disconnect it from My account at https://replywizard.app/account, or with
replywizard logout. What a command reads passes through the relay on its way to you and is not
stored there.
Pairing-code toast. The code is also shown as a desktop notification (
osascripton macOS, a PowerShell balloon on Windows,notify-sendon Linux), because the connector is usually launched in the background where stderr is invisible. It is best-effort and never fails the daemon;~/.replywizard-mcp/pairing-code.txt(0600) is the backstop. All three branches run in CI on their own platform,npm testin this package covers them, and the real-notifier test is skipped locally unlessCI=1orRW_TEST_NOTIFY=1so running the suite does not interrupt you with a toast.
Running from source instead? Clone the repo,
cd mcp-host && npm install, and point your client atnode /absolute/path/to/mcp-host/src/connector.js(multi-agent), ornode .../src/index.jsfor the legacy single-client, no-daemon hub.
Multiple AI assistants at once (daemon)
This happens automatically with the npx config above: replywizard-whatsapp-mcp runs the connector,
which starts one shared daemon. There is no limit on the number of clients. Measured
2026-09-10 against a running daemon: four concurrent MCP sessions, all four listing the same
eight tools, and three concurrent tool calls through the one browser socket answering in 24, 24
and 25ms. VS Code, Claude Code, Cursor, Antigravity, the replywizard command line and a second
window of any of them can all be attached at once. The one limit is on the other side: one
WhatsApp Web browser at a time, because a second extension socket is refused. (The legacy index.js hub, run directly via node .../src/index.js, binds
the WebSocket port itself, so only one AI client can use it at a time; a second launch hits
EADDRINUSE.) The daemon architecture:
client A (HTTP) ─┐
client B (stdio)→ rw-mcp-connector ─┤→ rw-mcp-daemon ⇄ Reply Wizard extension ⇄ WhatsApp Web
client C (stdio)→ rw-mcp-connector ─┘ (one daemon; owns ws://127.0.0.1:7869, serves http://127.0.0.1:7870/mcp)rw-mcp-daemon: long-lived; owns the extension WebSocket and serves MCP over Streamable HTTP to any number of clients (each gets its own session). Single-instance: a second daemon exits cleanly.rw-mcp-connector: a stdio shim for clients that only launch a command; it auto-starts the daemon if needed and transparently proxies. Reads the auth token for you (no token in your config).
Clients that speak HTTP (e.g. Claude Code) connect to the daemon directly, no connector needed:
TOKEN=$(node -e "console.log(require('os').homedir())")/.replywizard-mcp/daemon-token.json # see the token
claude mcp add rw --transport http http://127.0.0.1:7870/mcp --header "Authorization: Bearer <token>"Stdio-only clients use the connector (auto-token, auto-start):
{
"mcpServers": {
"replywizard": { "command": "npx", "args": ["-y", "replywizard-whatsapp-mcp"] }
}
}From source:
node /abs/path/mcp-host/src/connector.js(or…/daemon.jsto run the daemon directly).
Environment variables
| Var | Default | Meaning |
|-----|---------|---------|
| RW_MCP_PORT | 7869 | Extension WebSocket port. |
| RW_MCP_HTTP_PORT | 7870 | Daemon HTTP (client) port. |
| RW_MCP_IDLE_MS | 1800000 | Daemon exits after this long with no sessions (0 disables). |
| RW_MCP_STALE_SESSION_MS | 600000 | Reap sessions silent this long (client crashed without a clean disconnect). |
Auth token
The daemon generates a bearer token on first start at ~/.replywizard-mcp/daemon-token.json (mode
600). Every /mcp request must present it (Authorization: Bearer <token>); /healthz is open.
The connector reads it automatically. To revoke, delete that file (the daemon mints a new one on
next start): existing clients must pick up the new token.
First-run pairing
- Open WhatsApp Web in the browser with Reply Wizard installed.
- Open Reply Wizard Settings → WhatsApp MCP and turn on Enable AI access. The toolbar popup reports the same state under AI client access and has a Manage in Settings button that jumps straight there, but every control lives on the tab.
- Start your AI client (it launches this hub). The hub prints a 6-digit pairing code to its stderr / the client's MCP log.
- Enter that code in Settings → WhatsApp MCP. Once paired, the extension stores a session token and reconnects automatically on subsequent runs (no code needed again).
Use Revoke pairing on the same tab to drop the token and disable the bridge.
A code survives a reconnect. The extension's MV3 service worker reconnects on its own schedule, and until 2026-09-03 each reconnect threw the pairing code away and minted a new one, so the digits you were reading off the toast went stale while you typed them and another toast appeared. A code that is still within its five minutes is now reused instead: one code, one notification, and it stays valid for as long as it was ever going to.
Two things do not change on reuse, and each is tested:
- The deadline is not extended. The five minutes run from when the code was first issued, so a client that reconnects every four minutes cannot keep one code alive indefinitely.
- The wrong-attempt counter is not reset. This is the load-bearing half. Without it, closing and reopening the socket would hand an attacker another five guesses against the same code, over and over; the old behaviour was safe only because the code changed each time, so preserving one property without the other would have been a regression.
A code is still destroyed immediately on a successful pairing, on lockout, and on expiry.
More than one browser can stay paired, since 0.3.2. The session file held a single token until then, and the hub accepts one extension socket at a time, so somebody running two Chrome profiles (or Chrome and Edge) had each pairing invalidate the other's: whichever browser took the socket next was asked for a fresh six-digit code, and neither was doing anything wrong. Up to five browsers now stay paired at once. The least recently used is dropped when a sixth pairs, and a successful reconnect moves that browser to the front, so the ones actually in use are the ones that survive. Each token is still 256 bits, still minted only by a completed pairing, and still only ever compared.
If the hub cannot SAVE the pairing, it now says so. Before 0.3.2 a failed write was a line
on a stderr the daemon does not have (the connector spawns it detached), so an unwritable or
full home directory produced a pairing that worked until the daemon restarted and was then
demanded again, for ever, with nothing said anywhere. Writes are atomic and read back before
they count, the extension is told on the handshake (session_persisted: false) and puts a
sentence on the MCP tab, and replywizard status prints both the reason and the directory it
is using. That directory matters on its own: the daemon inherits the environment of whichever
client started it, so a sandboxed editor (flatpak, snap) gives its daemon a different HOME
and therefore a different session file, and the pairing it makes is invisible to every other
client. Printed, the two tell themselves apart.
Security
- Binds to
127.0.0.1only. - No tool runs until the pairing code is confirmed in the extension.
- Read + value tools only: v1 has no send/modify capability.
- Session tokens are stored at
~/.replywizard-mcp/session.json(mode 600), at most five, newest first. Override the directory withRW_MCP_STATE_DIR.
Troubleshooting
| Symptom | Fix |
|---------|-----|
| Reply Wizard extension is not connected | Open WhatsApp Web and enable AI access in the popup. |
| Not paired yet | Enter the 6-digit code (printed to the hub's stderr) in the popup. |
| It asks for a code again and again | Run replywizard status. A WARNING: this daemon cannot save its state line means the state directory is unwritable or full. Otherwise compare the state: line across your AI clients: a sandboxed editor gives its daemon a different HOME, so it keeps its own session file. |
| No WhatsApp Web tab is open | A read/value tool needs an open, logged-in WhatsApp Web tab. |
| Transcription error about a cloud provider | transcribe_voice_note needs a cloud STT key in Options → Speech-to-Text (on-device STT is not used over MCP). |
| Port in use | The default is 7869. Changing it is advanced (set RW_MCP_PORT on the hub and the rw_mcp_port value in the extension's storage to match). |
See ../ARCHITECTURE.md (MCP Integration) for the design, and
../ROADMAP.md §3.4 for the open MCP items.
