Blog ·
How an AI agent finds an email MCP server: AI Catalog and the MCP server card
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, 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 and in the changelog note 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. The connect URL is 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.
displayNameisCooper Email.identifierisdid:web:cooperemail.com.documentationUrlishttps://cooperemail.com/docs/discovery. The markdown twin is https://cooperemail.com/docs/discovery.md.Accept: text/markdownon the HTML docs URL rewrites to that twin. The catalog URL stays JSON.
entries has one object. Its fields are identifier, type, and url.
identifierisurn:air:cooperemail.com:mcp:cooper-email.typeisapplication/mcp-server-card+json.urlishttps://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:
{
"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.
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.
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.
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.
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. The authorization server document is 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
Acceptheader, or a blank one: card type. Accept: application/mcp-server-card+json: card type.*/*,application/*, or*: card type, even ifapplication/jsonis also listed.Accept: application/jsonand nothing that selects the card type:application/json; charset=utf-8. The body is the same object.Accept: text/html, orAccept: text/markdownalone: card type. This URL is not rewritten into the docs page.
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.
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:
link: <https://cooperemail.com/.well-known/ai-catalog.json>; rel="ai-catalog"
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 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 is the agent index. It links the AI Catalog, the SEP-2127 card, 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. Blog posts are listed newest first in that index and again at https://cooperemail.com/llms/blog.txt. The feed is 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, also at 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, the same string as the SEP-2127 name. Domain verification for that registry is 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 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. A disposable inbox for one run is 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.