Server Cards: recommend /server-card over .well-known (resolves #12) (#22)

Resolves #12.

## What & why

Moves the **single-server Server Card** off a `.well-known` path. We
reserve `GET <streamable-http-url>/server-card` as the recommended
location while allowing **any unreserved URI** — the Catalog
(`/.well-known/mcp/catalog.json`) is the discovery entrypoint and
already carries each card's `url`, so clients never need to guess where
a card lives.

Adopts wording close to the resolution discussed in #12 (credit: Darrel
Miller's point that `.well-known` is for *site-wide* metadata, not
application-level metadata like an individual server's card; proposed
wording from @SamMorrowDrums and @tadasant):

> MCP Servers MAY host their Server Card at `GET
<streamable-http-url>/server-card`, which we reserve for this purpose,
though any unreserved URI is valid. MCP Servers SHOULD respect the
agreed 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 Server Card media type.

### Content negotiation

A client fetching a Server Card requests its preferred representation
with the `Accept` header (`Accept: application/mcp-server-card+json`) —
the request-side content-negotiation header for a GET. The server echoes
the type it actually served back in the response's `Content-Type`.

> **Note on media type:** this PR is scoped to *placement* only and does
**not** perform the `application/mcp-server+json` →
`application/mcp-server-card+json` rename — that is handled in a
separate PR. So the two coexist deliberately here:
> - **Pre-existing strings** (catalog-entry description, the four
example `mediaType` values, validation prose, and the "Server Cards use
the media type" line) are left as `application/mcp-server+json`,
untouched, for the rename PR to sweep.
> - **New prose introduced by this PR** (the reserved-location
blockquote and the `Accept`-header example in the "Server Card Location"
section) names the type explicitly as the **post-rename
`application/mcp-server-card+json`**, since the rename PR's diff is
against `main` and would not touch text that doesn't yet exist there.
>
> The temporary mix resolves to a single consistent type once both PRs
land.

## Rationale for `/server-card` (also captured permanently in
`docs/discovery.md` → "Alternatives considered")

- **Not `.well-known`:** site-wide vs. application-level metadata; the
Catalog already provides the `url`, so `.well-known` adds no value for
the card.
- **Not the bare streamable-HTTP endpoint** (`GET <url>` with no
suffix): a GET there already opens the SSE stream in Streamable HTTP.
Hosting the card there overloads the connection-establishing endpoint
and forces content negotiation to disambiguate. Spec-*allowed*,
explicitly **not recommended** — this is the primary motivation for a
distinct suffix.
- **Not domain-root `/mcp/` nesting:** `/mcp` denotes the *transport
endpoint itself* in MCP's canonical-URI examples; there's no precedent
for `/mcp/` as a metadata sub-namespace, and it collides conceptually
with "the JSON-RPC endpoint."
- **Elegant consequence:** because the suffix is appended to the
streamable-HTTP URL, a server at `https://host/mcp` naturally yields
`https://host/mcp/server-card` — path-namespacing for free.

## Scope guardrail

This applies **only to the single-server Server Card**. `.well-known` is
unchanged and remains correct for the **Catalog**
(`/.well-known/mcp/catalog.json`) and OAuth metadata
(`/.well-known/oauth-protected-resource`, etc.) — those are genuinely
site-wide. The doc calls this out explicitly so it doesn't read as
"abandon `.well-known` everywhere."

## Files changed

- **`docs/discovery.md`** — new "Server Card Location" section with the
reserved convention + `Accept`-header guidance; permanent "Alternatives
considered" subsection; rewritten single- and multi-server example URLs
(`https://example.com/mcp/server-card`;
`https://acme.com/code-review/server-card`, `/docs-search/server-card`,
`/ci-cd/server-card`).
- **`schema.ts`** — updated the two `.well-known` doc comments
(`ServerCard` and `Server`) to the new convention.
- **`schema.json`** — regenerated from `schema.ts` (`npm run generate`).
- **`README.md`** — updated the "What is a Server Card?" line to the new
convention, normalizing the pre-existing README-vs-schema `.well-known`
mismatch.

## Verification

- [x] `npm run check` passes (schema.json in sync with schema.ts + `tsc
--noEmit` clean)
- [x] `npm run generate` run and regenerated `schema.json` committed in
the same change
- [x] `npm run validate` passes — all 7 examples validate against the
regenerated schema
- [x] `npm run format` run (prettier clean)
- [x] No single-server-card `.well-known` references remain in
`README.md`, `schema.ts`, `schema.json`, or `docs/discovery.md`
(verified via grep); remaining `.well-known` references are
Catalog/OAuth/the "Alternatives considered" explainer only
- [x] Pre-existing media-type strings left untouched — `git diff
origin/main` shows zero changes to existing
`application/mcp-server+json` strings (rename owned by a separate PR);
only the new placement prose names the post-rename
`application/mcp-server-card+json`
- [x] Examples updated and internally consistent with the new prose
- [x] Fresh-eyes subagent review performed; its one cosmetic finding was
addressed and CI re-confirmed green
- [x] CI green (see checks on this PR)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Agent (Claude) <peter@tadasant.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Tadas Antanavicius 2026-06-08 12:40:51 -07:00 • committed by GitHub
commit 10e958fa82
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 72 additions and 11 deletions

View file

@ -10,7 +10,7 @@ A prior attempt to land these types directly in the core spec ([modelcontextprot
## What is a Server Card? ## What is a Server Card?
A **Server Card** is a JSON document, typically published at `https://<host>/.well-known/mcp/server-card`, describing: A **Server Card** is a JSON document — hosted at any unreserved URI, with `GET <streamable-http-url>/server-card` reserved as the recommended location (see [discovery.md](docs/discovery.md)) — describing:
- The server's identity (`name`, `version`, `description`, optional `title` / `icons` / `repository` / `websiteUrl`) - The server's identity (`name`, `version`, `description`, optional `title` / `icons` / `repository` / `websiteUrl`)
- Its remote transport endpoints (URLs, headers, variable templates, supported protocol versions) - Its remote transport endpoints (URLs, headers, variable templates, supported protocol versions)

View file

@ -72,7 +72,7 @@ A domain hosting a single MCP server, using only the required fields:
"identifier": "urn:mcp:server:com.example/weather", "identifier": "urn:mcp:server:com.example/weather",
"displayName": "Weather Service", "displayName": "Weather Service",
"mediaType": "application/mcp-server-card+json", "mediaType": "application/mcp-server-card+json",
"url": "https://example.com/.well-known/mcp-server-card" "url": "https://example.com/mcp/server-card"
} }
] ]
} }
@ -90,19 +90,19 @@ A domain hosting several MCP servers, each with its own server card:
"identifier": "urn:mcp:server:com.acme/code-review", "identifier": "urn:mcp:server:com.acme/code-review",
"displayName": "Code Review Assistant", "displayName": "Code Review Assistant",
"mediaType": "application/mcp-server-card+json", "mediaType": "application/mcp-server-card+json",
"url": "https://acme.com/.well-known/mcp-server-card/code-review" "url": "https://acme.com/code-review/server-card"
}, },
{ {
"identifier": "urn:mcp:server:com.acme/docs-search", "identifier": "urn:mcp:server:com.acme/docs-search",
"displayName": "Documentation Search", "displayName": "Documentation Search",
"mediaType": "application/mcp-server-card+json", "mediaType": "application/mcp-server-card+json",
"url": "https://acme.com/.well-known/mcp-server-card/docs-search" "url": "https://acme.com/docs-search/server-card"
}, },
{ {
"identifier": "urn:mcp:server:com.acme/ci-cd", "identifier": "urn:mcp:server:com.acme/ci-cd",
"displayName": "CI/CD Pipeline", "displayName": "CI/CD Pipeline",
"mediaType": "application/mcp-server-card+json", "mediaType": "application/mcp-server-card+json",
"url": "https://acme.com/.well-known/mcp-server-card/ci-cd" "url": "https://acme.com/ci-cd/server-card"
} }
] ]
} }
@ -124,7 +124,9 @@ flowchart TD
1. Fetch `https://{domain}/.well-known/mcp/catalog.json` 1. Fetch `https://{domain}/.well-known/mcp/catalog.json`
2. If a valid MCP Catalog is returned, iterate over the `entries` array 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` 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 4. Use the server card metadata to configure and establish an MCP connection
Clients SHOULD validate that each entry has `mediaType` set to `application/mcp-server-card+json` Clients SHOULD validate that each entry has `mediaType` set to `application/mcp-server-card+json`
@ -148,6 +150,61 @@ A Server Card includes:
For the full Server Card specification, see For the full Server Card specification, see
[SEP-2127: MCP Server Cards](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127). [SEP-2127: MCP Server Cards](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127).
### 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 ## Relationship to AI Catalog
The MCP Catalog is designed as a transitional mechanism. The The MCP Catalog is designed as a transitional mechanism. The

View file

@ -456,7 +456,7 @@
"type": "object" "type": "object"
}, },
"Server": { "Server": {
"description": "A superset of {@link ServerCard} that additionally describes locally-runnable\npackages. This is the shape used by the MCP Registry's `server.json`.\n\n`Server` documents are typically published to a registry rather than served\nfrom a `.well-known` URI, since they may include instructions for installing\nand executing a server on a client's local machine.", "description": "A superset of {@link ServerCard} that additionally describes locally-runnable\npackages. This is the shape used by the MCP Registry's `server.json`.\n\n`Server` documents are typically published to a registry rather than served\nfrom a Server Card URI (e.g., `<streamable-http-url>/server-card`), since they\nmay include instructions for installing and executing a server on a client's\nlocal machine.",
"properties": { "properties": {
"$schema": { "$schema": {
"description": "The Server Card JSON Schema URI that this document conforms to. Required.\n\nMust be a `/v1/` URL under `static.modelcontextprotocol.io/schemas/`,\nnaming a Server Card / `server.json` schema (e.g.,\n`https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json`\nor `https://static.modelcontextprotocol.io/schemas/v1/server.schema.json`).\nSchema URLs are versioned by the `vN` segment rather than by date so that\nminor, additive revisions of the v1 shape don't bump every published\ndocument's `$schema` URL.", "description": "The Server Card JSON Schema URI that this document conforms to. Required.\n\nMust be a `/v1/` URL under `static.modelcontextprotocol.io/schemas/`,\nnaming a Server Card / `server.json` schema (e.g.,\n`https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json`\nor `https://static.modelcontextprotocol.io/schemas/v1/server.schema.json`).\nSchema URLs are versioned by the `vN` segment rather than by date so that\nminor, additive revisions of the v1 shape don't bump every published\ndocument's `$schema` URL.",
@ -527,7 +527,7 @@
"type": "object" "type": "object"
}, },
"ServerCard": { "ServerCard": {
"description": "A static metadata document describing a remote MCP server, suitable for\npublishing at a `.well-known/mcp-server-card` URI for pre-connection discovery.\n\nServer Cards intentionally describe only what is needed to discover and\nconnect to a remote server: identity, transport, and protocol versions.\nThey do not enumerate primitives (tools, resources, prompts) — those remain\nsubject to runtime listing via the protocol's standard list operations.\n\nThe companion {@link Server} shape is a strict superset that adds local\npackage metadata for use cases like the MCP Registry's `server.json`.", "description": "A static metadata document describing a remote MCP server, suitable for\npre-connection discovery. A Server Card MAY be hosted at any unreserved URI;\nMCP reserves `GET <streamable-http-url>/server-card` as the recommended\nlocation. Clients learn a card's URL from an MCP/AI Catalog rather than\nguessing it.\n\nServer Cards intentionally describe only what is needed to discover and\nconnect to a remote server: identity, transport, and protocol versions.\nThey do not enumerate primitives (tools, resources, prompts) — those remain\nsubject to runtime listing via the protocol's standard list operations.\n\nThe companion {@link Server} shape is a strict superset that adds local\npackage metadata for use cases like the MCP Registry's `server.json`.",
"properties": { "properties": {
"$schema": { "$schema": {
"description": "The Server Card JSON Schema URI that this document conforms to. Required.\n\nMust be a `/v1/` URL under `static.modelcontextprotocol.io/schemas/`,\nnaming a Server Card / `server.json` schema (e.g.,\n`https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json`\nor `https://static.modelcontextprotocol.io/schemas/v1/server.schema.json`).\nSchema URLs are versioned by the `vN` segment rather than by date so that\nminor, additive revisions of the v1 shape don't bump every published\ndocument's `$schema` URL.", "description": "The Server Card JSON Schema URI that this document conforms to. Required.\n\nMust be a `/v1/` URL under `static.modelcontextprotocol.io/schemas/`,\nnaming a Server Card / `server.json` schema (e.g.,\n`https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json`\nor `https://static.modelcontextprotocol.io/schemas/v1/server.schema.json`).\nSchema URLs are versioned by the `vN` segment rather than by date so that\nminor, additive revisions of the v1 shape don't bump every published\ndocument's `$schema` URL.",

View file

@ -11,7 +11,10 @@
/** /**
* A static metadata document describing a remote MCP server, suitable for * A static metadata document describing a remote MCP server, suitable for
* publishing at a `.well-known/mcp-server-card` URI for pre-connection discovery. * pre-connection discovery. A Server Card MAY be hosted at any unreserved URI;
* MCP reserves `GET <streamable-http-url>/server-card` as the recommended
* location. Clients learn a card's URL from an MCP/AI Catalog rather than
* guessing it.
* *
* Server Cards intentionally describe only what is needed to discover and * Server Cards intentionally describe only what is needed to discover and
* connect to a remote server: identity, transport, and protocol versions. * connect to a remote server: identity, transport, and protocol versions.
@ -126,8 +129,9 @@ export interface ServerCard {
* packages. This is the shape used by the MCP Registry's `server.json`. * packages. This is the shape used by the MCP Registry's `server.json`.
* *
* `Server` documents are typically published to a registry rather than served * `Server` documents are typically published to a registry rather than served
* from a `.well-known` URI, since they may include instructions for installing * from a Server Card URI (e.g., `<streamable-http-url>/server-card`), since they
* and executing a server on a client's local machine. * may include instructions for installing and executing a server on a client's
* local machine.
* *
* @see [SEP-2127: MCP Server Cards](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) * @see [SEP-2127: MCP Server Cards](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127)
* @category Server Cards * @category Server Cards