Skip to content

Add MCP Server Card (SEP-2127) types + handler#2768

Draft
SamMorrowDrums wants to merge 3 commits into
mainfrom
sammorrowdrums-server-card-handler
Draft

Add MCP Server Card (SEP-2127) types + handler#2768
SamMorrowDrums wants to merge 3 commits into
mainfrom
sammorrowdrums-server-card-handler

Conversation

@SamMorrowDrums

@SamMorrowDrums SamMorrowDrums commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

What

Makes the GitHub MCP Server discoverable via an MCP Server Card (SEP-2127). Adds a new pkg/http/servercard package with:

  1. Go types matching modelcontextprotocol/experimental-ext-server-card schema.jsonServerCard, Remote, Repository, Icon, Input, KeyValueInput. Remote-only (no packages — those stay in the registry server.json).
  2. A constructor (NewServerCard) for the GitHub MCP Server card, reusing the identity fields from the existing static server.json:
    • name io.github.github/github-mcp-server, title GitHub, description (reused, exactly 100 chars)
    • version wired from the build version var
    • repository { url, source: "github", id: "942771284" }
    • one streamable-http remote, default https://api.githubcopilot.com/mcp/ (per-environment host left to the remote repo via Config.RemoteURL)
    • $schema = https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json
  3. A public, no-auth http.Handler serving the card as application/mcp-server-card+json with read-only CORS (Allow-Origin *, Allow-Methods GET, Allow-Headers Content-Type), Cache-Control: public, max-age=3600, and Accept content 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.
  4. Table-driven httptest tests validating emitted cards against the canonical experimental-ext-server-card JSON Schema (embedded in testdata/) plus handler headers, media type, and method handling.

The handler is mounted at the reserved <streamable-http-url>/server-card location — both /server-card and /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):

  • Strong ETag = SHA-256 of the exact served body bytes, lowercase hex, double-quoted (deterministic for identical content).
  • GET and HEAD always set ETag alongside Content-Type, CORS, and Cache-Control: public, max-age=3600.
  • If-None-Match matching the strong tag, its weak W/"..." form, or *304 Not Modified with ETag + Cache-Control and an empty body (RFC 9110 weak comparison). Otherwise 200 + full body.
  • OPTIONS preflight and 405-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 the Handler (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 serverInfo at the source and lets the hosted/remote server consume servercard.NewHandler(...).RegisterRoutes(...) with a per-environment RemoteURL.

Validation

  • go build ./..., go test -race ./..., and golangci-lint all pass
  • Emitted JSON validated against schema.json (in unit tests, via jsonschema-go)
  • Live smoke test confirms GET → 200 + correct headers, OPTIONS → 200 preflight, HEAD → 200, POST → 405, incompatible Accept → 406, and no impact to existing MCP / OAuth routes
  • Live smoke test confirms ETag conditional behavior: GET returns a stable quoted strong ETag; matching / weak-form / wildcard If-None-Match304 empty body; non-matching → 200 + body

Refs

Draft — do not merge.

SamMorrowDrums and others added 3 commits June 25, 2026 12:05
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>
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