Docs / Start
MCP server
The wuapi MCP server gives an AI client that speaks the Model Context Protocol, such as Claude, Cursor or VS Code, 87 tools over your wuapi account: send a message, show the QR code that links a number, read a conversation, add people to a group, set up a webhook, invite a customer. Each tool call is one request to the REST API with your API key, so the key's scope, the limit of 600 requests per minute per key and the dashboard Logs apply as for any other client.
| local (stdio) | hosted (Streamable HTTP) | |
|---|---|---|
| runs | on your machine: npx -y @wuapidev/mcp | at https://wuapi.dev/api/mcp |
| key | WUAPI_API_KEY environment variable | Authorization: Bearer wu_live_... header, or OAuth |
| needs | Node 20 or later | a client that sends a custom header or speaks MCP's OAuth |
Connect a client
Create a key under API keys in the dashboard. A project key limits the server to one project. Then add the server to your client:
# Local: runs on your machine
claude mcp add wuapi --env WUAPI_API_KEY=wu_live_... -- npx -y @wuapidev/mcp
# Hosted: nothing to install
claude mcp add --transport http wuapi https://wuapi.dev/api/mcp --header "Authorization: Bearer $WUAPI_API_KEY"The dashboard's MCP server screen (/app/mcp) builds these for you from one of your keys, with read-only and a project as options, one-click install links for Cursor and VS Code and a connection test. Claude Desktop reads claude_desktop_config.json (Settings, Developer, Edit Config). Cursor reads ~/.cursor/mcp.json, or .cursor/mcp.json in a project. VS Code reads .vscode/mcp.json and asks for the key once, so it stays out of the file. Any other client that starts a local command works with the stdio server and WUAPI_API_KEY in its environment; any client that sends a custom header works with the hosted one. Clients that reach remote servers only through OAuth, as Grok, ChatGPT and Claude.ai do, use the next section.
Connect with OAuth
Chat apps that add remote MCP servers as connectors sign in with OAuth instead of a header. Give them the hosted URL, https://wuapi.dev/api/mcp. Most find the rest on their own: the server's 401 points to its metadata, the app registers itself and opens a wuapi page in your browser. Sign in, pick the organization (all of it or one project) and approve. The app gets an API key named MCP · <app>, listed on API keys. Revoke it there to disconnect the app.
If the app asks for the OAuth settings itself, as Grok's custom connector does, fill them in like this:
| field | value |
|---|---|
| Client ID | wuapi-mcp |
| Client secret | leave it empty |
| Authorization endpoint | https://api.wuapi.dev/oauth/authorize |
| Token endpoint | https://api.wuapi.dev/oauth/token |
| Scopes | wuapi |
| Token auth method | none (PKCE only) |
The consent page shows where the approval goes (the app's address). Approve only a connection you just started. An OAuth key has the full access of an organization or project key: read-only mode is not available through OAuth yet, so use a header client with Wuapi-Read-Only if you need it. The key does not expire. If you revoke it, the app asks you to connect again.
Configuration
| stdio variable | hosted header | does |
|---|---|---|
| WUAPI_API_KEY | Authorization: Bearer ... | Required. Your API key. |
| WUAPI_PROJECT | Wuapi-Project | Act inside one project: its id or ext:<externalId>. |
| WUAPI_MCP_READ_ONLY | Wuapi-Read-Only | true registers only the tools that read. The --read-only flag does the same. |
| WUAPI_BASE_URL | API base URL. Default https://api.wuapi.dev. Must be https, or http on localhost. |
Tools
| area | tools |
|---|---|
| Context | get_current_key |
| Accounts | list_accounts, get_account, get_account_qr_code, create_account, request_pairing_code, reconnect_account, list_proxy_locations, update_account, unlink_account, set_presence, set_disappearing_timer, reject_call |
| Messages | send_text, send_media, upload_file, send_location, send_contact, send_poll, send_buttons, reply_to_message, react_to_message, get_message, list_messages, edit_message, delete_message, cancel_message, vote_in_poll, forward_message, star_message |
| Chats | list_chats, get_chat, mark_chat_read, send_read_receipts, archive_chat, pin_chat, mute_chat, delete_chat, manage_labels |
| Contacts | list_contacts, get_contact, check_numbers, lookup_contacts, save_contacts, lookup_whatsapp_info, manage_block_list |
| Profile | manage_profile, manage_favorite_stickers, manage_privacy |
| Groups | list_groups, get_group, create_group, add_group_participants, remove_group_participants, promote_group_participants, demote_group_participants, get_group_invite_link, reset_group_invite_link, leave_group, manage_group_settings, manage_group_joins, manage_community |
| Channels | manage_channel |
| Stories | post_story, manage_stories |
| Webhooks | list_webhooks, get_webhook, create_webhook, update_webhook, delete_webhook |
| Incoming webhooks | list_incoming_webhooks, get_incoming_webhook, create_incoming_webhook, update_incoming_webhook, delete_incoming_webhook |
| Projects | list_projects, get_project, create_project, manage_project |
| Invitations | create_invitation, list_invitations, get_invitation, cancel_invitation, resend_invitation, manage_branding |
| Usage | get_usage, get_usage_by_project |
Every tool declares a JSON schema for its input and returns the API's own object as structured content, without nulls. Together they reach every operation of the REST API except the two that return a secret. Tools that read carry readOnlyHint. Tools that delete, revoke, reset, block or leave carry destructiveHint and take confirm: true, which the model has to set on purpose and your client shows you before the call: delete_message, cancel_message, delete_chat, remove_group_participants, reset_group_invite_link, leave_group, delete_webhook, delete_incoming_webhook, cancel_invitation, resend_invitation. Sends take an optional idempotencyKey, so a retried call does not send twice. get_account_qr_code returns the QR code as an image the client can show.
Resources with many small operations are one tool with an action argument, so the list stays short enough for a model to pick from. The tool's description lists its actions and each field says which actions use it. Only the destructive actions need confirm: true: unlink_account (logout, delete), manage_labels (delete), manage_block_list (block), manage_profile (delete_picture, reset_contact_link), manage_group_settings (delete_picture), manage_stories (delete), manage_project (delete, revoke_key). In read-only mode a tool that mixes reads and writes keeps only its reading actions. To post to a channel, use send_text or send_media with to set to the channel id.
| tool | actions |
|---|---|
| `unlink_account` | logout, delete |
| `set_presence` | online, offline, typing, recording, paused, subscribe |
| `manage_labels` | upsert, delete, label_chat, unlabel_chat, label_message, unlabel_message |
| `save_contacts` | one, many, backfill_auto_save |
| `lookup_whatsapp_info` | contact_picture, business_profile, resolve_link, bots, sticker_pack, order |
| `manage_block_list` | list, block, unblock |
| `manage_profile` | update, set_picture, delete_picture, get_contact_link, reset_contact_link |
| `manage_favorite_stickers` | list, get_file, add, remove |
| `manage_privacy` | get, get_story_privacy, update |
| `manage_group_settings` | update, set_picture, delete_picture |
| `manage_group_joins` | preview_invite, join, list_requests, approve_requests, reject_requests |
| `manage_community` | create, list_groups, list_members, link_group, unlink_group |
| `manage_channel` | list, get, preview_invite, list_messages, create, follow, unfollow, mute, unmute, react, mark_viewed |
| `manage_stories` | list, list_own, get, viewers, view, react, reply, delete |
| `manage_project` | update, delete, list_keys, revoke_key |
| `manage_branding` | get, update |
Projects, invitations, branding and usage need an organization key. Creating a project API key and rotating a webhook endpoint's signing secret are not tools: both return a secret exactly once, and tool results never carry secrets. Do them in the dashboard or with the SDK. Sending a test webhook event and reading the request log have no public endpoint, so they are not tools either; they are in the dashboard.
Resources and prompts
Resources a client can attach to the conversation: https://wuapi.dev/openapi.json, https://wuapi.dev/llms-full.txt, https://wuapi.dev/llms.txt and wuapi://webhook-events. Prompts, which Claude Code lists as slash commands: send_message (to, message), setup_webhook (url, events) invite_customer (customer, externalId, email) and use_streams (project, language).
Examples
| you ask | the tools it calls |
|---|---|
| Send "Your order shipped" to +584241112233 from the Front desk number. | list_accounts, check_numbers, send_text, get_message |
| Link a new number that exits from Mexico City. | list_proxy_locations, create_account, get_account_qr_code |
| What did +584241112233 write today? Reply that we're on it. | list_messages, reply_to_message |
| Send my webhook the incoming messages at https://example.com/hooks/wuapi. | list_webhooks, create_webhook |
| Invite Northwind (customer_8812) to link their WhatsApp. | list_projects, create_project, create_invitation |
| How much did each customer use last month? | get_usage_by_project |
| Follow the channel at https://whatsapp.com/channel/0029Va... and mute it. | manage_channel (preview_invite, follow, mute) |
Security
- The key stays in your client's configuration or header. The server never logs it, never returns it and never puts it in an error. The hosted endpoint stores nothing: it checks the key with
GET /v1/mewhen a client connects, then passes it to the API on each call. - Tool results carry no secrets. A webhook endpoint's signing secret is returned by the API only when the endpoint is created;
create_webhookdrops it and tells you to reveal it in the dashboard. No tool creates API keys or rotates a signing secret. - Narrow what the model can do: a project key reaches one project, and read-only mode removes every tool that writes.
- Media URLs are downloaded by the API, which refuses private and internal addresses; the MCP server fetches nothing on a tool's behalf.
- Every argument is checked against the tool's schema before any request. A base URL that is not https is refused, so the key never travels in clear text.
- A model can be steered by what it reads, including inbound messages. Keep your client's confirmation prompts on for tools that send or delete.
The source is the npm package @wuapidev/mcp (MIT), built on the TypeScript SDK. It also exports createWuapiMcpServer and a stateless HTTP handler, createWuapiMcpHttpHandler from @wuapidev/mcp/http, to host it yourself.
The tools call the REST API: every endpoint, error code and webhook event is in the docs and openapi.json.