wazap-mcp
An MCP server that puts your WhatsApp account — chats, messages, media, contacts, groups — behind 24 tools any MCP client can call.
Read conversations:
list_chats(filter unread/groups/individual/archived),read_messages(paged withbefore, filterable by message type),get_recent_messages(catch-up across all chats for the last N hours),search_messages(local text search),get_message(full message with quoted reply and reactions)Read contacts and groups:
search_contacts,sync_contacts(re-fetch the phone's address book),get_contact(name, number, about, picture, business/blocked flags),get_group_info(participants, admins, announcement mode, invite link)Handle media and voice:
download_media(save attachments, small images inline),transcribe_audio(voice/audio to text via local whisper.cpp or an OpenAI-compatible API, cached and capped at 10 calls/min)Send messages:
send_message(text, quote-reply, @-mentions),send_media(image/video/audio/document from path or URL, up to 100 MB),send_poll(2–12 options),send_location(map pin)Modify messages:
edit_message(within WhatsApp's 15-minute window),react_to_message(add/remove emoji),forward_message,delete_message(retract for everyone, destructive, within 2 days)Manage chats and groups:
manage_chat(archive, pin, mute, mark read/unread),create_group,manage_group(add/remove/promote/demote participants, leave, rename, set description, invite links)Orient and diagnose:
learn(tool guide, id formats, error codes — call first),get_status(connection, sync state, linked account, contact names, versions, data dir)Safe by design: writes are opt-in via read-only mode (write tools simply aren't registered), write calls are rate-limited, destructive actions are flagged, and every failure returns a structured
{error, message, fix}Deployable anywhere an agent is: stdio for local clients (Claude, Cursor, Codex, VS Code, Gemini, OpenCode, Windsurf), HTTP with bearer tokens or OAuth for hosted agents (claude.ai, ChatGPT, Grok Bot), plus webhooks for live inbound messages
Provides tools for interacting with a linked WhatsApp account, enabling management of chats, messages, media, contacts, and groups, including sending messages, searching history, downloading media, and administering group settings.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@wazap-mcpwhat did I miss on WhatsApp today?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
██╗ ██╗ █████╗ ███████╗ █████╗ ██████╗
██║ ██║██╔══██╗╚══███╔╝██╔══██╗██╔══██╗
██║ █╗ ██║███████║ ███╔╝ ███████║██████╔╝
██║███╗██║██╔══██║ ███╔╝ ██╔══██║██╔═══╝
╚███╔███╔╝██║ ██║███████╗██║ ██║██║
╚══╝╚══╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝╚═╝WhatsApp for your AI agent. An MCP server that puts your WhatsApp account — chats, messages, media, contacts, groups — behind 33 tools any MCP client can call. Pairing-code login, no browser, no phone-number reseller, ~20 MB of RAM.
Built on Baileys, which speaks the WhatsApp multi-device protocol over a WebSocket.
Get started
The npm package is wazap-mcp; the command it installs is wazap.
npx wazap-mcp setupThat is the whole install. It links your account, finds the MCP clients
installed on this machine, writes their config, copies the five skills where
that client reads them, and tells you what to restart. At a terminal it is one
black, centered screen per step: ghosted ASCII logo, step number, then the
QR or the question. Piped output stays a log. When you
started through npx, setup offers to install wazap globally so Claude
Desktop and the background service have a path that does not change. It also
offers to brew install whisper-cpp, ffmpeg or Tailscale when a step needs one
and it is missing, and to restart Claude Desktop itself once it has connected it.
Or the path your harness prefers
Harness | Fastest path |
Claude Code |
|
Claude Desktop | download |
Gemini CLI |
|
Cursor | the Install in Cursor badge, then |
VS Code | the Install in VS Code badge |
Codex CLI |
|
OpenCode |
|
Windsurf |
|
Grok Bot | |
Anything else | the MCP entry |
Each local harness registers the server. Grok Bot is a URL you paste. Linking
the WhatsApp account is a separate, one-time step: npx wazap-mcp login.
Or have your agent do it. Paste this:
Set up WhatsApp for me: run npx wazap-mcp setup --agent and follow what it prints.
Then ask your agent: "what did I miss on WhatsApp today?"
Below are the steps setup runs for you. Each is still its own command when you
want to run it by hand.
npx wazap-mcp login shows a QR code; scan it from Settings → Linked devices
→ Link a device. No camera handy, or linking over SSH? npx wazap-mcp login --phone +15550100
prints an 8-character code you type under Link with phone number instead.
It ends by asking whether the agent may send messages; the answer is no unless
you say yes, and npx wazap-mcp config writes on changes it later.
npx wazap-mcp connect claude-code writes the MCP entry for one client. The
table under Connect a client has the rest.
npx wazap-mcp on its own is safe to run: it prints where you stand and what to do
next, and starts no server. When something is off, npx wazap-mcp status is the
first thing to run — it checks Node, the data directory, the lock, the
credentials and whether a newer version is out, and prints the fix next to
anything broken.
Connect a client
wazap connect <client> writes the entry for you, keeping whatever else is in
the file and backing it up once before the first change. --dry-run shows what
it would write.
Client | What |
| runs |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Grok Bot | client's MCP URL field: |
anything remote | client's MCP URL field: |
Grok Bot / remote MCP
Grok Bot is an HTTP MCP client. It does not launch wazap over stdio.
On the machine that will run wazap,
npx wazap-mcp loginuntil the CLI says the account is linked.get_statusandlink_accountare MCP tools; they need HTTP already serving (step 3) and Grok already connected (step 4).Answer writes yes or no at login. Writes stay on when
WAZAP_READ_ONLYis unset. A Bearer write token is not writes being enabled. If write tools are missing, you need writes on (wazap config writes onand restart) and a write Bearer on this session. Config alone is not enough on HTTP.Serve HTTP with a read token:
WAZAP_READ_TOKEN=$(openssl rand -hex 32) \
npx wazap-mcp serve --httpSet WAZAP_WRITE_TOKEN too only if this client should send, and put that value in the header in step 4.
In Grok Bot, add an MCP server at
http://<host>:<port>/mcpwith headerAuthorization: Bearer <token>. Then calllearn, thenget_status.connectedmeans the WhatsApp socket is up. This session can send only when write tools are registered (write_tools: true, or send tools appear in the tool list). Then read.
wazap setup asks Remote client (Grok Bot / HTTP MCP)? and prints the same URL and header. Answering yes does not start expose.
Other MCP clients
Cursor and VS Code install from a link:
Both carry the same entry connect writes. Where a custom scheme is stripped
before you can click it, VS Code also takes
the https form.
node scripts/badges.mjs reprints all three.
Any other MCP client works the same way: the command is npx -y wazap-mcp, the
transport is stdio. Tell the agent to call learn first — it returns the id
formats, the workflows and every error code with what to do about it.
{
"mcpServers": {
"whatsapp": {
"command": "npx",
"args": ["-y", "wazap-mcp"]
}
}
}Claude Desktop, Cursor, Gemini CLI and Windsurf take exactly that. VS Code nests
it under servers and wants a "type": "stdio" alongside command. Codex CLI
is TOML:
[mcp_servers.whatsapp]
command = "npx"
args = ["-y", "wazap-mcp"]OpenCode takes the command and its arguments as one array, under mcp:
{
"mcp": {
"whatsapp": { "type": "local", "command": ["npx", "-y", "wazap-mcp"] }
}
}The skills/ folder follows the Agent Skills format, so Codex, Cursor and other skill-aware agents can load the same five skills.
Gemini CLI
gemini extensions install https://github.com/razvangirgiz/wazapThat reads gemini-extension.json at the repo root, so it registers the MCP
server and loads GEMINI.md — the five skills below, concatenated, because the
Gemini CLI takes one context file per extension rather than a skills directory.
wazap connect gemini writes the server alone, without the context.
GEMINI.md is generated: npm run context:build rebuilds it from
skills/*/SKILL.md, and a test fails if the two have drifted, so a workflow is
only ever edited in its skill.
Claude Desktop, without a terminal
Download wazap-<version>.mcpb from Releases
and double-click it. Claude Desktop installs the server, its Node dependencies
and the icon, and shows two settings: Read-only, ticked, and Data
directory, empty. wazap connect claude-desktop does the same job by editing
claude_desktop_config.json. Claude Desktop starts its servers without your
shell PATH, so that entry is the absolute path to node when wazap is installed
globally, and npx otherwise; wazap setup checks that the entry it wrote is
one Claude Desktop can actually launch.
Then ask Claude to link your WhatsApp. It calls link_account with your number,
hands back an 8-character code, and you type that code into WhatsApp →
Settings → Linked devices → Link a device → Link with phone number instead.
No terminal at any point. npx wazap-mcp login does the same job from a shell
when you have one.
Untick Read-only to let Claude send. It ships ticked because a bundle that
can message people from your number before you have said so is the wrong
default, and because the setting cannot be left unanswered: the manifest format
has no way to omit an argument, so the box you see is the answer the server gets.
link_account is registered either way. Read-only exists to stop Claude
messaging people from your number, and relinking your own dead session messages
nobody.
Build it yourself with npm run bundle:mcpb, which stages dist/, the
manifest, the icon and a fresh production node_modules, then packs them with
@anthropic-ai/mcpb.
Keep it running
A wazap started by a client lives as long as that client does. Quit Claude Code and the session is gone until you open it again. Two commands change that. Staying up and being reachable are separate choices.
npx wazap-mcp service installThat writes a launchd agent on macOS (~/Library/LaunchAgents/com.wazap.server.plist)
or a systemd user unit on Linux (~/.config/systemd/user/wazap.service), starts
it, and waits for /healthz to answer. The unit runs serve --http on
127.0.0.1:8766 with the absolute path of this Node and this install, so it
survives a reboot and a logout. Point any client at
http://127.0.0.1:8766/mcp, or keep using the stdio entry. A second wazap on
the same data directory becomes a bridge onto the session this one holds.
service status prints the pid, the health check and whether the unit still
runs the version you have installed. service logs tails it. service restart
picks up an upgrade; service uninstall removes the unit and leaves your
session and credentials alone. wazap login needs the session to itself, so it
stops the service, pairs, and starts it again on its own.
A sleeping Mac is an offline wazap. System Settings → Lock Screen, or Battery → Options, has the switch that keeps it awake on power.
npx wazap-mcp exposeThat gives the running service a public https URL, for agents that are not on
this machine: a cloud agent, claude.ai, ChatGPT. It uses Tailscale Funnel if
tailscale is installed, Cloudflare Tunnel if cloudflared is, opens the
tunnel, writes WAZAP_PUBLIC_URL and a fresh WAZAP_OAUTH_PASSWORD into
<data-dir>/.env, restarts the service and checks the URL from here. It then
prints the MCP URL and the password once.
Give an agent the URL only. It signs in on a consent page on your own host with
that password and picks read or read-and-send there; wazap status lists who
holds a grant. See Hosted agents (OAuth) for what that
page does. npx wazap-mcp expose off takes the tunnel down and keeps the
password, so the next expose hands agents the same one.
npx wazap-mcp setup asks all of this once, as its fourth step.
Upgrade
npx wazap-mcp updateOne command for what used to be three. It compares this install against the
registry, installs the new package when wazap is global, restarts the service so
it runs the new code, and copies the new skills into every harness that keeps
them. --dry-run prints the plan and touches nothing.
Related MCP server: wa-bridge
Tools
Tool | Kind | What it does |
| read | The guide to every tool, id format and error code. Call it first. |
| read | Connection status, sync state, linked account, named-contact count, versions, data dir. Top-level fields are the default account; |
| read | Every configured account: id, name, status, masked phone, owner name, writes policy. Call this first when more than one account is linked. |
| read | Pair an account that already exists ( |
| read | Conversations newest-first; filter |
| read | Messages in a chat; |
| read | Everything from the last N hours, grouped by chat. The catch-up tool. |
| read | Who is waiting on the user: chats whose last word is theirs and asks for something, with the ask quoted. Groups only when the user was @-mentioned or replied to. |
| local | Remember something about a person, on this machine only; it then shows next to their name everywhere. |
| local | Take a chat off |
| read | The stories (status updates) received in the last day, by author, with previews on request. They show nowhere else. |
| read | Block up to 55 s until a message arrives, then return it with a cursor for the next call. |
| read | Text search across the locally held messages; |
| read | One message in full, with its quoted message and reactions. |
| read | Find contacts by name or number. |
| read | Fetch the phone's address book from WhatsApp again, when names are missing. |
| read | Name, number, about text, profile picture. |
| read | Participants, admins, announcement mode, invite link (when you are admin). |
| read | Save an attachment to disk; small images also come back inline. |
| read | Turn a voice note or audio message into text, with the local or the API provider. |
| write | Draft text, optionally as a reply, with @-mentions. Does not send. |
| write | Draft an image, video, audio, voice note, document or GIF ( |
| write | Draft a poll with 2–12 options. Does not send. |
| write | Draft a map pin. Does not send. |
| write | Edit your own message, within WhatsApp's 15-minute window. |
| write | Add or remove an emoji reaction. |
| write | Draft a forward to another chat. Does not send. |
| write | Send a draft after the user has seen the preview and said yes. |
| write | Retract your own message, within WhatsApp's 2-day window. |
| write | Set the linked account's own profile photo from a local path or URL. Hits WhatsApp immediately. |
| write | Archive, pin, mute (8h by default), mark read/unread. |
| write | Create a group and add participants. |
| write | Add, remove, promote, demote, leave, rename, invite links. |
Seeing, waiting, following up
include_previews: true on get_recent_messages or read_messages attaches a
small JPEG of each photo as an image block, newest first, up to 12 per call,
and labels each message line with the preview it belongs to, so a catch-up can
say "a photo of a receipt" without a download. WhatsApp used to ship such a
preview inside every image message and in 2026 almost never does, so when
none is there wazap downloads the photo once, shrinks it to 320 px on this
machine with pure JavaScript, and keeps the result as a file under
previews/ in the data directory, so a restart does not redo it. A video gets
one frame, taken by ffmpeg a second in, when ffmpeg is installed. The first
call over a day of photos takes a few seconds; the next is instant.
wait_for_messages blocks until something arrives, up to 55 seconds, then
returns it with a cursor. Calling it again with that cursor replays whatever
landed in between, so an agent can sit in a loop and miss nothing. With
addressed_to_me only direct messages, @-mentions of the user and replies to
their messages wake it; group chatter does not. The user's own messages and
WhatsApp's notices never do.
get_unanswered returns the chats whose last word is the other side's and reads
as an ask: a question mark, a request word, or a voice note nobody has heard
yet. "Ok, thanks" is not an ask, a link is not a question, and an ask older
than two weeks (max_age_hours) was abandoned rather than left waiting. People
come first, then the oldest wait, each with the ask quoted and how long they
have been waiting; a WhatsApp Business account is marked, since its asks are
often automatic replies.
Every message comes back with a non-empty text: media and system messages
carry a placeholder such as [image] caption, [voice message · 0:42], [deleted] or
[poll] Pizza or pasta?. Timestamps are ISO 8601 with the machine's UTC offset,
alongside a human age like 2h ago.
Voice messages
A voice note is the one message an agent cannot read. Switch transcription on and
it becomes text: [voice message · 0:42] "sunt la notar, ajung în 20 de minute",
with the bare words also in a transcript field. get_recent_messages and
search_messages see that text, so a voice note becomes findable by what was
said in it.
Pick a provider once, in wazap setup or later:
wazap config transcribe local # free and private, one 574 MB model on disk
wazap config transcribe openai # cheap and fast, the audio leaves this machine
wazap config transcribe off
|
| |
Runs | whisper.cpp, here | any OpenAI-compatible |
Costs | nothing | per minute of audio, on your key |
Privacy | the audio never leaves this machine | the audio leaves this machine |
Needs |
| an API key |
Local, with whisper.cpp
brew install whisper-cpp ffmpeg # macOS; elsewhere build whisper.cpp, install ffmpeg from your package manager
wazap transcribe download # fetch and verify the model
wazap transcribe test recording.ogg # prove it before you trust itwazap setup and wazap transcribe download offer that brew install
themselves when either binary is missing, and go straight on to the model in the
same run. --no-brew turns the offer off everywhere.
Models land in <data-dir>/models/ and are checked against a SHA-256 pinned in
the source; an interrupted download resumes where it stopped.
| File | Size |
|
| 574 MB |
|
| 1.08 GB |
|
| 539 MB |
turbo is the default because it is the smallest model that still gets Romanian
right. medium and below drop diacritics and mangle names, which is worse than
no transcript at all: a missing transcript is a question, a wrong name is a wrong
answer. large-v3 is the same accuracy for several times the wait.
An API, OpenAI-compatible
wazap config transcribe openai asks for the key without echoing it and stores
it in <data-dir>/.env. The default endpoint is OpenAI; Groq works unchanged:
WAZAP_TRANSCRIBE_URL=https://api.groq.com/openai/v1
WAZAP_TRANSCRIBE_MODEL=whisper-large-v3-turboWith this provider the audio leaves your machine. Every voice note wazap
transcribes is uploaded to that endpoint. If that is not acceptable, use local,
which uploads nothing.
The key is treated as a secret rather than as a setting:
It is never accepted as a command-line argument, because an argument lands in your shell history and in
ps.The prompt echoes nothing, not even asterisks.
It is stored only in
<data-dir>/.env, mode0600.status,status --json,configandget_statusshow at mostapi key: set (…abcd).A provider's own error message has the key stripped out of it before wazap prints it.
A plain-
httpWAZAP_TRANSCRIBE_URLis refused unless it points back at this machine.
Without being asked
With a provider configured, incoming voice notes of up to ten minutes are
transcribed in the background as they arrive, one at a time, never holding up a
message. The transcript is cached by message id and persisted, so a voice note is
transcribed once and not again after a restart. Audio files are left alone,
since one can be an hour long; call transcribe_audio(message_id) for those.
WAZAP_TRANSCRIBE_AUTO=0 keeps the tool and stops the background work.
Skills
wazap ships five Agent Skills that teach an agent the workflows behind the tools, not just the tools:
Skill | What the agent does |
| Diagnose with |
| "What did I miss?" Triage into needs you / FYI / noise, ranked, plus forgotten replies. Read-only |
| "Find the invoice Dan sent." Search with query variants, page back in time, download and read the file. Read-only |
| Catch up on a 300-message group: decisions, dates, what is asked of you. Read-only |
| Draft in the chat's own register, show recipient and text, send only after the user says yes |
wazap setup copies them into every client it connects, so there is usually
nothing to run. The command behind it, for a harness setup never offered or
for a checkout you want to install by hand:
npx wazap-mcp skills install codex # or claude-code, cursor, opencode, agentsWith no harness named it installs into every client it finds on this machine. For Claude Code the other route is the plugin, which carries the server as well:
/plugin marketplace add razvangirgiz/wazap
/plugin install wazap@wazapHarness | Where the five directories land |
|
|
|
|
|
|
|
|
|
|
Re-running overwrites, so an upgrade is the same command. --dry-run lists
what it would copy.
A client with no skills directory is not left out. The server registers each of
the five as an MCP prompt of the same name, and sends a short instructions
block that names all five and says when each applies, so an agent that never saw
the skill files still follows them. That is how Claude Desktop, VS Code and
Windsurf get the workflows. A bridged session and a self-hosted HTTP server
carry them the same way.
Errors
Every failure is a structured { error, message, fix } rather than a stack
trace, so an agent can decide whether to retry, ask the user, or stop.
Code | Meaning |
| No account linked. Call |
|
|
| Unlinked from the phone. Run |
| Credentials unreadable. Run |
| Still connecting or reconnecting. |
| History sync has not finished; results may be partial. |
| Number is not in international format. |
| Not a WhatsApp chat, contact or group id. |
| That number has no WhatsApp account. |
| Unknown id. |
| Group permissions. |
| WhatsApp expired the file, or it was never synced here. |
| Outbound media problems. |
| Over WhatsApp's message limit. |
| WhatsApp's own limits on editing and deleting. |
| wazap is running read-only. |
| Too many writes; |
| The preview was already sent, unknown, or older than 15 minutes. Draft again. |
| More than one account could handle this, or a write named a chat no account knows. Pass |
| No account with that id. Run |
| That account is disabled. |
| WhatsApp did not answer, or rejected the operation. |
Data directory
Everything lives in ~/.wazap (override with --data-dir or WAZAP_DATA_DIR),
created 0700 with credentials written 0600. A data dir from before several
accounts moves into accounts/default/ the first time a wazap command runs.
~/.wazap/
accounts.json which accounts exist, and which is default
accounts/<id>/
auth/ WhatsApp credentials — treat this like a password
media/ downloads from download_media
history/ per-chat message history, so a restart is not amnesia
previews/ one small JPEG per photo or video already previewed
notes.json notes on contacts and "handled" marks; never sent anywhere
store.json chat-list snapshot
qr.png last QR, when login showed one
models/ whisper.cpp models, when transcription runs locally
server.lock pid of the running server
daemon.json loopback endpoint a second wazap bridges to
oauth.json registered agents and hashed OAuth grants, when OAuth is on
.env optional settings, see .env.example
migration.json written once when a flat dir moved into accounts/defaultCredential writes go to a temp file and are renamed into place, so killing the process mid-write cannot leave you re-linking your phone.
Several accounts
One wazap serve holds every enabled account in the data dir. Each account is
its own Baileys socket and its own folder under accounts/<id>/. The first
account is default. Add another with wazap account add work --name Work,
then wazap login --account work.
--account picks one on login, logout, status, config writes and
webhook test. MCP tools take an optional account_id. Call list_accounts
first when more than one is linked. A chat only one account knows selects that
account. A send to a chat no account knows, with two or more accounts, fails
AMBIGUOUS_ACCOUNT instead of falling back to default.
Reads without a chat and without account_id use the default account; the
response still carries account_id. link_account needs an account that
already exists. Five accounts is advice, not a cap. One phone number is one
account.
An account can override the global webhook URL and secret in accounts.json
(webhook_url, webhook_secret). wazap webhook test --account work posts
with that account's id and name.
Several clients at once
Claude Desktop, Claude Code and Cursor each launch their own wazap. The first
one on a data directory owns every enabled account, each on its own socket, and
opens an MCP endpoint on 127.0.0.1; every later one bridges to it over that
endpoint. There is nothing to configure, and no client can tell the difference.
The owner publishes <data-dir>/daemon.json (0600) with its pid, its port
and the token a bridge authenticates with.
A bridge serves whatever the owner exposes, so an owner started --read-only
makes every client read-only, whatever flags that client was launched with.
When the owner exits, the bridges exit with it, and the next wazap a client
starts becomes the new owner.
WAZAP_NO_SHARE=1 opts out: a second wazap on the same directory exits with
code 2 naming the pid of the one already running. An explicit --http is a
server of its own rather than a bridge, and is refused the same way.
Read-only mode
Writes are opt-in at login (the question defaults to no and stores the
answer in <data-dir>/.env). wazap config writes on|off changes it later.
wazap config and wazap status print the effective setting and where it
came from. If that line says off, write tools are not registered: run
wazap config writes on and restart the server.
An unset WAZAP_READ_ONLY and WAZAP_READ_ONLY=0 both register write tools
(wazap config then says "writes: on"). WAZAP_READ_ONLY=1 or
wazap serve --read-only does not register them at all. The agent never
sees them, so it cannot message anyone from your number even by mistake.
Writes are also rate limited to WAZAP_RATE_LIMIT per minute (default 20, 0
disables). Sending faster than a human is how accounts get banned.
HTTP mode
WAZAP_READ_TOKEN=$(openssl rand -hex 32) \
WAZAP_WRITE_TOKEN=$(openssl rand -hex 32) \
npx wazap-mcp serve --http --host 0.0.0.0 --port 8766Streamable HTTP at /mcp, with a health check at /healthz. That check answers
{ ok, status, since }. It turns 503 once the socket has been anything but
connected for two minutes, so a tunnel or a monitor sees a real outage rather
than a reconnect in progress. Two bearer tokens:
the read token gets the read tools, the write token can unlock the write
tools. A leaked read token can never message anyone. A write token is not
the same as writes being enabled: if the server is read-only, even a write
token session has no write tools. get_status says so and how to turn
writes on. wazap refuses to bind a non-loopback address without a read
token. Agents that cannot carry a header sign in with
OAuth instead.
Self-host
Run wazap on a server of your own when the agent is not on your laptop: another machine, a VPS, a client's infrastructure. The session stays on that server; nothing goes through a third party.
With systemd
npm install -g wazap-mcp
sudo useradd --system --home /var/lib/wazap --create-home wazap
sudo -u wazap WAZAP_DATA_DIR=/var/lib/wazap wazap login --phone +15550100 # pairing code works over SSH
sudo -u wazap tee /var/lib/wazap/.env >/dev/null <<END
WAZAP_READ_TOKEN=$(openssl rand -hex 32)
WAZAP_WRITE_TOKEN=$(openssl rand -hex 32)
END
sudo curl -fsSL https://raw.githubusercontent.com/razvangirgiz/wazap/main/deploy/wazap.service -o /etc/systemd/system/wazap.service
sudo systemctl enable --now wazap
curl -s http://127.0.0.1:8766/healthzThe unit binds loopback only. Put TLS in front with the two-line deploy/Caddyfile (caddy run --config deploy/Caddyfile after editing the hostname) or any reverse proxy, then point the client at https://your-host/mcp with Authorization: Bearer <read or write token>.
With Docker
git clone https://github.com/razvangirgiz/wazap && cd wazap
printf 'WAZAP_READ_TOKEN=%s\nWAZAP_WRITE_TOKEN=%s\n' $(openssl rand -hex 32) $(openssl rand -hex 32) > .env
docker compose run --rm wazap login --phone +15550100 # once; the session lands in the wazap-data volume
docker compose up -d
curl -s http://127.0.0.1:8766/healthzThe container publishes 8766 on loopback only; add the same TLS proxy in front. Upgrading is git pull && docker compose up -d --build; the volume keeps the session.
From a machine without a public address
A laptop or a box behind NAT can still serve hosted agents through a tunnel, with no port opened and TLS done at the edge. npx wazap-mcp expose does the whole thing with Tailscale Funnel or Cloudflare Tunnel, whichever is installed. See Keep it running.
wazap keeps binding loopback either way; only the tunnel reaches it.
cloudflared tunnel login
cloudflared tunnel create wazap
cloudflared tunnel route dns wazap wazap.example.com
cloudflared tunnel run --url http://127.0.0.1:8766 wazapSet WAZAP_PUBLIC_URL=https://wazap.example.com for OAuth and keep cloudflared running the way you keep wazap running (a systemd unit, a launchd agent). Tailscale Funnel or ngrok work the same way: whatever ends at https://your-host with /mcp behind it.
Which clients can reach it
Claude Code, Claude Desktop, Cursor, Codex, VS Code, Poke and any client with an "MCP URL + header" field connect with the bearer token. Keep the read token in clients that only need to read; hand out the write token deliberately.
claude.ai Connectors, ChatGPT and some hosted agents will not take a static header. They want OAuth, which is the next section.
Hosted agents (OAuth)
Two more lines in the same .env turn wazap into its own OAuth 2.1 server:
WAZAP_PUBLIC_URL=https://wazap.example.com
WAZAP_OAUTH_PASSWORD=$(openssl rand -base64 18)Then give an agent nothing but https://wazap.example.com/mcp. It finds the
authorization server at /.well-known/oauth-protected-resource/mcp, registers
itself (RFC 7591, so there is no client id to paste anywhere), and sends you to
a page on your own host that asks two things: the password above, and whether
this agent may only read or also send. A refresh token keeps the agent signed
in until you revoke it; access tokens rotate every 24 hours on their own.
Tested against the flow claude.ai, ChatGPT and Poke use: S256 PKCE, public
clients, /token with refresh, /revoke. The bearer tokens keep working next
to it, so a laptop client on a header and a hosted agent on OAuth share one
server.
What to know before exposing it:
WAZAP_PUBLIC_URLmust behttpsand a bare origin, no path: the endpoints live at its root. The password travels to it.The password is the whole identity layer. Use a long one. A consent page takes three wrong guesses and is gone; five from one address lock that address out for fifteen minutes; twenty from anywhere close the page for everyone for fifteen minutes.
With OAuth on,
/mcpnever answers an unauthenticated request, whether or not a read token is set.Grants live in
<data-dir>/oauth.jsonas hashes. Delete the file to sign every agent out at once, running server included;wazap statuslists who holds one. Disconnecting an agent on its side revokes its refresh token and every access token it minted. A refresh token unused for ninety days is dropped.A read grant never sees a write tool, whatever scope the agent requested. The radio button on the consent page is the only thing that decides.
Outbound webhook
A live inbound message can POST to one URL. Off by default. History sync
is not posted. The only event is message_received.
npx wazap-mcp config webhook on # asks for URL + secret (secret is not echoed)
npx wazap-mcp webhook test # POST a probe event
npx wazap-mcp webhook test --account work
npx wazap-mcp config webhook offOn without a URL or secret fails wazap status, doctor and setup. A failed
delivery retries twice (200 ms, then 500 ms), then sets webhook.last_error
and leaves WhatsApp and MCP running. An account may set webhook_url and
webhook_secret in accounts.json; those win over the global URL and secret.
HMAC: X-Wazap-Signature is sha256=<hex>, HMAC-SHA256 of the exact raw
JSON body with the secret that signed it. Verify that raw body, not a
re-serialized object. HTTPS only, except http:// on loopback.
{
"event": "message_received",
"from": "+15550100",
"chat_id": "15550100@s.whatsapp.net",
"ts": "2026-09-08T14:00:00+00:00",
"text": "hello, or a short preview",
"message_id": "false_15550100@s.whatsapp.net_3EB0…",
"account_id": "default",
"account_name": "default"
}Settings
Variable | Default | Meaning |
|
| Where everything is stored. |
| unset ( |
|
|
| Ask WhatsApp for a fuller history sync. |
|
| Keep chats and messages across restarts. |
|
| Write tool calls per minute; |
|
|
|
|
| HTTP bind address. |
| unset | HTTP bearer tokens. |
| unset | The |
| unset | What the consent page asks for. At least 8 characters. |
|
|
|
|
|
|
|
| Transcribe incoming voice notes in the background. |
|
| Spoken language, e.g. |
|
|
|
| unset | Path to a whisper.cpp binary that is not on |
| unset | API key; |
|
| OpenAI-compatible base URL. |
|
| Model at that URL. |
|
|
|
| unset | HTTPS endpoint. |
| unset | Shared secret for |
Flags beat environment variables, which beat <data-dir>/.env.
Known limitations
Unofficial. Baileys reverse-engineers the WhatsApp multi-device protocol. This is not the WhatsApp Business API and Meta does not support it.
Ban risk is real. Automated sending, bulk messaging or anything a human would not plausibly type can get the number banned, and that is not recoverable from here. The rate limit helps; it is not a guarantee.
Media keys expire. WhatsApp drops old attachments from its servers, so
download_mediaon an old message returnsMEDIA_UNAVAILABLE.History is what the phone syncs. wazap sees the history WhatsApp hands the linked device, not your full phone archive.
read_messageswithbeforeasks for more, within whatever WhatsApp still keeps.@lidids. Newer accounts are addressed by a privacy id rather than a phone number. wazap translates them back to phone numbers when it has learned the mapping, and passes the@lidthrough when it has not.Names come from the phone's address book. WhatsApp delivers it as an app state sync, and only to a connection asking for it from scratch. If contacts read as phone numbers and
get_statusshowscontacts_named: 0, ask for it again with thesync_contactstool orwazap contacts resync.Calls are WhatsApp calls only. A call shows up as a message with
type: "call", carrying its kind, direction, outcome and duration. WhatsApp's own call log and the missed-call notices arrive on their own; a call that starts and ends while wazap is running is recorded live, so calls placed or received while it is stopped can be missing entirely. A cellular call from the phone's dialler is never visible, on any device.Your phone must stay reachable. A linked device stops receiving once the phone has been offline long enough;
get_statussays so inhint.
Development
npm install
npm run typecheck
npm test # builds, then runs node --test
node test/smoke-stdio.mjs # drives the built binary over MCP stdio
npm run dev -- status # run from source with tsxnpm test needs no WhatsApp session. The stdio smoke test spawns the built
binary against a throwaway data directory and checks that an unlinked install
still answers initialize, tools/list and get_status.
MIT licensed.
Available Tools
33 toolsconfirm_sendSend a drafted WhatsApp messageA
Send a draft created by send_message, send_media, send_poll, send_location or forward_message. This is the only call that reaches WhatsApp. The draft is consumed. A missing or expired draft_id means draft again and show the new preview before calling this.
| Name | Required | Description | Default |
|---|---|---|---|
| draft_id | Yes | The draft_id returned by a send_* tool | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare not read-only, not idempotent, open-world, and non-destructive. The description goes beyond them by disclosing the draft-consumption side effect and the draft expiry/error path, which explains why the operation is non-idempotent and what a caller must do on failure. It stops short of describing the success response (e.g., resulting message id/status), which is the one remaining behavioral gap.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core action and the 'only call that reaches WhatsApp' distinction, then adds the consumption and expiry facts. Four short sentences, each carrying distinct information, with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a two-parameter mutation tool with no output schema, the description covers purpose, routing, side effects, and the error-recovery path, which is enough for correct invocation. It could optionally state what the caller receives on success, but nothing essential is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so both draft_id and account_id are already documented in the schema, including the account_id resolution fallback logic. The description repeats that draft_id originates from a send_* tool but adds no format, constraint, or usage detail beyond the schema. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (send/confirm) and resource (draft from send_* tools), and explicitly bounds the scope with 'the only call that reaches WhatsApp'. An agent can immediately tell it apart from send_message, send_media, and the other drafting siblings.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly routes the agent: drafts come from send_message/send_media/send_poll/send_location/forward_message, and this is the single call that actually delivers. It also gives a recovery rule for the missing/expired draft case (draft again and re-show the preview). Nothing is left to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
create_groupCreate a WhatsApp groupA
Create a group with the given name and participants; the linked account becomes the owner. Each participant comes back with a status: ok, invite_needed (their privacy settings require an invite link) or failed.
| Name | Required | Description | Default |
|---|---|---|---|
| name | Yes | Group name | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| participant_ids | Yes | Chat ids or phone numbers to add (1-256) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly=false, idempotent=false, destructive=false, and openWorld=true, so the safety profile is covered. The description adds genuinely useful behavior beyond that: the per-participant status vocabulary (ok, invite_needed, failed) tells the agent to expect partial success rather than all-or-nothing.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences with no filler; the core action and required inputs come first, and the return-status detail is efficiently packed into the second sentence.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 3-parameter mutation tool with no output schema, the description covers the action, required inputs, ownership effect, and return statuses. It stops short of explaining failure handling (e.g., what happens if every participant fails) or limits, but the essentials are present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents name, account_id, and participant_ids with constraints and defaults. The description only restates 'name and participants' and adds no format or resolution detail beyond the schema, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Create a group'), names the required inputs ('given name and participants'), and adds the ownership outcome ('the linked account becomes the owner'), which cleanly separates it from read-oriented siblings like get_group_info and manage_group.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The creation context is implied by the verb, but there is no guidance on when to use this versus manage_group, nor any prerequisites (e.g., the account must be linked) or exclusions. Adequate but leaves the agent to infer routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
delete_messageDelete a WhatsApp messageADestructive
Retract a message. DESTRUCTIVE and visible to everyone in the chat — confirm with the user first. Only works on messages the linked account sent, and only within 2 days of sending.
| Name | Required | Description | Default |
|---|---|---|---|
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| message_id | Yes | Message id from read_messages / search_messages / get_message, e.g. "false_4072...@s.whatsapp.net_3EB0..." | |
| for_everyone | No | Retract for all participants (WhatsApp supports no other kind of delete here) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true and idempotentHint=false, but the description adds non-obvious behavior the annotations cannot convey: the retraction is visible to everyone in the chat, it is restricted to the sender's own messages, and it has a 2-day eligibility window. That is exactly the extra context needed before a mutation.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, zero filler, and the DESTRUCTIVE warning is front-loaded ahead of the constraints. Every clause carries information the agent needs.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a destructive mutation with no output schema, the description covers the safety profile, the confirmation requirement, the visibility consequence, and the two eligibility constraints — the full set of facts an agent needs to call it responsibly. Annotations and schema fill the remaining structured gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so message_id, account_id, and for_everyone are already fully documented in the schema (including the default-resolution behavior of account_id). The description adds only the sender-restriction fact, which mildly relates to message_id semantics but does not go beyond the schema's own detail.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Retract a message') and immediately distinguishes the operation from siblings like edit_message or send_message by naming the retraction semantics. An agent can identify the correct tool without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit operational conditions: only messages the linked account sent, only within 2 days of sending, and a required user confirmation before invoking. These are the precise conditions under which the tool succeeds or must be avoided.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
download_mediaDownload media from a WhatsApp messageARead-onlyIdempotent
Download the photo/video/audio/document attached to a message and save it to disk on the machine running wazap. Images of 1 MB or less are also returned inline so you can look at them.
Fails with MEDIA_UNAVAILABLE when WhatsApp has expired the file.
| Name | Required | Description | Default |
|---|---|---|---|
| save_to | No | Absolute directory to save into (default: <data-dir>/media) | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| message_id | Yes | A message with has_media=true |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, openWorldHint and destructiveHint=false, so the safety profile is covered. The description adds real value beyond that: it discloses a filesystem side effect (writes to disk on the host), the inline-return behavior for images ≤1 MB, and the specific error code MEDIA_UNAVAILABLE for expired media.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with what is downloaded and where, followed by the inline-image nuance and the failure mode. No filler, no repetition of the name or title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
There is no output schema, yet the description covers the practical return behavior (file on disk, inline image for small files) and the key failure mode. A brief note on the returned path/format and whether existing files are overwritten would make it fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so message_id, save_to, and account_id are already fully documented in the schema, including the default save directory. The description adds no syntax, format, or constraint detail beyond that, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (download) and resource (photo/video/audio/document attached to a message) plus the destination (save to disk on the wazap host). An agent can distinguish this from read_messages, get_message, or transcribe_audio without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the purpose (you call this when you need the actual file for a message). There is no explicit when-to-use vs alternatives guidance, e.g. it never says to use this instead of transcribe_audio for audio or that the message must have has_media=true. The MEDIA_UNAVAILABLE note is a failure condition, not routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
edit_messageEdit a WhatsApp message you sentA
Replace the text of a message the linked account sent. WhatsApp only allows this within 15 minutes of sending; after that send a correction instead.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | The replacement text | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| message_id | Yes | A message the linked account sent |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare the mutation safety profile (readOnly=false, destructive=false, idempotent=false), so the description's key added value is the 15-minute time-limit constraint, which is a real behavioral trait the agent cannot infer from annotations. It stops short of describing edit side effects or the result payload, but the time window is the critical disclosure.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, zero filler, with the core action stated first and the constraint immediately after. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple three-parameter mutation with full schema coverage and annotations carrying the safety profile, the description supplies the one non-obvious constraint (the edit window). No output schema exists, so return values need not be explained; only minor gaps like side-effect detail remain.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so message_id, text, and account_id are already documented inline. The description confirms the target is a message 'the linked account sent' but adds no syntax or format detail beyond the schema. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Replace the text') and resource ('a message the linked account sent'), which cleanly separates it from siblings like send_message and delete_message. An agent can identify the operation without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit precondition (only within 15 minutes of sending) and the alternative action when that window has passed ('send a correction instead'). Both when-to-use and when-not-to-use are covered.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
forward_messageDraft a forwarded WhatsApp messageA
Draft a forward of an existing message to another chat. Does not send. The recipient will see it marked as forwarded. Show the preview; after the user says yes, call confirm_send.
| Name | Required | Description | Default |
|---|---|---|---|
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| message_id | Yes | Message id from read_messages / search_messages / get_message, e.g. "false_4072...@s.whatsapp.net_3EB0..." | |
| to_chat_id | Yes | Destination chat |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (readOnlyHint=false, destructiveHint=false, non-idempotent, openWorld). The description adds genuinely new behavioral context: nothing is transmitted until confirmed, the recipient sees the message marked as forwarded, and a preview is surfaced. That goes beyond what the structured fields convey.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences with zero filler; the critical constraint ('Does not send') and the next-step routing are front-loaded and unambiguous.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 3-parameter drafting tool with no output schema, the description covers the key concerns: no transmission occurs, the preview should be shown, and the confirmation path is named. It stops short of describing what the draft object returns, but nothing needed for correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so message_id, to_chat_id and account_id are already fully documented inline. The description mentions 'existing message' and 'another chat' but adds no format, resolution fallback, or constraint detail beyond the schema, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Specific verb ('Draft a forward') plus a specific resource ('an existing message to another chat'), with immediate disambiguation from sending tools via 'Does not send.' An agent can distinguish it from send_message/confirm_send without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description clearly scopes the operation ('Does not send') and routes the agent to the follow-up tool and its trigger ('after the user says yes, call confirm_send'). It doesn't explicitly compare against alternatives like send_message for non-forward cases, but the workflow condition is stated plainly.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_contactGet WhatsApp contact detailsARead-onlyIdempotent
Full details for one contact: name, number, about text, profile picture URL, whether they are a saved contact, a business, or blocked.
| Name | Required | Description | Default |
|---|---|---|---|
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| contact_id | Yes | Contact id from search_contacts / list_chats, or a phone number |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, openWorldHint=true, and destructiveHint=false, covering the safety profile. The description adds value by listing the returned fields, which helps the agent know what data is available, but it says nothing about error behavior, account requirements, or rate limits.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence with no waste. The list of returned fields is efficiently packed and directly useful.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description does the important work of enumerating returned fields, which compensates well. It omits parameter constraints and error cases, but for a simple read tool whose annotations and schema cover those areas, it is nearly complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema fully documents contact_id and account_id. The description does not mention either parameter by name or add any usage detail beyond what the schema provides, so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states the specific resource (one contact) and scope (full details), enumerating the exact fields returned. The singular 'one contact' distinguishes it from plural-list siblings like search_contacts and list_chats without ambiguity.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
There is no explicit when-to-use guidance, no mention of alternatives, and no prerequisites. The phrase 'Full details for one contact' implies a retrieval use case but offers no routing or exclusion rules.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_group_infoGet WhatsApp group infoARead-onlyIdempotent
Details of a group: name, description, owner, creation date, whether only admins may post, whether the linked account is an admin, and the participant list (up to 500; participant_count is always the true total). The invite link is included only when the linked account is an admin.
Call this before manage_group: most group actions need admin rights.
| Name | Required | Description | Default |
|---|---|---|---|
| group_id | Yes | Group chat id ("<id>@g.us") | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, so the bar is lower, and the description adds real traits beyond them: the participant list is capped at 500 while participant_count is the true total, and the invite link is only returned when the linked account is an admin. These conditional/truncation behaviors are genuinely useful and not derivable from the annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences: the return contents are front-loaded, followed by the conditional invite-link rule and a one-line usage note. Zero wasted text.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the return-value burden and does so well by enumerating fields and their conditions; the read-only safety profile is covered by annotations. Nothing essential for correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% (both group_id and account_id are documented in the schema, including the account resolution fallback), so the description adds no syntax or format detail. Baseline 3 is appropriate when the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description names a specific verb+resource ('Details of a group') and enumerates the exact fields returned (name, description, owner, creation date, admin-only posting, participant list), which an agent can use to distinguish it from manage_group or create_group without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It explicitly routes usage: 'Call this before manage_group: most group actions need admin rights,' giving the sequencing condition that selects this tool. It does not state explicit when-not-to-use cases, but the context is unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_messageGet one WhatsApp message in fullARead-onlyIdempotent
The complete message behind a message_id, including the quoted message it replies to, its reactions, and its media metadata. Use it after search_messages or read_messages when you need the context around a single message.
| Name | Required | Description | Default |
|---|---|---|---|
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| message_id | Yes | Message id from read_messages / search_messages / get_message, e.g. "false_4072...@s.whatsapp.net_3EB0..." |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint, idempotentHint, openWorldHint and non-destructive, so the safety profile is covered. The description adds value beyond that by disclosing the return content shape (quoted message, reactions, media metadata), which matters because there is no output schema. It does not cover error behavior for an unknown message_id, keeping it below a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two sentences, zero filler, and the payload content is front-loaded before the routing advice. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 2-param read tool with no output schema, the description supplies the missing return-content detail (quoted message, reactions, media metadata) plus entry-point guidance. Failure modes (unknown or inaccessible message_id) are not mentioned, so it is not fully complete.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both account_id (fallback resolution rules) and message_id (format example) are already fully documented in the schema. The description adds no parameter meaning beyond that, which is the baseline 3 case when the schema does the heavy lifting.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('The complete message behind a message_id') and immediately enumerates what 'complete' means: quoted message, reactions, media metadata. An agent can distinguish it from read_messages and search_messages without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says to use it 'after search_messages or read_messages when you need the context around a single message', naming the sibling tools that precede it. It stops short of stating when *not* to use it (e.g. bulk retrieval should use read_messages), so it is clear context without exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_recent_messagesGet every WhatsApp conversation from the last N hoursARead-onlyIdempotent
Everything that happened recently, grouped by chat. This is the catch-up tool: one call instead of list_chats plus a read_messages per chat. WhatsApp's own notices — device linking, group membership changes, encryption notices — are left out so the counts are conversation; pass include_system to see them.
| Name | Required | Description | Default |
|---|---|---|---|
| hours | No | Look-back window in hours (1-168) | |
| types | No | Keep only these message types; omit for every type. The limit counts matching messages, so ["call"] returns that many calls, not that many messages of which some are calls. | |
| filter | No | Restrict to unread chats, groups, or one-to-one chats | all |
| compact | No | Leave out media without a caption and messages with no words in them, fold what one person sent in a row into one line, and say per chat what was left out. About half the size; use it for a routine catch-up | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| include_system | No | Include WhatsApp's own system notices, which are excluded from the bodies and the counts by default | |
| include_previews | No | Attach a small JPEG of each photo, newest first, up to 12 per call, so you can see what was sent: the preview WhatsApp shipped when there is one, otherwise the photo is downloaded once and shrunk on the machine running wazap |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover the safety profile (readOnly, idempotent, non-destructive), so the description adds genuine behavioral context beyond them: WhatsApp system notices are excluded from bodies AND counts by default, with include_system to reveal them. It does not disclose output structure or pagination/size limits, keeping it below a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose and the replacement relationship; three sentences with little waste. The em-dash aside about left-out notices is slightly dense but earns its place by explaining the count semantics.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only, all-optional-parameter tool with 100% schema coverage and no output schema, the description covers scope, default exclusion behavior, and sibling routing adequately. Return-shape detail is not required given the schema and absence of an output contract.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents all 7 optional parameters in detail. The description restates only the include_system default behavior, adding marginal value over what the structured fields provide; baseline 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource and scope: 'everything that happened recently, grouped by chat' and self-labels as 'the catch-up tool.' Crucially it distinguishes itself from siblings by declaring it replaces 'list_chats plus a read_messages per chat,' so an agent can route without opening either schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Provides clear usage context ('catch-up') and names the alternatives it consolidates (list_chats + read_messages). It also flags the include_system parameter for system notices. It stops short of explicit when-not guidance, e.g., when to prefer search_messages or get_unanswered instead.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_statusGet the WhatsApp connection statusARead-onlyIdempotent
Check the session: connection status ("connected" means the tools work,
"not_linked" means the user must run npx wazap-mcp login), whether the initial
history sync has finished, which account is linked, when a message last arrived,
the versions and data directory in use, and how many contacts carry a name from
the phone's address book (contacts_named: 0 means it never arrived).
Call this whenever another tool reports NOT_CONNECTED, NOT_LINKED or
SYNC_IN_PROGRESS, or to confirm which account you are about to send from.
Without account_id the top-level fields are the default account, plus
accounts listing every live one. While a link is in progress the status
is "linking" and pairing carries the code the user still has to type
into their phone.
| Name | Required | Description | Default |
|---|---|---|---|
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already mark this as a safe, idempotent read, and the description adds substantial behavior beyond them: the meaning of each status value, the 'linking' intermediate state with a pairing code, the semantics of contacts_named: 0, and the default-account fallback. This is rich operational context an agent cannot get from annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core purpose and keeps the trigger conditions in the second paragraph. It is dense and long, but nearly every clause carries distinct information (status values, pairing code, contacts_named), so little is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the full burden of describing returns, and it does so thoroughly: status values and their meanings, sync state, linked account, last-message time, versions, data directory, and the accounts array. Nothing an agent needs to interpret the response is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The single parameter is fully documented in the schema (100% coverage), so baseline is 3. The description goes further by explaining the consequence of omitting account_id — top-level fields reflect the default account while `accounts` lists every live one — which adds meaning beyond the schema's wording.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Check the session: connection status') and enumerates the exact fields returned, so an agent knows precisely what this returns. It is clearly distinguished from siblings like link_account and list_accounts by its diagnostic scope.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly names the trigger conditions ('whenever another tool reports NOT_CONNECTED, NOT_LINKED or SYNC_IN_PROGRESS') and a second use case ('to confirm which account you are about to send from'). It also explains what the not_linked state implies and the remediation command, which is exactly the routing guidance an agent needs.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_storiesSee the stories people postedARead-onlyIdempotent
The stories (status updates) the linked account has received in the last N hours, newest first, each with its author, its text or caption and its time. WhatsApp keeps a story for a day and so does wazap; nothing older is held. With include_previews the photos come as small images, and download_media works on a story's message_id like on any message. Stories never appear in chats, catch-ups or waits; this is the only place they show.
| Name | Required | Description | Default |
|---|---|---|---|
| hours | No | Look-back window in hours (1-24) | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| include_previews | No | Attach a small JPEG of each photo, newest first, up to 12 per call, so you can see what was sent: the preview WhatsApp shipped when there is one, otherwise the photo is downloaded once and shrunk on the machine running wazap |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover read-only/idempotent/open-world safety, and the description adds real substance on top: a 24-hour retention rule ('nothing older is held'), newest-first ordering, and how previews are produced (WhatsApp's preview when available, otherwise downloaded once and shrunk locally). It stops short of covering auth or per-call cost implications.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four sentences, front-loaded with scope and ordering before mechanics; nearly every clause carries information. Slight redundancy in 'WhatsApp keeps a story for a day and so does wazap' costs a point but not much.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description carries the return shape (author, text/caption, time, optional preview images) and the retention/ordering rules, which is everything an agent needs to invoke and interpret this read-only tool correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, which sets a baseline of 3, but the description adds meaning beyond the schema: previews arrive as 'small images', the retention ceiling explains why hours caps at 24, and it links message_id semantics to download_media. account_id receives no additional explanation, keeping it from a 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: the stories (status updates) the linked account has received in the last N hours, newest first, each with author, text/caption and time. It explicitly distinguishes itself from siblings by noting stories never appear in chats, catch-ups or waits, so an agent can route correctly without opening a schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear when-not for sibling tools ('Stories never appear in chats, catch-ups or waits; this is the only place they show') and points to download_media for retrieving a story's media via message_id. It lacks explicit guidance on choosing hours or when previews are worth requesting, but the routing context is strong.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
get_unansweredFind who is waiting on the userARead-onlyIdempotent
Chats where the last word is theirs and it asks for something: a question, a request ("poți", "te rog", "can you", "when"…), or a voice note nobody has heard yet. A conversation that ended in "ok, thanks" is not listed, and neither is an ask older than max_age_hours (two weeks by default): that one was abandoned, not left waiting. Groups count only when the user was @-mentioned or replied to after their own last message. A [business] account's ask is often an automatic reply; weigh it accordingly.
People come first, then the oldest wait. Each entry quotes the ask, says how many of their messages arrived since the user's last one, and how long they have been waiting. This is the follow-up half of an inbox triage; use get_recent_messages for what happened, and this for who is still waiting.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of chats (1-50) | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| max_age_hours | No | Ignore asks older than this; an ask left for two weeks (the default) is abandoned, not waiting | |
| min_age_hours | No | Only asks at least this old, e.g. 48 for people the user forgot for two days |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already cover safety (read-only, idempotent, non-destructive), and the description goes well beyond them: abandonment semantics for max_age_hours, group-chat inclusion logic, business-account auto-reply weighting, and output ordering. This is rich behavioral context an agent cannot infer from the structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loads the core definition, then layers exclusions, group logic, output shape, and routing. It is dense but a few parenthetical example tokens and the caveat sentences make it longer than strictly necessary; still, nearly every sentence carries selection-relevant information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, and the description still tells the agent what each entry contains (the quoted ask, count of messages since the user's last reply, waiting duration) plus ordering (people first, then oldest wait). Nothing material for correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents all four parameters. The description still adds interpretive value: max_age_hours is framed as 'abandoned, not waiting,' and min_age_hours is illustrated with 48 hours for forgotten asks, giving an agent a reason to choose thresholds rather than just valid ranges.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific resource ('chats where the last word is theirs and it asks for something') with the exact inclusion heuristic, and explicitly distinguishes itself from the sibling get_recent_messages. An agent can tell what this returns and why it exists without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicit routing: 'use get_recent_messages for what happened, and this for who is still waiting.' It also gives exclusion conditions (ended in 'ok, thanks', older than max_age_hours, group chats without @-mention or reply) and caveats for business accounts, so the agent knows when not to expect a row.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
learnLearn how to use the WhatsApp toolsARead-onlyIdempotent
Read this FIRST, before any other WhatsApp tool. Returns the guide to the tools, the id formats, the recommended workflows, the message shape and every error code with what to do about it. Takes no arguments and never touches WhatsApp.
| Name | Required | Description | Default |
|---|---|---|---|
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive/closed-world, so the safety bar is low. The description adds value by specifying exactly what content is returned (guide, id formats, workflows, message shape, error remediation) and reinforcing that it 'never touches WhatsApp,' useful given no output schema. Minor tension: it says 'Takes no arguments' while the schema exposes an optional account_id.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences with the imperative 'Read this FIRST' front-loaded, then the return contents, then the no-side-effect guarantee. No wasted words for a documentation tool.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-required-param meta tool with no output schema, the description fully covers purpose, timing, return contents, and side-effect profile. Nothing an agent needs before calling it is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so account_id's meaning is already fully documented in the schema, which sets the baseline at 3. The description's claim that it 'takes no arguments' adds no detail and is slightly at odds with the optional account_id, so it does not earn above baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource: returns the guide covering tools, id formats, recommended workflows, message shape, and error codes. The title and description together make clear this is the meta/documentation entry point, cleanly distinguished from all 32 sibling action tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly instructs 'Read this FIRST, before any other WhatsApp tool,' which is an unambiguous when-to-use directive. For a bootstrap/documentation tool there is no meaningful alternative to exclude, so the guidance is complete.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
link_accountLink a WhatsApp accountARead-onlyIdempotent
Pair this wazap with the user's WhatsApp when get_status says not_linked, logged_out,
session_corrupt or auth_failure. Ask the user for their phone number in international format,
call this, and show them the code it returns with these exact steps:
WhatsApp → Settings → Linked devices → Link a device → Link with phone number instead → enter the code.
Then call get_status every 10 seconds until it says connected (up to 3 minutes). The code expires;
call this again for a fresh one if get_status goes back to not_linked with an error.
The account must already exist (wazap account add). Pass account_id when
more than one is configured. An unknown id is ACCOUNT_NOT_FOUND.
Never call this when the account is already linked.
| Name | Required | Description | Default |
|---|---|---|---|
| phone | Yes | International format, e.g. +15550100 | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description discloses rich operational behavior (returns a code, expires, requires polling, account prerequisite, ACCOUNT_NOT_FOUND), but it directly contradicts the annotations: readOnlyHint=true is false for a pairing/write action, and idempotentHint=true conflicts with 'call this again for a fresh one.' This is an annotation contradiction.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is front-loaded with purpose and trigger conditions, then gives the user-facing link steps, polling behavior, expiry handling, prerequisites, and error case. Despite its length, every sentence contributes to correct invocation.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a multi-step linking tool with no output schema, the description is complete enough: it explains what is returned (a code), how to present it, how to verify connection via get_status, how long to wait, and what to do if the code expires.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the schema already documents phone format and account_id. The description adds useful semantics beyond the schema: pass account_id when multiple accounts are configured and an unknown id returns ACCOUNT_NOT_FOUND.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific action and resource: pairing this wazap with the user's WhatsApp account. It clearly distinguishes this from get_status by making get_status the trigger/status-check sibling, not the linking action.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives explicit when-to-use conditions (get_status says not_linked, logged_out, session_corrupt, or auth_failure), explicit when-not-to-use guidance (never when already linked), prerequisites (account must exist), and the alternative action for an expired code.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_accountsList WhatsApp accounts on this serverARead-onlyIdempotent
List every configured WhatsApp account: id, name, connection status, masked phone, owner name, and whether that account allows writes. Call this first when more than one account is linked, then pass account_id on the other tools. Takes no arguments besides the optional account_id (ignored for the listing).
| Name | Required | Description | Default |
|---|---|---|---|
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive and openWorld, so the safety profile is covered. With no output schema, the description adds real behavioral value by naming the returned fields, including that phones are masked and that write permission is exposed, plus the note that account_id is effectively ignored for listing.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight sentences with the resource and field list front-loaded, followed by routing guidance and the argument note. Nothing is wasted, though the clause about account_id being ignored is slightly at odds with the schema's resolution wording and could be sharper.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-required-argument read tool with no output schema, the definition covers what is returned, when to call it, and how to chain account_id into other tools. Pagination or account-limit behavior is not mentioned, but for an account registry that is a minor gap.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100% so the schema already documents account_id (default, work, resolution from chat_id/message_id). The description adds a nuance the schema does not state plainly: that account_id is ignored for this listing call, clarifying that it does not filter results.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List every configured WhatsApp account') and enumerates the returned fields (id, name, connection status, masked phone, owner, write permission), so an agent knows exactly what the tool produces. No sibling tool overlaps this listing behavior, so differentiation is implicit but clear.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives explicit sequencing guidance: 'Call this first when more than one account is linked, then pass account_id on the other tools.' That is clear context for when to use it, though it offers no exclusions or naming of an alternative listing path (e.g., resolving accounts implicitly elsewhere).
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_chatsList WhatsApp chatsARead-onlyIdempotent
List conversations, most recently active first. Use it to discover the chat_id values the other tools need.
Each chat has: chat_id, name, type, unread_count, last_message {text, timestamp, from_me}, archived, pinned, muted_until, and left (groups you are no longer in).
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of chats (1-100) | |
| filter | No | Which chats to list; "all" (default) excludes archived ones | all |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/destructive=false, so the safety profile is covered. The description adds genuinely non-derivable behavior: result ordering, and the semantics of fields like 'left' (groups you are no longer in) — useful since there is no output schema to document them.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences, front-loaded with purpose and ordering before the return-shape list. Every clause carries information an agent needs; nothing is padded.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, enumerating the returned fields is the right compensating move, and the ordering plus 'left' semantics round it out. Only minor gaps remain, such as pagination guidance beyond the limit cap — which the schema already bounds.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, including the subtle note that filter="all" excludes archived chats, so the schema already carries the parameter meaning. The description adds no param syntax, defaults, or interaction guidance (e.g. how limit interacts with filter), so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('List conversations') plus the ordering guarantee ('most recently active first'), and frames the tool's role as a chat_id discovery step. This clearly separates it from siblings like read_messages or search_messages, which consume those ids rather than produce them.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
'Use it to discover the chat_id values the other tools need' gives a concrete condition for choosing this tool over its siblings. It stops short of naming specific alternatives or exclusions (e.g. when to prefer search_contacts for a known name), but the routing intent is unambiguous.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manage_chatManage a WhatsApp chatA
Change the state of a chat: archive/unarchive, pin/unpin, mute/unmute (mute_hours defaults to 8), mark_read (sends read receipts) or mark_unread.
| Name | Required | Description | Default |
|---|---|---|---|
| action | Yes | What to do with the chat | |
| chat_id | Yes | Chat id as returned by another tool ("<digits>@s.whatsapp.net" or "<id>@g.us"), or a phone number in international format | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| mute_hours | No | Hours to mute, default 8; only used by "mute" |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare the write/non-idempotent/non-destructive profile, and the description adds genuine side-effect detail: mark_read sends read receipts and mute defaults to 8 hours. It stops short of stating reversibility or permission requirements, but it goes beyond the annotations meaningfully.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single front-loaded sentence that packs the action list and two key defaults without padding. The dense parentheticals cost a little readability but every clause carries information.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 4-param mutation tool with full schema coverage and no output schema, the description covers actions, defaults and visible side effects. Auth requirements and whether archived/muted state is reversible are the only real gaps.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the parameters are already documented. The only added meaning is that mark_read triggers read receipts; the mute_hours default of 8 merely restates the schema, so baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a concrete verb+resource ('Change the state of a chat') and enumerates every supported mutation (archive/pin/mute/mark_read and inverses), so the agent immediately knows the scope. It is clearly separable from siblings like manage_group or send_message.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The list of actions implies when the tool applies, but there is no explicit statement of when to choose this over alternatives (e.g. manage_group for group-level settings) or any prerequisites. Usage must be inferred from the action enum.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
manage_groupManage a WhatsApp groupADestructive
Administer a group. Actions:
add / remove / promote / demote — need participant_ids; each participant comes back with status ok, invite_needed or failed
leave — DESTRUCTIVE, rejoining needs an invite
set_subject / set_description — need value
get_invite_link / revoke_invite_link
Everything except leave requires the linked account to be a group admin; call get_group_info first to check.
| Name | Required | Description | Default |
|---|---|---|---|
| value | No | New subject or description | |
| action | Yes | Group action to perform | |
| group_id | Yes | Group chat id ("<id>@g.us") | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| participant_ids | No | Targets of add/remove/promote/demote |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes beyond the annotations by documenting the per-participant result statuses (ok, invite_needed, failed), the irreversible consequence of leave, and the admin-permission requirement. Annotations already cover the safety profile (destructiveHint, idempotentHint), so this extra operational detail is genuinely additive; only return shape for the non-participant actions is undocumented.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded one-line purpose followed by a dense action list; every line carries either a parameter requirement or a behavioral warning. No filler sentences.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With 5 parameters, no output schema, and mutation semantics, the description supplies the prerequisites, destructive caveat, and partial result semantics an agent needs. Gaps are minor: no description of what get_invite_link returns or how failures for non-participant actions surface.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, but the description maps actions to their required parameters ('add / remove / promote / demote — need participant_ids', 'set_subject / set_description — need value'), which the schema alone does not express. That action-to-parameter binding is real added value.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb+resource ('Administer a group') and then enumerates the exact action set (add/remove/promote/demote/leave/set_subject/set_description/get_invite_link/revoke_invite_link), so an agent immediately knows the tool's scope. It does not explicitly distinguish itself from siblings like create_group or get_group_info, though the enum implicitly excludes creation.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly states the precondition ('Everything except leave requires the linked account to be a group admin') and prescribes a routing step ('call get_group_info first to check'). It also warns that leave is destructive and rejoining needs an invite, giving clear when/when-not guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
mark_handledTake a chat off the waiting listAIdempotent
The user dealt with what this chat was asking, outside WhatsApp or by a reply wazap did not see: a phone call, a meeting, a decision. The open ask is remembered as handled and the chat leaves get_unanswered. The next message from the other side makes a new ask and the chat comes back on its own. Kept on this machine only; nothing is sent or marked read on WhatsApp.
| Name | Required | Description | Default |
|---|---|---|---|
| chat_id | Yes | Chat id as returned by another tool ("<digits>@s.whatsapp.net" or "<id>@g.us"), or a phone number in international format | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial behavior beyond the annotations: the mark is local-only, nothing is sent or marked read on WhatsApp, and the handled state auto-reverses when the other side next messages. This mirrors and enriches the idempotentHint=true / destructiveHint=false annotations rather than contradicting them.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three sentences, front-loaded with the triggering situation before the state effect and the local-only caveat. Dense but each sentence carries distinct information; the WhatsApp reply aside is slightly incidental but still clarifying.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With annotations covering safety/idempotency and a fully described schema, the description supplies the remaining behavioral context an agent needs: what is mutated, what is not transmitted to WhatsApp, and how the state reverses on its own.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so both chat_id and account_id are already documented in the schema. The description adds no parameter-level syntax, format, or resolution detail beyond that, which is the expected baseline when the schema does the work.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (mark handled) on a specific resource (the open ask in a chat) and names the observable effect: the chat leaves get_unanswered. This lets an agent distinguish it from siblings like get_unanswered or manage_chat without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives a clear triggering condition: the user dealt with the ask outside WhatsApp (phone call, meeting, decision). It also contrasts the state transition with get_unanswered's listing behavior, though it does not explicitly name an alternative tool or an exclusion case.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
react_to_messageReact to a WhatsApp messageA
Add an emoji reaction to a message, or pass an empty string to remove your reaction.
| Name | Required | Description | Default |
|---|---|---|---|
| emoji | Yes | A single emoji such as "👍", or "" to remove your reaction | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| message_id | Yes | Message id from read_messages / search_messages / get_message, e.g. "false_4072...@s.whatsapp.net_3EB0..." |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover the safety profile (readOnlyHint=false, destructiveHint=false, openWorldHint=true), so the agent knows this is a non-destructive mutation. The description's one notable behavioral addition, that an empty string removes the reaction, is already duplicated in the schema parameter description. It adds no auth, rate-limit, or return-value context beyond what structured fields supply.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single well-formed sentence that front-loads the primary action and includes the removal case with zero padding. Nothing is wasted.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple 3-parameter mutation with no output schema, the description plus the 100%-covered schema convey everything needed to call it correctly. Only minor gaps remain (no mention of account context or response behavior on success/failure), which are low-stakes here.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all three parameters (emoji, account_id, message_id) are fully documented in the schema, including the empty-string removal trick and account resolution fallback. The description adds no syntax or semantic detail beyond that, so the baseline of 3 is appropriate.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb+resource ('add an emoji reaction to a message') and includes the inverse operation (removal), making the tool's scope unambiguous. It does not explicitly name a sibling tool, but the reaction semantics are specific enough to distinguish it from send_message or edit_message.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is implied by the description ('add an emoji reaction' / pass empty string to remove), which effectively tells an agent how to invoke both modes. However, there is no explicit guidance about when to use this versus alternatives like send_message, nor any prerequisites or account resolution notes.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
read_messagesRead messages from a WhatsApp chatARead-onlyIdempotent
Read messages from one chat, oldest to newest.
Without before you get the most recent messages. Pass before (the oldest
message_id you already have) to page further back; wazap asks the phone for
older history when the local store runs out, which takes a few seconds.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of messages (1-200) | |
| types | No | Keep only these message types; omit for every type. The limit counts matching messages, so ["call"] returns that many calls, not that many messages of which some are calls. | |
| before | No | Return the messages immediately older than this message_id | |
| chat_id | Yes | Chat id as returned by another tool ("<digits>@s.whatsapp.net" or "<id>@g.us"), or a phone number in international format | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| include_previews | No | Attach a small JPEG of each photo, newest first, up to 12 per call, so you can see what was sent: the preview WhatsApp shipped when there is one, otherwise the photo is downloaded once and shrunk on the machine running wazap |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already establish readOnly/idempotent/non-destructive, so the bar is lower. The description adds genuine behavioral context beyond them: default ordering, and that paging past the local store makes wazap query the phone and take a few seconds. It omits pagination termination cues (e.g. what an empty result means), so it stops short of 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two short sentences, front-loaded with the core action, then the paging mechanic. Every clause carries information; nothing is padding or restated from the schema.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description should carry return-shape burden; it states ordering and the default/most-recent behavior, which is the essential part for a read tool. It does not describe what a message record contains, so it is not fully complete for a six-parameter tool with no output schema.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the baseline is 3. The description nonetheless adds meaning over the schema's terse 'Return the messages immediately older than this message_id' by framing `before` as the oldest message_id you already have, i.e. the paging cursor. Other parameters (limit, types, include_previews) get no description-level elaboration.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Read), resource (messages), and scope (one chat, oldest to newest), which is more than a tautology of the name/title. It does not explicitly differentiate itself from close siblings like get_recent_messages or search_messages, so the agent must infer the boundary from the ordering/paging phrasing.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear conditional guidance: 'Without `before` you get the most recent messages' and 'Pass `before` ... to page further back'. That tells the agent when to supply the parameter, but it never names when to prefer a sibling such as search_messages or get_recent_messages, leaving the tool-selection boundary implicit.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_contactsSearch WhatsApp contactsARead-onlyIdempotent
Find contacts by name or phone number (substring match on the name, digit match on the number). Returns contact_id values usable as chat_id.
| Name | Required | Description | Default |
|---|---|---|---|
| limit | No | Maximum number of results (1-50) | |
| query | Yes | Name fragment or phone number (at least 2 characters) | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive and open-world, so the safety profile is covered. The description still adds real behavioral detail: how matching is performed (substring vs digit match) and what the returned identifiers can be used for. It stops short of ordering, cross-account behavior, or limits, which keeps it out of 5 territory.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences with no filler, front-loading the find operation and using the second sentence to state the return value. Every clause earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a three-parameter, read-only lookup with no output schema, the description covers purpose, matching behavior, and the meaning of the returned id, which is the key piece of output information. Gaps remain around result ordering, whether the search spans accounts, and truncation behavior, but the essentials are present.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3, and the description goes beyond it by clarifying how `query` is interpreted — substring on names and digit matching on numbers, which tells the agent that phone formatting does not matter. It adds nothing for `limit` or `account_id`, so it is not a full 5.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Find) and resource (contacts) plus the exact matching semantics (substring on name, digit match on number) and what the output is good for. That is far more precise than a bare 'search contacts'. However, it never names or contrasts with the nearest sibling, get_contact, so an agent must infer the boundary between the two from the names alone.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Usage is only implied: the note that results are 'usable as chat_id' hints at when you would reach for this tool, but there is no explicit 'use this when you know a name/phone but not the id' statement and no mention of get_contact or sync_contacts as alternatives. Adequate but leaves the agent to infer the routing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_messagesSearch WhatsApp messagesARead-onlyIdempotent
Case-insensitive text search over the messages wazap holds locally — all chats, or one chat. It cannot reach messages the phone never synced to this device.
| Name | Required | Description | Default |
|---|---|---|---|
| from | No | Only messages this person sent: "me", a contact id or a phone number | |
| limit | No | Maximum number of results (1-50) | |
| query | Yes | Text to search for | |
| since | No | Only messages from this moment on: a date ("2026-09-01") or an ISO timestamp | |
| until | No | Only messages up to this moment: a date or an ISO timestamp | |
| chat_id | No | Restrict the search to this chat | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly, idempotent, non-destructive, open-world. The description adds a genuinely useful non-obvious constraint — the local-sync data boundary — that annotations cannot convey. It does not describe result ordering or pagination behavior.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences with the core capability front-loaded and the limitation following immediately. Every clause earns its place; no restatement of the name or title.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a read-only search tool with a rich, fully documented 7-parameter schema and no output schema, the description covers purpose, scope, and the key data-boundary caveat. It omits anything about result volume defaults/ordering, but those are secondary.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all seven parameters (from, limit, query, since, until, chat_id, account_id) are already documented in the schema. The description's 'all chats, or one chat' allusion to chat_id adds nothing beyond the schema. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (case-insensitive text search) and resource (messages), plus scope (all chats or one chat). This distinguishes it from read_messages and get_recent_messages, which retrieve rather than search.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear context for use (local search over all chats or one chat) and an explicit boundary — it cannot reach messages never synced to the device. It does not name sibling alternatives (e.g., read_messages for a whole thread), so routing is implied rather than spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
send_locationDraft a WhatsApp locationA
Draft a map pin, optionally labelled with a place name and address. Does not send. Show the preview; after the user says yes, call confirm_send.
| Name | Required | Description | Default |
|---|---|---|---|
| name | No | Place name shown on the pin | |
| address | No | Street address shown under the name | |
| chat_id | Yes | Chat id as returned by another tool ("<digits>@s.whatsapp.net" or "<id>@g.us"), or a phone number in international format | |
| latitude | Yes | Latitude in decimal degrees | |
| longitude | Yes | Longitude in decimal degrees | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare readOnlyHint=false, destructiveHint=false, idempotentHint=false, and openWorldHint=true, so the safety profile is partially covered. The description adds the non-obvious behavioral fact that this call produces a draft only and never transmits, which is meaningful beyond the annotations. It does not mention auth/account requirements, but the schema's account_id description covers that.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three short sentences, front-loaded with the core action, followed by the critical 'does not send' caveat and the required next step. No filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a mutation tool with no output schema, the description supplies the workflow context an agent needs: it drafts rather than sends, a preview is shown, and confirm_send completes the action. Nothing essential to correct invocation is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents every parameter (name, address, chat_id, latitude, longitude, account_id). The description only restates that name/address are optional labels, adding little semantics beyond the structured fields. Baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb ('Draft') and resource ('a map pin, optionally labelled with a place name and address'), and explicitly says 'Does not send', which separates it from the send-family siblings (send_media, send_message, confirm_send). An agent can identify the tool without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit operating procedure: show the preview, wait for user confirmation, then call confirm_send. This names the required follow-up sibling and the condition that triggers it, leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
send_mediaDraft a WhatsApp media messageA
Draft an image, video, audio file, document or GIF, from a local path on the machine running wazap or from a public URL. Does not send. Exactly one of file_path / url. Maximum 100 MB. Show the preview; after the user says yes, call confirm_send. A GIF is sent with as_gif: an mp4 goes out looping, a .gif is converted to mp4 first (needs ffmpeg on the machine running wazap).
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | Public http(s) URL to fetch and send | |
| as_gif | No | Send a .gif or an mp4 as a looping GIF, the way WhatsApp plays them | |
| caption | No | Text shown under the media | |
| chat_id | Yes | Chat id as returned by another tool ("<digits>@s.whatsapp.net" or "<id>@g.us"), or a phone number in international format | |
| as_voice | No | Send an audio file as a voice note (push-to-talk) | |
| file_path | No | Absolute path of a local file to send | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| as_document | No | Send as a plain document instead of rendered media |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations declare mutation and non-destructiveness but say nothing about limits or side effects; the description supplies that the call only drafts, caps at 100 MB, and requires ffmpeg on the host for GIF conversion (.gif converted to mp4, mp4 loops). This is exactly the extra behavioral context annotations cannot carry.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action and source options, then the draft/confirm workflow, then limits and format detail. Every sentence carries information, though the GIF/ffmpeg clause is dense enough to be slightly hard to parse on first read.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema and 8 parameters, the description covers the drafting-vs-sending distinction, the mutually exclusive inputs, the size ceiling, dependency requirements, and the handoff to confirm_send. Nothing an agent needs to invoke it correctly is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the per-field descriptions do the bulk of the work (baseline 3). The description still adds non-obvious constraints absent from the schema: exactly one of file_path/url is required, and the as_gif semantics for mp4 vs .gif. That pushes it above baseline.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (Draft) plus the resource types (image, video, audio, document, GIF) and the source locations (local path or public URL). It is clearly distinguishable from send_message, download_media, and confirm_send, which all deal with adjacent but different actions.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly says 'Does not send' and routes the agent to the follow-up tool: 'after the user says yes, call confirm_send.' It also clarifies the alternation between file_path and url. The when-to-use and what-happens-next are both spelled out.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
send_messageDraft a WhatsApp text messageA
Draft a text message. Does not send. Returns a draft_id and a preview of the recipient and exact text. Show that preview to the user; after they say yes, call confirm_send. A draft lasts 15 minutes.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | The message text | |
| chat_id | Yes | Chat id as returned by another tool ("<digits>@s.whatsapp.net" or "<id>@g.us"), or a phone number in international format | |
| reply_to | No | Quote-reply to this message | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| mention_ids | No | Chat ids to @-mention; include their names in the text yourself |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Adds substantial behavior beyond the annotations: the call does not send, it returns a draft_id and a recipient/text preview, and the draft expires in 15 minutes. The annotations only declare non-readOnly, non-destructive and non-idempotent; the description supplies the stateful draft lifecycle and the required user-confirmation step that drive correct usage.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four short sentences, front-loaded with the most important fact (it drafts, it does not send), then return values, then the required next step, then the expiry. Every sentence carries load with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, but the description compensates by naming the return value (draft_id plus preview) and the mandatory confirm_send step, plus the 15-minute validity window. For a stateful two-step mutation flow, this is sufficient for an agent to call it correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so the schema already documents chat_id, text, reply_to, account_id and mention_ids in detail. The description adds no parameter-level syntax or defaults, so the baseline 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The name 'send_message' implies a send, but the description immediately corrects this with 'Draft a text message. Does not send.' — a specific verb+resource combined with a scope correction that an agent cannot infer from the name alone. It also names the follow-up sibling confirm_send, distinguishing this from the tool that actually sends.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear workflow guidance: show the preview to the user, and after approval call confirm_send. This tells the agent when to escalate to the sibling. It stops short of stating when to prefer send_media/send_poll/send_location for non-text payloads, so it is strong but not fully exhaustive.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
send_pollDraft a WhatsApp pollA
Draft a poll. Does not send. Participants vote in WhatsApp; wazap cannot read the votes back. Show the preview; after the user says yes, call confirm_send.
| Name | Required | Description | Default |
|---|---|---|---|
| chat_id | Yes | Chat id as returned by another tool ("<digits>@s.whatsapp.net" or "<id>@g.us"), or a phone number in international format | |
| options | Yes | Answer options (2-12) | |
| question | Yes | The poll question | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| multi_select | No | Allow voters to pick more than one option |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations cover safety flags (destructiveHint=false, openWorldHint=true), but the description adds genuinely non-obvious behavior beyond them: the tool drafts rather than sends, and votes are cast in WhatsApp and cannot be read back by wazap. That limitation is valuable context an agent could not infer. It stops short of describing the returned preview payload or whether drafts expire.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four short sentences, front-loaded with the core fact ('Draft a poll. Does not send.') and ending on the required next action. The line break is slightly awkward but every sentence earns its place.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description still conveys that a preview is produced and must be confirmed, which is the key return-value behavior. It leaves the exact preview contents and any draft lifetime unstated, but is otherwise sufficient for correct invocation.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so chat_id, options, question, account_id, and multi_select are all documented in the schema itself, including the 2-12 option bound and multi-select semantics. The description adds no parameter-level detail beyond that, so the baseline 3 is correct.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Draft a poll') and immediately disambiguates from the send path by asserting 'Does not send.' An agent can distinguish it from confirm_send and send_message without inspecting any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Prescribes the exact workflow: show the preview, wait for the user to say yes, then call confirm_send. This is an explicit when-to-use plus the named alternative, leaving nothing to inference.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
set_contact_noteNote something about a contactAIdempotent
Remember something about a person, on this machine only: "Hermi, my own agent", "the accountant", "always answers late". The note then rides along wherever the contact shows: list_chats, search_contacts, get_contact, get_recent_messages and get_unanswered. Nothing is sent to WhatsApp and the contact never sees it. An empty note removes it.
| Name | Required | Description | Default |
|---|---|---|---|
| note | Yes | What to remember, or "" to remove the note | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| contact_id | Yes | Contact id or phone number |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Beyond the annotations (which only declare it is a non-read, idempotent, non-destructive write), the description adds high-value behavior: the note is stored on this machine only, nothing is sent to WhatsApp, the contact never sees it, and it propagates to five named tools. These are exactly the traits an agent cannot infer from structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then effect, then privacy, then removal — a logical order with no filler sentences. The inline quoted examples are slightly informal in formatting but earn their place by demonstrating note format.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a 3-parameter tool with full schema coverage and no output schema, the description covers everything an agent needs: local-only scope, cross-tool visibility, privacy, and removal. Nothing material is missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so all three parameters are already documented in the schema, including the empty-string removal semantics. The description's examples of note content add mild meaning but do not go beyond what the schema provides, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource (remember/set a note about a person) and immediately differentiates itself by explaining where the note surfaces (list_chats, search_contacts, get_contact, get_recent_messages, get_unanswered). This lets an agent distinguish set_contact_note from sibling read tools without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives concrete context for use via examples ("the accountant", "always answers late") and states the removal condition for empty notes. There is no explicit when-not or named alternative tool, but no sibling competes for this function, so the guidance is clear without exclusions.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
set_profile_pictureSet the linked WhatsApp profile pictureADestructive
Set the linked WhatsApp account's own profile picture from a local path on the machine running wazap or from a public URL. Exactly one of file_path / url. JPEG, PNG or WebP only, at most 10 MB. DESTRUCTIVE and visible to every contact. Show the image and wait for a yes first. This call hits WhatsApp immediately; there is no draft.
| Name | Required | Description | Default |
|---|---|---|---|
| url | No | Public http(s) URL to fetch and use as the photo | |
| file_path | No | Absolute path of a local JPEG, PNG or WebP | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare destructiveHint=true, idempotentHint=false and openWorldHint=true, yet the description adds genuinely new context: the picture is 'visible to every contact', the call 'hits WhatsApp immediately', there is 'no draft', plus format and 10 MB size limits. That is real behavioral disclosure beyond the structured hints.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with the core action, then constraints, then the safety workflow. Every sentence earns its place; no padding.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, so the description carries return expectations on its own, and it does. Source alternation, format/size limits, visibility, immediacy and the confirmation requirement together make this callable and safe without further reference.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100% so the baseline is 3. The description adds the 'Exactly one of file_path / url' mutual-exclusivity rule, which the schema (additionalProperties:false but no oneOf) does not encode, plus format/size constraints. That is added value beyond the schema.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (set) and resource (the linked account's own profile picture), with the scope 'own' clarifying this is not editing someone else's picture. An agent can distinguish this from send_media or download_media without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear operating context: 'Show the image and wait for a yes first' and 'This call hits WhatsApp immediately; there is no draft.' It also states the source constraint. It stops short of naming an alternative tool for a different profile-photo scenario, but the when-to-use confirmation workflow is well specified.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
sync_contactsFetch the phone's address book againARead-onlyIdempotent
Ask WhatsApp to send the linked phone's address book from scratch, and wait up to 15 seconds for it. Nothing on WhatsApp changes: this only refills wazap's own contact list.
Use it when get_status reports contacts_named: 0, or when senders in a group read as phone numbers for people you know are saved on the phone. Returns named_before and named_after so you can tell whether it helped; if both are 0 the phone has no saved contacts for these people.
| Name | Required | Description | Default |
|---|---|---|---|
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnly/idempotent/non-destructive, and the description adds genuinely new behavioral facts: a bounded up-to-15-second wait, an explicit assurance that nothing on WhatsApp changes, and the names and interpretation of the return fields (named_before/named_after, with both zero meaning the phone has no saved contacts).
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Four tight sentences, front-loaded with the action and its cost (up to 15 seconds), followed by scope, trigger conditions, and return-value interpretation. No filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
With no output schema, the description compensates by explaining what is returned and how to read it, and it covers trigger conditions, latency, and side-effect scope. An agent has everything needed to call and interpret this correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Only one optional parameter exists and schema description coverage is 100%, so the schema already documents account_id fully. The description adds nothing about parameters, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Ask WhatsApp to send the linked phone's address book from scratch') and immediately scopes it ('this only refills wazap's own contact list'), which cleanly separates it from siblings like search_contacts and get_contact that read the existing list.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives two explicit triggering conditions ('when get_status reports contacts_named: 0', or 'when senders in a group read as phone numbers for people you know are saved on the phone') and names the sibling tool to check first, leaving no inference needed about when to reach for this.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
transcribe_audioTranscribe a WhatsApp voice messageARead-onlyIdempotent
Turn a voice note or an audio message into text. The transcript is cached, so a second call on the same message costs nothing, and from then on the message reads as [voice message · 0:42] "what was said" in read_messages, get_recent_messages and get_message, and its words become searchable through search_messages.
What it costs depends on how the user set transcription up: the local provider (whisper.cpp) is free and the audio never leaves the machine, while the API provider uploads the audio to a third-party service and is billed per minute. Either way this is capped at 10 calls a minute.
TRANSCRIBE_UNAVAILABLE means transcription is off or unfinished on this machine; the fix names the command the user has to run. Do not retry it.
| Name | Required | Description | Default |
|---|---|---|---|
| language | No | ISO 639-1 code of what is spoken, e.g. "ro"; "auto" detects it. Omit to use the configured default. | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| message_id | Yes | A message whose type is voice or audio |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Goes well past the annotations (readOnly/idempotent/destructive=false) by disclosing caching (a repeat call is free), the cost model (local whisper.cpp free and offline vs API provider uploading audio to a third party and billed per minute), a rate limit (10 calls/minute), and precise error semantics for TRANSCRIBE_UNAVAILABLE with the instruction not to retry. This is exactly the extra behavioral context an agent cannot get from structured fields.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three tight paragraphs: what it does and its downstream effect first, then cost/privacy/rate limits, then error handling. Every sentence carries load-bearing information (cache, cost, offline vs upload, rate cap, error fix) with no filler.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
No output schema exists, and the description compensates by showing the transcript's rendered form ('[voice message · 0:42] "what was said"') and its searchability. Combined with cost, caching, rate-limit and error coverage, an agent has everything needed to call and interpret this tool.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 100%, so language, account_id and message_id are already documented in the schema (defaults, resolution order, ISO codes, accepted message types). The description adds no parameter-specific syntax or format detail beyond that, so the baseline of 3 applies.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb (turn into text) and resource (voice note / audio message), which is a distinct operation from siblings like download_media or read_messages. An agent can immediately tell this is the speech-to-text conversion tool and not a media-download or message-reader.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Clear context for use (voice/audio messages) and it spells out the downstream effect in read_messages, get_recent_messages, get_message and search_messages, plus explicit when-not guidance: do not retry on TRANSCRIBE_UNAVAILABLE. It stops short of naming a concrete alternative tool or a prerequisite chain, so it is clear context rather than fully explicit when/when-not/alternatives.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
wait_for_messagesWait for new WhatsApp messagesARead-onlyIdempotent
Block until a message arrives, then return it, or return empty when the timeout passes. This is how an agent stays on the line without polling: call it in a loop, and pass the cursor it returns into the next call so nothing that landed between two calls is missed. The first matching message starts a one-second settle so a burst comes back together.
Only messages from other people are returned, never the user's own, and never WhatsApp's system notices. With addressed_to_me, only direct messages, group messages that @-mention the user, and replies to the user's own messages wake the wait; everything else in a group is ignored. A cursor from a previous run of wazap cannot be honoured: the wait then starts from now and says cursor_reset.
The timeout is capped at 55 seconds because MCP clients give up at 60.
| Name | Required | Description | Default |
|---|---|---|---|
| cursor | No | The cursor returned by the previous call | |
| chat_id | No | Only messages in this chat | |
| account_id | No | Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account. | |
| addressed_to_me | No | Only direct messages, @-mentions of the user and replies to the user's messages | |
| timeout_seconds | No | How long to wait (1-55 s) |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
The description adds substantial behavioral context beyond the annotations: it blocks until a message arrives, returns empty on timeout, returns only messages from other people, ignores WhatsApp system notices, supports addressed_to_me filtering, resets cursors from previous runs, and caps timeouts at 55 seconds due to MCP client limits. These are exactly the operational details an agent needs and are not covered by the readOnly/idempotent annotations.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The core purpose is stated first, followed by usage, filtering behavior, cursor rules, and timeout limits. Every sentence adds a distinct operational detail, and there is no redundant or filler content.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the lack of an output schema, the description explains what is returned (a message or empty) and how cursors work. Annotations cover safety and openness, while the description covers blocking semantics, filtering, cursor reset, and timeout constraints. Nothing essential for correct use appears missing.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Although schema description coverage is 100%, the description adds meaningful semantics beyond the schema: cursor must be passed from the previous call and cannot be honored across runs, addressed_to_me has specific wake conditions, and the timeout cap is explained by MCP client limits. This meaningfully improves correct invocation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific blocking behavior: wait until a message arrives, return it, or return empty on timeout. It distinguishes itself from polling-based siblings by explaining it is how an agent stays on the line without polling, though it does not name specific sibling tools like read_messages or get_recent_messages.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear operational guidance: call in a loop, pass the cursor from the previous call, and rely on the timeout. It explains filtering behavior and cursor reset conditions, but does not explicitly name alternative tools or state when not to use this tool versus read_messages.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
33 tool updates
v0.15.0- Added
confirm_send - Changed
create_group1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
delete_message1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
download_media1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
edit_message1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
forward_message1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
get_contact1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
get_group_info1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
get_message1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
get_recent_messages3 fields changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +} - added
Input schema / properties / compactAdded value: +{ + "default": false, + "description": "Leave out media without a caption and messages with no words in them, fold what one person sent in a row into one line, and say per chat what was left out. About half the size; use it for a routine catch-up", + "type": "boolean" +} - added
Input schema / properties / include_previewsAdded value: +{ + "default": false, + "description": "Attach a small JPEG of each photo, newest first, up to 12 per call, so you can see what was sent: the preview WhatsApp shipped when there is one, otherwise the photo is downloaded once and shrunk on the machine running wazap", + "type": "boolean" +}
- Changed
get_status2 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Added
get_stories - Added
get_unanswered - Changed
learn2 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Added
link_account - Added
list_accounts - Changed
list_chats1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
manage_chat1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
manage_group1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Added
mark_handled - Changed
react_to_message1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
read_messages2 fields changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +} - added
Input schema / properties / include_previewsAdded value: +{ + "default": false, + "description": "Attach a small JPEG of each photo, newest first, up to 12 per call, so you can see what was sent: the preview WhatsApp shipped when there is one, otherwise the photo is downloaded once and shrunk on the machine running wazap", + "type": "boolean" +}
- Changed
search_contacts1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
search_messages4 fields changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +} - added
Input schema / properties / fromAdded value: +{ + "description": "Only messages this person sent: \"me\", a contact id or a phone number", + "minLength": 1, + "type": "string" +} - added
Input schema / properties / sinceAdded value: +{ + "description": "Only messages from this moment on: a date (\"2026-09-01\") or an ISO timestamp", + "minLength": 4, + "type": "string" +} - added
Input schema / properties / untilAdded value: +{ + "description": "Only messages up to this moment: a date or an ISO timestamp", + "minLength": 4, + "type": "string" +}
- Changed
send_location1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
send_media2 fields changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +} - added
Input schema / properties / as_gifAdded value: +{ + "default": false, + "description": "Send a .gif or an mp4 as a looping GIF, the way WhatsApp plays them", + "type": "boolean" +}
- Changed
send_message1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
send_poll1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Added
set_contact_note - Added
set_profile_picture - Changed
sync_contacts2 fields changed- added
Input schema / additionalPropertiesAdded value: +false - added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Changed
transcribe_audio1 field changed- added
Input schema / properties / account_idAdded value: +{ + "description": "Registry account id (default, work, …). Omit to resolve from chat_id or message_id, or the default account.", + "minLength": 1, + "type": "string" +}
- Added
wait_for_messages
4 tool updates
v0.10.0- Changed
get_recent_messages2 fields changed- added
Input schema / properties / include_systemAdded value: +{ + "default": false, + "description": "Include WhatsApp's own system notices, which are excluded from the bodies and the counts by default", + "type": "boolean" +} - added
Input schema / properties / typesAdded value: +{ + "description": "Keep only these message types; omit for every type. The limit counts matching messages, so [\"call\"] returns that many calls, not that many messages of which some are calls.", + "items": { + "enum": [ + "text", + "image", + "video", + "audio", + "voice", + "document", + "sticker", + "location", + "contact", + "poll", + "reaction", + "deleted", + "view_once", + "call", + "system", + "unknown" + ], + "type": "string" + }, + "type": "array" +}
- Changed
read_messages1 field changed- added
Input schema / properties / typesAdded value: +{ + "description": "Keep only these message types; omit for every type. The limit counts matching messages, so [\"call\"] returns that many calls, not that many messages of which some are calls.", + "items": { + "enum": [ + "text", + "image", + "video", + "audio", + "voice", + "document", + "sticker", + "location", + "contact", + "poll", + "reaction", + "deleted", + "view_once", + "call", + "system", + "unknown" + ], + "type": "string" + }, + "type": "array" +}
- Added
sync_contacts - Added
transcribe_audio
22 tool updates
v0.9.3- First observed
create_group - First observed
delete_message - First observed
download_media - First observed
edit_message - First observed
forward_message - First observed
get_contact - First observed
get_group_info - First observed
get_message - First observed
get_recent_messages - First observed
get_status - First observed
learn - First observed
list_chats - First observed
manage_chat - First observed
manage_group - First observed
react_to_message - First observed
read_messages - First observed
search_contacts - First observed
search_messages - First observed
send_location - First observed
send_media - First observed
send_message - First observed
send_poll
TDQS
Scored across 33 tools
Most tools have clearly distinct purposes, but the set includes several message-reading and account-related tools (read_messages, get_recent_messages, get_unanswered, wait_for_messages; get_status, list_accounts) that could cause hesitation despite descriptive guidance. The draft/send pattern is well differentiated.
Nearly all tools use snake_case with a verb-first pattern, but a few names (learn, get_unanswered, mark_handled, wait_for_messages, confirm_send) deviate from strict verb_noun structure. Still predictable overall.
33 tools is well above the typical 3–15 range for a focused server, and many granular operations (five send_* drafts, multiple get_* variants, manage_group bundling many actions) could be consolidated. This increases selection overhead and cognitive load.
The surface covers core messaging, contacts, groups, media, account linking, and chat management with CRUD-like operations, leaving few dead ends. Minor gaps exist (e.g., no unlink/logout, no block/unblock contacts, no WhatsApp-side contact editing), but agents can work around most.
Maintenance
Related MCP Connectors
Drive WhatsApp from any MCP client: pair devices, send text and media, manage contacts and groups.
MCP server connecting AI agents to 100+ apps (Gmail, Slack, Notion, GitHub) via one-click OAuth.
- ZapierOAuthcom.zapier
Hosted MCP server connecting AI assistants to 9,000+ apps and 40,000+ actions via Zapier.
MCP server for building and testing AI agents with multi-model experimentation and insights.
Related MCP Servers
- FlicenseNot gradedqualityCmaintenanceWhatsApp MCP server that exposes messaging, groups, contacts, and profile management as tools and resources for AI agents, supporting Baileys and Meta Cloud API.22-
- AlicenseNot gradedqualityAmaintenanceA self-hosted WhatsApp bridge that exposes a stdio MCP server with ~20 tools for reading conversations, sending messages, managing groups, contacts, and aliases, enabling AI agents to operate WhatsApp directly.2MIT
- AlicenseNot gradedqualityCmaintenanceMCP server that connects AI agents to WhatsApp using the multi-device API, enabling messaging, group management, and more as a regular user.9 npmMIT
- AlicenseNot gradedqualityAmaintenanceA native MCP server for SocialMate that gives your AI a WhatsApp, enabling it to send and read messages, manage contacts and groups, and more through 44 tools.11 npm1MIT