agent-mail — Mail MCP

Mail for<seat>[+session]@staging.agents.klappy.devNo send path.
<seat> seat[+session] session

Connect

.mcp.json
{
  "mcpServers": {
    "agent-mail": {
      "type": "http",
      "url": "https://staging.agents.klappy.dev/mcp"
    }
  }
}
Sign in
claude mcp add --transport http agent-mail https://staging.agents.klappy.dev/mcp
For agents:
200 · 1802 bytes · text/markdown; charset=utf-8curl -H 'Accept: text/markdown' https://staging.agents.klappy.dev//llms.txt

Try it

persona+demo@staging.agents.klappy.dev

Watch one land: mail persona+demo@staging.agents.klappy.dev; GET /demo/wait?since=<epoch ms> answers {address, landed, age_ms, observed_at} within 25 s, metadata only.

Waiting for mail
since 14:15:04Z
landed ago
observed
Worker not deployed
not observed

Auth

OAuth as the owner: your MCP client signs you in through Cloudflare Access (your email) on first connect, once per client. A seat is a label you pass and every read is logged; you may read only seats you own (owners table). Without a grant, docs() answers with login_url.

Journeys

  • Newest sign-in code for persona+run42 → execute {method:"GET", path:"/latest", query:{to:"persona+run42@staging.agents.klappy.dev"}}
  • Did an invite arrive (admin) → execute {method:"GET", path:"/admin/delivery", query:{to:"..."}}
Full pass

Tools

docs({query?, depth?})this pass; deeper rungs; canon via oddkit.
execute({method, path, query?, fields?})GET/HEAD; one write, POST /admin/seats.
telemetry({sql})one SELECT over agent_mail_telemetry.

Resources

  • GET /whoami (any owner)
  • GET /inbox (owner of seat) — seat, session?, newer_than?, older_than?, from?, subject?, include_expired?, limit?
  • GET /messages/{id} (owner of the message's seat) — seat?
  • GET /latest (owner of the address's seat) — to, newer_than?
  • GET /admin/inbox (admin) — seat?, session?, q?, older_than?, limit?
  • GET /admin/messages/{id} (admin)
  • GET /admin/delivery (admin) — to, since?
  • POST /admin/seats (admin) — seat, owner_email?, retention_days?

HUMAN-ONLY

Approve the email allowlist in the Cloudflare Access app (callback https://staging.agents.klappy.dev/callback). Vars ACCESS_ISSUER, ACCESS_CLIENT_ID; Worker secrets ACCESS_CLIENT_SECRET, COOKIE_ENCRYPTION_KEY.

# agent-mail — Mail MCP

Mail for `<seat>[+session]@staging.agents.klappy.dev`. No send path.

## Auth
OAuth as the owner: your MCP client signs you in through Cloudflare Access (your email) on first connect, once per client. A seat is a label you pass and every read is logged; you may read only seats you own (`owners` table). Without a grant, `docs()` answers with `login_url`.

## Tools
- `docs({query?, depth?})` — this pass; deeper rungs; canon via oddkit.
- `execute({method, path, query?, fields?})` — GET/HEAD; one write, POST /admin/seats.
- `telemetry({sql})` — one SELECT over `agent_mail_telemetry`.

## Resources
- `GET /whoami` (any owner)
- `GET /inbox` (owner of seat) — seat, session?, newer_than?, older_than?, from?, subject?, include_expired?, limit?
- `GET /messages/{id}` (owner of the message's seat) — seat?
- `GET /latest` (owner of the address's seat) — to, newer_than?
- `GET /admin/inbox` (admin) — seat?, session?, q?, older_than?, limit?
- `GET /admin/messages/{id}` (admin)
- `GET /admin/delivery` (admin) — to, since?
- `POST /admin/seats` (admin) — seat, owner_email?, retention_days?

## Journeys
- Newest sign-in code for persona+run42 → `execute {method:"GET", path:"/latest", query:{to:"persona+run42@staging.agents.klappy.dev"}}`
- Did an invite arrive (admin) → `execute {method:"GET", path:"/admin/delivery", query:{to:"..."}}`
- Watch one land: mail persona+demo@staging.agents.klappy.dev; `GET /demo/wait?since=<epoch ms>` answers `{address, landed, age_ms, observed_at}` within 25 s, metadata only.

## HUMAN-ONLY
Approve the email allowlist in the Cloudflare Access app (callback `https://staging.agents.klappy.dev/callback`). Vars `ACCESS_ISSUER`, `ACCESS_CLIENT_ID`; Worker secrets `ACCESS_CLIENT_SECRET`, `COOKIE_ENCRYPTION_KEY`.
EOF · 1802 bytes

Live