Skip to main content
zapo ships two optional packages aimed purely at development and testing — neither is meant for production:
  • MCP server (@zapo-js/mcp-server) — expose a live WaClient to an AI agent (Claude Code, Cursor) so it can connect, pair, send, and inspect state interactively.
  • Fake server (@zapo-js/fake-server) — an in-process fake WhatsApp Web server that drives the real WaClient end-to-end, so you can test deterministically without touching WhatsApp’s servers.

MCP server

Development & testing only. @zapo-js/mcp-server is a debugging aid, not a production protocol server. It exposes a live WaClient — and the whole zapo-js module — to an AI agent that can send messages, read state, and run arbitrary library calls on a real WhatsApp account. Run it only against accounts you control.
It exposes a live WaClient instance and the zapo-js module namespace as MCP tools. An LLM agent can then drive end-to-end WhatsApp flows — connect, pair, send, query groups/newsletters, inspect events, walk SQL state — without you writing throwaway scripts.

Tool surface

Each tool inlines its own schema and examples — the agent reads them at runtime rather than memorizing flags.

Install & register

Register with Claude Code at user scope (build first), so it works from any directory:
For tight iteration on the library itself, register the source through tsx — no build step, and zapo-js resolves straight from src/:
An HTTP transport with node --watch gives the smoothest dev loop: edit a .ts → the process restarts → the next tool call reconnects automatically, with no manual /mcp reconnect. See the package README for the dev script and HTTP setup.

Pairing gotcha

client.connect() blocks until pairing finishes, so always start it without awaiting, then poll the event buffer:
Surface the auth_qr string to the user, wait for auth_paired, then continue.

Key environment variables

One WaClient per process (multi-session needs multiple servers with distinct MCP_AUTH_PATH + MCP_SESSION_ID); no auto-reconnect (call connect again on connection: close); restart soft does not pick up code changes while process_exit + a supervisor does. Full reference: packages/mcp-server/README.md.

Fake server

@zapo-js/fake-server is an in-process fake WhatsApp Web server that drives the real zapo-js WaClient end-to-end — full Noise XX/IK handshake, QR pairing, Signal Protocol (X3DH + Double Ratchet), group SenderKey, media upload/download over self-signed HTTPS, and app-state sync — all without touching WhatsApp’s servers. It powers the library’s own cross-check test suite and benchmarks, and you can use it to test your integration deterministically.

Quick start

The wiring uses three client options built for exactly this: chatSocketUrls (point at the fake WebSocket), testHooks.noiseRootCa (trust the fake server’s certificate without bypassing verification — the full cert-chain check still runs), and proxy.mediaUpload / proxy.mediaDownload (route media to the fake HTTPS server).

What it simulates

  • FakeWaServer — the WebSocket listener, Noise handshake, an IQ router that answers every IQ the lib emits during normal operation (prekey upload/fetch, usync, media-conn, app-state sync, groups, privacy, profile, blocklist, …), plus state registries for peers and groups.
  • FakePeer — a simulated contact with real Signal crypto: peer.sendConversation(text) / peer.sendGroupConversation(groupJid, text) push messages to the client, and peer.expectMessage() captures and decrypts what the client sends.
  • Pairing — server.runPairing(pipeline, { deviceJid }, materialFn) drives the full QR-pairing handshake; afterward the lib reconnects with the IK handshake (capture it via waitForNextAuthenticatedPipeline()).

Standalone CLI

Run it as a standalone server for manual experiments:

Benchmarking

The package ships a messaging profiler (send/recv × 1:1/group) used to track the library’s performance, plus focused scenario suites for connect lifecycle, history sync, bulk usync, group provisioning, media upload, receipts flood, reconnect/resume, app-state, media-on-the-wire, and the Signal retry round-trip:
bench:media:messaging sweeps every media type (image / video / audio / ptt / document / sticker) across 1:1 send, group fan-out (SKDM + SKMSG), and receive + download. It defaults to streaming input (Readable.from(...)) so the lib walks its streaming-upload path; switch to in-memory mode with ZAPO_BENCH_MEDIA_INPUT=buffer to A/B. bench:retry validates the full retry round-trip against wa-web’s reference parser: incoming retry, recovery, and outbound retry replay after the peer rotates its prekey bundle. Tune the workload with ZAPO_BENCH_* env vars (ZAPO_BENCH_CONTACTS, ZAPO_BENCH_GROUP_MEMBERS, ZAPO_BENCH_MESSAGES, ZAPO_BENCH_SCENARIOS, …) and add --cpu / --heap / --separate-process for profiles. With --separate-process, the bench drives the fake server in a child process over an RPC bridge and emits a matching server-side CPU profile and heap snapshot alongside the lib-side ones. Pick a store backend with ZAPO_BENCH_STORE (memory, sqlite, postgres, mysql, redis, or mongo) — the same ZAPO_TEST_* connection env vars as the cross-store test harness apply. To sweep every bench across multiple stores in one shot:
Add --start-docker to bring up the bundled Postgres/MySQL/Redis/Mongo services on ephemeral ports and tear them down at the end. See packages/fake-server/README.md for the full flag reference.
bench:all-stores only sweeps the eight scenario suites (connect-lifecycle, history-sync, bulk-usync, group-provision, media-upload, receipts-flood, reconnect-resume, appstate) plus messaging. The newer bench:media:messaging and bench:retry aren’t in its --benches= set yet; run them directly when you need them.
The fake server is an in-process testing harness, not a runtime you deploy. Pair it with the memory store for fast, isolated tests that reset on every run.
Last modified on May 31, 2026