Add experimental Server Card extension support (SEP-2127) - #2527
Add experimental Server Card extension support (SEP-2127)#2527claude[bot] wants to merge 11 commits into
Conversation
…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 detectedLatest commit: 426aaa1 The changes in this PR will be included in the next version bump. This PR includes changesets to release 10 packages
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 |
@modelcontextprotocol/client
@modelcontextprotocol/codemod
@modelcontextprotocol/core
@modelcontextprotocol/server
@modelcontextprotocol/server-legacy
@modelcontextprotocol/express
@modelcontextprotocol/fastify
@modelcontextprotocol/hono
@modelcontextprotocol/node
commit: |
knoal
left a comment
There was a problem hiding this comment.
Reviewing via MCE A/B pilot 8 (sophia@hermes.local).
Summary
Adds experimental Server Card extension support (SEP = Spec Enhancement Proposal). 4k LOC, 61 files, DRAFT.
What's good
- Marked as experimental — appropriate for a new spec extension.
- Big new public API for Server Card schemas.
⚠️ Pre-merge consideration
Per MCP TS SDK CONTRIBUTING.md:
"Features that might require spec changes (these need a SEP first)"
The PR title literally says "SEP" — verify the PR body links to a SEP document (the modelcontextprotocol/seps repo or similar). If the SEP link is missing, this is a policy violation and should be sent back to the author before merge.
Pre-merge verifications:
- PR body links to a SEP document for review
- All new public APIs are marked @experimental in JSDoc
- The extension is disabled by default (opt-in via configuration)
APPROVE_WITH_CAVEATS — the implementation is in place; the policy gate is the SEP link.
— sophia
Every exported symbol on the four experimental/server-card subpaths (core schemas/types/constants, server builders and responders, client discovery helpers, express router) now carries an @experimental tag in its JSDoc, so editors and API docs surface the stability level at the symbol itself rather than only in module headers.
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-cataloglisting 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-cardsubpath 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 fromserverInfo, explicit fields win, throwsZodErroron invalid input) plus web-standardserverCardResponseandaiCatalogResponseresponders that take aRequestand return aResponse. The responders handle GET/HEAD/OPTIONS, permissive CORS for public metadata,Cache-Control, and a SHA-256 basedETagwithIf-None-Match304 handling. Helpers cover catalog assembly (serverCardCatalogEntry,buildAICatalog) and URL derivation (getServerCardUrl).@modelcontextprotocol/client/experimental/server-card:fetchServerCard,fetchAICatalog, anddiscoverServerCardswith 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 viaonEntryErrorwithout sinking the rest).resolveRemoteandrequiredRemoteInputsturn a card's remote entry plus user inputs into a connect-ready URL and headers.reconcileServerCardcompares a card against the live server'sserverInfoand returns advisory mismatches only, it never blocks a connection. Errors are typed viaServerCardErrorwith a stable code enum.@modelcontextprotocol/express/experimental/server-card:mcpServerCardRouter, a thin Express adapter over the two responders.Links
Notes
examples/server-card-discoverystory (server + client) picked up by the examples runner.pnpm build:all,pnpm check:all, andpnpm test:allare green at the head commit.Generated by Claude Code