Skip to content

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

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

Add experimental Server Cards support (SEP-2127)#2951
SamMorrowDrums wants to merge 9 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 current schemas

  • $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.
  • AI Catalog entries use the serialized type field, require specVersion, and accept optional displayName. server_card_entry() emits the minimal MCP shape (identifier, type, and url) so human-readable fields cannot drift from the referenced card.
  • Media type — serving and catalog entries use application/mcp-server-card+json; the client recognizes only this type and skips catalog entries declaring anything else.
  • Catalog identifiers are urn:air:{publisher}:mcp:{name} — the card name's reverse-DNS namespace turned back into the publisher's forward-DNS domain (com.example/weatherurn:air:example.com:mcp:weather).
  • Discovery uses the AI Catalog at /.well-known/ai-catalog.json. Discovery responses carry the CORS headers the spec requires (MUST), Cache-Control (SHOULD), strong SHA-256 ETags, and If-None-Match handling for conditional requests.
  • 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.
  • Unsettled AI Catalog metadata remains available but is documented as unstable pending the outcome of Resolve #58: Add ADR-0017 (Loose Coupling) and migrate to extensions array Agent-Card/ai-catalog#77.

Verification

  • ./scripts/test — full coverage suite and strict-no-cover
  • pre-commit run --all-files — formatting, lint, pyright, markdownlint, lock consistency, and README snippets
  • Focused Server Card and AI Catalog tests cover URL and inline discovery, entry filtering, relative URLs, CORS on both 200 and 304, ETags, validation, and client/server helpers

🤖 The implementation and cleanup were AI-assisted and human-directed.

@SamMorrowDrums
SamMorrowDrums force-pushed the experimental-server-card-v2 branch from ffce78e to e087080 Compare June 22, 2026 20:18
Comment thread src/mcp/shared/experimental/ai_catalog/types.py Outdated
Comment thread src/mcp/server/experimental/ai_catalog.py Outdated
dsp-ant and others added 8 commits July 18, 2026 23:06
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.
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>
Co-authored-by: Tadas Antanavicius <tadas@tadasant.com>
Upstream moved MCPModel into the new mcp-types workspace package and
turned mcp.types into a versioned package, so the old
`mcp.types._types.MCPModel` and `mcp.types.Icon` import paths no longer
resolve after rebasing onto main. Import Icon and MCPModel from mcp_types
instead.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot-Session: b7d5eea4-c5d2-4401-b7d8-93d0a9b6259a
🤖 Authored via Claude Code, on behalf of @tadasant.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 96e7cd05-3257-4fde-ad04-bf7098028bb6
@SamMorrowDrums
SamMorrowDrums force-pushed the experimental-server-card-v2 branch from daaee6a to 0d55856 Compare July 18, 2026 21:13
Comment thread src/mcp/server/experimental/ai_catalog.py Outdated
Comment thread src/mcp/server/experimental/ai_catalog.py

@tadasant tadasant left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Did a pass at an automated review to ensure alignment with some of the latest Server Card changes --

Comment thread src/mcp/shared/experimental/ai_catalog/types.py Outdated
Comment thread src/mcp/shared/experimental/ai_catalog/types.py Outdated
Comment thread src/mcp/shared/experimental/ai_catalog/types.py Outdated
Comment thread src/mcp/shared/experimental/ai_catalog/types.py
Adopt the current AI Catalog entry wire shape and MCP identifier namespace. Remove the retired MCP catalog fallback, keep metadata instability explicit, and tighten the focused tests and documentation.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

Copilot-Session: 13eb8021-1971-4c4f-9d07-4e98c0aaff7b
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.

Reference implementations in Python and Go

3 participants