The usual way an agent integrates an API is a curl loop, and Validate Email Addresses From an AI Agent walks that path. But if your host already speaks Model Context Protocol — Claude Desktop, an MCP-enabled IDE, an agent framework with a tool client — there is nothing to script. nobounce exposes the same surface as MCP tools at a single endpoint: POST /mcp, JSON-RPC 2.0 over streamable HTTP. No server to install, no configuration file to maintain, no transport to run locally.
This is the whole loop over MCP: discover, evaluate without credentials, get a key, check, branch on the verdict.
Discover the tools
curl -X POST https://nobounce.dev/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
Eight tools come back:
| tool | what it does | key? |
|---|---|---|
get_config |
verdict and reason enums, tier limits, full fixture list | no |
demo_check |
validate one address against the frozen fixture corpus | no |
redeem_access_code |
exchange an operator-minted access code for a live key | no |
hash_email |
SHA-256 an address in memory, for feedback | no |
check_email |
validate one address against live DNS | yes |
check_email_batch |
validate up to 1,000 addresses in one call | yes |
get_usage |
tier, checks used, remaining quota | yes |
report_feedback |
report bounced / delivered / opened, by hash only | yes |
Each tool's inputSchema is in the tools/list result, so an MCP host can present the tools without any hand-written glue.
Evaluate with no credentials
demo_check needs no key and no account. Call it as a tool:
curl -X POST https://nobounce.dev/mcp \
-H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"demo_check","arguments":{"email":"user@gmai.com"}}}'
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{
"type": "text",
"text": "{\n \"status\": 200,\n \"body\": {\n \"verdict\": \"undeliverable\",\n \"reason\": \"typosquat_mx\",\n \"suggestion\": \"user@gmail.com\",\n \"confidence\": 0.94,\n \"checked\": { \"syntax\": true, \"mx\": true, \"typo\": true, \"disposable\": true },\n \"cached\": false\n }\n}"
}
]
}
}
Two things to note in that shape. The tool result wraps the underlying HTTP response as {status, body} inside a text content block, so parse the inner text and branch on status — a JSON-RPC result can still carry an HTTP 401 inside it. And demo_check resolves the frozen fixture corpus only, so it is an evaluation surface, not a production validator. It cannot check arbitrary live addresses; get_config lists every address it knows.
Get a key
There is no free tier. Live DNS checks require a paid key, from $1/mo — hobby 1,000 checks, pro $19/100,000, scale $99/1.5M. The agent-friendly path is an operator-minted access code, and redemption is itself a tool call:
{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"redeem_access_code","arguments":{"coupon":"NB-YOUR-CODE"}}}
One call, no browser, no captcha, no email. The key is returned exactly once — store it immediately. The self-serve card and stablecoin rails also exist, via POST /v1/keys over plain HTTP, and land in the same entitlement.
Here is the difference from the raw HTTP API: on /v1/check the key travels in the Authorization header and never in a URL. Over MCP there is no header juggling for the model to get wrong — the key is a declared argument of check_email, check_email_batch, get_usage and report_feedback, right in the tool schema. The host asks the model to fill api_key, and the schema enforces that it is present.
The verdict handling is still your job
MCP changes the transport, not the contract. The body of check_email is the same frozen verdict object the REST API returns, and the mapping is the same:
undeliverablewith asuggestion— this is not an error. The domain the user typed is not the domain they meant, and the suggestion names the correction. Offer it with a one-click accept and a real "keep what I typed" option; the mechanics are in Suggest the Correction Instead of Rejecting the Signup.undeliverablewithout a suggestion — reject, withreasonnaming the cause (domain_not_found,null_mx,syntax_invalid, …).risky— disposable domain or role account such asadmin@. Proceed; blocking is your product decision, made by branching onreason.unknown— the check did not complete.checked.mx: falseis the fail-open signal: record it and proceed, never reject. An agent that treats a validator outage as a bad address will confidently discard good signups — the reasoning is in Fail Open: Email Validation Must Never Block a Signup.
Quota, batches and feedback
Before a bulk run, call get_usage and compare remaining quota against the list size — the batch endpoint refuses the whole call with 429 rather than half-processing it. check_email_batch takes up to 1,000 addresses and keys each result by email_sha256, so logs stay free of plaintext addresses; the chunking loop and hash join are in How to Batch-Validate a List of Email Addresses. Cache hits still count against the monthly quota.
When you later learn an address bounced, report it: hash_email computes the digest (in memory — no plaintext address is ever persisted; anything stored is SHA-256, domains in clear), then report_feedback takes the hash with the event. Sending a raw address to that tool fails.
When to use MCP, and when not to
Use MCP when the caller is an MCP client: an assistant wiring validation into a repo, an agent evaluating the API, a chat-driven ops loop. Use raw HTTP for your signup server — the language how-tos cover that path — because a server-side handler wants a bounded timeout and its own secret management, not a tool call. Both surfaces return the identical verdict object, so the branching logic you write once carries over.
For the ordered, end-to-end integration procedure, hand your agent /integrate.md — it is written to be fetched and followed.