Give your agent a phone number.
Cosend's MCP server exposes your WhatsApp number as tools any MCP-capable agent can call — read the inbox, answer carefully, and author, deploy and repair automations without clicking through a flow builder.
Connecting a client
A remote server at mcp.cosend.app, speaking MCP revision
2026-07-28 over Streamable HTTP. It is stateless: no session
header, no handshake, any request to any instance.
{
"mcpServers": {
"cosend": {
"url": "https://mcp.cosend.app/mcp"
}
}
} There is no key to paste. The server is an OAuth 2.1 resource server — it issues no tokens of its own — so your client runs the authorization flow the first time it connects, and you approve the scopes on a Cosend screen. The token's claims resolve exactly one organization, and every read and write underneath runs under row-level security bound to it.
The MCP server is not on the Free tier. An organization on Free that
reaches it gets a 403 with the upgrade URL, before any tool
resolves. Everything else on this page is on every paid plan — see
pricing.
The nineteen tools
tools/list returns them in exactly this order, always, filtered
by the scopes you granted and by your plan. The list is generated from the same contracts
package the API validates against, so a tool cannot exist here and not on
the API.
| Tool | Class | What it does |
|---|---|---|
| whoami | RO | Organization, plan, connected numbers and granted scopes. The right first call in any conversation |
| list_conversations | RO | Recent threads with unread counts |
| search_conversations | RO | Find a thread by its content |
| read_conversation | RO | Message history for one thread |
| send_message | MUT · CONFIRM | One message, into one conversation that already exists. 20 a minute, 200 a day, one per recipient per 30 seconds |
| list_contacts | RO | The contact list, filtered and paged |
| upsert_contact | MUT | Create or update one contact |
| set_contact_consent | MUT | Record an opt-in or an opt-out |
| list_templates | RO | Templates with their review status |
| create_template | MUT | Draft a template |
| submit_template | MUT · CONFIRM | Send one to Meta for review. Ten a day |
| list_automations | RO | What is deployed, and which version is live |
| validate_automation | RO | The compiler’s full report on a draft — resolved actions, derived budget, and every error with a pointer into the source and a machine-actionable fix The one to call first on a draft |
| upsert_automation | MUT · CONFIRM | Write a new automation, or a new version of one |
| deploy_automation | MUT · CONFIRM | Push a version live |
| set_automation_state | MUT · CONFIRM | Enable or disable. Enabling needs the confirmation; disabling does not |
| list_runs | RO | Runs, filtered by automation and state |
| get_run | RO | One run and its per-step event trace |
| get_usage | RO | Meters, what is included, and what is left |
CONFIRM means the tool cannot complete on the agent's say-so: it returns asking for a human decision, and the client has to come back with one. That is a protocol-level round-trip, not a prompt asking the model to be careful.
validate_automation is the tool worth building a pipeline
around. It returns the compiler's critique of a draft before anything is written —
every error with a pointer into the source and a fix an agent can apply — which is what
makes an AI-authored automation safe enough to deploy, instead of a runtime failure at
3am.
Why there is no bulk send, and will not be
This server can put text on a stranger's phone. The design assumption is not that the agent is well behaved; it is that the agent is capable, eager, and occasionally under somebody else's influence.
The whole risk is one sentence: inbound content becoming outbound action. Someone messages the business with text shaped like an instruction, an agent reads the thread, treats it as one, and messages the contact list.
The mitigation is architectural rather than a prompt. send_message takes exactly one conversation, and that
conversation must already exist. There is no array of recipients, no send-to-tag, no
send-to-all, and no way to create a conversation with a number. An agent that wants to
reach five hundred people has to call it five hundred times — and the limiter stops it
around twenty while the audit alarm fires.
Reaching an audience deliberately is a different thing and lives somewhere else: an automation whose audience is a saved, human-approved segment, enabled by a person clicking approve in the dashboard.
Everything a good client is told about a tool — read-only, destructive, idempotent — is a hint, and the protocol says so. We publish them honestly and enforce the same policy server-side regardless. Rate limits, plan gates and scope checks apply to an agent exactly as they apply to a key.
Message bodies are data, not instructions
Enforced structurally rather than by asking politely. Every string that originated outside Cosend comes back wrapped and marked, with the marker in the output schema so a client can render it differently:
{ "text": { "untrusted": true,
"value": "⟦EXTERNAL⟧ Hi, is 3pm still ok? ⟦/EXTERNAL⟧" } } The sentinels are stripped from the inbound text first, so a sender cannot close the fence and escape into trusted context. Text is Unicode-normalised, bidi-override and zero-width characters are removed, and a single body is capped before it reaches a model's context. A classifier flags imperative-to-assistant patterns and marks the message rather than hiding it, because a suppressed message is a support ticket and a flagged one is a decision.