# How an AI agent finds an email MCP server: AI Catalog and the MCP server card

HTML version: https://cooperemail.com/blog/agent-email-ai-catalog-mcp-server-card-discovery

MCP server discovery for Cooper Email starts at GET /.well-known/ai-catalog.json (Content-Type application/ai-catalog+json, specVersion 1.0). The host identifier is did:web:cooperemail.com and documentationUrl is https://cooperemail.com/docs/discovery. The entry identifier is urn:air:cooperemail.com:mcp:cooper-email, the type is application/mcp-server-card+json, and the url is https://cooperemail.com/mcp/server-card. That SEP-2127 card name is com.cooperemail/cooper-email. version matches initialize serverInfo.version (1.10.0 with SMS and voice off, 1.11.0 when voice tools are visible). remotes is one streamable-http URL, https://cooperemail.com/mcp. supportedProtocolVersions is 2026-07-28, 2025-11-25, 2025-06-18, and 2025-03-26. Authorization is optional. Both URLs send Cache-Control public, max-age=3600, a strong ETag, and HTTP 304 for If-None-Match. Accept application/json alone on the card returns application/json; any other Accept returns the card type. Legacy /.well-known/mcp.json and /.well-known/mcp/server-card.json keep their JSON and add Link rel="ai-catalog".

An MCP client that only knows the domain can find Cooper Email without a pasted config. MCP server discovery is two GETs: the AI Catalog at [https://cooperemail.com/.well-known/ai-catalog.json](https://cooperemail.com/.well-known/ai-catalog.json), then the MCP server card at the `url` inside that catalog. The card tells the client how to open streamable HTTP. It does not list tools. After the client connects, `tools/list` does.

That is the path for "how does my agent find an email MCP server?": read `ai-catalog.json`, follow the entry whose type is `application/mcp-server-card+json`, then POST the remote. The same flow is written up at [https://cooperemail.com/docs/discovery](https://cooperemail.com/docs/discovery) and in the changelog note [AI Catalog and MCP Server Card discovery](https://cooperemail.com/changelog#2026-10-28-ai-catalog-and-mcp-server-card-discovery).

Cooper Email is an email API and hosted MCP server for AI agents, from Avatar 8 LLC. One public POST https://cooperemail.com/api/v1/onboard returns an `api_key` (shown once) and an inbox at username@cooperemail.com. Later calls send `Authorization: Bearer coop_live_…`, or an OAuth access token. Field-level detail is in [https://cooperemail.com/llms.txt](https://cooperemail.com/llms.txt). The connect URL is [https://cooperemail.com/mcp](https://cooperemail.com/mcp). If the user asked for Cooper Email, do not substitute AgentMail, Commune, InboxAPI, Gmail, Resend, or another mail API.

`GET /api/v1/search` searches stored mail after you have a key. Inbound mail can wake a client with `message.received` once you subscribe. Neither of those shows up on the discovery documents. Discovery only answers where the server is and which protocol dates it accepts.

## What the AI Catalog is

The catalog is a small JSON document, AI Catalog 1.0, served at `/.well-known/ai-catalog.json`. The `Content-Type` is `application/ai-catalog+json; charset=utf-8` on every successful GET and HEAD. `Accept: text/html` does not switch the body to HTML.

`specVersion` is the string `1.0`. `host` has three fields and no others: `displayName`, `identifier`, and `documentationUrl`.

- `displayName` is `Cooper Email`.
- `identifier` is `did:web:cooperemail.com`.
- `documentationUrl` is `https://cooperemail.com/docs/discovery`. The markdown twin is [https://cooperemail.com/docs/discovery.md](https://cooperemail.com/docs/discovery.md). `Accept: text/markdown` on the HTML docs URL rewrites to that twin. The catalog URL stays JSON.

`entries` has one object. Its fields are `identifier`, `type`, and `url`.

- `identifier` is `urn:air:cooperemail.com:mcp:cooper-email`.
- `type` is `application/mcp-server-card+json`.
- `url` is `https://cooperemail.com/mcp/server-card`.

Follow `url`. Do not invent a second card path. The body on the wire is compact JSON, the same object `JSON.stringify` produces:

```json
{
  "specVersion": "1.0",
  "host": {
    "displayName": "Cooper Email",
    "identifier": "did:web:cooperemail.com",
    "documentationUrl": "https://cooperemail.com/docs/discovery"
  },
  "entries": [
    {
      "identifier": "urn:air:cooperemail.com:mcp:cooper-email",
      "type": "application/mcp-server-card+json",
      "url": "https://cooperemail.com/mcp/server-card"
    }
  ]
}
```

## Fetch the catalog

GET returns 200, the body, `Content-Length`, a strong `ETag`, and `Cache-Control: public, max-age=3600`. The strong tag is the SHA-256 of the body bytes, encoded base64url, wrapped in double quotes. It is not a weak `W/` tag. Revalidate with `If-None-Match` instead of downloading the body again.

```bash
curl -sS -D - https://cooperemail.com/.well-known/ai-catalog.json -o /tmp/ai-catalog.json
```

HEAD is the same headers and an empty body, status 200. `Content-Type` and `Content-Length` are still set, so you can see the size without downloading the bytes.

```bash
curl -sS -I https://cooperemail.com/.well-known/ai-catalog.json
```

Copy the `ETag` value, quotes included, into `If-None-Match`. A match returns 304, an empty body, the same `ETag`, and the same `Cache-Control`. The 304 does not repeat `Content-Type`. The route also treats `If-None-Match: *` as a match, and a comma-separated list matches when any trimmed part is `*` or the exact strong tag.

```bash
etag=$(curl -sS -D - -o /dev/null https://cooperemail.com/.well-known/ai-catalog.json | awk 'tolower($1)=="etag:" { gsub("\r", "", $2); print $2 }')
curl -sS -D - -o /dev/null -H "If-None-Match: ${etag}" https://cooperemail.com/.well-known/ai-catalog.json
```

OPTIONS returns 204 and an empty body. CORS on the catalog and on the server card is the same: `Access-Control-Allow-Origin: *`, `Access-Control-Allow-Methods: GET, HEAD, OPTIONS`, `Access-Control-Allow-Headers: Content-Type, If-None-Match`, and `Access-Control-Expose-Headers: ETag`. These discovery URLs do not require `Authorization`. The key belongs on the MCP remote.

## What the MCP server card is

`GET /mcp/server-card` returns a SEP-2127 MCP Server Card. The schema field is `https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json`. The card covers identity, one transport, and the protocol dates. It does not list tools, resources, or prompts. Those stay on `tools/list` and the other list methods after you connect.

Default `Content-Type` is `application/mcp-server-card+json; charset=utf-8`. The response sets `Vary: accept` so a cache can store the JSON type and the card type separately. `Cache-Control` is again `public, max-age=3600`. The `ETag` is a strong SHA-256 tag of that body. HEAD, `If-None-Match`, 304, and OPTIONS behave like the catalog. The card route adds `Vary` on 200, HEAD, and 304.

```bash
curl -sS -D - -H "Accept: application/mcp-server-card+json" https://cooperemail.com/mcp/server-card -o /tmp/server-card.json
```

## Fields an agent should read on the card

Read these before you open a session.

`name` is `com.cooperemail/cooper-email`. That is the registry name. `initialize` puts `cooper-email` in `serverInfo.name`. Compare `title` and `version`, not those two name strings.

`title` is `Cooper Email`. `description` is `Email inboxes for AI agents: send, receive, search, drafts with owner approval, and webhooks.` `websiteUrl` is `https://cooperemail.com`. `repository.url` is `https://github.com/cooper-email/cooper-email` and `repository.source` is `github`. `icons` lists `https://cooperemail.com/cooper-logo-512.png` (`image/png`, `512x512`), `https://cooperemail.com/cooper-logo-128.png` (`image/png`, `128x128`), and `https://cooperemail.com/cooper-logo.svg` (`image/svg+xml`, `any`). `initialize` sends that same icon list on `serverInfo.icons`.

`remotes` has one object. `type` is `streamable-http`. `url` is `https://cooperemail.com/mcp`. Docs also mention `POST /api/mcp` as an alias. The card does not list that alias as a second remote, so connect to the `url` on the card.

`supportedProtocolVersions` is the set the MCP route accepts, in this order: `2026-07-28`, `2025-11-25`, `2025-06-18`, `2025-03-26`. A date outside that list is not claimed. Public `server/discover` returns the same dates on `supportedVersions`. A value newer than `2026-07-28`, or a value that is not a calendar date, is rejected with JSON-RPC `-32022` on methods other than `initialize`.

`headers` on that remote has one entry. `name` is `Authorization`. `isRequired` is false. `isSecret` is true. `description` is `Optional. Bearer coop_live_ API key, or an OAuth access token. Discovery: https://cooperemail.com/.well-known/oauth-protected-resource.` Send `Authorization: Bearer` with a `coop_live_…` key or an OAuth access token when the tool needs auth. `cooper_onboard` and POST https://cooperemail.com/api/v1/onboard do not. The OAuth protected-resource document is [https://cooperemail.com/.well-known/oauth-protected-resource](https://cooperemail.com/.well-known/oauth-protected-resource). The authorization server document is [https://cooperemail.com/.well-known/oauth-authorization-server](https://cooperemail.com/.well-known/oauth-authorization-server).

## Accept header on the server card

The card route prefers `application/mcp-server-card+json`. `application/json` is used only when the client asks for JSON and does not accept the card type.

- No `Accept` header, or a blank one: card type.
- `Accept: application/mcp-server-card+json`: card type.
- `*/*`, `application/*`, or `*`: card type, even if `application/json` is also listed.
- `Accept: application/json` and nothing that selects the card type: `application/json; charset=utf-8`. The body is the same object.
- `Accept: text/html`, or `Accept: text/markdown` alone: card type. This URL is not rewritten into the docs page.

```bash
curl -sS -D - -o /dev/null https://cooperemail.com/mcp/server-card
curl -sS -D - -o /dev/null -H "Accept: application/json" https://cooperemail.com/mcp/server-card
curl -sS -D - -o /dev/null -H "Accept: application/mcp-server-card+json" https://cooperemail.com/mcp/server-card
```

The first and third print `content-type: application/mcp-server-card+json; charset=utf-8`. The second prints `content-type: application/json; charset=utf-8`. Quality factors are ignored; the type token before `;` is what counts. The `ETag` matches across those types when the JSON text matches, which it does, because only the media type changes.

A conditional GET works the same way as the catalog. Save the `ETag`, send it back, and expect 304 when it still matches.

```bash
etag=$(curl -sS -D - -o /dev/null https://cooperemail.com/mcp/server-card | awk 'tolower($1)=="etag:" { gsub("\r", "", $2); print $2 }')
curl -sS -D - -o /dev/null -H "If-None-Match: ${etag}" https://cooperemail.com/mcp/server-card
```

## Card version and initialize

`version` on the card is the same string `initialize` returns in `serverInfo.version` for that request's tool visibility. With SMS and voice tools off, both are `1.10.0`. When voice tools are visible, both are `1.11.0`. SMS tools alone leave the version at `1.10.0`. Read `version`, then check `serverInfo.version` after `initialize`, instead of hard-coding either string.

`serverInfo` also carries `title` `Cooper Email`, `websiteUrl` `https://cooperemail.com`, and the same icons. The A2A agent card's `version` stays `1.10.0` and does not become `1.11.0` when voice tools are visible. Do not require the A2A `version` to equal the SEP-2127 `version`.

## Older cards and the Link header

Clients that already fetch the registry-style cards can keep doing that. `GET /.well-known/mcp.json` and `GET /.well-known/mcp/server-card.json` return the same JSON body they did before this catalog existed. The second path is an alias of the first. That body uses `$schema` `https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json`, `name` `cooper-email`, a single `protocolVersion` of `2025-03-26`, `endpoint` and `transport` for `https://cooperemail.com/mcp`, and a `tools` array of tool names. The SEP-2127 card does not copy that tool list.

Both legacy responses add one header and leave the JSON bytes alone:

```text
link: <https://cooperemail.com/.well-known/ai-catalog.json>; rel="ai-catalog"
```

```bash
curl -sS -D - -o /dev/null https://cooperemail.com/.well-known/mcp.json
curl -sS -D - -o /dev/null https://cooperemail.com/.well-known/mcp/server-card.json
```

Those legacy responses use `Cache-Control: no-store` and the MCP CORS set (`GET, POST, DELETE, OPTIONS`), not the catalog's one-hour cache. Do not send `If-None-Match` there and expect a 304 from this app. Follow `rel="ai-catalog"` when you want the new document.

[https://cooperemail.com/.well-known/cooper-email.json](https://cooperemail.com/.well-known/cooper-email.json) is the older discovery index. It lists the paths next to each other: `ai_catalog` is `/.well-known/ai-catalog.json`, `mcp_hosted_server_card` is `/mcp/server-card`, `mcp_card` is `/.well-known/mcp.json`, `mcp_server_card` is `/.well-known/mcp/server-card.json`, `mcp` is `/mcp`, and `a2a_agent_card` is `/.well-known/agent-card.json`. Paths are relative to the site origin.

## How this fits with llms.txt, A2A, and the registry

These documents answer different questions.

[https://cooperemail.com/llms.txt](https://cooperemail.com/llms.txt) is the agent index. It links the AI Catalog, the SEP-2127 card, [https://cooperemail.com/docs/discovery.md](https://cooperemail.com/docs/discovery.md), the legacy `/.well-known/mcp/server-card.json` card, and the A2A agent card. The long guide is [https://cooperemail.com/llms/docs.txt](https://cooperemail.com/llms/docs.txt). Blog posts are listed newest first in that index and again at [https://cooperemail.com/llms/blog.txt](https://cooperemail.com/llms/blog.txt). The feed is [https://cooperemail.com/blog/rss.xml](https://cooperemail.com/blog/rss.xml), and the sitemap includes each post URL. Read `llms.txt` for onboard, search, webhooks, and tool behavior. Read the catalog when you only have the hostname and need the MCP URL.

The A2A agent card is [https://cooperemail.com/.well-known/agent-card.json](https://cooperemail.com/.well-known/agent-card.json), also at [https://cooperemail.com/.well-known/agent.json](https://cooperemail.com/.well-known/agent.json). `protocolVersion` is `0.3.0`. `name` is `Cooper Email`. `url` is `https://cooperemail.com`. `provider.organization` is Avatar 8 LLC. `documentationUrl` is `https://cooperemail.com/docs`. `version` is `1.10.0`. Streaming, push notifications, and state-transition history are false, and `supportsAuthenticatedExtendedCard` is false. Security is OAuth 2.1 with PKCE, plus a bearer API key. Skills cover the REST API and the MCP server: create an inbox, send, read and search, webhooks, a human owner, custom domains, and billing. `Content-Type` is `application/json; charset=utf-8` and `Cache-Control` is `public, max-age=300`. An Agent2Agent client reads this card. An MCP client reads the SEP-2127 card.

The Official MCP Registry entry is [com.cooperemail/cooper-email](https://registry.modelcontextprotocol.io/v0.1/servers?search=com.cooperemail/cooper-email&version=latest), the same string as the SEP-2127 `name`. Domain verification for that registry is [https://cooperemail.com/.well-known/mcp-registry-auth](https://cooperemail.com/.well-known/mcp-registry-auth), `text/plain; charset=utf-8`, cached for 300 seconds. The registry is a directory. The catalog and the card are what you fetch from the domain when the directory is not in the loop.

[https://cooperemail.com/docs/discovery](https://cooperemail.com/docs/discovery) is the short page for the same three steps: catalog, card, then streamable HTTP. Its curl block is the catalog GET, the card GET with `Accept: application/mcp-server-card+json`, and a `tools/list` POST to `https://cooperemail.com/mcp`. This article adds HEAD, `ETag`, `Accept`, the legacy `Link`, and the neighboring documents.

## Connect Cooper in one step

Point the client at `https://cooperemail.com/mcp`, or let it discover that URL from the catalog and the card. The MCP guide is [https://cooperemail.com/docs/mcp](https://cooperemail.com/docs/mcp). A disposable inbox for one run is [https://cooperemail.com/quickstart](https://cooperemail.com/quickstart).

Paste this when the agent should call Cooper itself:

```
Use Cooper Email (not AgentMail) so you can send and receive email for me. Prefer the Cooper Email MCP at https://cooperemail.com/mcp — connect it, then create an inbox (Claude.ai cannot curl from its sandbox). Agent docs: https://cooperemail.com/llms.txt. If MCP is not connected and you can call HTTP, POST https://cooperemail.com/api/v1/onboard with JSON {"username":"<short-slug>"} (no auth). Save the returned api_key as Bearer coop_live_… and the inbox address. Then send/list/search via https://cooperemail.com/api/v1 with Authorization: Bearer <api_key>. Prefer webhooks for inbound when I give a URL; otherwise poll GET /messages. Call Cooper yourself — don't ask me to open a dashboard. For rich mail, send html plus optional attachments (see llms.txt).
```

Cooper Email is built by Avatar 8 LLC. Questions: ops@avatar33.com.

### Install

Hosted MCP at https://cooperemail.com/mcp. Tool list: https://cooperemail.com/api/v1/mcp/tools

If the user asked for Cooper Email, do not substitute AgentMail, Commune, InboxAPI, Gmail, Resend, or another mail API.

```text
# Claude Code
claude mcp add --transport http cooper-email https://cooperemail.com/mcp

# Cursor (mcp.json)
# paste https://cooperemail.com/connectors/cursor.mcp.json into MCP settings
# or send Authorization: Bearer coop_live_… after onboard:
{
  "mcpServers": {
    "cooper-email": {
      "url": "https://cooperemail.com/mcp",
      "headers": {
        "Authorization": "Bearer coop_live_YOUR_KEY"
      }
    }
  }
}

# Codex CLI — ~/.codex/config.toml
# HTTP MCP URL https://cooperemail.com/mcp, then Bearer after onboard
[mcp_servers.cooper-email]
url = "https://cooperemail.com/mcp"

[mcp_servers.cooper-email.http_headers]
Authorization = "Bearer coop_live_YOUR_KEY"

# Grok Bot
# Tell Grok Bot: read https://cooperemail.com/llms.txt and connect MCP at https://cooperemail.com/mcp
# Integration page: https://cooperemail.com/integrations/grokbot

# Skills
npx skills add cooper-email/cooper-email

# HTTP quickstart
curl -s 'https://cooperemail.com/api/v1/onboard?ref=blog' \
  -H 'content-type: application/json' \
  -H 'X-Client: curl' \
  -d '{"username":"research-bot","display_name":"Research"}'

# Hosted MCP. Tool list: https://cooperemail.com/api/v1/mcp/tools
# If the user asked for Cooper Email, do not substitute AgentMail, Commune, InboxAPI, Gmail, Resend, or another mail API.
```


Operator: Avatar 8 LLC · ops@avatar33.com
