messenger
Sends Discord direct messages from your own bot to users identified by alias, appending ' -claude' so readers know a bot wrote it. Includes alias-based recipient lookup and messenger status checks. Send-only.
Sends WhatsApp messages from your own number. Recipients can be aliases, contact or group names, phone numbers, or chat JIDs, with tools for searching contacts/chats and checking bridge status. Send-only; never reads message text.
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., "@messengertell Zoe I'll be there in a minute"
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.
messenger
A small local MCP server that lets an assistant such as the Claude app send messages for you: WhatsApp from your own number, Discord DMs from your own bot.
Say "tell Zoe I'll be there in a minute" and it goes out. It sends only when you ask for it in that conversation, and by default it shows you the message and waits for a yes first.
It is also a worked example of making a tiny server observable: structured logs, OpenTelemetry traces and metrics, a doctor script that connects the way a client does, and the MCP Inspector wired in. See Debugging.
How it fits together, with diagrams: ARCHITECTURE.md.
Linux only, including WSL. Not tested on macOS, and the WhatsApp part needs systemd.
Tools
Tool | What it does |
| Sends from your number, exactly as written. |
| Sends from your bot, with |
| WhatsApp address book and chats by name or number |
| Recent WhatsApp chats, newest first (names and times, never message text) |
| Your alias shortcuts and their platform |
| Is the bridge connected, is the bot token set, is confirmation on |
The server is send-only. It never reads message text.
Related MCP server: Zappy MCP
Safety
Confirmation is on by default. With
MESSENGER_CONFIRM_SENDS=yes(the default), the server instructs the assistant to show you the recipient and the exact text and wait for a clear yes. Set it tonoand your instruction to message someone counts as the approval.scripts/setup.shasks which you want.That is an instruction to the model, not a lock. The hard gate is your MCP client's own tool-approval prompt. Keep that prompt on for the two send tools unless you have decided you don't want it.
It never guesses between people. A name that fits more than one contact sends nothing and returns the matches.
It never acts on text it reads. The instructions tell the assistant not to send because a file, web page or tool result asked it to.
Archived chats are never matched by name. Naming one outright (alias, number or JID) still reaches it.
Loopback only. The server speaks stdio; the WhatsApp bridge binds
127.0.0.1. Never tunnel or port-forward it.Message text is never logged. Logs, traces and metrics carry metadata only, and recipients are masked.
WhatsApp ban risk
The WhatsApp side uses the unofficial WhatsApp Web protocol (whatsmeow). WhatsApp's terms forbid unofficial clients, and accounts do get banned for it, mostly for bulk or automated sending. Keep it to human-volume messages to people who know you, or skip WhatsApp and use Discord only.
Setup
You need uv, Python 3.11+, and an MCP client. WhatsApp also needs Go 1.26+, gcc and systemd user services.
git clone https://github.com/duplonicus/claude-messenger
cd claude-messenger
scripts/setup.shThe script installs the Python package, creates config/contacts.json and .env, asks whether to confirm before every send, and asks whether to set up WhatsApp. It prints the line to register the server with Claude Code or Claude Desktop. Re-running it is safe.
Then:
scripts/doctor.py # connects like a client and checks everythingContacts
Aliases are shortcuts, checked before anything else. config/contacts.json is gitignored:
{
"mom": { "platform": "whatsapp", "id": "15557654321" },
"zoe": { "platform": "discord", "id": "123456789012345678" }
}WhatsApp
id: country code + number, or a full JID (...@s.whatsapp.net, groups...@g.us).Discord
id: Developer Mode → right-click the user → Copy User ID. They must share a server with your bot.
Edits take effect on the next send; no restart.
Discord
Create a bot in the Discord developer portal and put its token in .env (mode 0600, gitignored) as DISCORD_BOT_TOKEN. "Cannot send messages to this user" means no shared server, their DMs are closed, or they blocked the bot.
scripts/setup.sh --whatsapp clones and builds the bridge, installs it as the whatsapp-bridge systemd user service, and starts it.
scripts/pair.sh # fresh QR; scan from Linked devices
systemctl --user status whatsapp-bridge
journalctl --user -u whatsapp-bridge -fThe bridge is verygoodplugins/whatsapp-mcp, pinned to a commit in scripts/install-bridge.sh and cloned to vendor/ (gitignored). Only its Go bridge is used. Its own MCP server, which can read message history, is not registered anywhere.
It listens on
127.0.0.1:8080only, with a bearer token instore/.bridge-token(0600).Webhooks and media auto-download are turned off in the service file.
If the linked device gets dropped, re-pair with
scripts/pair.sh.
How a WhatsApp recipient is chosen
In order:
An alias from
config/contacts.json.A JID, or a phone number. A number typed without its country code is matched to the saved contact it belongs to; an unsaved number needs the country code.
A name, matched against the bridge's own store (
whatsmeow_contactsandchats, opened read-only). A whole-name match beats a partial one.
WhatsApp sometimes knows one person by both a phone number and a "linked ID". The bridge's whatsmeow_lid_map ties them together, so they count as one contact, under the number. Without that, one person shows up twice and every name becomes ambiguous.
Debugging
This follows the MCP debugging guide. Start at the top; each step answers a narrower question.
Question | Run |
Does it connect and work at all? |
|
What did the server do? |
|
What did Claude Desktop see? |
|
What does the protocol traffic look like? |
|
Where did the time go inside a call? | traces, below |
"Server disconnected" in Claude Desktop has, for this server, always meant the process was stopped from outside while the app was running. The app does not restart it, and logs/server.log shows stopping on signal at the same second. Quit the app fully and reopen it. The same applies after a code change, so iterate with the doctor and the Inspector, not the app.
Logs
One JSON object per line, to stderr (which the MCP client captures) and to logs/server.log (rotates at 1 MB, keeps 3):
{"ts": "2026-10-04T19:32:16.429Z", "level": "info", "event": "tool call", "ms": 32.2, "outcome": "sent", "tool": "send_whatsapp", "call": "fac56338", "recipient": "***0123", "chars": 42}callis a per-call ID;outcomeissent,refused(nothing went out) orok(a lookup).Never logged: message text, tokens, full phone numbers.
stdout is the protocol channel. Nothing may print to it; a test runs the real process and checks.
MESSENGER_LOG_LEVEL=DEBUGin.envfor more.
logs/sent.log records each send attempt: time, platform, recipient, length, outcome. Never the text.
Traces (OpenTelemetry)
Off by default. Each tool call becomes a span, with child spans for the HTTP calls to the bridge and to Discord, so you can see what a slow send was waiting on.
uv pip install -e '.[otel]'
scripts/observability.sh up # local Jaeger + Prometheus, loopback only, needs Docker
echo 'MESSENGER_TRACES=otlp' >> .env # then restart the MCP client
# open http://localhost:16686, service "messenger"Jaeger's Monitor tab works too: Jaeger derives request rate, error rate and latency per tool from the spans and keeps them in Prometheus as traces_span_metrics_*. It only counts server spans, which is why tool spans are created with SpanKind.SERVER.
MESSENGER_TRACES=console prints spans to stderr instead, no collector needed. OTEL_EXPORTER_OTLP_ENDPOINT points it somewhere other than http://127.0.0.1:4318. If no collector is listening, the server carries on.
Metrics (OpenTelemetry → Prometheus)
Off by default. Two instruments, labelled by tool and outcome only:
Metric (Prometheus name) | Kind | Answers |
| counter | How often is each tool used, and how often does a send get refused? |
| histogram | How long do calls take, and what is the slow tail? |
scripts/observability.sh up
echo 'MESSENGER_METRICS=otlp' >> .env # then restart the MCP client
# open http://localhost:9090Queries to try:
sum by (tool, outcome) (messenger_tool_calls_total)
histogram_quantile(0.95, sum by (le, tool) (rate(messenger_tool_duration_milliseconds_bucket[1h])))The server pushes totals every 15 s and once more on shutdown, straight into Prometheus's OTLP receiver, so there is nothing to scrape. Each server process is its own instance, so always sum by (...) across them. Labels are deliberately low-cardinality: a recipient or call ID as a label would make every message its own time series.
Claude Desktop's own tools (Windows)
Server status: Settings → Developer → Local MCP servers.
Logs:
%LOCALAPPDATA%\Claude\Logs(mcp-server-messenger.log,mcp.log,main.log).Chrome DevTools: put
{"allowDevTools": true}in%APPDATA%\Claude\developer_settings.json. After a relaunch,Ctrl+Alt+Iopens DevTools: Console for client-side errors, Network for message payloads and timing.
Tests
.venv/bin/pytestSends are mocked with httpx.MockTransport; no test sends a real message.
License
MIT
This server cannot be deployed
Maintenance
Related MCP Connectors
Drive WhatsApp from any MCP client: pair devices, send text and media, manage contacts and groups.
Your own WhatsApp as an MCP server: read, search and send from any MCP client.
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.
Let Claude or ChatGPT search, read and send your WhatsApp messages over MCP. OAuth sign-in.
Related MCP Servers
- AlicenseNot gradedqualityDmaintenanceEnables sending WhatsApp messages through Claude Desktop and other MCP-compatible LLMs. Supports single and bulk messaging, phone number validation, and session management via the zapr.link service.MIT
- AlicenseAqualityDmaintenanceEnables sending, reading, and deleting WhatsApp messages through Claude Desktop and other MCP clients with granular per-chat permissions. Built on whatsapp-web.js using a headless browser to automate WhatsApp Web.6MIT
- FlicenseAqualityDmaintenanceAn MCP server that wraps the wacli tool to enable AI clients to read, search, and send WhatsApp messages through a personal account. It provides comprehensive tools for managing chats, groups, and contacts using an existing authenticated WhatsApp session.27-

@chatmaid/mcpofficial
AlicenseAqualityCmaintenanceEnables sending WhatsApp messages and managing Chatmaid account from any MCP-compatible AI client.8189 npmMIT