Skip to content

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

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

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

Conversation

@SamMorrowDrums

@SamMorrowDrums SamMorrowDrums commented Jun 22, 2026

Copy link
Copy Markdown

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.

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>
@SamMorrowDrums SamMorrowDrums force-pushed the experimental-server-card-v2 branch from 254b791 to 321181b Compare June 22, 2026 20:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants