From fd7940ff48bea0df876fc5d559ac5b9e62922c1f Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Thu, 11 Jun 2026 18:46:01 +0100 Subject: [PATCH 1/2] Remove registry Server/package types; make the extension card-only Server Cards describe remote connectivity only. The Server type and the package-installation machinery (Package, PackageTransport, StdioTransport, StreamableHttpPackageTransport, SsePackageTransport, Argument helpers) were carried over from the abandoned core-spec PR when this repo was bootstrapped; locally-installable package metadata is owned by the MCP Registry's server.json schema, not by this extension. The schema is still pre-release, so the v1 family is edited in place rather than minting a v2. - schema.ts: drop Server and all package types; keep ServerCard, Remote, Repository, Icon, MetaObject, and the Input/KeyValueInput helpers that remotes still use for URL templating and headers - generate with --noExtraProps so a document containing packages (or any unknown property) is rejected; _meta stays open as the extension point - pin the $schema pattern to the single server-card.schema.json name - remove Server examples; add invalid examples asserting that a card with packages and a card naming the removed server.schema.json both fail validation - README: replace the companion-Server framing with a 'Relationship to the MCP Registry' section --- README.md | 15 +- examples/Server/valid/with-package.json | 27 -- .../ServerCard/invalid/with-packages.json | 19 + .../ServerCard/invalid/wrong-schema-name.json | 6 + schema.json | 417 +----------------- schema.ts | 286 ++---------- scripts/generate-schema.ts | 2 +- scripts/validate-examples.ts | 40 +- 8 files changed, 95 insertions(+), 717 deletions(-) delete mode 100644 examples/Server/valid/with-package.json create mode 100644 examples/ServerCard/invalid/with-packages.json create mode 100644 examples/ServerCard/invalid/wrong-schema-name.json diff --git a/README.md b/README.md index c76c238..041f1bb 100644 --- a/README.md +++ b/README.md @@ -16,9 +16,15 @@ A **Server Card** is a JSON document — hosted at any unreserved URI, with `GET - Its remote transport endpoints (URLs, headers, variable templates, supported protocol versions) - Optional registry-style extension metadata (`_meta`) -The companion **Server** document is a strict superset that adds locally-runnable `packages` — it is the shape the [MCP Registry](https://github.com/modelcontextprotocol/registry) uses for `server.json`. +Server Cards intentionally omit primitive listings (tools, resources, prompts) — those remain subject to runtime listing via the protocol's standard list operations. They also intentionally omit local installation metadata — see [Relationship to the MCP Registry](#relationship-to-the-mcp-registry). -Server Cards intentionally omit primitive listings (tools, resources, prompts) — those remain subject to runtime listing via the protocol's standard list operations. +## Relationship to the MCP Registry + +A Server Card describes **remote connectivity only**. Metadata for locally-installable servers — packages, registries (npm, PyPI, OCI, NuGet, MCPB), runtime hints, command-line arguments, environment variables — lives in the [MCP Registry](https://github.com/modelcontextprotocol/registry)'s [`server.json` schema](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/generic-server-json.md), which is owned by the registry, not by this extension. + +A registry entry MAY reference or embed a Server Card's remote connection info, but this repository does not define any package types, and a document containing `packages` is not a valid Server Card. + +Vendors who genuinely need to attach install hints to a Server Card can use namespaced [`_meta`](https://modelcontextprotocol.io/specification/draft/basic#meta) extension metadata, which remains the card's extension point. ## Layout @@ -30,7 +36,6 @@ scripts/ validate-examples.ts # Validates examples/ against the generated schema examples/ ServerCard/{valid,invalid} # Example Server Card documents - Server/{valid,invalid} # Example Server (registry-shaped) documents ``` The generated `schema.json` is checked into the repo so consumers can grab it without running the toolchain. @@ -55,14 +60,14 @@ The `$schema` field on every document MUST be a URL of the form: https://static.modelcontextprotocol.io/schemas/v1/.schema.json ``` -Schema URLs are versioned by their `vN` segment rather than by date, so additive revisions of the v1 shape don't bump every published document's `$schema`. Breaking changes would publish a `v2` family. +Schema URLs are versioned by their `vN` segment rather than by date. Server Card objects are closed (`additionalProperties: false`): a document using a field the schema doesn't declare is rejected, and vendor-specific data belongs in namespaced `_meta`, which stays open. Because the schema is closed, once `v1` is published a breaking revision of the shape would publish a new `vN` family rather than mutating `v1` in place. The `v1` shape is still pre-release and card-only — it intentionally does not include the registry-shaped `Server` / `packages` types. ## Graduation plan When the SEP is accepted and Server Cards graduate from this experimental extension: 1. The contents of `schema.ts` in this repo move into `schema/draft/schema.ts` of [`modelcontextprotocol/modelcontextprotocol`](https://github.com/modelcontextprotocol/modelcontextprotocol). The two `MetaObject` and `Icon` definitions inlined here at the bottom of `schema.ts` already exist in the main spec and are dropped from the migration. -2. The main spec's existing `scripts/generate-schemas.ts` regenerates `schema/draft/schema.json` (and downstream `docs/specification/draft/schema.mdx`) — no per-extension generator is required there. +2. The main spec's existing `scripts/generate-schemas.ts` regenerates `schema/draft/schema.json` (and downstream `docs/specification/draft/schema.mdx`) — no per-extension generator is required there. One caveat: this repo passes `--noExtraProps` to `typescript-json-schema`, which is what makes Server Card objects closed (`additionalProperties: false`) and rejects registry-style `packages` documents. The main spec's generator does not use that flag, so the closed-object behavior must be carried over (or re-expressed) during migration — otherwise the graduated schema would silently accept unknown properties again. 3. Published documents update their `$schema` to point at the main spec's hosted schema URL (e.g., `https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json` served from `modelcontextprotocol/static`). 4. This repository is archived with a pointer to the relevant section of `schema/draft/schema.ts` in the main spec. diff --git a/examples/Server/valid/with-package.json b/examples/Server/valid/with-package.json deleted file mode 100644 index 55159a9..0000000 --- a/examples/Server/valid/with-package.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server.schema.json", - "name": "example-org/with-package", - "version": "0.4.2", - "description": "Server document with a locally-runnable npm package.", - "repository": { - "url": "https://github.com/example-org/with-package", - "source": "github" - }, - "packages": [ - { - "registryType": "npm", - "identifier": "@example-org/with-package", - "version": "0.4.2", - "runtimeHint": "npx", - "transport": { "type": "stdio" }, - "environmentVariables": [ - { - "name": "EXAMPLE_API_KEY", - "description": "Example API key.", - "isRequired": true, - "isSecret": true - } - ] - } - ] -} diff --git a/examples/ServerCard/invalid/with-packages.json b/examples/ServerCard/invalid/with-packages.json new file mode 100644 index 0000000..e0eda0a --- /dev/null +++ b/examples/ServerCard/invalid/with-packages.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json", + "name": "example-org/with-packages", + "version": "0.4.2", + "description": "Package metadata belongs to the registry's server.json, not a Server Card.", + "remotes": [ + { + "type": "streamable-http", + "url": "https://example.com/mcp" + } + ], + "packages": [ + { + "registryType": "npm", + "identifier": "@example-org/with-packages", + "transport": { "type": "stdio" } + } + ] +} diff --git a/examples/ServerCard/invalid/wrong-schema-name.json b/examples/ServerCard/invalid/wrong-schema-name.json new file mode 100644 index 0000000..e4f822b --- /dev/null +++ b/examples/ServerCard/invalid/wrong-schema-name.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server.schema.json", + "name": "example-org/wrong-schema-name", + "version": "1.0.0", + "description": "References the removed registry server.json schema; only server-card.schema.json is valid in v1." +} diff --git a/schema.json b/schema.json index 781fc9f..36043df 100644 --- a/schema.json +++ b/schema.json @@ -1,18 +1,8 @@ { "$schema": "https://json-schema.org/draft/2020-12/schema", "$defs": { - "Argument": { - "anyOf": [ - { - "$ref": "#/$defs/PositionalArgument" - }, - { - "$ref": "#/$defs/NamedArgument" - } - ], - "description": "A command-line argument supplied to a package's binary or runtime." - }, "Icon": { + "additionalProperties": false, "description": "An optionally-sized icon that can be displayed in a user interface.", "properties": { "mimeType": { @@ -41,7 +31,8 @@ "type": "object" }, "Input": { - "description": "A user-supplied or pre-set input value, used in {@link Package} argument\nand environment-variable definitions.", + "additionalProperties": false, + "description": "A user-supplied or pre-set input value, used for {@link Remote} URL\nvariables and header values.", "properties": { "choices": { "description": "Allowed values for the input. If provided, the user must select one.", @@ -64,7 +55,7 @@ "type": "string" }, "isRequired": { - "description": "Whether the input must be supplied for the package to run.", + "description": "Whether the input must be supplied for the connection to succeed.", "type": "boolean" }, "isSecret": { @@ -82,57 +73,9 @@ }, "type": "object" }, - "InputWithVariables": { - "description": "An {@link Input} whose `value` may reference variables for substitution.", - "properties": { - "choices": { - "description": "Allowed values for the input. If provided, the user must select one.", - "items": { - "type": "string" - }, - "type": "array" - }, - "default": { - "description": "Default value for the input. SHOULD be a valid value for the input.", - "type": "string" - }, - "description": { - "description": "Human-readable explanation of the input. Clients can use this to provide\ncontext to the user.", - "type": "string" - }, - "format": { - "description": "Specifies the input format. `\"filepath\"` should be interpreted as a file\non the user's filesystem. When the input is converted to a string,\nbooleans should be represented by `\"true\"`/`\"false\"`, and numbers by\ndecimal values.", - "enum": ["boolean", "filepath", "number", "string"], - "type": "string" - }, - "isRequired": { - "description": "Whether the input must be supplied for the package to run.", - "type": "boolean" - }, - "isSecret": { - "description": "Whether the input is a secret value (e.g., password, token). If true,\nclients should handle the value securely.", - "type": "boolean" - }, - "placeholder": { - "description": "Placeholder displayed during configuration to provide examples or\nguidance about the expected form of the input.", - "type": "string" - }, - "value": { - "description": "Pre-set value for the input. If set, the value should not be configurable\nby end users. Identifiers wrapped in `{curly_braces}` will be replaced\nwith the corresponding entries from the input's `variables` map (if any).", - "type": "string" - }, - "variables": { - "additionalProperties": { - "$ref": "#/$defs/Input" - }, - "description": "Variables referenced by `{curly_braces}` identifiers in `value`. The map\nkey is the variable name; the value defines the variable's properties.", - "type": "object" - } - }, - "type": "object" - }, "KeyValueInput": { - "description": "A named input — used for environment variables and HTTP headers.", + "additionalProperties": false, + "description": "A named {@link Input} — used for HTTP headers — whose `value` may reference\nvariables for substitution.", "properties": { "choices": { "description": "Allowed values for the input. If provided, the user must select one.", @@ -155,7 +98,7 @@ "type": "string" }, "isRequired": { - "description": "Whether the input must be supplied for the package to run.", + "description": "Whether the input must be supplied for the connection to succeed.", "type": "boolean" }, "isSecret": { @@ -163,7 +106,7 @@ "type": "boolean" }, "name": { - "description": "Name of the header or environment variable.", + "description": "Name of the header.", "type": "string" }, "placeholder": { @@ -186,214 +129,12 @@ "type": "object" }, "MetaObject": { + "additionalProperties": {}, "description": "Represents the contents of a `_meta` field, which clients and servers use to attach additional metadata to their interactions.\n\nCertain key names are reserved by MCP for protocol-level metadata; implementations MUST NOT make assumptions about values at these keys. Additionally, specific schema definitions may reserve particular names for purpose-specific metadata, as declared in those definitions.\n\nValid keys have two segments:\n\n**Prefix:**\n- Optional — if specified, MUST be a series of _labels_ separated by dots (`.`), followed by a slash (`/`).\n- Labels MUST start with a letter and end with a letter or digit. Interior characters may be letters, digits, or hyphens (`-`).\n- Any prefix consisting of zero or more labels, followed by `modelcontextprotocol` or `mcp`, followed by any label, is **reserved** for MCP use. For example: `modelcontextprotocol.io/`, `mcp.dev/`, `api.modelcontextprotocol.org/`, and `tools.mcp.com/` are all reserved.\n\n**Name:**\n- Unless empty, MUST start and end with an alphanumeric character (`[a-z0-9A-Z]`).\n- Interior characters may be alphanumeric, hyphens (`-`), underscores (`_`), or dots (`.`).", "type": "object" }, - "NamedArgument": { - "description": "A named command-line input — a `--flag={value}` parameter.", - "properties": { - "choices": { - "description": "Allowed values for the input. If provided, the user must select one.", - "items": { - "type": "string" - }, - "type": "array" - }, - "default": { - "description": "Default value for the input. SHOULD be a valid value for the input.", - "type": "string" - }, - "description": { - "description": "Human-readable explanation of the input. Clients can use this to provide\ncontext to the user.", - "type": "string" - }, - "format": { - "description": "Specifies the input format. `\"filepath\"` should be interpreted as a file\non the user's filesystem. When the input is converted to a string,\nbooleans should be represented by `\"true\"`/`\"false\"`, and numbers by\ndecimal values.", - "enum": ["boolean", "filepath", "number", "string"], - "type": "string" - }, - "isRepeated": { - "description": "Whether the argument can be repeated multiple times.", - "type": "boolean" - }, - "isRequired": { - "description": "Whether the input must be supplied for the package to run.", - "type": "boolean" - }, - "isSecret": { - "description": "Whether the input is a secret value (e.g., password, token). If true,\nclients should handle the value securely.", - "type": "boolean" - }, - "name": { - "description": "The flag name, including any leading dashes (e.g., `\"--port\"`).", - "type": "string" - }, - "placeholder": { - "description": "Placeholder displayed during configuration to provide examples or\nguidance about the expected form of the input.", - "type": "string" - }, - "type": { - "const": "named", - "type": "string" - }, - "value": { - "description": "Pre-set value for the input. If set, the value should not be configurable\nby end users. Identifiers wrapped in `{curly_braces}` will be replaced\nwith the corresponding entries from the input's `variables` map (if any).", - "type": "string" - }, - "variables": { - "additionalProperties": { - "$ref": "#/$defs/Input" - }, - "description": "Variables referenced by `{curly_braces}` identifiers in `value`. The map\nkey is the variable name; the value defines the variable's properties.", - "type": "object" - } - }, - "required": ["name", "type"], - "type": "object" - }, - "Package": { - "description": "Metadata for installing and running a packaged MCP server locally.", - "properties": { - "environmentVariables": { - "description": "Environment variables to be set when running the package.", - "items": { - "$ref": "#/$defs/KeyValueInput" - }, - "type": "array" - }, - "fileSha256": { - "description": "SHA-256 hash of the package file for integrity verification. Required for\nMCPB packages and optional for other package types. If present, MCP\nclients MUST validate the downloaded file matches the hash before running\npackages to ensure file integrity.", - "pattern": "^[a-f0-9]{64}$", - "type": "string" - }, - "identifier": { - "description": "Package identifier — either a package name (for registries)\nor a URL (for direct downloads).", - "type": "string" - }, - "packageArguments": { - "description": "Arguments passed to the package's binary.", - "items": { - "$ref": "#/$defs/Argument" - }, - "type": "array" - }, - "registryBaseUrl": { - "description": "Base URL of the package registry.", - "format": "uri", - "type": "string" - }, - "registryType": { - "description": "Registry type indicating how to download packages\n(e.g., `\"npm\"`, `\"pypi\"`, `\"oci\"`, `\"nuget\"`, `\"mcpb\"`).", - "type": "string" - }, - "runtimeArguments": { - "description": "Arguments passed to the package's runtime command (such as `docker` or\n`npx`). The `runtimeHint` field should be provided when `runtimeArguments`\nare present.", - "items": { - "$ref": "#/$defs/Argument" - }, - "type": "array" - }, - "runtimeHint": { - "description": "A hint to help clients determine the appropriate runtime for the package\n(e.g., `\"npx\"`, `\"uvx\"`, `\"docker\"`, `\"dnx\"`). Should be provided when\n`runtimeArguments` are present.", - "type": "string" - }, - "supportedProtocolVersions": { - "description": "MCP protocol versions actively supported by this package.", - "items": { - "type": "string" - }, - "type": "array" - }, - "transport": { - "$ref": "#/$defs/PackageTransport", - "description": "Transport configuration for invoking this package after installation." - }, - "version": { - "description": "Package version.", - "minLength": 1, - "type": "string" - } - }, - "required": ["identifier", "registryType", "transport"], - "type": "object" - }, - "PackageTransport": { - "anyOf": [ - { - "$ref": "#/$defs/StdioTransport" - }, - { - "$ref": "#/$defs/StreamableHttpPackageTransport" - }, - { - "$ref": "#/$defs/SsePackageTransport" - } - ], - "description": "Transport protocol configuration for a locally-runnable package." - }, - "PositionalArgument": { - "description": "A positional command-line input — a value inserted verbatim into the\ncommand line.", - "properties": { - "choices": { - "description": "Allowed values for the input. If provided, the user must select one.", - "items": { - "type": "string" - }, - "type": "array" - }, - "default": { - "description": "Default value for the input. SHOULD be a valid value for the input.", - "type": "string" - }, - "description": { - "description": "Human-readable explanation of the input. Clients can use this to provide\ncontext to the user.", - "type": "string" - }, - "format": { - "description": "Specifies the input format. `\"filepath\"` should be interpreted as a file\non the user's filesystem. When the input is converted to a string,\nbooleans should be represented by `\"true\"`/`\"false\"`, and numbers by\ndecimal values.", - "enum": ["boolean", "filepath", "number", "string"], - "type": "string" - }, - "isRepeated": { - "description": "Whether the argument can be repeated multiple times in the command line.", - "type": "boolean" - }, - "isRequired": { - "description": "Whether the input must be supplied for the package to run.", - "type": "boolean" - }, - "isSecret": { - "description": "Whether the input is a secret value (e.g., password, token). If true,\nclients should handle the value securely.", - "type": "boolean" - }, - "placeholder": { - "description": "Placeholder displayed during configuration to provide examples or\nguidance about the expected form of the input.", - "type": "string" - }, - "type": { - "const": "positional", - "type": "string" - }, - "value": { - "description": "Pre-set value for the input. If set, the value should not be configurable\nby end users. Identifiers wrapped in `{curly_braces}` will be replaced\nwith the corresponding entries from the input's `variables` map (if any).", - "type": "string" - }, - "valueHint": { - "description": "Identifier for the positional argument. It is not part of the command\nline; it may be used by client configuration as a label identifying the\nargument, and it identifies the value in transport URL variable\nsubstitution.\n\nImplementations SHOULD ensure that at least one of `valueHint` or\n`value` is set so the positional argument resolves to a concrete value.", - "type": "string" - }, - "variables": { - "additionalProperties": { - "$ref": "#/$defs/Input" - }, - "description": "Variables referenced by `{curly_braces}` identifiers in `value`. The map\nkey is the variable name; the value defines the variable's properties.", - "type": "object" - } - }, - "required": ["type"], - "type": "object" - }, "Remote": { + "additionalProperties": false, "description": "Metadata for connecting to a remote (HTTP-based) MCP server endpoint.", "properties": { "headers": { @@ -424,7 +165,7 @@ "additionalProperties": { "$ref": "#/$defs/Input" }, - "description": "Configuration variables that can be referenced as `{curly_braces}`\nplaceholders in `url` (and inside header values via\n{@link InputWithVariables.variables}). The map key is the variable\nname; the value defines the variable's properties (e.g., human-readable\ndescription, default, whether it is required or secret).", + "description": "Configuration variables that can be referenced as `{curly_braces}`\nplaceholders in `url` (and inside header values via\n{@link KeyValueInput.variables}). The map key is the variable\nname; the value defines the variable's properties (e.g., human-readable\ndescription, default, whether it is required or secret).", "type": "object" } }, @@ -432,6 +173,7 @@ "type": "object" }, "Repository": { + "additionalProperties": false, "description": "Repository metadata for the MCP server source code. Enables users and\nsecurity experts to inspect the code, improving transparency.", "properties": { "id": { @@ -455,84 +197,14 @@ "required": ["source", "url"], "type": "object" }, - "Server": { - "description": "A superset of {@link ServerCard} that additionally describes locally-runnable\npackages. This is the shape used by the MCP Registry's `server.json`.\n\n`Server` documents are typically published to a registry rather than served\nfrom a Server Card URI (e.g., `/server-card`), since they\nmay include instructions for installing and executing a server on a client's\nlocal machine.", - "properties": { - "$schema": { - "description": "The Server Card JSON Schema URI that this document conforms to. Required.\n\nMust be a `/v1/` URL under `static.modelcontextprotocol.io/schemas/`,\nnaming a Server Card / `server.json` schema (e.g.,\n`https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json`\nor `https://static.modelcontextprotocol.io/schemas/v1/server.schema.json`).\nSchema URLs are versioned by the `vN` segment rather than by date so that\nminor, additive revisions of the v1 shape don't bump every published\ndocument's `$schema` URL.", - "format": "uri", - "pattern": "^https://static\\.modelcontextprotocol\\.io/schemas/v1/[^/]+\\.schema\\.json$", - "type": "string" - }, - "_meta": { - "$ref": "#/$defs/MetaObject", - "description": "Extension metadata using reverse-DNS namespacing for vendor-specific data.\n\nFollows the protocol's standard `_meta` definition." - }, - "description": { - "description": "Clear human-readable explanation of server functionality. Should focus on\ncapabilities, not implementation details.", - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "icons": { - "description": "Optional set of sized icons that the client can display in a user interface.\n\nClients that support rendering icons MUST support at least the following\nMIME types: `image/png` and `image/jpeg` (safe, universal compatibility).\nClients SHOULD also support: `image/svg+xml` (scalable but requires security\nprecautions) and `image/webp` (modern, efficient format).", - "items": { - "$ref": "#/$defs/Icon" - }, - "type": "array" - }, - "name": { - "description": "Server name in reverse-DNS format. Must contain exactly one forward slash\nseparating namespace from server name.", - "maxLength": 200, - "minLength": 3, - "pattern": "^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$", - "type": "string" - }, - "packages": { - "description": "Metadata helpful for running and connecting to local instances of this MCP server.", - "items": { - "$ref": "#/$defs/Package" - }, - "type": "array" - }, - "remotes": { - "description": "Metadata helpful for making HTTP-based connections to this MCP server.", - "items": { - "$ref": "#/$defs/Remote" - }, - "type": "array" - }, - "repository": { - "$ref": "#/$defs/Repository", - "description": "Optional repository metadata for the MCP server source code.\nRecommended for transparency and security inspection." - }, - "title": { - "description": "Optional human-readable title or display name for the MCP server.\nMCP subregistries or clients MAY choose to use this for display purposes.", - "maxLength": 100, - "minLength": 1, - "type": "string" - }, - "version": { - "description": "Version string for this server. SHOULD follow semantic versioning\n(e.g., '1.0.2', '2.1.0-alpha'). Equivalent of `Implementation.version`\nin the MCP specification. Non-semantic versions are allowed but may not\nsort predictably. Version ranges are rejected (e.g., '^1.2.3', '~1.2.3',\n'>=1.2.3', '1.x', '1.*').", - "maxLength": 255, - "type": "string" - }, - "websiteUrl": { - "description": "Optional URL to the server's homepage, documentation, or project website.\nProvides a central link for users to learn more about the server.\nParticularly useful when the server has custom installation instructions\nor setup requirements.", - "format": "uri", - "type": "string" - } - }, - "required": ["$schema", "description", "name", "version"], - "type": "object" - }, "ServerCard": { - "description": "A static metadata document describing a remote MCP server, suitable for\npre-connection discovery. A Server Card MAY be hosted at any unreserved URI;\nMCP reserves `GET /server-card` as the recommended\nlocation. Clients learn a card's URL from an MCP/AI Catalog rather than\nguessing it.\n\nServer Cards intentionally describe only what is needed to discover and\nconnect to a remote server: identity, transport, and protocol versions.\nThey do not enumerate primitives (tools, resources, prompts) — those remain\nsubject to runtime listing via the protocol's standard list operations.\n\nThe fields a Server Card does declare (identity, transport, protocol\nversions) are advisory, not authoritative: they SHOULD be consistent with\nthe server's `server/discover` response, and clients MUST NOT treat them as\nauthoritative for security decisions. See \"Consistency with Runtime\nBehavior\" in docs/discovery.md for the normative requirement.\n\nThe companion {@link Server} shape is a strict superset that adds local\npackage metadata for use cases like the MCP Registry's `server.json`.", + "additionalProperties": false, + "description": "A static metadata document describing a remote MCP server, suitable for\npre-connection discovery. A Server Card MAY be hosted at any unreserved URI;\nMCP reserves `GET /server-card` as the recommended\nlocation. Clients learn a card's URL from an MCP/AI Catalog rather than\nguessing it.\n\nServer Cards intentionally describe only what is needed to discover and\nconnect to a remote server: identity, transport, and protocol versions.\nThey do not enumerate primitives (tools, resources, prompts) — those remain\nsubject to runtime listing via the protocol's standard list operations.\nThey also do not describe how to install or run a server locally — that\npackage metadata belongs to the MCP Registry's `server.json` schema, not to\nServer Cards.\n\nThe fields a Server Card does declare (identity, transport, protocol\nversions) are advisory, not authoritative: they SHOULD be consistent with\nthe server's `server/discover` response, and clients MUST NOT treat them as\nauthoritative for security decisions. See \"Consistency with Runtime\nBehavior\" in docs/discovery.md for the normative requirement.", "properties": { "$schema": { - "description": "The Server Card JSON Schema URI that this document conforms to. Required.\n\nMust be a `/v1/` URL under `static.modelcontextprotocol.io/schemas/`,\nnaming a Server Card / `server.json` schema (e.g.,\n`https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json`\nor `https://static.modelcontextprotocol.io/schemas/v1/server.schema.json`).\nSchema URLs are versioned by the `vN` segment rather than by date so that\nminor, additive revisions of the v1 shape don't bump every published\ndocument's `$schema` URL.", + "description": "The Server Card JSON Schema URI that this document conforms to. Required.\n\nMust be the `/v1/` Server Card schema URL under\n`static.modelcontextprotocol.io/schemas/` (i.e.,\n`https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json`).\nSchema URLs are versioned by the `vN` segment rather than by date; a\nbreaking revision of the Server Card shape publishes a new `vN` family.", "format": "uri", - "pattern": "^https://static\\.modelcontextprotocol\\.io/schemas/v1/[^/]+\\.schema\\.json$", + "pattern": "^https://static\\.modelcontextprotocol\\.io/schemas/v1/server-card\\.schema\\.json$", "type": "string" }, "_meta": { @@ -589,63 +261,6 @@ }, "required": ["$schema", "description", "name", "version"], "type": "object" - }, - "SsePackageTransport": { - "description": "Server-sent events (SSE) transport for a locally-runnable package.", - "properties": { - "headers": { - "description": "HTTP headers to include when connecting to the package's local endpoint.", - "items": { - "$ref": "#/$defs/KeyValueInput" - }, - "type": "array" - }, - "type": { - "const": "sse", - "type": "string" - }, - "url": { - "description": "SSE endpoint URL template. See {@link StreamableHttpPackageTransport.url}\nfor variable-substitution semantics.", - "pattern": "^(https?://[^\\s]+|\\{[a-zA-Z_][a-zA-Z0-9_]*\\}[^\\s]*)$", - "type": "string" - } - }, - "required": ["type", "url"], - "type": "object" - }, - "StdioTransport": { - "description": "Stdio transport — the client launches the package as a subprocess and\ncommunicates over standard input and output.", - "properties": { - "type": { - "const": "stdio", - "type": "string" - } - }, - "required": ["type"], - "type": "object" - }, - "StreamableHttpPackageTransport": { - "description": "Streamable-HTTP transport for a locally-runnable package that exposes\nitself over HTTP after launch.", - "properties": { - "headers": { - "description": "HTTP headers to include when connecting to the package's local endpoint.", - "items": { - "$ref": "#/$defs/KeyValueInput" - }, - "type": "array" - }, - "type": { - "const": "streamable-http", - "type": "string" - }, - "url": { - "description": "URL template for the streamable-http transport. Must start with\n`http://`, `https://`, or a `{template-variable}`. Variables in\n`{curly_braces}` reference argument value-hints, argument names, or\nenvironment variable names from the parent {@link Package}.", - "pattern": "^(https?://[^\\s]+|\\{[a-zA-Z_][a-zA-Z0-9_]*\\}[^\\s]*)$", - "type": "string" - } - }, - "required": ["type", "url"], - "type": "object" } } } diff --git a/schema.ts b/schema.ts index a8211d8..694a94f 100644 --- a/schema.ts +++ b/schema.ts @@ -2,9 +2,9 @@ * MCP Server Cards * * Experimental schema for SEP-2127. This file is the single source of truth - * for the Server Card and `server.json` types and is intended to be lifted - * directly into `schema/draft/schema.ts` of the main MCP specification when - * Server Cards graduate from this experimental extension. + * for the Server Card types and is intended to be lifted directly into + * `schema/draft/schema.ts` of the main MCP specification when Server Cards + * graduate from this experimental extension. * * @see https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127 */ @@ -20,6 +20,9 @@ * connect to a remote server: identity, transport, and protocol versions. * They do not enumerate primitives (tools, resources, prompts) — those remain * subject to runtime listing via the protocol's standard list operations. + * They also do not describe how to install or run a server locally — that + * package metadata belongs to the MCP Registry's `server.json` schema, not to + * Server Cards. * * The fields a Server Card does declare (identity, transport, protocol * versions) are advisory, not authoritative: they SHOULD be consistent with @@ -27,9 +30,6 @@ * authoritative for security decisions. See "Consistency with Runtime * Behavior" in docs/discovery.md for the normative requirement. * - * The companion {@link Server} shape is a strict superset that adds local - * package metadata for use cases like the MCP Registry's `server.json`. - * * @see [SEP-2127: MCP Server Cards](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) * @category Server Cards */ @@ -37,16 +37,14 @@ export interface ServerCard { /** * The Server Card JSON Schema URI that this document conforms to. Required. * - * Must be a `/v1/` URL under `static.modelcontextprotocol.io/schemas/`, - * naming a Server Card / `server.json` schema (e.g., - * `https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json` - * or `https://static.modelcontextprotocol.io/schemas/v1/server.schema.json`). - * Schema URLs are versioned by the `vN` segment rather than by date so that - * minor, additive revisions of the v1 shape don't bump every published - * document's `$schema` URL. + * Must be the `/v1/` Server Card schema URL under + * `static.modelcontextprotocol.io/schemas/` (i.e., + * `https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json`). + * Schema URLs are versioned by the `vN` segment rather than by date; a + * breaking revision of the Server Card shape publishes a new `vN` family. * * @format uri - * @pattern ^https://static\.modelcontextprotocol\.io/schemas/v1/[^/]+\.schema\.json$ + * @pattern ^https://static\.modelcontextprotocol\.io/schemas/v1/server-card\.schema\.json$ */ $schema: string; @@ -130,25 +128,6 @@ export interface ServerCard { _meta?: MetaObject; } -/** - * A superset of {@link ServerCard} that additionally describes locally-runnable - * packages. This is the shape used by the MCP Registry's `server.json`. - * - * `Server` documents are typically published to a registry rather than served - * from a Server Card URI (e.g., `/server-card`), since they - * may include instructions for installing and executing a server on a client's - * local machine. - * - * @see [SEP-2127: MCP Server Cards](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) - * @category Server Cards - */ -export interface Server extends ServerCard { - /** - * Metadata helpful for running and connecting to local instances of this MCP server. - */ - packages?: Package[]; -} - /** * Repository metadata for the MCP server source code. Enables users and * security experts to inspect the code, improving transparency. @@ -217,7 +196,7 @@ export interface Remote { /** * Configuration variables that can be referenced as `{curly_braces}` * placeholders in `url` (and inside header values via - * {@link InputWithVariables.variables}). The map key is the variable + * {@link KeyValueInput.variables}). The map key is the variable * name; the value defines the variable's properties (e.g., human-readable * description, default, whether it is required or secret). */ @@ -231,167 +210,8 @@ export interface Remote { } /** - * Metadata for installing and running a packaged MCP server locally. - * - * @category Server Cards - */ -export interface Package { - /** - * Registry type indicating how to download packages - * (e.g., `"npm"`, `"pypi"`, `"oci"`, `"nuget"`, `"mcpb"`). - */ - registryType: string; - - /** - * Package identifier — either a package name (for registries) - * or a URL (for direct downloads). - */ - identifier: string; - - /** - * Transport configuration for invoking this package after installation. - */ - transport: PackageTransport; - - /** - * Base URL of the package registry. - * - * @format uri - */ - registryBaseUrl?: string; - - /** - * Package version. - * - * @minLength 1 - */ - version?: string; - - /** - * MCP protocol versions actively supported by this package. - */ - supportedProtocolVersions?: string[]; - - /** - * A hint to help clients determine the appropriate runtime for the package - * (e.g., `"npx"`, `"uvx"`, `"docker"`, `"dnx"`). Should be provided when - * `runtimeArguments` are present. - */ - runtimeHint?: string; - - /** - * Arguments passed to the package's runtime command (such as `docker` or - * `npx`). The `runtimeHint` field should be provided when `runtimeArguments` - * are present. - */ - runtimeArguments?: Argument[]; - - /** - * Arguments passed to the package's binary. - */ - packageArguments?: Argument[]; - - /** - * Environment variables to be set when running the package. - */ - environmentVariables?: KeyValueInput[]; - - /** - * SHA-256 hash of the package file for integrity verification. Required for - * MCPB packages and optional for other package types. If present, MCP - * clients MUST validate the downloaded file matches the hash before running - * packages to ensure file integrity. - * - * @pattern ^[a-f0-9]{64}$ - */ - fileSha256?: string; -} - -/** - * Transport protocol configuration for a locally-runnable package. - * - * @category Server Cards - */ -export type PackageTransport = - | StdioTransport - | StreamableHttpPackageTransport - | SsePackageTransport; - -/** - * Stdio transport — the client launches the package as a subprocess and - * communicates over standard input and output. - * - * @category Server Cards - */ -export interface StdioTransport { - type: "stdio"; -} - -/** - * Streamable-HTTP transport for a locally-runnable package that exposes - * itself over HTTP after launch. - * - * @category Server Cards - */ -export interface StreamableHttpPackageTransport { - type: "streamable-http"; - - /** - * URL template for the streamable-http transport. Must start with - * `http://`, `https://`, or a `{template-variable}`. Variables in - * `{curly_braces}` reference argument value-hints, argument names, or - * environment variable names from the parent {@link Package}. - * - * @pattern ^(https?://[^\s]+|\{[a-zA-Z_][a-zA-Z0-9_]*\}[^\s]*)$ - */ - url: string; - - /** - * HTTP headers to include when connecting to the package's local endpoint. - */ - headers?: KeyValueInput[]; -} - -/** - * Server-sent events (SSE) transport for a locally-runnable package. - * - * @category Server Cards - */ -export interface SsePackageTransport { - type: "sse"; - - /** - * SSE endpoint URL template. See {@link StreamableHttpPackageTransport.url} - * for variable-substitution semantics. - * - * @pattern ^(https?://[^\s]+|\{[a-zA-Z_][a-zA-Z0-9_]*\}[^\s]*)$ - */ - url: string; - - /** - * HTTP headers to include when connecting to the package's local endpoint. - */ - headers?: KeyValueInput[]; -} - -/** - * A command-line argument supplied to a package's binary or runtime. - * - * @remarks - * Arguments construct command-line parameters that may contain user-provided - * input. This creates potential command-injection risks if clients execute - * commands in a shell environment. Clients SHOULD prefer non-shell execution - * methods (e.g., `posix_spawn`) when possible to eliminate injection risks - * entirely. Where not possible, clients SHOULD obtain user consent to run the - * resolved command before execution. - * - * @category Server Cards - */ -export type Argument = PositionalArgument | NamedArgument; - -/** - * A user-supplied or pre-set input value, used in {@link Package} argument - * and environment-variable definitions. + * A user-supplied or pre-set input value, used for {@link Remote} URL + * variables and header values. * * @category Server Cards */ @@ -403,7 +223,7 @@ export interface Input { description?: string; /** - * Whether the input must be supplied for the package to run. + * Whether the input must be supplied for the connection to succeed. */ isRequired?: boolean; @@ -446,73 +266,22 @@ export interface Input { } /** - * An {@link Input} whose `value` may reference variables for substitution. + * A named {@link Input} — used for HTTP headers — whose `value` may reference + * variables for substitution. * * @category Server Cards */ -export interface InputWithVariables extends Input { +export interface KeyValueInput extends Input { /** - * Variables referenced by `{curly_braces}` identifiers in `value`. The map - * key is the variable name; the value defines the variable's properties. - */ - variables?: { [key: string]: Input }; -} - -/** - * A named input — used for environment variables and HTTP headers. - * - * @category Server Cards - */ -export interface KeyValueInput extends InputWithVariables { - /** - * Name of the header or environment variable. + * Name of the header. */ name: string; -} - -/** - * A positional command-line input — a value inserted verbatim into the - * command line. - * - * @category Server Cards - */ -export interface PositionalArgument extends InputWithVariables { - type: "positional"; - - /** - * Identifier for the positional argument. It is not part of the command - * line; it may be used by client configuration as a label identifying the - * argument, and it identifies the value in transport URL variable - * substitution. - * - * Implementations SHOULD ensure that at least one of `valueHint` or - * `value` is set so the positional argument resolves to a concrete value. - */ - valueHint?: string; /** - * Whether the argument can be repeated multiple times in the command line. - */ - isRepeated?: boolean; -} - -/** - * A named command-line input — a `--flag={value}` parameter. - * - * @category Server Cards - */ -export interface NamedArgument extends InputWithVariables { - type: "named"; - - /** - * The flag name, including any leading dashes (e.g., `"--port"`). - */ - name: string; - - /** - * Whether the argument can be repeated multiple times. + * Variables referenced by `{curly_braces}` identifiers in `value`. The map + * key is the variable name; the value defines the variable's properties. */ - isRepeated?: boolean; + variables?: { [key: string]: Input }; } /* ---------- Inlined dependencies from the main MCP spec ---------- */ @@ -536,7 +305,14 @@ export interface NamedArgument extends InputWithVariables { * @see [General fields: `_meta`](https://modelcontextprotocol.io/specification/draft/basic#meta) for more details. * @category Common Types */ -export type MetaObject = Record; +export interface MetaObject { + // The interface-with-index-signature form is load-bearing: the schema is + // generated with --noExtraProps, and this is the form that keeps the + // generated MetaObject open (`additionalProperties: {}`) so `_meta` remains + // the card's extension point. `Record` generates a closed + // object under that flag. + [key: string]: unknown; +} /** * An optionally-sized icon that can be displayed in a user interface. diff --git a/scripts/generate-schema.ts b/scripts/generate-schema.ts index b1f6520..f497f0e 100644 --- a/scripts/generate-schema.ts +++ b/scripts/generate-schema.ts @@ -30,7 +30,7 @@ function applyJsonSchema202012Transformations(content: string): string { async function generate(): Promise { const { stdout } = await execAsync( - `npx typescript-json-schema --defaultNumberType integer --required --skipLibCheck "${SCHEMA_TS}" "*"`, + `npx typescript-json-schema --defaultNumberType integer --required --noExtraProps --skipLibCheck "${SCHEMA_TS}" "*"`, ); const transformed = applyJsonSchema202012Transformations(stdout); const config = (await prettier.resolveConfig(SCHEMA_JSON)) ?? {}; diff --git a/scripts/validate-examples.ts b/scripts/validate-examples.ts index 1b23adb..af7eb8c 100644 --- a/scripts/validate-examples.ts +++ b/scripts/validate-examples.ts @@ -2,15 +2,14 @@ /** * Validate the example documents under `examples/` against the generated - * `schema.json`. Each example is run against either `#/$defs/ServerCard` or - * `#/$defs/Server` based on the directory it lives under, and against the - * schema's expected acceptance — `valid/` examples must validate cleanly, - * `invalid/` examples must fail at least one constraint. + * `schema.json`. Each example is run against `#/$defs/ServerCard` and + * against the schema's expected acceptance — `valid/` examples must validate + * cleanly, `invalid/` examples must fail at least one constraint. */ import Ajv2020 from "ajv/dist/2020.js"; import addFormats from "ajv-formats"; -import { readdirSync, readFileSync, statSync } from "fs"; +import { readdirSync, readFileSync } from "fs"; import { join } from "path"; const SCHEMA_JSON = "schema.json"; @@ -41,17 +40,13 @@ function listJsonFiles(dir: string): string[] { } } -function runDirectory( - ajv: Ajv2020, - defName: "ServerCard" | "Server", -): Outcome[] { - const root = join(EXAMPLES_DIR, defName); +function runServerCardExamples(ajv: Ajv2020): Outcome[] { + const root = join(EXAMPLES_DIR, "ServerCard"); const outcomes: Outcome[] = []; - if (!safeIsDir(root)) return outcomes; - const validate = ajv.getSchema(`schema.json#/$defs/${defName}`); + const validate = ajv.getSchema(`schema.json#/$defs/ServerCard`); if (!validate) { - throw new Error(`Could not resolve ${defName} schema in ${SCHEMA_JSON}`); + throw new Error(`Could not resolve ServerCard schema in ${SCHEMA_JSON}`); } for (const expected of ["valid", "invalid"] as const) { @@ -62,7 +57,7 @@ function runDirectory( const passed = expected === "valid" ? !!ok : !ok; const errors = validate.errors ?? []; outcomes.push({ - name: `${defName}/${expected}/${file.split("/").pop()}`, + name: `ServerCard/${expected}/${file.split("/").pop()}`, expected, passed, message: passed @@ -79,24 +74,13 @@ function runDirectory( return outcomes; } -function safeIsDir(p: string): boolean { - try { - return statSync(p).isDirectory(); - } catch { - return false; - } -} - function main(): void { const ajv = loadSchema(); - const outcomes = [ - ...runDirectory(ajv, "ServerCard"), - ...runDirectory(ajv, "Server"), - ]; + const outcomes = runServerCardExamples(ajv); if (outcomes.length === 0) { - console.log(`No examples found under ${EXAMPLES_DIR}/. Skipping.`); - return; + console.error(`No examples found under ${EXAMPLES_DIR}/.`); + process.exit(1); } let failed = 0; From 5174a77147584436f5b1cb17f07668e02c310bb9 Mon Sep 17 00:00:00 2001 From: David Soria Parra Date: Mon, 15 Jun 2026 16:49:48 +0100 Subject: [PATCH 2/2] wording update --- README.md | 10 ++++------ schema.ts | 27 ++++++++++----------------- 2 files changed, 14 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index 041f1bb..686e427 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ > #### **Status:** Experimental. This work is for prototyping and feedback only, and is not an accepted or official MCP extension. -This repository defines a TypeScript source-of-truth and generated JSON Schema for **MCP Server Cards** — a static metadata document that describes a remote MCP server enough for clients to discover and connect to it before initialization. +This repository defines a TypeScript source-of-truth and generated JSON Schema for **MCP Server Cards**. Server Cards are a static metadata document that describes a remote MCP server enough for clients to discover and connect to it before initialization. It tracks [SEP-2127](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) and is intended to be lifted directly into the main spec when Server Cards graduate (see [Graduation plan](#graduation-plan) below). @@ -20,11 +20,9 @@ Server Cards intentionally omit primitive listings (tools, resources, prompts) ## Relationship to the MCP Registry -A Server Card describes **remote connectivity only**. Metadata for locally-installable servers — packages, registries (npm, PyPI, OCI, NuGet, MCPB), runtime hints, command-line arguments, environment variables — lives in the [MCP Registry](https://github.com/modelcontextprotocol/registry)'s [`server.json` schema](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/generic-server-json.md), which is owned by the registry, not by this extension. +A Server Card describes **remote connectivity only**. Metadata for locally-installable servers, packages, registries (npm, PyPI, OCI, NuGet, MCPB), runtime hints, command-line arguments, environment variables, lives in the [MCP Registry](https://github.com/modelcontextprotocol/registry)'s [`server.json` schema](https://github.com/modelcontextprotocol/registry/blob/main/docs/reference/server-json/generic-server-json.md), which is owned by the registry, not by this extension. -A registry entry MAY reference or embed a Server Card's remote connection info, but this repository does not define any package types, and a document containing `packages` is not a valid Server Card. - -Vendors who genuinely need to attach install hints to a Server Card can use namespaced [`_meta`](https://modelcontextprotocol.io/specification/draft/basic#meta) extension metadata, which remains the card's extension point. +Vendors who genuinely need to attach install hints to a Server Card can use namespaced [`_meta`](https://modelcontextprotocol.io/specification/latest/basic#meta) extension metadata, which remains the card's extension point. ## Layout @@ -60,7 +58,7 @@ The `$schema` field on every document MUST be a URL of the form: https://static.modelcontextprotocol.io/schemas/v1/.schema.json ``` -Schema URLs are versioned by their `vN` segment rather than by date. Server Card objects are closed (`additionalProperties: false`): a document using a field the schema doesn't declare is rejected, and vendor-specific data belongs in namespaced `_meta`, which stays open. Because the schema is closed, once `v1` is published a breaking revision of the shape would publish a new `vN` family rather than mutating `v1` in place. The `v1` shape is still pre-release and card-only — it intentionally does not include the registry-shaped `Server` / `packages` types. +Schema URLs are versioned by their `vN` segment. Server Card objects are closed (`additionalProperties: false`): a document using a field the schema doesn't declare is rejected, and vendor-specific data belongs in namespaced `_meta`, which stays open. Once `v1` is published a breaking revision of the shape would publish a new `vN` family rather than mutating `v1` in place. The `v1` shape is still pre-release and card-only — it intentionally does not include the registry-shaped `Server` / `packages` types. ## Graduation plan diff --git a/schema.ts b/schema.ts index 694a94f..463c2ff 100644 --- a/schema.ts +++ b/schema.ts @@ -1,36 +1,29 @@ /** * MCP Server Cards * - * Experimental schema for SEP-2127. This file is the single source of truth - * for the Server Card types and is intended to be lifted directly into - * `schema/draft/schema.ts` of the main MCP specification when Server Cards - * graduate from this experimental extension. - * - * @see https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127 + * Schema for the experimental Server Card extension. This file is the single + * source of truth for the Server Card types. */ /** * A static metadata document describing a remote MCP server, suitable for - * pre-connection discovery. A Server Card MAY be hosted at any unreserved URI; + * pre-connection discovery. A Server Card may be hosted at any unreserved URI; * MCP reserves `GET /server-card` as the recommended - * location. Clients learn a card's URL from an MCP/AI Catalog rather than - * guessing it. + * location. Clients learn a card's URL from an [AI Catalog](https://github.com/Agent-Card/ai-catalog) + * rather than guessing it. + * + * Server Cards describe only what is needed to discover and connect to a remote server: + * identity, transport, and protocol versions. * - * Server Cards intentionally describe only what is needed to discover and - * connect to a remote server: identity, transport, and protocol versions. * They do not enumerate primitives (tools, resources, prompts) — those remain * subject to runtime listing via the protocol's standard list operations. - * They also do not describe how to install or run a server locally — that - * package metadata belongs to the MCP Registry's `server.json` schema, not to - * Server Cards. * * The fields a Server Card does declare (identity, transport, protocol - * versions) are advisory, not authoritative: they SHOULD be consistent with - * the server's `server/discover` response, and clients MUST NOT treat them as + * versions) are advisory, not authoritative: they should be consistent with + * the server's `server/discover` response, and clients must not treat them as * authoritative for security decisions. See "Consistency with Runtime * Behavior" in docs/discovery.md for the normative requirement. * - * @see [SEP-2127: MCP Server Cards](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2127) * @category Server Cards */ export interface ServerCard {