Add MCP Server Card (SEP-2127) types + handler#2768
Draft
SamMorrowDrums wants to merge 3 commits into
Draft
Conversation
Introduce a `servercard` package that defines the GitHub MCP Server's Server Card (SEP-2127) and a public, no-auth HTTP handler that serves it. The Server Card is a static, remote-only discovery document: it reuses the identity fields from the registry `server.json` (name, title, description, repository) and advertises a single streamable-http remote (default https://api.githubcopilot.com/mcp/, overridable per environment). Following the spec it omits installable packages, which stay in the registry document. The handler mirrors the OAuth protected-resource-metadata handler: it serves `application/mcp-server-card+json` with read-only CORS, a one-hour Cache-Control, Accept content negotiation, and no authentication. It is registered at the reserved `<streamable-http-url>/server-card` location (both `/server-card` and `/mcp/server-card`) so the remote server repository can mount it identically. Tests validate emitted cards against the canonical experimental-ext-server-card JSON Schema and cover the handler's headers, media type, and method handling. Refs github/copilot-mcp-core#1855, github/copilot-mcp-core#1853 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add strong ETag (SHA-256 of the served body) and RFC 9110 If-None-Match handling to the Server Card response: GET and HEAD always emit ETag alongside Content-Type, CORS, and Cache-Control, and a matching If-None-Match (strong, weak W/ form, or *) returns 304 Not Modified with ETag + Cache-Control and an empty body. Expose the serving logic two ways so the multi-tenant remote can reuse byte-for-byte identical ETag/header behavior: - ServeCard(w, r, *ServerCard): package-level canonical response writer for callers that build a card per request. - Config.RemoteURLFunc func(*http.Request) string: per-request remote URL deriver (recommended for proxima) consumed by the Handler. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Populate the optional forward fields the v1 Server Card schema already defines so the card is complete for discovery: - icons: the GitHub mark in light and dark themes, reusing the embedded Octicons as self-contained data URIs so the card has no external image dependency. The icon order is fixed to keep the serialized card — and thus its ETag — deterministic. - supportedProtocolVersions on the streamable-http remote, defaulted from DefaultProtocolVersions (mirroring the bundled go-sdk's supported versions, which are unexported) and overridable via Config. The canonical card name io.github.github/github-mcp-server is unchanged, preserving the downstream AI Catalog identity derived from it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Makes the GitHub MCP Server discoverable via an MCP Server Card (SEP-2127). Adds a new
pkg/http/servercardpackage with:modelcontextprotocol/experimental-ext-server-cardschema.json—ServerCard,Remote,Repository,Icon,Input,KeyValueInput. Remote-only (nopackages— those stay in the registryserver.json).NewServerCard) for the GitHub MCP Server card, reusing the identity fields from the existing staticserver.json:io.github.github/github-mcp-server, titleGitHub, description (reused, exactly 100 chars)versionwired from the buildversionvarrepository{ url, source: "github", id: "942771284" }streamable-httpremote, defaulthttps://api.githubcopilot.com/mcp/(per-environment host left to the remote repo viaConfig.RemoteURL)$schema=https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.jsonhttp.Handlerserving the card asapplication/mcp-server-card+jsonwith read-only CORS (Allow-Origin *,Allow-Methods GET,Allow-Headers Content-Type),Cache-Control: public, max-age=3600, andAcceptcontent negotiation. It mirrors the existing OAuth Protected-Resource-Metadata handler (pkg/http/oauth) so the remote repo can mount it identically and wire env-specific hosts.httptesttests validating emitted cards against the canonical experimental-ext-server-card JSON Schema (embedded intestdata/) plus handler headers, media type, and method handling.The handler is mounted at the reserved
<streamable-http-url>/server-cardlocation — both/server-cardand/mcp/server-card(GitHub's edge strips/mcp) — and wired into the local HTTP server alongside the OAuth metadata routes.ETag + conditional requests
The card response now carries a strong ETag and honors conditional requests, matching a contract shared byte-for-byte across our implementations (proposed upstream as a SHOULD in experimental-ext-server-card#33):
ETag= SHA-256 of the exact served body bytes, lowercase hex, double-quoted (deterministic for identical content).GETandHEADalways setETagalongsideContent-Type, CORS, andCache-Control: public, max-age=3600.If-None-Matchmatching the strong tag, its weakW/"..."form, or*→304 Not ModifiedwithETag+Cache-Controland an empty body (RFC 9110 weak comparison). Otherwise200+ full body.OPTIONSpreflight and405-for-other-methods are unchanged.Reusable, request-aware serving for multi-tenant remotes
To let the hosted (multi-tenant) remote server reuse identical ETag/header logic when the card URL varies per request (proxima is multi-tenant; URL derives from
X-Forwarded-Host), two reuse points are exposed:func ServeCard(w http.ResponseWriter, r *http.Request, card *ServerCard)— package-level canonical response writer (single source of truth for headers + conditional handling) for callers that build a card per request.Config.RemoteURLFunc func(*http.Request) string— per-request remote-URL deriver consumed by theHandler(recommended for proxima — the remote supplies only the URL deriver and all serving logic stays here).Design decision (flagged)
Types + handler live in OSS here, mirroring the OAuth PRM pattern, rather than entirely in the remote repo. This keeps the card's identity/version consistent with runtime
serverInfoat the source and lets the hosted/remote server consumeservercard.NewHandler(...).RegisterRoutes(...)with a per-environmentRemoteURL.Validation
go build ./...,go test -race ./..., andgolangci-lintall passschema.json(in unit tests, viajsonschema-go)GET→ 200 + correct headers,OPTIONS→ 200 preflight,HEAD→ 200,POST→ 405, incompatibleAccept→ 406, and no impact to existing MCP / OAuth routesGETreturns a stable quoted strong ETag; matching / weak-form / wildcardIf-None-Match→304empty body; non-matching →200+ bodyRefs