Skip to main content
Glama
tarun101

WhatsApp Read Agent MCP Server

by tarun101
README.md
# WhatsApp Read Agent

Strictly **read-only** access to your own WhatsApp history and attachments, for an AI
assistant (via the Model Context Protocol) or any script that reads the local archive.

It is built so that reading and writing are physically separated:

| Component | Talks to WhatsApp? | What it can do |
|-----------|:------------------:|----------------|
| **Collector** (`whatsapp-collector`) | Yes — links a companion device | Receives history sync + new messages, downloads attachments under a size cap. Nothing else. |
| **MCP server** (`whatsapp-read-mcp`) | **No** | Reads the local archive only. Enumerate chats, page history deterministically, read exact timestamps/content, return approved attachment bytes. |

The MCP server has **no code path to send, reply, react, call, archive, delete, mark-read,
pay, or transfer** — those tools are not registered and the WhatsApp socket is not even
imported into that process. `src/server.test.ts` asserts the tool surface is exactly the
seven read-only tools, so a mutating tool cannot be added without a test failing.

## ⚠️ Read this before linking a device

- **This is unofficial.** It links a companion device using the multi-device protocol
  (via [`@whiskeysockets/baileys`](https://github.com/WhiskeySockets/Baileys)), the same
  mechanism as WhatsApp Web, but programmatically. **This is not an official WhatsApp API
  and may violate WhatsApp's Terms of Service. Automating a client can get the account
  banned.** There is no sanctioned API that exposes personal 1:1/group history — the
  official WhatsApp Cloud API only handles messages to a registered *business* number. Use
  this only on your own account, understanding that risk.
- **Everything derived from a linked account stays local and gitignored:** companion-device
  credentials (`auth/`), the message archive (`data/`), and downloaded media (`media/`).
  Never commit them. The device credentials grant full account access — treat them like a
  password.
- **Attachment downloads are double-gated:** an explicit per-message approval flag *and* an
  optional operator allowlist of chat JIDs. The MCP server only ever surfaces bytes the
  collector already downloaded; it never reaches out to WhatsApp to fetch anything.

## Install & test

```bash
cd whatsapp-agent
npm install            # MCP SDK + zod (Baileys is an optional dep, see below)
npm run check          # build + 31 unit/integration tests (no device or network needed)
```

The pure read model, deterministic pagination, consent gate, and path-safety checks are all
covered by tests that run offline. The collector's WhatsApp handling requires a live device
and cannot be exercised in CI.

## Link a device and sync (collector)

The collector needs the optional dependency:

```bash
npm install @whiskeysockets/baileys qrcode-terminal
cp .env.example .env    # adjust paths / limits if desired
npm run collect         # prints a QR (or a pairing code if WHATSAPP_PAIR_NUMBER is set)
```

On your phone: **WhatsApp → Settings → Linked devices → Link a device**, then scan the QR
(or choose "Link with phone number" and enter the printed pairing code). The collector then
receives the history sync and writes it to `data/` (and attachments to `media/`). Keep it
running to keep the archive current. Re-run it anytime to resume.

## Wire up the MCP server

Build first (`npm run build`), then point your MCP host at the built entry:

```json
{
  "mcpServers": {
    "whatsapp-read": {
      "command": "node",
      "args": ["/absolute/path/to/rs-website-firebase/whatsapp-agent/dist/index.js"],
      "env": {
        "WHATSAPP_DATA_DIR": "/absolute/path/to/whatsapp-agent/data",
        "WHATSAPP_MEDIA_DIR": "/absolute/path/to/whatsapp-agent/media",
        "WHATSAPP_ATTACHMENT_ALLOWLIST": ""
      }
    }
  }
}
```

The server loads the archive at startup. After a fresh sync, restart it to pick up new
messages.

### Tools (all read-only)

| Tool | Purpose |
|------|---------|
| `get_sync_status` | Is a device linked, when it last synced, how many chats/messages archived. |
| `list_chats` | Enumerate chats (name-ordered, paginated). |
| `find_chats_by_name` | Locate a named contact or group. |
| `get_history` | Deterministic page of a chat's history; `forward` (oldest→newest) or `backward` (scroll up). Iterate until `hasMore` is false for complete readback. |
| `get_message` | A single message with exact timestamp and content. |
| `list_attachments` | Messages in a chat that carry attachments. |
| `get_attachment` | Return an approved attachment's local bytes (`approved: true` required). |

Cursors are opaque — pass them back verbatim.

## Verification / readback test

The task's acceptance test ("prove complete readback against **Anuj Kapoor** and **Rajesh
Aggarwal**") requires a linked device and therefore must be run on a machine with the
phone available:

1. `npm run collect` and link the device; wait for `get_sync_status` to report a non-zero
   `messageCount` and stable `lastSyncAt`.
2. Start the MCP server (or use the archive directly).
3. `find_chats_by_name` → `"Anuj Kapoor"`, then `"Rajesh Aggarwal"` to get each chat JID.
4. `get_history` with `direction: "forward"`, paging on `nextCursor` until `hasMore` is
   false — that is the complete, ordered history with exact `isoTime` timestamps.
5. For any attachment, `list_attachments` then `get_attachment` with `approved: true`.

The deterministic pagination and the end-to-end "locate named chat → page complete history"
flow are proven in `src/read-api.test.ts` and `src/server.test.ts` against seeded Anuj/Rajesh
fixtures. The **live** readback against the real account was **not** run here: this
environment is headless (no phone to scan the QR, no persistent host), so a device cannot be
linked from it. Run steps 1–5 on your own machine to complete the acceptance test.

TDQS

A4.2/5.0

Scored across 7 tools

Disambiguation3/5

Most tools are distinct, but list_chats and find_chats_by_name overlap since list_chats already supports name-substring filtering. This could confuse an agent choosing between them, though descriptions help clarify the intended use case.

Naming Consistency4/5

Tool names consistently follow a verb_noun pattern with lowercase and underscores. Minor deviation: list_chats vs find_chats_by_name for similar operations, but overall the pattern is readable and predictable.

Tool Count5/5

Seven tools is well-scoped for a read-only WhatsApp archive agent. Each tool covers a specific aspect (sync status, chat listing, history, single messages, attachments), and none feel redundant or missing at this scale.

Completeness5/5

The tool set provides full coverage for reading archived WhatsApp data: sync status, chat discovery, full message history retrieval, individual message access, and attachment handling. No significant operations are missing for the stated purpose of a read agent.

Maintenance

ActivitySlowing
ResponsivenessNo issues