Skip to content

Add experimental Server Cards support (SEP-2127)#2951

Draft
SamMorrowDrums wants to merge 5 commits into
modelcontextprotocol:mainfrom
SamMorrowDrums:experimental-server-card-v2
Draft

Add experimental Server Cards support (SEP-2127)#2951
SamMorrowDrums wants to merge 5 commits into
modelcontextprotocol:mainfrom
SamMorrowDrums:experimental-server-card-v2

Conversation

@SamMorrowDrums

@SamMorrowDrums SamMorrowDrums commented Jun 22, 2026

Copy link
Copy Markdown

Closes modelcontextprotocol/experimental-ext-server-card#16

Summary

Experimental SDK support for MCP Server Cards (SEP-2127) and AI Catalog discovery, opt-in under mcp.{shared,server,client}.experimental. As an experimental extension, these APIs may change without notice.

Takes over #2696 (thanks @dsp-ant — the original two commits are preserved with authorship), rebased cleanly onto current main. #2696 was stacked on the Tasks (SEP-1686) work, since removed from main (#2714).

Opening as a draft for maintainer review — per the extension repo's contribution rules, AI-assisted changes are not self-merged.

What it does

A server builds a Server Card from its own identity, serves it from a Starlette app, and advertises it through an AI Catalog. A client discovers a host's catalog, then fetches and validates the cards it references. Cards describe identity and remote (HTTP) transport only — locally-runnable package metadata stays in the MCP Registry's server.json.

Conformance to the v1 schema

  • $schema is pinned to https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json; a card pointing at the registry's server.schema.json is rejected. A document that omits $schema is defaulted to the v1 URL rather than rejected.
  • Media type — serving and catalog entries use the canonical application/mcp-server-card+json; the client recognizes only this type and skips catalog entries declaring anything else.
  • Catalog identifiers are urn:air:{publisher}:{name} — the card name's reverse-DNS namespace turned back into the publisher's forward-DNS domain (com.example/weatherurn:air:example.com:weather).
  • Discovery goes through an AI Catalog at /.well-known/ai-catalog.json, with the MCP-scoped /.well-known/mcp/catalog.json as a fallback. Discovery responses carry the CORS headers the spec requires (MUST) and Cache-Control (SHOULD).
  • version accepts exact versions only; ranges and wildcards (^1.2.3, ~1.2.3, >=1.2.3, 1.x, 1 || 2, 1 - 2) are rejected.

Verification

pytest tests/experimental/, ruff check / ruff format --check, and pyright all pass; the full CI matrix (Python 3.10–3.14, Ubuntu + Windows, client/server conformance) is green.

🤖 Conformance commit assisted by GitHub Copilot.


Update: Added strong SHA-256 ETag headers and If-None-Match conditional request handling for both Server Card and AI Catalog discovery responses, matching modelcontextprotocol/experimental-ext-server-card#33.

dsp-ant added 2 commits June 22, 2026 21:24
Adds SDK support for MCP Server Cards: static metadata documents that
describe a remote server's identity, transport endpoints, and supported
protocol versions for pre-connection discovery.

- mcp.shared.experimental.server_card: Pydantic models (ServerCard, Server,
  Remote, Package, ...) mirroring mcp.types conventions and validating purely
  through Pydantic.
- mcp.server.experimental.server_card: build_server_card derives a card from a
  server's identity; server_card_route / mount_server_card serve it from a
  Starlette app at /.well-known/mcp/server-card.
- mcp.client.experimental.server_card: fetch_server_card / load_server_card /
  well_known_url ingest and validate a card.

Full test coverage for the new modules.
Server cards are no longer served from a fixed .well-known path. Discovery
now goes through an AI Catalog (https://github.com/Agent-Card/ai-catalog)
published at /.well-known/ai-catalog.json, whose entries point at server
cards hosted anywhere:

- Add mcp.shared.experimental.ai_catalog: Pydantic models for the AI
  Catalog CDDL schema (entries, host, publisher, trust manifest), enforcing
  the url/data exclusivity and trust-manifest identity binding rules. The
  transitional MCP Catalog (/.well-known/mcp/catalog.json) is a structural
  subset and parses with the same models.
- Add mcp.server.experimental.ai_catalog: build catalog entries from server
  cards (urn:mcp:server:<name>) and serve catalogs from the well-known path.
- Add discover_server_cards(): fetch a host's catalog (AI Catalog path with
  fallback to the MCP Catalog path), then fetch or inline-validate every
  MCP server entry. Non-http(s) card URLs from the catalog are rejected.
- Drop WELL_KNOWN_PATH and well_known_url; fetch_server_card now takes the
  card URL directly and server_card_route/mount_server_card require an
  explicit path.

Review fixes:

- Fix the version-range validator rejecting valid semver prereleases like
  1.0.0-x; wildcard segments now only count in the release part, and bare
  "x"/"*" are caught.
- Serve discovery documents with the CORS headers the spec requires
  (MUST) and Cache-Control (SHOULD), exported as DISCOVERY_HEADERS.
- Restrict URL resolution to http(s) schemes to match its error message.
- Rename httpx_client to http_client and default to create_mcp_http_client()
  (30s timeout) to match SDK conventions.
- Document that lenient ingestion defaults a missing $schema/specVersion,
  diverging from the JSON Schema's required fields.
- Correct the mount_server_card docstring: mounting does not bypass auth
  middleware.
- Add missing test package __init__.py files; assert response headers and
  bodies in route tests; patch the SDK's own client factory instead of
  httpx.AsyncClient.
@SamMorrowDrums SamMorrowDrums force-pushed the experimental-server-card-v2 branch from ffce78e to e087080 Compare June 22, 2026 20:18
display_name: str
"""Human-readable name for the artifact."""

media_type: str

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note for reviewers: this field stays mediaType (its serialized alias) to match the currently merged discovery spec.

The extension repo's PR modelcontextprotocol/experimental-ext-server-card#32 (ADR 0014) renames the Catalog Entry mediaType member to type, but a maintainer has it on hold pending the upstream AI Catalog PR. So I'm intentionally not renaming this yet — I'll flip the alias here (and the client-side check in client/experimental/server_card.py) to type once #32 lands, rather than pre-empting an unresolved spec change. The value itself (application/mcp-server-card+json) is unaffected by that rename.

@SamMorrowDrums SamMorrowDrums force-pushed the experimental-server-card-v2 branch 2 times, most recently from ac79b6f to 254b791 Compare June 22, 2026 20:43
Bring the experimental Server Card support up to date with the latest
extension spec (modelcontextprotocol/experimental-ext-server-card) and
the AI Catalog discovery docs. This takes over and supersedes modelcontextprotocol#2696,
which was stacked on the now-removed Tasks (SEP-1686) work.

Conformance fixes:

- Pin the Server Card `$schema` to
  `.../schemas/v1/server-card.schema.json` instead of accepting any
  `/v1/*.schema.json`; a card referencing the registry `server.schema.json`
  is now correctly rejected.
- Use the canonical artifact media type `application/mcp-server-card+json`
  when serving and in catalog entries.
- Derive AI Catalog entry identifiers as `urn:air:{publisher}:{name}`:
  the card name's reverse-DNS namespace is turned back into the publisher's
  forward-DNS domain (`com.example/weather` -> `urn:air:example.com:weather`),
  replacing the old `urn:mcp:server:` scheme.
- Drop the registry-shaped `Server`/`packages` types (and the removed
  `server.schema.json` reference); v1 is card-only, with locally-runnable
  package metadata owned by the MCP Registry. `variables` now lives directly
  on `KeyValueInput`.
- Default `server_card_route`/`mount_server_card` to the spec-reserved
  `/server-card` path.

Restore the `experimental/__init__.py` package markers (regular packages, as
on the original branch) so `py.typed` propagates and pyright stays clean now
that the modules are no longer carried by the Tasks work.

Document that a Server Card must be registered in an AI Catalog to be
discoverable: clients learn a card's URL from a catalog entry rather than
guessing it.

Co-authored-by: David Soria Parra <davidsp@anthropic.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Compute strong SHA-256 ETags for server-card and AI Catalog response bodies, handle matching If-None-Match requests with 304 responses, and cover conditional request behavior in tests.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread src/mcp/server/experimental/ai_catalog.py Outdated
Co-authored-by: Tadas Antanavicius <tadas@tadasant.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

3 participants