From 9222cf6527473240f93191151b11b1e3dce3ffd8 Mon Sep 17 00:00:00 2001 From: Tadas Antanavicius Date: Mon, 8 Jun 2026 13:36:19 -0700 Subject: [PATCH] docs: require Server Card to be consistent with runtime behavior (#25) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #23. ## What Adds the missing normative requirement that a Server Card's declared values must not contradict the server's actual runtime behavior. The schema already encodes the *omission* half of the threat model (primitives are deliberately excluded because a static manifest drifts from runtime); this adds the *complementary* requirement for the fields the card **does** declare. ### `docs/discovery.md` - **New "Consistency with Runtime Behavior" subsection** under "MCP Server Cards": - A Server Card SHOULD accurately reflect the server's actual runtime behavior. The `serverInfo` (`name`, `version`) and `supportedVersions` returned by [`server/discover`](https://modelcontextprotocol.io/specification/draft/server/discover), plus the transport actually served at each `remotes[]` endpoint (and descriptive `title`/`description`/`icons`), SHOULD NOT contradict the equivalent values declared in the Server Card. - Clients MUST NOT treat Server Card contents as authoritative for security or access-control decisions, and SHOULD verify claims against the live connection — the same posture the spec takes toward primitives, generalized to the declared fields. - **New "Server Card Accuracy" subsection** under "Security Considerations": frames an inaccurate (stale or crafted) card as a mild confusion/downgrade vector, so the consistency requirement is partly a security property, not just correctness. It is explanatory and points back to the single normative statement above rather than re-asserting the MUST NOT. ### `schema.ts` - Short pointer comment in the `ServerCard` JSDoc referencing the discovery-doc requirement, complementing the existing primitives-omission rationale. `schema.json` regenerated via `npm run generate` in the same commit (CI `check` enforces sync). RFC 2119 keywords are used consistently with the rest of `docs/discovery.md`. The runtime reference is phrased around the draft spec's [`server/discover`](https://modelcontextprotocol.io/specification/draft/server/discover) operation — the pre-`initialize` exchange that actually returns `serverInfo`/`supportedVersions` — rather than SEP-2127's `DiscoverResultResponse`, which has no term in this repo. ## Security rationale Because a Server Card is consumed *before* the client connects, an inaccurate card can steer a client toward a weaker configuration (downgrade) or a misidentified server (confusion) before it has observed the server's actual `server/discover` response. Treating the card as advisory rather than authoritative contains this risk. ## Scope Small and focused strictly on the card-vs-runtime consistency requirement. Distinct from #10 (the inverse — primitives the card omits) and #13/#9/#11/#12/#14/#21 (none touch card-vs-runtime consistency). No unrelated changes. ## Verification - [x] `npm run check` passes — `schema.json` is in sync with `schema.ts` and `tsc --noEmit` is clean - [x] `npm run validate` passes — all 7 examples validate against the regenerated schema - [x] `npm run format` clean — no formatting changes left - [x] `schema.json` regenerated and committed alongside the `schema.ts` change (CI `check` enforces this) - [x] Fresh-eyes review by a separate agent — feedback addressed (full-term "Server Card" wording for consistency with the rest of the doc; de-duplicated the MUST NOT so the security section is explanatory and points to the single normative statement) - [x] Maintainer review (Sam) — substance approved; applied the requested brevity pass, tightening both new subsections with no change to any normative statement - [x] `server/discover` spec link verified to resolve to a real draft-spec page documenting `serverInfo` / `supportedVersions` - [x] CI green on the final commit (`0d0389b`) — build, Analyze (actions), and CodeQL all pass This is a docs/comment-only change (no runtime code paths), so the verification is the build/schema toolchain rather than e2e tests. Local toolchain output: ``` ✓ Generated schema.json ✓ schema.json is up to date (npm run check) All 7 example(s) passed. (npm run validate) ``` Opened for human review — **not** merged, per repo policy. --------- Co-authored-by: Claude Opus 4.8 --- docs/discovery.md | 30 ++++++++++++++++++++++++++++++ schema.json | 2 +- schema.ts | 6 ++++++ 3 files changed, 37 insertions(+), 1 deletion(-) diff --git a/docs/discovery.md b/docs/discovery.md index 6768df6..7faffd9 100644 --- a/docs/discovery.md +++ b/docs/discovery.md @@ -150,6 +150,25 @@ A Server Card includes: 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 @@ -233,6 +252,17 @@ information such as: - 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: diff --git a/schema.json b/schema.json index ea100c5..781fc9f 100644 --- a/schema.json +++ b/schema.json @@ -527,7 +527,7 @@ "type": "object" }, "ServerCard": { - "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`.", + "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 fields a Server Card does declare (identity, transport, protocol\nversions) are advisory, not authoritative: they SHOULD be consistent with\nthe server's `server/discover` response, and clients MUST NOT treat them as\nauthoritative for security decisions. See \"Consistency with Runtime\nBehavior\" in docs/discovery.md for the normative requirement.\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 32eccca..a8211d8 100644 --- a/schema.ts +++ b/schema.ts @@ -21,6 +21,12 @@ * They do not enumerate primitives (tools, resources, prompts) — those remain * subject to runtime listing via the protocol's standard list operations. * + * The fields a Server Card does declare (identity, transport, protocol + * versions) are advisory, not authoritative: they SHOULD be consistent with + * the server's `server/discover` response, and clients MUST NOT treat them as + * authoritative for security decisions. See "Consistency with Runtime + * Behavior" in docs/discovery.md for the normative requirement. + * * The companion {@link Server} shape is a strict superset that adds local * package metadata for use cases like the MCP Registry's `server.json`. *