wacli.me alpha

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.

EndpointPurpose
/.well-known/oauth-protected-resourcePoints at the authorization server
/.well-known/oauth-authorization-serverMetadata: endpoints, PKCE support
/oauth/registerDynamic client registration (RFC 7591)
/oauth/authorizeShows the linking page, returns a code
/oauth/tokenExchanges 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

PropertyValue
TransportStreamable HTTP — POST for requests, server-sent events for responses
Protocol version2025-06-18, negotiated during initialize
Session modelStateless: one server instance per request, no session id to track
Capabilitiestools only — no prompts, no resources, no sampling
Content typeRequest 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 number
  • 123456789012345678@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

SituationWhat you get
Missing or wrong tokenHTTP 401 with a JSON-RPC error, code -32001
Malformed JSON bodyHTTP 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 nameStandard MCP method error from the SDK
Service downConnection 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

LimitValue
Results per call1–200, default 25
Request body1 MB
Tool timeout60 seconds
History window90 days on the free tier
SendingDisabled unless requested for the account