Email for agents, as MCP tools
A hosted Model Context Protocol server at https://api.mailgi.xyz/mcp. Point Claude Code, Claude Desktop or Cursor at it and the agent can register an address, send, read and file mail — with nothing to install on the server side.
What it is
The MCP server runs inside the Mailgi API itself. Each tool forwards to an existing REST endpoint and returns that endpoint's response, so a tool call and the equivalent curl behave identically: the same validation, the same rate limits, the same metering against the same wallet. Nothing is cached or reshaped in between. The server is stateless — there is no MCP session to keep alive — and credentials travel as HTTP headers on the connection, not as tool arguments.
The server also exposes one resource, mailgi://skill, which serves SKILL.md — the plain-language guide to the API — so a client can load it as context without fetching the website.
Connect a client
Transport is Streamable HTTP. Already have an API key? Add it as the Authorization header and you're done. Don't have one yet? Start without headers and follow the bootstrap below.
Claude Desktop's config file only launches local processes, so it reaches a remote server through the mcp-remote bridge, which needs Node.js. Cursor speaks HTTP directly.
# With an agent API key claude mcp add --transport http mailgi https://api.mailgi.xyz/mcp \ --header "Authorization: Bearer amb_..." # Org tools as well — add the dashboard session header claude mcp add --transport http mailgi https://api.mailgi.xyz/mcp \ --header "Authorization: Bearer amb_..." \ --header "X-Mailgi-Session: <session token>" # No credentials yet — public tools only, for bootstrap claude mcp add --transport http mailgi https://api.mailgi.xyz/mcp
// claude_desktop_config.json
{
"mcpServers": {
"mailgi": {
"command": "npx",
"args": [
"-y", "mcp-remote", "https://api.mailgi.xyz/mcp",
"--header", "Authorization:${MAILGI_AUTH}"
],
"env": { "MAILGI_AUTH": "Bearer amb_..." }
}
}
}
// The env indirection keeps the space in "Bearer amb_..."
// from being split when the arguments are passed on.// .cursor/mcp.json (project) or ~/.cursor/mcp.json (global)
{
"mcpServers": {
"mailgi": {
"url": "https://api.mailgi.xyz/mcp",
"headers": {
"Authorization": "Bearer amb_..."
}
}
}
}Bootstrap: from nothing to a key
The server has no OAuth, so a credential can't be attached to the connection for you. The agent obtains one through a tool, and a human puts it into the client config once.
An agent of its own
Connect with no headers and ask the agent to call register_agent (optionally with a label, or a domainToken and localPart to claim an address on a company domain). It returns the email address and an API key starting amb_ — shown exactly once. Re-add the server with Authorization: Bearer amb_… and the agent tools appear. In Claude Code that is claude mcp remove mailgi followed by the add command above.
Org admin, by emailed code
For the organisation tools — domains, members, registration tokens, the org wallet — the agent calls org_login_request_code with a human's email address. Mailgi emails that person a six-digit code; they tell the agent, which passes it to org_login_verify and gets back a dashboard session token. Add it to the connection as X-Mailgi-Session. Call org_logout to end that session when you're done with it.
Both at once
The two headers are independent. Send both and the connection has agent and org tools together — for example an admin agent that creates a registration token with regtoken_create and hands it to another agent to self-register on the company domain.
All 67 tools
Tool names map one-to-one to REST endpoints, and each behaves exactly like the endpoint it names. For request and response fields, read the endpoint in the API docs or SKILL.md. Tools that delete, revoke or remove carry the MCP destructiveHint annotation, so a client that honours it asks before calling them.
Agent tools · 25 · need Authorization: Bearer amb_…
Everything an agent does with its own mailbox and wallet.
| Tool | REST endpoint | What it does |
|---|---|---|
| agent_get | GET /v1/agents/me | This agent's profile and address |
| agent_usage | GET /v1/agents/me/usage | This agent's usage, by day |
| agent_delete | DELETE /v1/agents/me | Delete the agent — permanent, burns the address destructive |
| mail_list | GET /v1/mail | List messages |
| mail_get | GET /v1/mail/{id} | Read a message with its body |
| mail_send | POST /v1/mail/send | Send a message (metered exactly like REST) |
| mail_delete | DELETE /v1/mail/{id} | Move a message to Trash destructive |
| mail_move | PATCH /v1/mail/{id}/move | Move a message to another mailbox |
| mail_set_flags | PATCH /v1/mail/{id}/flags | Set seen / flagged |
| mailbox_list | GET /v1/mailboxes | List mailboxes (folders) |
| mailbox_create | POST /v1/mailboxes | Create a mailbox |
| mailbox_rename | PATCH /v1/mailboxes/{id} | Rename a mailbox |
| mailbox_delete | DELETE /v1/mailboxes/{id} | Delete a mailbox destructive |
| apikey_list | GET /v1/apikeys | List API keys |
| apikey_create | POST /v1/apikeys | Create an API key |
| apikey_revoke | DELETE /v1/apikeys/{keyId} | Revoke an API key destructive |
| billing_get | GET /v1/billing | Wallet balance, prices, how to top up |
| billing_topup | POST /v1/billing/topup | Create a card top-up link for a human to pay |
| billing_transactions | GET /v1/billing/transactions | Wallet ledger |
| webhook_list | GET /v1/webhook-endpoints | List webhook endpoints |
| webhook_create | POST /v1/webhook-endpoints | Register a webhook endpoint for inbound mail |
| webhook_delete | DELETE /v1/webhook-endpoints/{id} | Delete a webhook endpoint destructive |
| webhook_deliveries | GET /v1/webhook-endpoints/{id}/deliveries | Recent delivery attempts |
| webhook_resend | POST /v1/webhook-endpoints/{id}/deliveries/{deliveryId}/resend | Re-queue a delivery |
| webhook_enable | POST /v1/webhook-endpoints/{id}/enable | Re-enable an endpoint switched off by failures |
Org tools · 35 · need X-Mailgi-Session
What a human does in the dashboard at app.mailgi.xyz, with that human's role in each organisation.
| Tool | REST endpoint | What it does |
|---|---|---|
| org_me | GET /v1/auth/me | The signed-in user and their orgs |
| org_list | GET /v1/orgs | List organisations |
| org_create | POST /v1/orgs | Create an organisation |
| org_get | GET /v1/orgs/{orgId} | Get an organisation |
| org_rename | PATCH /v1/orgs/{orgId} | Rename an organisation |
| org_member_list | GET /v1/orgs/{orgId}/members | List members |
| org_member_set_role | PATCH /v1/orgs/{orgId}/members/{userId} | Change a member's role |
| org_member_remove | DELETE /v1/orgs/{orgId}/members/{userId} | Remove a member (or leave) destructive |
| org_invite_create | POST /v1/orgs/{orgId}/invites | Invite someone by email |
| org_invite_list | GET /v1/orgs/{orgId}/invites | List pending invites |
| org_invite_revoke | DELETE /v1/orgs/{orgId}/invites/{inviteId} | Revoke an invite destructive |
| invite_accept | POST /v1/invites/accept | Accept an invite |
| domain_attach | POST /v1/orgs/{orgId}/domains | Attach a custom domain |
| domain_list | GET /v1/orgs/{orgId}/domains | List custom domains |
| domain_get | GET /v1/orgs/{orgId}/domains/{domainId} | Verification status and the DNS records to publish |
| domain_detach | DELETE /v1/orgs/{orgId}/domains/{domainId} | Detach a custom domain destructive |
| domain_verify | POST /v1/orgs/{orgId}/domains/{domainId}/verify | Re-check DNS |
| domain_usage | GET /v1/orgs/{orgId}/usage/domains/{domainId} | Usage for one domain, by day |
| org_usage | GET /v1/orgs/{orgId}/usage | Usage for the organisation, by day |
| org_billing_get | GET /v1/orgs/{orgId}/billing | Organisation wallet |
| org_billing_topup | POST /v1/orgs/{orgId}/billing/topup | Create a card top-up link for the org wallet |
| org_billing_transactions | GET /v1/orgs/{orgId}/billing/transactions | Organisation wallet ledger |
| org_webhook_list | GET /v1/orgs/{orgId}/webhook-endpoints | List the org's webhook endpoints |
| org_webhook_create | POST /v1/orgs/{orgId}/webhook-endpoints | Create a webhook endpoint for one of the org's inboxes |
| org_webhook_delete | DELETE /v1/orgs/{orgId}/webhook-endpoints/{id} | Delete a webhook endpoint destructive |
| org_webhook_deliveries | GET /v1/orgs/{orgId}/webhook-endpoints/{id}/deliveries | Recent delivery attempts |
| org_webhook_resend | POST /v1/orgs/{orgId}/webhook-endpoints/{id}/deliveries/{deliveryId}/resend | Re-queue a delivery |
| org_webhook_enable | POST /v1/orgs/{orgId}/webhook-endpoints/{id}/enable | Re-enable an endpoint |
| regtoken_create | POST /v1/orgs/{orgId}/domains/{domainId}/registration-tokens | Create a registration token (shown once) |
| regtoken_list | GET /v1/orgs/{orgId}/domains/{domainId}/registration-tokens | List registration tokens |
| regtoken_revoke | DELETE /v1/orgs/{orgId}/domains/{domainId}/registration-tokens/{tokenId} | Revoke a registration token destructive |
| domain_inbox_list | GET /v1/orgs/{orgId}/domains/{domainId}/agents | List inboxes on a domain |
| domain_inbox_create | POST /v1/orgs/{orgId}/domains/{domainId}/agents | Create an inbox with a chosen local part |
| domain_inbox_delete | DELETE /v1/orgs/{orgId}/domains/{domainId}/agents/{agentId} | Remove an inbox from a domain destructive |
| org_logout | POST /v1/auth/logout | End the dashboard session |
Public tools · 7 · no credentials
Bootstrap, paying for any agent's wallet by address, and a health check.
| Tool | REST endpoint | What it does |
|---|---|---|
| register_agent | POST /v1/agents/register | Register an agent; returns the API key once |
| org_login_request_code | POST /v1/auth/email/request-code | Email a 6-digit login code |
| org_login_verify | POST /v1/auth/email/verify-code | Verify the code; returns a session token |
| topup_lookup | GET /v1/topup/lookup | Who a top-up for an address would pay for |
| topup_start | POST /v1/topup | Start a card payment for any agent's wallet |
| topup_status | GET /v1/topup/sessions/{sessionId} | Status of a top-up payment |
| health | GET /health/ready | Readiness of the API and mail server |
Not exposed over MCP: DID authentication (not available in the API either), OAuth sign-in redirects, signup attribution, and the inbound webhooks from Mailgi's own providers.
Limits, stated plainly
One config edit after bootstrap
A key obtained through register_agent or a session from org_login_verify doesn't attach itself to the connection. A human has to add the header to the client config and reconnect. Until then only the public tools work.
No OAuth, so no hosted connectors yet
claude.ai and ChatGPT add remote MCP servers as hosted custom connectors, which authenticate with OAuth rather than a header you type in. The server doesn't implement OAuth, so it can't be used from those today. Clients where you control the headers — Claude Code, Claude Desktop through mcp-remote, Cursor — work.
Org tools give an agent admin reach
A session token carries the signed-in human's role. An agent holding an owner's or admin's session can detach domains, remove members, revoke tokens and delete inboxes. The destructiveHint annotation only helps if your client asks before acting on it — only add X-Mailgi-Session to a connection you would trust with the dashboard, and end it with org_logout.
Same limits and prices as REST
MCP is not a separate tier. A mail_send is metered and rate-limited exactly like POST /v1/mail/send — see pricing — and errors come back as the endpoint's own error.
Not using MCP?
The same API is plain HTTP, a CLI and a TypeScript SDK, and SKILL.md teaches any agent to use it directly.