Agent Identity
← GUIDES · BUILDING

How do I give Claude an email address with MCP?

By Naol Ketema, founder · · Markdown

SHORT ANSWER

Create an agent in Agent Identity, which gives it a real mailbox, mint a key scoped to that agent, and expose the SDK's mail calls as tools in a small MCP server that Claude Desktop or Claude Code launches. Agent Identity's published MCP server, @agentidentity/mcp, covers Agent2Agent tasks today rather than mail, so for email you register your own server next to it. It is about 60 lines of TypeScript.

What MCP actually gives Claude

The Model Context Protocol (MCP) is an open standard for connecting a model to tools. An MCP client, such as Claude Desktop, Claude Code or Cursor, starts an MCP server as a local process or connects to one over HTTP, reads the list of tools it offers, and lets the model call them.

So giving Claude an email address has two parts. Something has to own a real address that can send and receive. Then an MCP server has to expose that address as tools: send, list, read, wait. Claude does the rest.

What the Agent Identity MCP server covers today

Agent Identity ships aid-mcp as the npm package @agentidentity/mcp. Its documented tools are all Agent2Agent tools: a2a_send_task, a2a_get_task, a2a_list_tasks, a2a_reply_to_task, a2a_find_agents, a2a_call_remote_agent and a few more. With it, Claude can send work to other agents and answer work sent to it, as one specific agent. It does not list mail tools, so it will not send email for you on its own.

{
  "mcpServers": {
    "agent-identity": {
      "command": "npx",
      "args": ["-y", "@agentidentity/mcp"],
      "env": {
        "AID_API_KEY": "aid_live_…",
        "AID_BASE_URL": "https://api.agent-identity.dev"
      }
    }
  }
}

AID_API_KEY must be an agent key. The server refuses an org key at startup, because every tool acts as one agent. In Claude Code you can install it as a plugin instead, with /plugin marketplace add ALPHACOD3RS/agent-identity and then /plugin install agent-identity@agent-identity; Claude Code asks for the agent key and keeps it in secure storage.

Step 1: create the agent and its key

Every agent gets a mailbox the moment it is created, at its handle on your sending domain. Do this once with your org key, then hand Claude only the agent key.

import { AgentClient } from "@agentidentity/sdk";

const admin = new AgentClient({
  baseUrl: "https://api.agent-identity.dev",
  apiKey: process.env.AID_KEY!, // org key
});

const agent = await admin.identities.create({ handle: "claude", displayName: "Claude" });
console.log(agent.mailboxAddress); // claude@…

const { key } = await admin.apiKeys.create({ name: "claude mcp", identityId: agent.id });
console.log(key); // aid_live_… shown once, store it now

Step 2: write a small mail MCP server

This server uses the official MCP TypeScript SDK and the Agent Identity SDK. It reads the agent id from the key itself with whoami(), so the only secret it needs is the agent key. Run it as an ES module so top-level await works.

import { AgentClient } from "@agentidentity/sdk";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const client = new AgentClient({
  baseUrl: process.env.AID_BASE_URL ?? "https://api.agent-identity.dev",
  apiKey: process.env.AID_API_KEY!,
});
const { actorId: identityId } = await client.whoami();

const json = (value: unknown) => ({
  content: [{ type: "text" as const, text: JSON.stringify(value, null, 2) }],
});

const server = new McpServer({ name: "agent-mail", version: "0.1.0" });

server.registerTool(
  "send_email",
  {
    description: "Send an email from your own address. Starts a new thread.",
    inputSchema: { to: z.array(z.string().email()).min(1), subject: z.string(), text: z.string() },
  },
  async ({ to, subject, text }) => {
    const message = await client.mail.send({ identityId, to, subject, text });
    return json({ id: message.id, threadId: message.threadId, status: message.status });
  },
);

server.registerTool(
  "reply_to_email",
  {
    description: "Reply to everyone on a message, inside the same thread.",
    inputSchema: { messageId: z.string(), text: z.string() },
  },
  async ({ messageId, text }) => {
    const message = await client.mail.send({ identityId, replyAll: true, inReplyToMessageId: messageId, text });
    return json({ id: message.id, threadId: message.threadId });
  },
);

server.registerTool(
  "list_unread",
  { description: "List unread conversations in the inbox.", inputSchema: {} },
  async () => json((await client.mail.listThreads({ identityId, folder: "inbox", isRead: false, limit: 20 })).items),
);

server.registerTool(
  "read_thread",
  { description: "Read every message in a thread, oldest first.", inputSchema: { threadId: z.string() } },
  async ({ threadId }) => json(await client.mail.getThread(identityId, threadId)),
);

server.registerTool(
  "wait_for_reply",
  { description: "Block up to 55 seconds for a reply on a thread.", inputSchema: { threadId: z.string() } },
  async ({ threadId }) =>
    json(await client.events.wait({ type: "mail.received", identityId, filter: { threadId }, timeoutMs: 55_000 })),
);

await server.connect(new StdioServerTransport());

A stdio MCP server talks to the client over stdout, so never log to stdout from it. Use stderr for debugging.

Step 3: register it with Claude Desktop or Claude Code

In Claude Desktop, add the server to claude_desktop_config.json next to the Agent2Agent server and restart the app. Use an absolute path.

{
  "mcpServers": {
    "agent-mail": {
      "command": "npx",
      "args": ["-y", "tsx", "/absolute/path/to/mail-server.ts"],
      "env": {
        "AID_API_KEY": "aid_live_…",
        "AID_BASE_URL": "https://api.agent-identity.dev"
      }
    }
  }
}

In Claude Code, add it from the terminal. Cursor and other MCP clients take the same command, arguments and environment in their own config files.

claude mcp add agent-mail -e AID_API_KEY=aid_live_… -- npx -y tsx /absolute/path/to/mail-server.ts

Limits and guardrails to know before Claude sends anything

  • Give Claude an agent key, never the org key. An agent key reaches only its own mailbox; another agent's mailbox reads as 404.
  • Until the organization's owner verifies their email, the agent can only email that owner, a few times a day. That is a safe way to test.
  • Sends count against a daily quota per organization. Past it, the API answers 429 mail.outbound_quota_exceeded until 00:00 UTC.
  • A 202 from send means queued, not delivered. Delivery, bounces and complaints arrive later as mail.delivered, mail.bounced and mail.complained events.
  • Contact rules with action block stop a send before it is queued, so you can fence off addresses Claude must never write to.
  • Treat inbound email as untrusted input. A message that says to forward the inbox somewhere is a request from a stranger, not an instruction from you.

When you do not need this

If you only want Claude to draft text that you paste into your own mail client, skip all of it. A dedicated address earns its keep when Claude acts on its own: it sends from an address that is not yours, the replies come back to a mailbox it can read, and every send and reply is in an event log you can audit later.

FAQ

Does @agentidentity/mcp include email tools?

Not according to its docs. Its tools are Agent2Agent tools for sending, answering and finding tasks and agents. For email, run a small MCP server that wraps the SDK's mail calls, as shown above, next to it.

Can I use my org key in the MCP config?

Do not. The Agent Identity MCP server refuses an org key at startup, and your own server should follow the same rule: an agent key acts as exactly one agent and cannot create agents or keys.

Can Claude wait for a reply inside a single tool call?

Yes, up to 55 seconds, which is the server's cap on one event wait. For longer waits, have Claude call the tool again, or run a separate process that subscribes to mail.received events.

Can the address be on my own domain?

Yes. Verify the domain with Agent Identity, then pass domain when creating the agent, for example handle claude with domain acme.dev gives claude@acme.dev.

Related guides

Give your agent its own identity in a couple of minutes. Get started free → · Docs · All guides