Skip to content

Add experimental Server Card extension support (SEP-2127)#2527

Draft
claude[bot] wants to merge 10 commits into
mainfrom
feat/experimental-server-card
Draft

Add experimental Server Card extension support (SEP-2127)#2527
claude[bot] wants to merge 10 commits into
mainfrom
feat/experimental-server-card

Conversation

@claude

@claude claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Requested by David Soria Parra · Slack thread

Before / After

Before: a client has no way to learn anything about a remote MCP server before opening a connection. There is no standard place to find a server's endpoints, auth requirements, or capabilities, and no standard way for a domain to list the MCP servers it hosts.

After: servers can publish a Server Card (a JSON document describing the server and its remote endpoints) and an AI Catalog at /.well-known/ai-catalog listing the cards a domain hosts or references. Clients can discover a domain's catalog, fetch and validate the cards it points to, resolve a remote entry into a concrete URL plus headers, and after connecting compare the card against what the live server actually reported.

How

Everything ships behind a new ./experimental/server-card subpath on four packages, so nothing changes for existing users and the stable barrels stay untouched.

  • @modelcontextprotocol/core/experimental/server-card: Zod schemas, inferred types, and wire constants for Server Cards and AI Catalogs (ServerCardSchema, AICatalogSchema, SERVER_CARD_MEDIA_TYPE, AI_CATALOG_WELL_KNOWN_PATH, and friends). Schema tests round-trip the spec repo's fixture corpus.
  • @modelcontextprotocol/server/experimental/server-card: buildServerCard (prefills from serverInfo, explicit fields win, throws ZodError on invalid input) plus web-standard serverCardResponse and aiCatalogResponse responders that take a Request and return a Response. The responders handle GET/HEAD/OPTIONS, permissive CORS for public metadata, Cache-Control, and a SHA-256 based ETag with If-None-Match 304 handling. Helpers cover catalog assembly (serverCardCatalogEntry, buildAICatalog) and URL derivation (getServerCardUrl).
  • @modelcontextprotocol/client/experimental/server-card: fetchServerCard, fetchAICatalog, and discoverServerCards with hardening for fetching attacker-suppliable URLs: HTTPS required by default (localhost exempt), a private/reserved address guard covering IPv4 and IPv6 including NAT64, 6to4, and mapped-IPv4 embeddings, a 1 MiB response size cap, a redirect cap of 3 with every hop re-checked, and per-entry failure isolation in discovery (one bad catalog entry surfaces via onEntryError without sinking the rest). resolveRemote and requiredRemoteInputs turn a card's remote entry plus user inputs into a connect-ready URL and headers. reconcileServerCard compares a card against the live server's serverInfo and returns advisory mismatches only, it never blocks a connection. Errors are typed via ServerCardError with a stable code enum.
  • @modelcontextprotocol/express/experimental/server-card: mcpServerCardRouter, a thin Express adapter over the two responders.

Links

Notes

  • Changeset included: minor for core, client, and server, patch for express. The change is purely additive.
  • Docs: a Server Cards guide under Advanced with a runnable, self-verifying companion snippet, plus a runnable examples/server-card-discovery story (server + client) picked up by the examples runner.
  • Tests: ~112 new tests across schema fixtures, responder and builder matrices, client guard/fetch/discover/resolve/reconcile suites, the Express router, export-map topology pins, and an end-to-end discovery integration test, plus further tests added during an internal review round (SSRF guard edge cases, discovery cap semantics, error-code coverage). pnpm build:all, pnpm check:all, and pnpm test:all are green at the head commit.
  • The spec fixture files are vendored from the spec repo at 1491bd2 but were reformatted by this repo's Prettier config, so they are semantically identical rather than byte-identical to upstream.
  • CONTRIBUTING asks for a pre-PR issue for new features. None was opened: this implements a direct maintainer request from the linked Slack thread in lieu of one. Two cross-SDK questions worth settling during review: this client accepts lenient media types while the Python SDK is stricter, and the Python implementation uses a 3-segment URN form that diverges slightly from this one.

Generated by Claude Code

claude added 10 commits July 21, 2026 06:38
…2127)

New public subpath @modelcontextprotocol/core/experimental/server-card:
Zod v4 schemas hand-ported from the extension repo's authoritative
schema.ts, inferred types, and the five wire constants. Objects are open
(looseObject), IconSchema is reused from the core spec schemas, and the
schema stays strict (missing/wrong $schema rejected) so the vendored
conformance fixtures round-trip verbatim. Version ranges are rejected at
the validator level per spec prose.
New public subpath @modelcontextprotocol/server/experimental/server-card:
buildServerCard/buildAICatalog validate at startup, getServerCardUrl
computes the reserved <mcp-url>/server-card location,
serverCardCatalogEntry derives the 4-segment urn:air identifier, and
serverCardResponse/aiCatalogResponse answer GET/HEAD/OPTIONS with
permissive CORS, Cache-Control, and a strong SHA-256 ETag with
If-None-Match 304 handling. Matching is synchronous and unmatched paths
fall through as undefined, mirroring oauthMetadataResponse.
…liation

New public subpath @modelcontextprotocol/client/experimental/server-card:
fetchServerCard/fetchAICatalog send credential-free GETs with the
canonical Accept type, enforce HTTPS-only and private-address guards on
every redirect hop, cap response size and redirect count, verify the
media type essence, apply lenient missing-$schema ingestion, and return
etag/cacheControl so the caller owns the cache. discoverServerCards
walks the well-known AI Catalog (404/410 is an empty cacheable miss)
with listing-chain provenance and per-entry error reporting.
requiredRemoteInputs/resolveRemote handle the {var} template grammar
with one aggregated missing-input error, and reconcileServerCard diffs
card claims against the live serverInfo without ever throwing.
…ders

New subpath @modelcontextprotocol/express/experimental/server-card: a
thin Router that converts the Express request to a fetch Request,
delegates to serverCardResponse/aiCatalogResponse, and writes the
Response back. No MCP functionality; unmatched paths call next().
…tegration test

The topology pins gain the ./experimental/server-card entries on core,
client, server, and express. The integration workspace composes the
card and catalog responders in front of createMcpHandler on a real HTTP
server, then discovers, resolves, connects, and reconciles with the
public client helpers.
docs/advanced/server-cards.md covers serving (fetch-API and Express,
including the exact-mount caveat), domain discovery with listing-chain
consent guidance, input resolution and reconciliation, and caller-owned
ETag caching. All fences sync from
examples/guides/advanced/server-cards.examples.ts, which runs the whole
flow in process and produces the quoted output.
HTTP-only self-verifying pair: the server exposes an MCP endpoint plus
its card and AI Catalog; the client is told only the origin, probes the
well-known catalog, validates and resolves the card, connects, calls a
tool, and reconciles the card against the live serverInfo.
Minor for the fixed group (core, client, server), patch for express.
Additive experimental subpaths only; no migration-guide entry needed.
- apply discoverServerCards' maxEntries after the card-type filter, so
  non-card entries in a mixed catalog can no longer silently starve
  discovery of every Server Card
- always exempt the local-dev hosts (localhost, 127.0.0.1, [::1]) from
  the private-address guard, matching the allowHttp JSDoc and the
  extension's local-dev policy; drop the now-unneeded overrides from the
  integration test and example story
- block NAT64 (64:ff9b::/96), 6to4 (2002::/16), and IPv4-compatible
  IPv6 embeddings of private/metadata IPv4 targets in the SSRF guard,
  via a full 8-word IPv6 expansion that fails closed
- report unparseable discovery URLs as 'invalid-url' and per-entry
  transport failures as 'network-error' instead of misfiling them under
  'blocked-host'/'invalid-server-card'; document the SyntaxError cause
  for non-JSON bodies on the two validation codes
- stop listing nested variables of valueless headers in
  requiredRemoteInputs: resolveRemote never reads them, so a consent UI
  would prompt for inputs that are then ignored
- print 'inline' instead of undefined for inline entries in the docs
  guide snippet and the discovery example story
- serverCardCatalogEntry re-validates card.name, so a hand-cast card
  without a namespace fails at boot with a ZodError instead of minting
  a garbage URN
- barrelClean runtime-neutrality traversal now follows ../-relative
  chunk imports, so nested subpath entries' shared chunks are actually
  scanned instead of only the thin entry file
- getServerCardUrl documents that query and fragment are dropped;
  readBodyWithCap documents the buffered fallback for stream-less
  responses; reconcileServerCard documents the namespaced-name
  comparison; requiredRemoteInputs warns it returns optional inputs too
- CLAUDE.md experimental section describes the new server-card subpaths
  instead of claiming the directory is empty; the vendored fixtures
  README acknowledges the Prettier reformatting so the refresh workflow
  does not produce spurious diffs
@changeset-bot

changeset-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: f07aff9

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 10 packages
Name Type
@modelcontextprotocol/core Minor
@modelcontextprotocol/client Minor
@modelcontextprotocol/server Minor
@modelcontextprotocol/express Major
@modelcontextprotocol/core-internal Patch
@modelcontextprotocol/server-legacy Minor
@modelcontextprotocol/fastify Major
@modelcontextprotocol/hono Major
@modelcontextprotocol/node Major
@modelcontextprotocol/codemod Minor

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@pkg-pr-new

pkg-pr-new Bot commented Jul 21, 2026

Copy link
Copy Markdown

Open in StackBlitz

@modelcontextprotocol/client

npm i https://pkg.pr.new/@modelcontextprotocol/client@2527

@modelcontextprotocol/codemod

npm i https://pkg.pr.new/@modelcontextprotocol/codemod@2527

@modelcontextprotocol/core

npm i https://pkg.pr.new/@modelcontextprotocol/core@2527

@modelcontextprotocol/server

npm i https://pkg.pr.new/@modelcontextprotocol/server@2527

@modelcontextprotocol/server-legacy

npm i https://pkg.pr.new/@modelcontextprotocol/server-legacy@2527

@modelcontextprotocol/express

npm i https://pkg.pr.new/@modelcontextprotocol/express@2527

@modelcontextprotocol/fastify

npm i https://pkg.pr.new/@modelcontextprotocol/fastify@2527

@modelcontextprotocol/hono

npm i https://pkg.pr.new/@modelcontextprotocol/hono@2527

@modelcontextprotocol/node

npm i https://pkg.pr.new/@modelcontextprotocol/node@2527

commit: f07aff9

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.

1 participant