Skip to content

Back to the catalog

Show HN: WhatsApp MCP running free on Cloudflare
github.comrepo

Show HN: WhatsApp MCP running free on Cloudflare

whatsapp-mcp is an MCP server that runs in the user's own Cloudflare account and lets MCP clients such as Claude Code and Claude Desktop search and read WhatsApp chats. It links as a WhatsApp Web-style device through Baileys, stores messages in SQLite with FTS5 inside a Durable Object, and is read-only. The README documents deployment, OAuth sign-in, contact naming through agents, and workerd shims for Baileys.

TL;DR Deploys a read-only WhatsApp MCP server on Cloudflare Workers so agents can search and summarize your chats.
Document

What it claims

whatsapp-mcp is a single-owner, read-only MCP server that holds a full WhatsApp message history in a Durable Object's SQLite database inside the owner's Cloudflare account. It does not keep a live WhatsApp connection. It connects only when an agent asks for data and once a day by cron at 04:00 UTC. Each time it collects what WhatsApp queued, writes it, and disconnects. The phone keeps its notifications. Using it breaks WhatsApp's terms of service. The author calls the risk "low, but it isn't zero" for a device that only reads. The licence is MIT.

How it works

The Worker in src/index.ts serves /mcp and the setup and sign-in pages. It is stateless except for a KV namespace that holds OAuth tokens. One Durable Object, WhatsAppStore, is addressed by the fixed name owner. Its SQLite stores the Baileys auth keys, messages, chats, contacts, a custom_names table and an FTS5 index kept in sync by triggers. An alias(lid, pn) table maps WhatsApp's newer anonymous @lid identifiers to phone-number JIDs, so one person appears as one chat.

flowchart TD
  A[MCP read tool call] --> B{Stored copy older than 10 min?}
  B -- no --> D[Query SQLite / FTS5]
  B -- yes --> C[pull: open Baileys socket]
  E[Daily cron 04:00 UTC] --> C
  C --> F[Ingest queued messages, upsert]
  F --> D

Ingest is idempotent: re-importing history upserts rows and never duplicates them, which makes relinking safe. Media is stored only as labels such as [image] or [voice note 12s]. Edits update the message body, and deletes replace it with [deleted]. The exposed tools are list_chats, get_messages, search_messages, unnamed_chats, set_contact_names, link_status and relink. Contact naming is delegated to the agent: it lists unnamed chats, looks the numbers up through whatever contacts connector it already has, and writes names back. The server itself never connects to a contacts service.

How to use it

It needs the Cloudflare Workers Paid plan ("a few dollars a month"), Node 20 or later, and the phone.

git clone https://github.com/finereli/whatsapp-mcp
cd whatsapp-mcp
npm install
npx wrangler kv namespace create OAUTH_KV
npx wrangler secret put OWNER_PASSWORD
npm run deploy

Paste the KV id into wrangler.jsonc. The password must be 12 or more characters. Then open the deployed URL and scan the QR code under Settings, Linked Devices. A phone-number option exists for linking from the phone itself. To connect Claude Code:

claude mcp add --transport http whatsapp https://your-url/mcp

Run /mcp and sign in with the password. /debug/* endpoints, enabled when a PIN is set, include a read-only /debug/sql?q=. npm test needs Node 22.13 or later.

Limits

  • Free plan. The paid plan is required because wrangler.jsonc raises the CPU limit for the first history import. The free plan is untested, and the first import there is expected to be fragile.
  • History. History arrives once per link, and WhatsApp decides how much to send, so it may be incomplete. A failed first import loses that history until the device is relinked.
  • Names and media. Many chats show as phone numbers, and names are never refreshed automatically. Media is not downloaded or transcribed.
  • Silent logout. If the daily pull finds the session logged out, nothing reports it. link_status shows it the next time the agent asks.
  • Testing. Edit and delete handling is untested against real protocol messages. The whole system has run against one real account. The test suite does not cover search, because Node's built-in SQLite lacks FTS5.
  • workerd workarounds. Baileys is a prerelease and must be pinned exactly. It needs shims for ws, the wasm bridge and timer .unref(), plus an lru-cache shim to stay under workerd's 10,000 pending-timer limit per object.
  • Deploys. A deploy does not evict a busy Durable Object. Polling a stale object keeps it running the old code.
  • Scope. It serves one user only. Multi-user support would require keying the Durable Object on the signed-in identity. Sending is deliberately not built, and markOnlineOnConnect must stay false or the phone stops showing notifications.

Organisations named here

  • Cloudflare
  • WhatsApp
  • Anthropic
  • Google
  • Claude Code
  • Model Context Protocol

Technologies named here

  • WhatsApp MCP
  • Model Context Protocol
  • Claude Code
  • Claude Desktop
  • Cloudflare Workers
  • Cloudflare Durable Objects
  • Cloudflare KV
  • Wrangler
  • Baileys
  • SQLite FTS5
  • ws-shim.ts
  • whatsapp-rust-bridge / vendor-bridge.mjs
  • polyfills.ts
  • lru-shim.ts
  • Google Contacts or Gmail connector
  • Cloudflare
  • SQLite
  • TypeScript
  • Node.js
  • WebAssembly
  • Durable Objects
  • FTS5
  • OAuth

Techniques named here

  • Linked-device pull sessions
  • Agent-assisted contact naming
  • Idempotent history ingest
  • Display name resolution order
  • Idle detection on data events
  • Tool results that hint next steps