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:
parent
7c340f43e7
commit
10e958fa82
4 changed files with 72 additions and 11 deletions
|
|
@ -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://<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`)
|
||||
- Its remote transport endpoints (URLs, headers, variable templates, supported protocol versions)
|
||||
|
|
|
|||
|
|
@ -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 <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
|
||||
|
|
|
|||
|
|
@ -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., `<streamable-http-url>/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 <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": {
|
||||
"$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.",
|
||||
|
|
|
|||
10
schema.ts
10
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 <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
|
||||
* 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., `<streamable-http-url>/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
|
||||
|
|
|
|||
Loading…
Reference in a new issue