From 10e958fa82b8d0440fac855ad55a2093e4ab1665 Mon Sep 17 00:00:00 2001 From: Tadas Antanavicius Date: Mon, 8 Jun 2026 12:40:51 -0700 Subject: [PATCH] Server Cards: recommend `/server-card` over `.well-known` (resolves #12) (#22) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Resolves #12. ## What & why Moves the **single-server Server Card** off a `.well-known` path. We reserve `GET /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 /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 ` 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) Co-authored-by: Claude Opus 4.8 --- README.md | 2 +- docs/discovery.md | 67 +++++++++++++++++++++++++++++++++++++++++++---- schema.json | 4 +-- schema.ts | 10 ++++--- 4 files changed, 72 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index 7ca95ff..c76c238 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ A prior attempt to land these types directly in the core spec ([modelcontextprot ## What is a Server Card? -A **Server Card** is a JSON document, typically published at `https:///.well-known/mcp/server-card`, describing: +A **Server Card** is a JSON document — hosted at any unreserved URI, with `GET /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`) - Its remote transport endpoints (URLs, headers, variable templates, supported protocol versions) diff --git a/docs/discovery.md b/docs/discovery.md index 3aea775..2a0cf24 100644 --- a/docs/discovery.md +++ b/docs/discovery.md @@ -72,7 +72,7 @@ A domain hosting a single MCP server, using only the required fields: "identifier": "urn:mcp:server:com.example/weather", "displayName": "Weather Service", "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", "displayName": "Code Review Assistant", "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", "displayName": "Documentation Search", "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", "displayName": "CI/CD Pipeline", "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` 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 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 [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 /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 ` 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 + `` + `/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 diff --git a/schema.json b/schema.json index af69faa..ea100c5 100644 --- a/schema.json +++ b/schema.json @@ -456,7 +456,7 @@ "type": "object" }, "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., `/server-card`), since they\nmay include instructions for installing and executing a server on a client's\nlocal machine.", "properties": { "$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.", @@ -527,7 +527,7 @@ "type": "object" }, "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 /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": { "$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.", diff --git a/schema.ts b/schema.ts index 34cb067..32eccca 100644 --- a/schema.ts +++ b/schema.ts @@ -11,7 +11,10 @@ /** * 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 /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 * 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`. * * `Server` documents are typically published to a registry rather than served - * from a `.well-known` URI, since they may include instructions for installing - * and executing a server on a client's local machine. + * from a Server Card URI (e.g., `/server-card`), since they + * 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) * @category Server Cards