AI agents & MCP

Email inbox for Claude Code, Cursor and Windsurf via MCP

Every coding agent stalls at the same sentence: “check your email for the code”. The fix is one MCP server, one URL, no key and no account — and the configuration is different in every client. Here it is for Claude Code, Claude Desktop, Cursor, Windsurf, VS Code, Codex CLI and Gemini CLI, with the four tool calls that carry a sign-up through and the paragraph to put in the agent’s own instructions.

  • Beginner
  • 14 min read
An open grey laptop with a blue envelope plugged into its side like a USB key, and a blue plug resting in a grey socket in front

One server, seven clients

The server is a single HTTPS endpoint that speaks the Model Context Protocol over Streamable HTTP: one POST carrying JSON-RPC, one JSON reply, no stream held open. There is nothing to install, nothing to run locally and nothing to sign up for on the public domains — the URL is the entire configuration:

MCP endpointhttps://grabmail.io/mcp

Every MCP client takes a remote HTTP server, but each keeps its configuration in a different file with a slightly different key name. The sections below give the exact line for each. The formats are the ones current in September 2026; each client’s own documentation is the authority if one has moved since.

Check it answers, from a shell

Before touching any client, prove the server is there and see what it offers. Because the transport is plain HTTP, one curl is enough:

list the tools
$ curl -s -X POST https://grabmail.io/mcp -H "Content-Type: application/json" -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}' | jq '.result.tools[].name'
what comes back
"create_inbox"
"list_domains"
"list_messages"
"read_message"
"wait_for_message"
"delete_message"

If that works, every client below will work too, and a client that then fails is a configuration problem in the client rather than a server problem. If it does not, check that your network allows outbound HTTPS to grabmail.io — that is the whole of its footprint.

Claude Code

One command, from any directory. It registers the server for your user, so it is available in every project:

shell
$ claude mcp add --transport http grabmail https://grabmail.io/mcp

To share it with a team through the repository instead, scope it to the project. That writes a .mcp.json at the root, which is checked in and which teammates get prompted to approve:

shell — project scope
$ claude mcp add --transport http --scope project grabmail https://grabmail.io/mcp
.mcp.json — what the project scope writes
{
  "mcpServers": {
    "grabmail": {
      "type": "http",
      "url": "https://grabmail.io/mcp"
    }
  }
}

Restart Claude Code, run /mcp, and grabmail is listed with its six tools. The server also answers the protocol’s initialize call with a short paragraph of instructions, which Claude Code feeds to the model — so the agent arrives already knowing to hand out the alias and wait on the address.

Claude Desktop

Remote servers are added through the application rather than the configuration file:

  1. Settings → Connectors → Add custom connector.
  2. Paste https://grabmail.io/mcp as the URL and give it a name.
  3. Start a new conversation and the tools appear under the connector.

On a version that only takes local servers in claude_desktop_config.json, bridge the remote endpoint with mcp-remote, which runs as a local process and forwards to the URL:

claude_desktop_config.json — through the mcp-remote bridge
{
  "mcpServers": {
    "grabmail": {
      "command": "npx",
      "args": ["-y", "mcp-remote", "https://grabmail.io/mcp"]
    }
  }
}

Cursor

Cursor reads .cursor/mcp.json in the project (or ~/.cursor/mcp.json for every project). A remote server is a url:

.cursor/mcp.json
{
  "mcpServers": {
    "grabmail": {
      "url": "https://grabmail.io/mcp"
    }
  }
}

Open Cursor Settings → MCP to see it listed and enable its tools. In Agent mode the model calls them on its own; in the chat you can ask for them by name.

Windsurf

Windsurf keeps its servers in ~/.codeium/windsurf/mcp_config.json, and the key for a remote server is serverUrl rather than url — the one place the shape differs:

~/.codeium/windsurf/mcp_config.json
{
  "mcpServers": {
    "grabmail": {
      "serverUrl": "https://grabmail.io/mcp"
    }
  }
}

Cascade lists the server after a refresh from the MCP panel. The same file can be reached from Windsurf Settings → Cascade → MCP servers → View raw config.

VS Code

VS Code’s agent mode reads .vscode/mcp.json in the workspace, or the user-level file that MCP: Add Server in the command palette writes. Servers live under servers, not mcpServers, and a remote one declares its transport:

.vscode/mcp.json
{
  "servers": {
    "grabmail": {
      "type": "http",
      "url": "https://grabmail.io/mcp"
    }
  }
}

A small “Start” link appears above the entry in the editor; after that the tools show in the chat’s tool picker, and agent mode calls them without being asked.

Codex CLI and Gemini CLI

Codex CLI keeps its configuration in TOML at ~/.codex/config.toml. A remote server is a table with a url:

~/.codex/config.toml
[mcp_servers.grabmail]
url = "https://grabmail.io/mcp"

Gemini CLI reads ~/.gemini/settings.json (or .gemini/settings.json in the project), and the key for a Streamable HTTP server is httpUrl:

~/.gemini/settings.json
{
  "mcpServers": {
    "grabmail": {
      "httpUrl": "https://grabmail.io/mcp"
    }
  }
}

A version of either that only takes local servers can reach the endpoint through the same mcp-remote bridge shown for Claude Desktop: command = "npx", args = ["-y", "mcp-remote", "https://grabmail.io/mcp"].

The six tools

Whatever the client, the model sees the same six tools with the same names. There is no state to manage between calls, and none of them needs an argument the previous one did not return.

create_inbox
Invents a fresh address and returns it with its alias and a next_step saying which to use where. Nothing is reserved server-side, so it cannot fail. Takes an optional readable prefix.
wait_for_message
Blocks until a message arrives at the address, up to 25 seconds, then returns it in full — subject, sender, plain text, HTML. Filter with subject_contains or from_contains; pass since_id to ignore what was already there. After a quiet wait it answers timed_out and asks to be called again.
read_message
One message in full by id. Rarely needed, because the wait already returns the whole message.
list_messages
Everything waiting at an address, newest first, immediately — including when there is nothing.
list_domains
The public domains anyone may use, for when a form has just refused one of them.
delete_message
Removes a message now rather than in 5 days. Idempotent, so a retrying agent costs nothing.

The sign-up loop in four calls

This is the sequence almost every task needs, and the client does not change it:

  1. create_inbox. Back come an address, an alias, and the note saying which is which.
  2. The alias goes into the form. The site gets a working address that reaches the mailbox and cannot be used to open it.
  3. wait_for_message on the address, immediately after submitting, with subject_contains set to a word the confirmation mail will carry. It blocks; the agent does not loop.
  4. The code comes out of the message the wait returned. There is usually no further call at all.
a prompt that exercises the whole loop
Sign up for a trial at https://app.example.com/signup with a fresh GrabMail inbox.
Use the ALIAS in the form, wait for the confirmation code with wait_for_message
on the ADDRESS, enter it, and tell me the resulting login.

What to put in the agent’s own instructions

The server tells the model how to use it at connection time, but a model that has read the same four rules in its own project instructions follows them every time rather than most times. Add this to CLAUDE.md, .cursor/rules, .windsurfrules, AGENTS.md or GEMINI.md — whichever the client reads:

CLAUDE.md, .cursor/rules, AGENTS.md — the same paragraph
## Email

- To receive email, use the `grabmail` MCP server. Call `create_inbox` once per task.
- Put the **alias** it returns into forms; poll the **address** it returns with `wait_for_message`.
- Call `wait_for_message` right after submitting a form, with `subject_contains` set to a word
  you expect ("code", "verify", "confirm"). If it returns `timed_out`, call it again — up to
  three times — before concluding the mail was not sent.
- Never reuse an inbox across tasks. Never send anything confidential to one: it is public.

The two rules that matter most are the ones an agent gets wrong without being told: put the alias in the form and poll the address, and treat timed_out as “call again”, not as “the mail was never sent”. An inbox an AI agent can read goes through both in depth, including why the wait returns before the mail does.

When it does not work

SymptomCauseFix
The server is not listedThe configuration file is in the wrong place, uses the wrong key (url / serverUrl / httpUrl / servers), or the client was not restarted.Copy the block for your client exactly, restart, and run the curl above to rule the server out.
The tools are listed but the model never calls themThe tools are disabled in the client’s MCP panel, or the model was not told an email step exists.Enable them, and add the instructions paragraph above.
wait_for_message keeps returning timed_outThe form was never submitted, the alias was mistyped, the site refused the domain, or 8 waits are already running.Call again up to three times; check the form’s own error; read why sign-up forms block disposable email.
The site says the address is invalidThe public domain is on a disposable-mail blocklist.Use a domain of your own (one MX record) or a domain from the pool kept off the lists.
The bridge (mcp-remote) fails to startNo Node on the machine, or npx cannot reach the registry.Install Node 18+, or use a client version that takes the URL directly.

Before you call it done

  • The curl above lists six tools from your machine.
  • The server appears in the client’s MCP panel after a restart, tools enabled.
  • The instructions paragraph is in the file your client reads.
  • A test prompt completed a sign-up: alias in the form, wait on the address, code read out.
  • Nothing confidential will ever be sent to one of these inboxes — they are public.

That is the whole setup. The same server works from any framework that speaks MCP, and for agents built without MCP — a plain tool function in LangChain, the OpenAI Agents SDK or your own loop — email for AI agents shows the REST version of the same four steps.

Questions

Do I need an API key or an account for the MCP server?

No. The public domains take no key, no account and no header, and the MCP server exposes exactly what the REST API exposes. Only the paid pool of domains kept off the disposable-mail blocklists uses a bearer token, passed as an Authorization header on the endpoint.

Is it Streamable HTTP or SSE?

Streamable HTTP: one POST, one JSON reply. There is no event stream to hold open, which is why wait_for_message is bounded at 25 seconds — a client that opens a GET expecting SSE is told, in plain JSON, that there is none.

Can several agents share the server at once?

Yes. There is no session state; each call carries everything it needs. The only shared limit is that 8 wait_for_message calls run at once across everyone — past that the tool answers timed_out immediately and asks to be called again, which the instructions paragraph above handles.

Why does wait_for_message return before the mail arrives?

Because a server worker sleeping for minutes is a worker nobody else can use. The wait is bounded at 25 seconds and says timed_out honestly rather than failing; the agent calls again. Three calls is over a minute of waiting, which covers any transactional mail that was actually sent.

Can the agent send email too?

No. The service receives only, by design — a free server with no account that could send would be a spam relay within the hour. An agent that has to send mail needs a sending provider; this one is for reading what comes back.

Is the inbox private to my agent?

No. Anyone who knows the address can read it, on a public domain and on your own. That is why the alias exists: the site gets an address that reaches the mailbox and cannot be used to open it. Never let an agent send anything confidential to one of these inboxes.

Which client configuration is the reference if these change?

Each client’s own documentation. The shapes above are the ones current in September 2026; the server side does not change with them — it is one URL, and any client that can call a remote MCP server over HTTP can call it.

Try it while it is fresh

An address takes one click, no account and no card. Everything in this guide works on it straight away.

Welcome back

Your inboxes and your domains, in one place.