diff --git a/README.md b/README.md index 48299d7..038345b 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A prior attempt to land these types directly in the core spec ([modelcontextprot A **Server Card** is a JSON document — hosted at any unreserved URI, with `GET /server-card` reserved as the recommended location (see [discovery.md](docs/discovery.md)) — describing: - The server's identity (`name`, `version`, `description`, optional `title` / `icons` / `repository` / `websiteUrl`) -- Its remote transport endpoints (URLs, headers, variable templates, supported protocol versions) +- Its remote transport endpoints (URLs, headers, variable templates, supported protocol versions, server-level extension information) - Optional registry-style extension metadata (`_meta`) Clients can automatically discover Server Cards through [AI Catalog](https://github.com/Agent-Card/ai-catalog) entries advertised on domains present in agentic sessions. diff --git a/docs/discovery.md b/docs/discovery.md index bc4bdf7..256be5c 100644 --- a/docs/discovery.md +++ b/docs/discovery.md @@ -153,6 +153,12 @@ the transport served at each `remotes[]` endpoint, and descriptive fields (`titl `description`, `icons`) — SHOULD NOT contradict the equivalent values declared in the Server Card. +A remote's `extensions` map carries server-level information defined by each extension; it +is not a declaration of extension support. Clients MUST NOT infer from the presence or +absence of an entry whether the server supports that extension — support is negotiated via +`capabilities.extensions` in `server/discover`, which may vary per user and need not match +the Server Card. + As with the deliberately omitted primitives (tools, resources, prompts), a static manifest can drift from runtime, so even the fields a Server Card does declare are advisory rather than binding. Accordingly: diff --git a/examples/ServerCard/invalid/non-object-extension-info.json b/examples/ServerCard/invalid/non-object-extension-info.json new file mode 100644 index 0000000..8928b25 --- /dev/null +++ b/examples/ServerCard/invalid/non-object-extension-info.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json", + "name": "example-org/bad-extension", + "version": "1.0.0", + "description": "Extension information must be an object, not a boolean.", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.example.com/mcp", + "extensions": { + "io.modelcontextprotocol/tasks": true + } + } + ] +} diff --git a/examples/ServerCard/valid/remote-extensions.json b/examples/ServerCard/valid/remote-extensions.json new file mode 100644 index 0000000..36500a8 --- /dev/null +++ b/examples/ServerCard/valid/remote-extensions.json @@ -0,0 +1,30 @@ +{ + "$schema": "https://static.modelcontextprotocol.io/schemas/v1/server-card.schema.json", + "name": "example-org/with-extensions", + "version": "1.0.0", + "description": "Server Card whose remote publishes extension information.", + "remotes": [ + { + "type": "streamable-http", + "url": "https://mcp.example.com/mcp", + "supportedProtocolVersions": ["2025-11-25"], + "extensions": { + "io.modelcontextprotocol/events": { + "webhooks": { + "signingKeys": { + "keys": [ + { + "kty": "OKP", + "crv": "Ed25519", + "kid": "2026-10", + "x": "11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo" + } + ] + }, + "egressIpRanges": ["203.0.113.0/24", "2001:db8::/32"] + } + } + } + } + ] +} diff --git a/schema.json b/schema.json index a93de94..88407a7 100644 --- a/schema.json +++ b/schema.json @@ -71,6 +71,34 @@ }, "type": "object" }, + "JSONArray": { + "items": { + "$ref": "#/$defs/JSONValue" + }, + "type": "array" + }, + "JSONObject": { + "additionalProperties": { + "$ref": "#/$defs/JSONValue" + }, + "type": "object" + }, + "JSONValue": { + "anyOf": [ + { + "$ref": "#/$defs/JSONObject" + }, + { + "items": { + "$ref": "#/$defs/JSONValue" + }, + "type": "array" + }, + { + "type": ["string", "integer", "boolean"] + } + ] + }, "KeyValueInput": { "description": "A named {@link Input} — used for HTTP headers — whose `value` may reference\nvariables for substitution.", "properties": { @@ -133,6 +161,13 @@ "Remote": { "description": "Metadata for connecting to a remote (HTTP-based) MCP server endpoint.", "properties": { + "extensions": { + "additionalProperties": { + "$ref": "#/$defs/JSONObject" + }, + "description": "Optional server-level information about MCP extensions for this remote\nendpoint. Keys are extension identifiers (e.g.,\n`\"io.modelcontextprotocol/events\"`), and values are objects whose contents\nare defined by the respective extension — typically information a client\nneeds before connecting, such as keys for verifying webhook signatures.\n\nThe presence or absence of an entry does not indicate whether the server\nsupports that extension, and an entry need not match the server's\n`capabilities.extensions`. Extension support is negotiated at runtime via\n`server/discover` and may vary per user.\n\nKeys MUST follow the {@link MetaObject`_meta` key naming rules}, with a\nmandatory prefix.", + "type": "object" + }, "headers": { "description": "HTTP headers required or accepted when connecting to this remote\nendpoint. Each header is described as a {@link KeyValueInput} so that\nclients can prompt users for required values, mark secrets, surface\ndefaults, and constrain to a list of choices.", "items": { diff --git a/schema.ts b/schema.ts index 20f168d..dbc3f82 100644 --- a/schema.ts +++ b/schema.ts @@ -200,6 +200,23 @@ export interface Remote { * clients to select a compatible protocol version before connecting. */ supportedProtocolVersions?: string[]; + + /** + * Optional server-level information about MCP extensions for this remote + * endpoint. Keys are extension identifiers (e.g., + * `"io.modelcontextprotocol/events"`), and values are objects whose contents + * are defined by the respective extension — typically information a client + * needs before connecting, such as keys for verifying webhook signatures. + * + * The presence or absence of an entry does not indicate whether the server + * supports that extension, and an entry need not match the server's + * `capabilities.extensions`. Extension support is negotiated at runtime via + * `server/discover` and may vary per user. + * + * Keys MUST follow the {@link MetaObject | `_meta` key naming rules}, with a + * mandatory prefix. + */ + extensions?: { [key: string]: JSONObject }; } /** @@ -279,6 +296,27 @@ export interface KeyValueInput extends Input { /* ---------- Inlined dependencies from the main MCP spec ---------- */ +/** + * @category Common Types + */ +export type JSONValue = + | string + | number + | boolean + | null + | JSONObject + | JSONArray; + +/** + * @category Common Types + */ +export type JSONObject = { [key: string]: JSONValue }; + +/** + * @category Common Types + */ +export type JSONArray = JSONValue[]; + /** * Represents the contents of a `_meta` field, which clients and servers use to attach additional metadata to their interactions. *