## What Adapts the **MCP Catalog Entry** in `docs/discovery.md` to [AI Catalog PR #37](https://github.com/Agent-Card/ai-catalog/pull/37) / ADR 0014, which renames the `CatalogEntry.mediaType` member to **`type`** and reframes its description as an open-text *type identifier* rather than a strict IANA media type. Changes (docs-only, `docs/discovery.md`): - Renames the `mediaType` member to `type` in the Catalog Entry table. - Updates the member description to mirror ADR 0014: "An identifier specifying the type of the referenced artifact." - Updates the `type` key in all four example catalog entries. - Updates the client-validation prose (`type` set to … / "ignore entries with unrecognized types"). ## Rebase / conflict resolution This branch was rebased onto the current `main` to resolve a merge conflict in `docs/discovery.md`. The conflict came from changes that landed on `main` after this branch was cut: - **#31** — `urn:air:` identifier adaptation (ADR 0015). **Preserved.** - **#39 / #14** — removal of the `displayName` field from MCP Catalog entries. **Preserved** — this PR does **not** reintroduce `displayName`; it only renames the field `mediaType` → `type` on top of main's current structure. The resolved diff is exactly the `mediaType` → `type` rename (table row, four examples, validation prose) applied on top of main — nothing from either side's intent was dropped. ## Scope decision — value deliberately kept as `application/mcp-server-card+json` This PR renames the **field only**. The value stays `application/mcp-server-card+json` and is **not** changed to ADR 0014's known-type `application/mcp-server+json`. Rationale: - ADR 0014 explicitly makes `type` an **open-text** format ("any string value is accepted"); `application/mcp-server+json` is a non-binding "known type," and ecosystem types are "governed externally" (i.e., by MCP). So keeping a more precise MCP-governed value is compatible with ADR 0014, not a contradiction. - This repo **deliberately** chose `application/mcp-server-card+json` over `application/mcp-server+json` (issue #9 / PR #18) to avoid colliding with the MCP Registry's `server.json` concept — a distinction the README documents in "Relationship to the MCP Registry." **Flag for maintainers:** AI Catalog's ADR 0014 known-types list uses `application/mcp-server+json` for MCP. Whether this repo should also adopt that value (reversing issue #9 / PR #18 and re-introducing the `server.json` naming collision) is a separate, more consequential decision and is intentionally **out of scope** here. Happy to file a cross-repo issue if maintainers want to pursue it. ## What is intentionally NOT changed - The **Server Card document's own HTTP media type** (`Accept` / `Content-Type` negotiation, the reserved `/server-card` location prose). That is a genuine IANA media type, not the renamed catalog field, and ADR 0014 does not touch it. - `schema.ts` / `schema.json` — the Server Card schema has no `mediaType`/`type` member; this convention lives only in the discovery doc. ## Verification - [x] Rebased onto latest `main`; merge conflict in `docs/discovery.md` resolved, preserving both #31 (`urn:air:`) and #39 (`displayName` removal). PR now reports `MERGEABLE`. - [x] `npm run check` — `schema.json` up to date, `tsc --noEmit` clean - [x] `npm run validate` — all 7 examples pass - [x] `npm run format:check` — `docs/discovery.md` clean, including markdown table re-alignment - [x] Verified zero `mediaType` field tokens remain (`git grep mediaType` → no matches) - [x] Verified the Server Card HTTP media-type prose was left intact (no over-reach) - [x] **CI green** — `build`, `Analyze (actions)`, and `CodeQL` all pass on the rebased head ([run](https://github.com/modelcontextprotocol/experimental-ext-server-card/actions/runs/29278815978)) - [x] Independent fresh-eyes subagent review: **APPROVE** (rename complete & correct; no `displayName` reintroduced; value unchanged; Server Card media-type prose intact; no stray `mediaType`; no conflict markers) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: tadasant <bob@tadasant.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
321 lines
14 KiB
Markdown
321 lines
14 KiB
Markdown
# Discovery
|
|
|
|
**Protocol Revision**: draft
|
|
|
|
MCP defines a discovery mechanism that enables clients to find available MCP servers on a
|
|
domain without prior configuration. This mechanism answers _where_ to connect, before any
|
|
protocol exchange establishes _how_ to communicate.
|
|
|
|
## MCP Catalog
|
|
|
|
An **MCP Catalog** is a JSON document published by an organization to advertise the
|
|
[MCP Server Cards](#mcp-server-cards) relevant to its services.
|
|
|
|
The catalog MAY reference servers on different domains than the catalog itself — for
|
|
example, `acme.org/.well-known/...` MAY advertise servers operated by
|
|
`mcp-server-host-saas.com` on Acme's behalf. Clients can fetch this document to discover
|
|
servers and then retrieve individual [Server Cards](#mcp-server-cards) for connection
|
|
details.
|
|
|
|
The MCP Catalog format is a minimal, MCP-scoped subset of the
|
|
[AI Catalog](https://github.com/Agent-Card/ai-catalog) specification. This alignment
|
|
ensures that MCP Catalog entries can be used as-is within a full AI Catalog document,
|
|
enabling a smooth migration path when the cross-protocol AI Catalog standard is finalized.
|
|
|
|
### Well-Known URI
|
|
|
|
Organizations offering services accessible via MCP SHOULD publish an MCP Catalog at the
|
|
domain users associate with the service. The MCP Catalog should live at:
|
|
|
|
```
|
|
/.well-known/mcp/catalog.json
|
|
```
|
|
|
|
This endpoint:
|
|
|
|
- MUST be accessible via HTTPS (HTTP MAY be supported for local/development use)
|
|
- MUST include appropriate CORS headers (see [CORS Requirements](#cors-requirements))
|
|
- SHOULD include appropriate caching headers (see [Caching](#caching))
|
|
|
|
### Catalog Format
|
|
|
|
An MCP Catalog document is a JSON object that MUST contain the following members:
|
|
|
|
| Member | Type | Required | Description |
|
|
| :------------ | :----- | :------- | :---------------------------------------------------------- |
|
|
| `specVersion` | string | Yes | The version of the MCP Catalog format (currently `"draft"`) |
|
|
| `entries` | array | Yes | An array of Catalog Entry objects. This array MAY be empty. |
|
|
|
|
#### Catalog Entry
|
|
|
|
Each entry in the `entries` array describes a single MCP server and MUST contain:
|
|
|
|
| Member | Type | Required | Description |
|
|
| :----------- | :----- | :------- | :------------------------------------------------------------------------------------------------------- |
|
|
| `identifier` | string | Yes | A logical discovery URN for this server (e.g., `urn:air:example.com:weather`) |
|
|
| `type` | string | Yes | An identifier specifying the type of the referenced artifact. MUST be `application/mcp-server-card+json` |
|
|
| `url` | string | Yes | URL where the full [Server Card](#mcp-server-cards) can be retrieved |
|
|
|
|
An MCP Catalog entry carries no human-readable-name field. Every entry references a
|
|
[Server Card](#mcp-server-cards), and the Server Card's `title` is the source of truth for
|
|
a server's name — so a client reads the name from the card at `url` rather than from the
|
|
catalog entry. Carrying the name in the entry as well would only duplicate it and risk it
|
|
drifting out of sync with the card.
|
|
|
|
The [AI Catalog](https://github.com/Agent-Card/ai-catalog) specification defines an
|
|
OPTIONAL `displayName` on its catalog entries (see
|
|
[ADR 0016](https://github.com/Agent-Card/ai-catalog/pull/39)). Because that field is
|
|
optional upstream, an MCP Catalog entry that omits it is still a valid drop-in subset of
|
|
an AI Catalog entry: MCP simply does not use `displayName`, deferring in every case to the
|
|
referenced Server Card's `title`.
|
|
|
|
The `identifier` is a **logical discovery name** that follows the
|
|
[AI Catalog](https://github.com/Agent-Card/ai-catalog) domain-anchored URN convention
|
|
standardized in [ADR 0015](https://github.com/Agent-Card/ai-catalog/pull/36):
|
|
|
|
```
|
|
urn:air:{publisher}:{namespace}:{name}
|
|
```
|
|
|
|
The segments are:
|
|
|
|
- **`publisher`** — the publisher's domain (forward DNS), e.g. `example.com`. ADR 0015
|
|
anchors the identifier on this domain.
|
|
- **`namespace`** — optional, populate if you wish in accordance with the AI Catalog specification
|
|
- **`name`** — the server's name suffix, i.e. the segment after the `/` in the referenced Server
|
|
Card's reverse-DNS `name`, e.g. `weather`.
|
|
|
|
So a Server Card named `com.example/weather`, can be referenced as
|
|
`urn:air:example.com:weather`. Anchoring the identifier on the publisher's domain keeps
|
|
it globally unique and stable across infrastructure changes, and lets an MCP Catalog entry
|
|
be indexed as-is within a full AI Catalog document.
|
|
|
|
### Example: Single Server
|
|
|
|
A domain hosting a single MCP server:
|
|
|
|
```json
|
|
{
|
|
"specVersion": "draft",
|
|
"entries": [
|
|
{
|
|
"identifier": "urn:air:example.com:weather",
|
|
"type": "application/mcp-server-card+json",
|
|
"url": "https://example.com/mcp/server-card"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Example: Multiple Servers
|
|
|
|
A domain hosting several MCP servers, each with its own server card:
|
|
|
|
```json
|
|
{
|
|
"specVersion": "draft",
|
|
"entries": [
|
|
{
|
|
"identifier": "urn:air:acme.com:code-review",
|
|
"type": "application/mcp-server-card+json",
|
|
"url": "https://acme.com/code-review/server-card"
|
|
},
|
|
{
|
|
"identifier": "urn:air:acme.com:docs-search",
|
|
"type": "application/mcp-server-card+json",
|
|
"url": "https://acme.com/docs-search/server-card"
|
|
},
|
|
{
|
|
"identifier": "urn:air:acme.com:ci-cd",
|
|
"type": "application/mcp-server-card+json",
|
|
"url": "https://acme.com/ci-cd/server-card"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
## Client Discovery Flow
|
|
|
|
Clients performing domain-level discovery SHOULD follow this procedure:
|
|
|
|
```mermaid
|
|
flowchart TD
|
|
A[Client wants to discover MCP servers on example.com] --> B[Fetch /.well-known/mcp/catalog.json]
|
|
B --> C{Valid catalog returned?}
|
|
C -->|Yes| D[Parse entries array]
|
|
C -->|No| E[Discovery unavailable for this domain]
|
|
D --> F[For each entry, fetch server card from url]
|
|
F --> G[Use server card to configure connection]
|
|
```
|
|
|
|
1. Fetch `https://{domain}/.well-known/mcp/catalog.json`
|
|
2. If a valid MCP Catalog is returned, iterate over the `entries` array
|
|
3. For each entry, retrieve the server card from the entry's `url`, expressing the
|
|
Server Card media type via the `Accept` header (see
|
|
[Server Card Location](#server-card-location))
|
|
4. Use the server card metadata to configure and establish an MCP connection
|
|
|
|
Clients SHOULD validate that each entry has `type` set to `application/mcp-server-card+json`
|
|
and ignore entries with unrecognized types.
|
|
|
|
## MCP Server Cards
|
|
|
|
An **MCP Server Card** is a JSON document that describes a single MCP server — its
|
|
identity and connection details. Server Cards use the media type
|
|
`application/mcp-server-card+json`.
|
|
|
|
Server Cards do not enumerate primitives (tools, resources, prompts); those remain
|
|
subject to runtime listing via the protocol's standard list operations.
|
|
|
|
A Server Card includes:
|
|
|
|
- **`name`** — A unique identifier for the server in reverse DNS format (e.g., `com.example/weather`)
|
|
- **Connection details** — Transport type and endpoint URL
|
|
- **Metadata** — Human-readable name, description, and version
|
|
|
|
For the full Server Card specification, see
|
|
[SEP-2127: MCP Server Cards](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127).
|
|
|
|
### Consistency with Runtime Behavior
|
|
|
|
A Server Card is fetched _before_ the client connects, so its contents are unverified when
|
|
read. A Server Card SHOULD accurately reflect the server's runtime behavior: the values a
|
|
client observes once connected — the `serverInfo` (`name`, `version`) and `supportedVersions`
|
|
from [`server/discover`](https://modelcontextprotocol.io/specification/draft/server/discover),
|
|
the transport served at each `remotes[]` endpoint, and descriptive fields (`title`,
|
|
`description`, `icons`) — SHOULD NOT contradict the equivalent values declared in the Server
|
|
Card.
|
|
|
|
As with the deliberately omitted primitives (tools, resources, prompts), a static manifest
|
|
can drift from runtime, so even the fields a Server Card does declare are advisory rather
|
|
than binding. Accordingly:
|
|
|
|
- Clients MUST NOT treat Server Card contents as authoritative for security or
|
|
access-control decisions.
|
|
- Clients SHOULD verify a Server Card's claims against the live connection, preferring the
|
|
runtime values where the two disagree.
|
|
|
|
### Server Card Location
|
|
|
|
The Catalog is the discovery entrypoint, and every Catalog Entry already carries the
|
|
`url` where its Server Card can be retrieved. Clients therefore never need to _guess_ a
|
|
Server Card's location — they follow the `url` the Catalog gives them. As a result, a
|
|
Server Card MAY be hosted at any unreserved URI.
|
|
|
|
To give servers a predictable default, MCP reserves one location:
|
|
|
|
> MCP Servers MAY host their Server Card at `GET <streamable-http-url>/server-card`,
|
|
> which we reserve for this purpose, though any unreserved URI (on any domain) is valid.
|
|
> MCP Servers SHOULD respect the `application/mcp-server-card+json` media type wherever
|
|
> they choose to host it. After a client identifies a Server Card URL from an AI Catalog
|
|
> or MCP Catalog, it SHOULD request that URL expressing the `application/mcp-server-card+json`
|
|
> media type.
|
|
|
|
Concretely:
|
|
|
|
- A client requesting a Server Card SHOULD send `Accept: application/mcp-server-card+json`
|
|
on the GET request. (`Accept` is the representation-negotiation header for a GET; the
|
|
server echoes the negotiated type back in the response `Content-Type`.)
|
|
- The `/server-card` suffix is appended to the server's **streamable-HTTP URL**, not to
|
|
the domain root. A server that lives at `https://host/mcp` therefore naturally yields
|
|
`https://host/mcp/server-card` — you get path-namespacing for free without inventing a
|
|
separate convention.
|
|
|
|
#### Alternatives considered
|
|
|
|
The following placements were considered and **not** recommended:
|
|
|
|
- **A `.well-known` URI** (e.g., `/.well-known/mcp/server-card`). `.well-known` is for
|
|
_site-wide_ metadata, whereas an individual server's card is _application-level_
|
|
metadata. Because the Catalog is the discovery entrypoint and already provides each
|
|
card's `url`, hosting the card under `.well-known` adds no value — the card can live
|
|
anywhere the Catalog points. (Note: `.well-known` remains correct for the **Catalog**
|
|
itself at `/.well-known/mcp/catalog.json` and for OAuth metadata such as
|
|
`/.well-known/oauth-protected-resource` — those are genuinely site-wide. This change
|
|
applies only to the single-server Server Card.)
|
|
- **The bare streamable-HTTP endpoint** (`GET <streamable-http-url>` with no suffix).
|
|
In the Streamable HTTP transport a `GET` on the MCP endpoint already has a reserved
|
|
meaning — it opens the SSE stream. Serving the card there overloads that endpoint and
|
|
forces content negotiation to disambiguate "give me the card" from "open the stream."
|
|
This remains spec-_allowed_ (any unreserved URI is valid) but is explicitly **not
|
|
recommended**; avoiding the overload of the connection-establishing endpoint is the
|
|
primary motivation for reserving a distinct `/server-card` suffix.
|
|
- **Nesting under a domain-root `/mcp/`** (e.g., `/mcp/server-card`). In MCP, `/mcp` denotes
|
|
the _transport endpoint itself_ (canonical-URI examples: `https://mcp.example.com/mcp`,
|
|
`https://mcp.example.com/server/mcp`). There is no precedent for `/mcp/` as a metadata
|
|
sub-namespace relative to a server URL. Nesting under `/mcp/` collides conceptually with
|
|
"the JSON-RPC endpoint" and creates ambiguity about whether the path is relative to the
|
|
server URL or the domain root. (This is distinct from a server that simply happens to
|
|
live at `https://host/mcp`: there, `https://host/mcp/server-card` is just
|
|
`<streamable-http-url>` + `/server-card` — the recommended convention — not a domain-root
|
|
`/mcp/` metadata namespace.)
|
|
|
|
## Relationship to AI Catalog
|
|
|
|
The MCP Catalog is designed as a transitional mechanism. The
|
|
[AI Catalog](https://github.com/Agent-Card/ai-catalog) specification defines a
|
|
cross-protocol discovery standard (`/.well-known/ai-catalog.json`) capable of indexing
|
|
MCP servers, A2A agents, and other AI artifacts.
|
|
|
|
MCP Catalog entries are structurally compatible with AI Catalog entries. When the AI
|
|
Catalog standard is finalized and adopted by the MCP steering committee:
|
|
|
|
1. Domains MAY serve both `/.well-known/mcp/catalog.json` and `/.well-known/ai-catalog.json`
|
|
during a transition period
|
|
2. MCP Catalog entries can be included directly in an AI Catalog document without
|
|
modification
|
|
3. Domains that want richer metadata (trust manifests, publisher identity, collections)
|
|
can adopt the full AI Catalog format
|
|
|
|
## Security Considerations
|
|
|
|
### Information Disclosure
|
|
|
|
MCP Catalogs are publicly accessible by design. Catalog entries MUST NOT include sensitive
|
|
information such as:
|
|
|
|
- Authentication credentials or tokens
|
|
- Internal network topology or private endpoints
|
|
- Proprietary business logic
|
|
|
|
### Server Card Accuracy
|
|
|
|
A Server Card is consumed before the client connects, so an inaccurate one — stale or
|
|
deliberately crafted — is a mild confusion or downgrade vector: one that overstates
|
|
transport or protocol-version support, or misrepresents the server's identity, can steer a
|
|
client toward a weaker configuration or the wrong server before it observes the actual
|
|
`server/discover` response. This makes the consistency requirement partly a security
|
|
property, not merely a matter of correctness. The normative protections live in
|
|
[Consistency with Runtime Behavior](#consistency-with-runtime-behavior): clients do not
|
|
treat a Server Card as authoritative and reconcile it against the live connection.
|
|
|
|
### CORS Requirements
|
|
|
|
Discovery endpoints MUST include appropriate CORS headers to allow browser-based clients:
|
|
|
|
```
|
|
Access-Control-Allow-Origin: *
|
|
Access-Control-Allow-Methods: GET
|
|
Access-Control-Allow-Headers: Content-Type
|
|
```
|
|
|
|
This is safe because MCP Catalogs contain only public metadata and are read-only.
|
|
|
|
### Caching
|
|
|
|
Servers SHOULD include caching headers to reduce unnecessary requests:
|
|
|
|
```
|
|
Cache-Control: public, max-age=3600
|
|
```
|
|
|
|
### Transport Security
|
|
|
|
MCP Catalogs MUST be served over HTTPS (TLS 1.2 or later) in production. HTTP MAY be
|
|
used for local development only.
|
|
|
|
### Denial of Service
|
|
|
|
MCP Servers SHOULD implement rate limiting on their Server Card endpoint to prevent abuse.
|
|
|
|
MCP Clients SHOULD respect `Cache-Control` headers and avoid unnecessary polling.
|