AI agents
MCP server
Kawaa speaks the Model Context Protocol, so an AI agent can verify addresses, watch a bulk job and read the results without anyone writing an integration. One URL: sign in, or use an API key.
Connect
The endpoint is https://api.kawaa.com/mcp. Authenticate with the same API key you would use for the REST API, as an HTTP bearer token. Create one in Settings → API keys.
It uses the current stable MCP protocol (2026-07-28) over Streamable HTTP, with compatibility for deployed 2024/2025 clients. There is no vendor-specific server: every tab below reaches the same endpoint and the same tool schemas.
export KAWAA_API_KEY=ev_your_key_here
claude mcp add --transport http kawaa https://api.kawaa.com/mcp \
--header "Authorization: Bearer $KAWAA_API_KEY"
# Check it connected
claude mcp listexport KAWAA_API_KEY=ev_your_key_here
codex mcp add kawaa \
--url https://api.kawaa.com/mcp \
--bearer-token-env-var KAWAA_API_KEY
# Check it connected
codex mcp list// ~/.cursor/mcp.json — prefer this over .cursor/mcp.json inside a
// repository, which puts the key somewhere a commit can publish it.
// Cursor interpolates ${env:...}, so the key stays in your environment.
{
"mcpServers": {
"kawaa": {
"url": "https://api.kawaa.com/mcp",
"headers": {
"Authorization": "Bearer ${env:KAWAA_API_KEY}"
}
}
}
}
// Cursor will not load a newly configured server until you approve it,
// and the approval is per project. Run this once in each:
// cursor-agent mcp enable kawaaexport KAWAA_API_KEY=ev_your_key_here
copilot mcp add --transport http \
--header "Authorization: Bearer $KAWAA_API_KEY" \
kawaa https://api.kawaa.com/mcp
# Check the saved server
copilot mcp get kawaaexport KAWAA_API_KEY=ev_your_key_here
grok mcp add --transport http kawaa https://api.kawaa.com/mcp \
--header "Authorization: Bearer $KAWAA_API_KEY"
# Check it connected
grok mcp doctor// ~/.gemini/config/mcp_config.json
{
"mcpServers": {
"kawaa": {
"serverUrl": "https://api.kawaa.com/mcp",
"headers": {
"Authorization": "Bearer ev_your_key_here"
}
}
}
}Or sign in instead of pasting a key
A client that supports MCP’s OAuth sign-in needs no key at all. Add the server with just the URL; the client finds Kawaa’s sign-in from the endpoint, opens a Kawaa page where you approve it, and receives a short-lived token it refreshes on its own. We have run this end to end with Claude Code and Codex:
claude mcp add --transport http kawaa https://api.kawaa.com/mcp
# Opens Kawaa's consent screen in your browser; approve, and you're connected
claude mcp login kawaacodex mcp add kawaa --url https://api.kawaa.com/mcp
# Opens Kawaa's consent screen in your browser; approve, and you're connected
codex mcp login kawaa- You see who is asking and for what. The approval page names the app — and says whether a domain vouches for that name or the app chose it itself — lists each permission it wants, and lets you untick any of them. By default an agent asks to verify, follow its jobs, read results and see usage.
- Some things are never on offer. A connected app can never change your plan or payment details, manage your team, change your password or keys, or delete the account, whatever it asks for.
- You can end it at any time. Settings → AI agents lists each connected app with what it may do and when it was last used. Disconnecting stops it on its next call. A connection left unused for 30 days ends on its own.
Using a key? Give the agent one of its own
Create a separate key for each agent rather than reusing the one your application holds. Two things follow from that, and both matter the first time you want to know what the agent has been doing:
- You can see its activity on its own. The dashboard’s analytics page reports verifications, API calls and last-used per key, so you can tell whether an agent is busy and when it last ran. Credit spend is not broken down that way — the ledger is per account.
- You can cut it off on its own. Revoking one key from Settings → API keys stops the agent on its next call and leaves your other integrations untouched.
Treat the key the way you treat a password: prefer an environment variable over a value pasted into a config file that might end up in a repository.
What the agent can do
Eleven tools, designed around what someone actually asks an agent to do rather than around the shape of the API. Seven of them read and four verify.
Five carry the protocol’s readOnlyHint, which is what a client uses to decide what may run without asking you. Two of the reads deliberately do not, because that hint is about consequence rather than price: get_email_history spends 0.5 credits, and get_job_export_link is free but writes the whole result set to storage behind an hour-long link that anyone holding it can read. Neither changes a record; both deserve the same pause as spending money.
Reading
| Tool | Cost | What it does |
|---|---|---|
get_account | Free | Plan, credit balance, usage against the current allowance, rate limit. |
get_usage | Free | Credits, API calls, verifications and bulk jobs against the current allowance. Read `credits.allowance_basis` before calling it monthly — a Free plan and a white-label sub-account never reset, so their figures are cumulative. |
list_jobs | Free | Your verification jobs, newest first. |
get_job | Free | One job's progress and verdict counts. |
get_job_results | Free | A job's per-address verdicts, a page at a time, filterable by status. |
get_job_export_link | Free | A time-limited link to a large job’s results, rather than fifty pages of them. Says whether the file is the complete set. |
get_email_history | 0.5 credits | Engagement history for an address, where the data has been shared. |
Verifying
These spend credits. Each one says its price in its own description, so the agent knows before it calls.
| Tool | Cost | What it does |
|---|---|---|
verify_email | 1 credit (0.5 cached) | Verify one address and return the verdict. |
verify_emails | ~1 per address | Submit a list as a job — how many at once depends on your plan. |
find_business_email | 5 credits | Work out a work address from a name and a company domain. |
check_domain_setup | 10 credits | SPF, DKIM, DMARC, MX and blocklist check for a domain. |
Nothing here can change your account
There is no tool to change a plan, buy credits, invite a teammate, rotate a key or delete anything. Those stay in the REST API, for a person with the right credential.
Try it
Once the server is connected, ask in plain language. The agent picks the tools.
Check how many Kawaa credits I have left.
Verify sales@example.com and tell me whether it is safe to send to.
Clean the list in subscribers.csv: verify every address, then tell me
which ones to remove and which need a human decision.
Why is mail from example.com landing in spam?The last two are also built-in prompts — clean_email_list and diagnose_deliverability — which walk the agent through the whole workflow, including budgeting, paging the results, and waiting for a bulk job properly instead of resubmitting it.
A prompt has to be invoked, not described
Typing the sentence does not load the prompt. In Claude Code the server’s prompts appear to you as slash commands under /mcp, not to the model as something it can choose — so asking in plain language gets you the tools and the server’s connection-time instructions, which is usually enough, but not the step-by-step the prompt carries. Run /mcp and pick clean_email_list when you want that.
Clients differ here, and it is a client behaviour, not a server one: check how your client lists an MCP server’s prompts before relying on one.
You do not need to prompt-engineer this
The server tells the agent, at connection time, that verification costs credits, which tools are free, and that catch_all and unknown are not verdicts to act on mechanically. Each tool states its own price and its own arguments. There is nothing to paste into a system prompt, and nothing that will quietly go stale in one.
The one line worth adding to your repository’s AGENTS.md or CLAUDE.md is the fact that it exists, so an agent looks for it:
Email verification, list hygiene and deliverability checks are available
through the `kawaa` MCP server. Use it rather than writing HTTP calls.Give the key only what the agent needs
A key can be created with a list of scopes, and a scoped key is refused — with a 403 naming the scope it needed — for anything outside them. An agent doing list hygiene has no reason to reach billing, the team or the audit log, so do not hand it a key that can.
curl -X POST https://api.kawaa.com/v1/api-keys -H "Authorization: Bearer $KAWAA_API_KEY" -H "Content-Type: application/json" -d '{
"name": "Claude Code agent",
"scopes": ["verification:write", "verification:read", "account:read", "analytics:read"]
}'That list is what GET /v1/api-keys publishes as recommended_agent_scopes, and it covers every tool except check_domain_setup — add deliverability:write if the agent should run domain checks too. Omit scopes entirely and the key is unrestricted. In the dashboard, the key form’s AI agent choice creates this same key, and Full access creates an unrestricted one.
Scopes are chosen when the key is created
They cannot be changed afterwards. To narrow an agent’s access, create a new key with the scopes you want and revoke the old one — revocation takes effect on the agent’s next call.
Keeping a lid on what it spends
An agent can call an API thousands of times faster than a person can. Three things keep that from turning into a surprise on your bill.
A ceiling you set
verify_emails takes a max_credits argument. If the list would cost more, nothing is submitted and nothing is charged.
A prompt retry is free
Four of the five charging tools send an idempotency key and hand it back, so a retry that reuses it returns the original result rather than buying it again — for as long as the receipt lasts, which is a day for get_email_history, a month for check_domain_setup and at least 30 days for the two verification tools. With one caveat worth knowing: when a timeout loses the response it loses the key with it, and the key the server derives instead is bucketed by a fixed ten-minute window — so a retry that crosses the boundary gets a different key and can charge again. Retry promptly, or pass your own key. find_business_email is the exception: that route takes no key at all, so its tool says plainly not to retry blind.
Mistakes cost nothing
A malformed address, an oversized batch or a list over your ceiling is refused before the request is sent — not after it is paid for.
Your plan’s rate limits apply to MCP exactly as they do to the REST API, because every tool call is a REST call made with your key. The protocol chatter around them — connecting, listing the tools, reading the reference documents — is answered inside the server without reaching the API, so it neither counts against your quota nor costs anything.
Reading the results properly
The server publishes a resource, kawaa://reference/verification-statuses, that explains what each verdict means — in particular the three that are easiest to misread.
- catch_all means the domain accepts mail for every recipient, so nothing could be learned about that particular mailbox. It is not a yes and not a no.
- unknown means the check could not conclude — often something temporary worth re-checking later.
- risky covers several unrelated situations, and
sub_statussays which.
Agents read that resource before interpreting results, and the built-in prompts tell them to hand these cases to you rather than deciding silently. The full status reference says the same thing for humans.
When something goes wrong
A failed tool call comes back structured, so the agent knows whether to retry, to wait, to fix its arguments, or to ask you for something.
{
"error": {
"kind": "payment_required",
"code": "INSUFFICIENT_CREDITS",
"message": "Insufficient credits. Required: 500, Available: 12.",
"retryable": false,
"next_step": "Top up at https://app.kawaa.com/settings/billing, then retry. Nothing was charged for this call.",
"request_id": "b2c1…"
}
}That is the tool result’s structuredContent, so the discriminator is at structuredContent.error.kind rather than at the top level. The result’s one text block carries it too, for clients that show their model only text: the human-readable message and remedy first, then this object as JSON. Successful results are shaped the same way — a one-line summary, then the full result as JSON.
kind is the thing to branch on, and code is the same error code the REST API returns, so an MCP failure and an API log entry read the same.
What it does not do
- No streaming. Responses are JSON. There is no server-sent-events stream and no server-initiated messages, because nothing in the tool set needs them.
- No sessions. The server is stateless; every request stands on its own.
- Browser-based MCP clients may be blocked. The endpoint’s CORS policy, and that of the sign-in endpoints, allows Kawaa’s own origins. Command-line and editor clients, and clients that connect from a server, are unaffected.
Everything the agent can reach through MCP is also available through the REST API, and anything MCP does not cover is there.