Skip to main content
Glama
ionutsss1

WhatsApp MCP

by ionutsss1
README.md
# WhatsApp MCP

An [MCP](https://modelcontextprotocol.io) server that connects Claude, or any other MCP
client, to your **personal WhatsApp account**. It links to your account as a device, the way
WhatsApp Web does, and keeps a searchable copy of your chats on your own computer. From
there the assistant can read and search your history, send messages and files, and open
the photos and documents people send you.

Things you can ask once it's set up:

- "What are my unread WhatsApp messages?"
- "Summarise what the book club group talked about this week."
- "Find the invoice Alex sent me in August."
- "Tell Sam I'm running ten minutes late."
- "What's in the photo Zoe sent this morning?"

> [!WARNING]
> This uses [Baileys](https://github.com/WhiskeySockets/Baileys), an unofficial WhatsApp
> client, and using it breaks WhatsApp's terms of service. Accounts that behave like a
> normal linked device (reading, sending the odd message) are rarely affected. Bulk or
> automated sending is what gets numbers banned, so don't use this for either. Use it at
> your own risk.
>
> The `data/` folder it creates holds your session keys. Anyone with a copy can read and
> send as you, so never commit, sync or share it.

## Requirements

- Node.js 22.16 or newer (24 recommended). Older versions' built-in SQLite lacks the
  full-text search this uses.
- Windows, macOS or Linux
- The phone with the WhatsApp account you want to link

## Install

```bash
git clone https://github.com/ionutsss1/whatsapp-mcp.git
cd whatsapp-mcp
npm install
```

`npm install` also builds the server into `dist/`.

## Link your WhatsApp

Use whichever of these is most convenient:

- **From your MCP client:** once it's connected (next section), ask it to check the WhatsApp
  connection. The `connection_status` tool returns a QR code.
- **In a browser:** run `npm run status` to start the bridge, then open
  <http://127.0.0.1:47620/link>. The page keeps the QR code fresh and also offers linking
  by phone number.
- **In a terminal:** `npm run login` prints the QR code. For an 8-character pairing code
  instead, run `npm run login -- --phone 15555550123` with your own number and country code.

<p align="center">
  <picture>
    <source media="(prefers-color-scheme: dark)" srcset="docs/link-page-dark.png">
    <img src="docs/link-page.png" width="360" alt="The linking page: three steps to follow on the phone, a QR code to scan, and an option to link with a phone number instead">
  </picture>
  <br>
  <sub>The linking page at <code>http://127.0.0.1:47620/link</code>. The QR code here is a placeholder, not a real pairing code.</sub>
</p>

On the phone, go to **Linked devices** (under Settings on iPhone, the ⋮ menu on Android),
then **Link a device**. This computer then appears in the list as "WhatsApp MCP", and your
history syncs over the next few minutes; large accounts take longer.

## Connect it to your MCP client

Every client needs the **absolute path** to `dist/mcp.js`. Run `pwd` (macOS/Linux) or `cd`
(Windows) inside the folder to find it.

**Claude Code**

```bash
claude mcp add --scope user whatsapp -- node /absolute/path/to/whatsapp-mcp/dist/mcp.js
```

**Claude Desktop.** Add the server to `claude_desktop_config.json`, which lives in
`~/Library/Application Support/Claude/` on macOS and `%APPDATA%\Claude\` on Windows:

```json
{
  "mcpServers": {
    "whatsapp": {
      "command": "node",
      "args": ["/absolute/path/to/whatsapp-mcp/dist/mcp.js"]
    }
  }
}
```

Quit Claude Desktop completely and reopen it. Closing the window isn't enough, because the
app keeps running in the tray or menu bar. If the server doesn't start, Claude Desktop
probably can't find `node`: replace `"node"` with its full path, which `which node` or
`where node` prints. On Windows that is something like `"C:\\Program Files\\nodejs\\node.exe"`,
with the backslashes doubled as JSON requires.

**Other clients.** Any client that runs local (stdio) servers works: the command is `node`,
and its one argument is the path to `dist/mcp.js`.

## Tools

| Tool | What it does |
| --- | --- |
| `connection_status` | Shows whether you're linked and connected, and how much is synced. Returns a QR code (or pairing code) when you're not linked. |
| `list_chats` | Recent chats with unread counts and the last message. Can filter to unread, direct or group chats. |
| `list_messages` | A chat's messages, oldest first. Page back with `before`/`after`, or read the context around a search hit with `around_message_id`. |
| `search_messages` | Full-text search, across all chats or one, ignoring case and accents: "cafe" finds "café". Covers captions and document names too. |
| `search_contacts` | Finds a person or group by name or number and returns the ID the other tools take. |
| `send_message` | Sends text, optionally as a reply to a message. |
| `send_file` | Sends a photo, video, audio file, voice note (`.ogg`/`.opus`) or document from a path on your computer. |
| `download_media` | Saves an attachment. Photos and stickers are also shown to the assistant, so it can describe them. |

Sending is deliberately strict about the recipient. It accepts a chat ID, a phone number with
country code, an **exact** contact or group name, or "myself". A partial or ambiguous name
is refused with a list of candidates, so a message never goes to a best guess.

## How it works

```
MCP client ──stdio──> dist/mcp.js ──reads──> data/whatsapp.db  (SQLite + full-text index)
                           │                        ^
                           └──HTTP 127.0.0.1──> dist/bridge.js ──WebSocket──> WhatsApp
```

- **The bridge** is a single background process. It holds the WhatsApp connection, mirrors
  chats, contacts and messages into SQLite, and does all sending and downloading. The MCP
  server starts it when needed, and it keeps running after your client exits, so messages
  keep syncing.
- **The MCP server** is what the client talks to. It reads straight from SQLite and passes
  everything else to the bridge over localhost.

The two are separate on purpose. Clients start one MCP server per session, and two
connections using the same session keys knock each other offline. However many sessions
and apps you run, they all share the one bridge.

## Privacy and safety

- **Your data stays local.** History lives in `data/whatsapp.db`. The only other
  connections the bridge makes are to WhatsApp itself and to GitHub, to check which
  WhatsApp Web version Baileys currently supports.
- **It stays quiet.** The bridge doesn't mark you as online, so your phone keeps its
  notifications, and it never sends read receipts (blue ticks).
- **Deletions are respected.** Messages deleted for everyone are wiped locally too.
  View-once media is only ever delivered to the phone, so it can't be opened here.
- **The local API is locked down.** The bridge only listens on `127.0.0.1` and needs a
  random token, which is stored in `data/bridge-token`. It also rejects foreign `Host` and
  `Origin` headers, so no web page can send messages through it.
- **Messages are data, not instructions.** Messages are written by other people. The server
  tells the assistant never to act on instructions inside them, and every result that
  contains message text repeats that warning. Most clients also ask you before each send.

## Day to day

```bash
npm run status   # connection state and sync counts
npm run stop     # stop the bridge (you stay linked; it restarts when needed)
npm run logout   # unlink this computer from WhatsApp and delete the session keys
```

- **Log:** the bridge writes to `data/bridge.log`, rotated at 5 MB.
- **Downloads:** saved to `data/media/<chat>/` unless you pass `save_to_dir`.
- **Removing everything:** run `npm run logout`, then delete the `data/` folder.

## Configuration

Set these for both processes; with an MCP client, set them in its server config (`env`).

| Variable | Default | Notes |
| --- | --- | --- |
| `WHATSAPP_BRIDGE_PORT` | `47620` | The bridge's port, which also stops a second bridge from starting. |
| `WHATSAPP_MCP_DATA_DIR` | `data/` in the project folder | Session keys, database, media and log. |
| `WHATSAPP_LOG_LEVEL` | `warn` | Baileys log level: `info`, `debug` and so on. |

## Troubleshooting

- **No QR code, and the log says `428 Connection Terminated`.** WhatsApp now drops clients
  that claim to be the native Windows or Mac desktop app (`Browsers.windows('Desktop')` in
  Baileys). The bridge identifies as `['WhatsApp MCP', 'Desktop', …]`, which still gets the
  full history. Keep it that way if you fork this.
- **State `logged_out`.** The device was removed on the phone. Link it again.
- **State `replaced`.** Something else connected with the same keys, usually a second copy
  of `data/`. Stop that copy, then run `npm run stop` so the bridge starts fresh.
- **Old media won't download.** WhatsApp expires media after a while. The bridge asks the
  phone to upload it again, which only works if the phone is online and still has the file.
- **Contacts show as `~Name`.** A name with `~` is the one the person set for themselves.
  Linked devices only get the names your phone chooses to share; WhatsApp Web shows the
  same thing.

## Development

```bash
npm test        # builds, then runs the offline test suite; no WhatsApp account needed
npm run smoke   # checks every tool against your own linked account; prints only headers
```

| File | Role |
| --- | --- |
| `src/bridge.ts` | The WhatsApp connection, local HTTP API and linking page (`link-page.ts`). |
| `src/store.ts` | Writes Baileys events to SQLite, and merges each LID into its phone number (see `db.ts`). |
| `src/content.ts` | Turns a WhatsApp message into a type, text and media details. |
| `src/mcp.ts` | The MCP tools. |
| `src/read.ts` | Queries, name resolution and the text the assistant sees. |
| `src/cli.ts` | `npm run login`, `status`, `stop` and `logout`. |

WhatsApp is moving people from phone numbers to LIDs, private per-account IDs
(`…@lid`). The store keeps each person under their phone number once the LID↔phone mapping
is known, and moves anything filed under a LID as soon as the mapping arrives.

## License

[MIT](LICENSE). This project is not affiliated with, endorsed by or connected to WhatsApp or
Meta.