Documentation
Everything needed to connect a client and understand what happens on the other side. The endpoint speaks the Model Context Protocol over Streamable HTTP; there is no SDK to install and no proprietary client.
Quickstart
Three steps, about two minutes.
Link the account
You get a QR code. Scan it in WhatsApp under Linked devices. The service then syncs your history into a store that belongs to you alone.
Take your token
32 random bytes, hex encoded. It is the only credential; treat it the way you treat a password manager entry.
Add the server
Point any MCP client at /mcp with an Authorization header. The tools appear immediately.
# Claude Code claude mcp add --transport http whatsapp https://wacli.me/mcp \ --header "Authorization: Bearer <your-token>" # Config file form, understood by most clients { "mcpServers": { "whatsapp": { "type": "http", "url": "https://wacli.me/mcp", "headers": { "Authorization": "Bearer <your-token>" } } } }
Verify it works without a client: curl https://wacli.me/healthz returns the service version.
Authentication
Every request to /mcp carries a bearer token:
Authorization: Bearer 9f3c…<64 hex characters>
The token resolves to exactly one account store. There are no user names, no sessions and no cookies — a request either carries a valid token or is refused with 401 before anything reads a database.
Tokens are compared in constant time and never written to logs. If one leaks, ask for a rotation: the old value stops working the moment the new one is issued, and clients only need the header updated.
Or skip the token entirely
The server is also an OAuth 2.1 authorization server, which is what most MCP clients try first. Add https://wacli.me/mcp with no credentials: the client discovers the authorization server from the 401, registers itself, opens a browser, you scan the WhatsApp QR code there, and the client collects its own token. Nothing is copied by hand.
| Endpoint | Purpose |
|---|---|
/.well-known/oauth-protected-resource | Points at the authorization server |
/.well-known/oauth-authorization-server | Metadata: endpoints, PKCE support |
/oauth/register | Dynamic client registration (RFC 7591) |
/oauth/authorize | Shows the linking page, returns a code |
/oauth/token | Exchanges code + PKCE verifier for a token |
PKCE with S256 is required, codes are single-use and expire after five minutes, and a redirect URI must match the client's registration exactly.
Transport and protocol
| Property | Value |
|---|---|
| Transport | Streamable HTTP — POST for requests, server-sent events for responses |
| Protocol version | 2025-06-18, negotiated during initialize |
| Session model | Stateless: one server instance per request, no session id to track |
| Capabilities | tools only — no prompts, no resources, no sampling |
| Content type | Request application/json, accept application/json, text/event-stream |
Stateless is deliberate. Every request builds its own server object bound to one store, so a bug in request handling cannot leak state between accounts, and a restart never invalidates a session someone was in the middle of.
Addressing chats: JIDs
WhatsApp identifies conversations with a JID, and every tool that takes a chat argument expects one:
[email protected]— a person, addressed by phone number123456789012345678@lid— a linked id, the pseudonymous form most contacts have had since 2024[email protected]— a group
You rarely type one by hand. Call list_chats or search_contacts with a name, take the jid field from the result, and pass it on. The send_message tool additionally accepts a plain phone number or an exact chat name and resolves it itself.
Errors
| Situation | What you get |
|---|---|
| Missing or wrong token | HTTP 401 with a JSON-RPC error, code -32001 |
| Malformed JSON body | HTTP 400, code -32700 |
| Tool fails (account not linked, bad JID, locked store) | HTTP 200 with isError: true and the CLI's message as text |
| Unknown tool name | Standard MCP method error from the SDK |
| Service down | Connection refused or a 5xx from the edge — see status |
Tool failures are returned as tool errors rather than protocol errors on purpose: an assistant can read the message, correct the argument and try again, which is what you want when it guessed a JID wrong.
Limits
| Limit | Value |
|---|---|
| Results per call | 1–200, default 25 |
| Request body | 1 MB |
| Tool timeout | 60 seconds |
| History window | 90 days on the free tier |
| Sending | Disabled unless requested for the account |