From 30c9eeb25fab01fd8d23cc322bad9916f16c9683 Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Mon, 15 Jun 2026 17:07:07 +0000 Subject: [PATCH 1/7] Protocol types for 2026-07-28: superset monolith, per-version packages, and wire-method maps One public type set (mcp.types) covering every protocol revision through 2026-07-28; two per-version model packages that act as the schema-exact validating layer; and plain (method, version) maps with two-step parse functions. Version gating is data: key absence is the gate. Outbound serialization is the model dump, with the new 2026-07-28 result fields as ordinary serialized defaults. Includes a surface-to-monolith field-parity test that asserts every wire alias in either per-version package is carried by the monolith. --- docs/migration.md | 10 +- src/mcp/shared/version.py | 1 + src/mcp/types/__init__.py | 90 +- src/mcp/types/_types.py | 1049 ++++++++++-- src/mcp/types/_wire_base.py | 19 + src/mcp/types/jsonrpc.py | 45 +- src/mcp/types/methods.py | 602 +++++++ src/mcp/types/v2025_11_25/__init__.py | 1485 ++++++++++++++++ src/mcp/types/v2026_07_28/__init__.py | 1492 +++++++++++++++++ tests/interaction/lowlevel/test_resources.py | 4 +- .../transports/test_hosting_resume.py | 1 + tests/server/test_session.py | 2 +- tests/test_types.py | 72 + tests/types/__init__.py | 0 tests/types/test_methods.py | 742 ++++++++ tests/types/test_parity.py | 192 +++ tests/types/test_wire_frames.py | 87 + 17 files changed, 5700 insertions(+), 193 deletions(-) create mode 100644 src/mcp/types/_wire_base.py create mode 100644 src/mcp/types/methods.py create mode 100644 src/mcp/types/v2025_11_25/__init__.py create mode 100644 src/mcp/types/v2026_07_28/__init__.py create mode 100644 tests/types/__init__.py create mode 100644 tests/types/test_methods.py create mode 100644 tests/types/test_parity.py create mode 100644 tests/types/test_wire_frames.py diff --git a/docs/migration.md b/docs/migration.md index f6170d0e1e..36f7b29430 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -221,6 +221,10 @@ Common renames: Because `populate_by_name=True` is set, the old camelCase names still work as constructor kwargs (e.g., `Tool(inputSchema={...})` is accepted), but attribute access must use snake_case (`tool.input_schema`). +### Results now serialize `resultType` and cache-directive defaults + +Serialized results now include `resultType` by default (and `ttlMs`/`cacheScope` on cacheable results). Peers ignore unknown result fields, so this interoperates across protocol versions, but tests or recorded fixtures that compare exact serialized payloads need the new keys added. + ### `args` parameter removed from `ClientSessionGroup.call_tool()` The deprecated `args` parameter has been removed from `ClientSessionGroup.call_tool()`. Use `arguments` instead. @@ -1181,7 +1185,7 @@ Behavior changes: ### Experimental Tasks support removed -Tasks (SEP-1686) have been removed from the MCP specification and are no longer part of this SDK. The `mcp.client.experimental`, `mcp.server.experimental`, `mcp.shared.experimental`, and `mcp.server.lowlevel.experimental` modules have been removed, along with all `Task*` types, the `tasks` capability fields, `Tool.execution`, and the `experimental` properties on `ClientSession`, `ServerSession`, `Server`, and `ServerRequestContext`. +Tasks (SEP-1686) have been removed from the MCP specification and are no longer part of this SDK. The `mcp.client.experimental`, `mcp.server.experimental`, `mcp.shared.experimental`, and `mcp.server.lowlevel.experimental` modules have been removed, along with the `experimental` properties on `ClientSession`, `ServerSession`, `Server`, and `ServerRequestContext`. The corresponding `Task*` types remain in `mcp.types` as types-only definitions. Tasks are expected to return as a separate MCP extension in a future release. @@ -1227,6 +1231,10 @@ If you relied on extra fields round-tripping through MCP types, move that data i ## New Features +### 2025-11-25 and 2026-07-28 protocol fields modeled + +`mcp.types` models the 2025-11-25 and 2026-07-28 protocol fields (e.g. `resultType`, `ttlMs`/`cacheScope` on cacheable results, `inputResponses`/`requestState` on retried requests), so inbound payloads carrying these keys parse into typed fields and round-trip. Most are optional with `None` defaults; the result-directive fields carry serialized defaults - see [Results now serialize `resultType` and cache-directive defaults](#results-now-serialize-resulttype-and-cache-directive-defaults). + ### `streamable_http_app()` available on lowlevel Server The `streamable_http_app()` method is now available directly on the lowlevel `Server` class, not just `MCPServer`. This allows using the streamable HTTP transport without the MCPServer wrapper. diff --git a/src/mcp/shared/version.py b/src/mcp/shared/version.py index 44154da365..2299de72ec 100644 --- a/src/mcp/shared/version.py +++ b/src/mcp/shared/version.py @@ -16,6 +16,7 @@ "2025-03-26", "2025-06-18", "2025-11-25", + "2026-07-28", ) """Every released protocol revision, oldest to newest.""" diff --git a/src/mcp/types/__init__.py b/src/mcp/types/__init__.py index b2d537fb70..7628f9ac20 100644 --- a/src/mcp/types/__init__.py +++ b/src/mcp/types/__init__.py @@ -1,22 +1,30 @@ """This module defines the types for the MCP protocol. Check the latest schema at: -https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/2025-11-25/schema.json +https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.json """ # Re-export everything from _types for backward compatibility from mcp.types._types import ( + CLIENT_CAPABILITIES_META_KEY, + CLIENT_INFO_META_KEY, DEFAULT_NEGOTIATED_VERSION, LATEST_PROTOCOL_VERSION, + LOG_LEVEL_META_KEY, + PROTOCOL_VERSION_META_KEY, Annotations, AudioContent, BaseMetadata, BlobResourceContents, + CacheableResult, CallToolRequest, CallToolRequestParams, CallToolResult, CancelledNotification, CancelledNotificationParams, + CancelTaskRequest, + CancelTaskRequestParams, + CancelTaskResult, ClientCapabilities, ClientNotification, ClientRequest, @@ -33,6 +41,9 @@ CreateMessageRequestParams, CreateMessageResult, CreateMessageResultWithTools, + CreateTaskResult, + DiscoverRequest, + DiscoverResult, ElicitationCapability, ElicitationRequiredErrorData, ElicitCompleteNotification, @@ -49,6 +60,12 @@ GetPromptRequest, GetPromptRequestParams, GetPromptResult, + GetTaskPayloadRequest, + GetTaskPayloadRequestParams, + GetTaskPayloadResult, + GetTaskRequest, + GetTaskRequestParams, + GetTaskResult, Icon, IconTheme, ImageContent, @@ -58,6 +75,12 @@ InitializeRequest, InitializeRequestParams, InitializeResult, + InputRequest, + InputRequests, + InputRequiredResult, + InputResponse, + InputResponseRequestParams, + InputResponses, ListPromptsRequest, ListPromptsResult, ListResourcesRequest, @@ -66,12 +89,15 @@ ListResourceTemplatesResult, ListRootsRequest, ListRootsResult, + ListTasksRequest, + ListTasksResult, ListToolsRequest, ListToolsResult, LoggingCapability, LoggingLevel, LoggingMessageNotification, LoggingMessageNotificationParams, + MissingRequiredClientCapabilityErrorData, ModelHint, ModelPreferences, Notification, @@ -92,6 +118,7 @@ ReadResourceRequest, ReadResourceRequestParams, ReadResourceResult, + RelatedTaskMetadata, Request, RequestParams, RequestParamsMeta, @@ -105,6 +132,7 @@ ResourceUpdatedNotification, ResourceUpdatedNotificationParams, Result, + ResultType, Role, Root, RootsCapability, @@ -124,17 +152,29 @@ StopReason, SubscribeRequest, SubscribeRequestParams, + SubscriptionFilter, + SubscriptionsAcknowledgedNotification, + SubscriptionsAcknowledgedNotificationParams, + SubscriptionsListenRequest, + SubscriptionsListenRequestParams, + Task, + TaskMetadata, + TaskStatus, + TaskStatusNotification, + TaskStatusNotificationParams, TextContent, TextResourceContents, Tool, ToolAnnotations, ToolChoice, + ToolExecution, ToolListChangedNotification, ToolResultContent, ToolsCapability, ToolUseContent, UnsubscribeRequest, UnsubscribeRequestParams, + UnsupportedProtocolVersionErrorData, UrlElicitationCapability, client_notification_adapter, client_request_adapter, @@ -150,9 +190,12 @@ INTERNAL_ERROR, INVALID_PARAMS, INVALID_REQUEST, + JSONRPC_VERSION, METHOD_NOT_FOUND, + MISSING_REQUIRED_CLIENT_CAPABILITY, PARSE_ERROR, REQUEST_TIMEOUT, + UNSUPPORTED_PROTOCOL_VERSION, URL_ELICITATION_REQUIRED, ErrorData, JSONRPCError, @@ -168,17 +211,28 @@ # Protocol version constants "LATEST_PROTOCOL_VERSION", "DEFAULT_NEGOTIATED_VERSION", + # Reserved request _meta keys + "PROTOCOL_VERSION_META_KEY", + "CLIENT_INFO_META_KEY", + "CLIENT_CAPABILITIES_META_KEY", + "LOG_LEVEL_META_KEY", # Type aliases and variables "ContentBlock", "ElicitRequestedSchema", "ElicitRequestParams", "IncludeContext", + "InputRequest", + "InputRequests", + "InputResponse", + "InputResponses", "LoggingLevel", "ProgressToken", + "ResultType", "Role", "SamplingContent", "SamplingMessageContentBlock", "StopReason", + "TaskStatus", # Base classes "BaseMetadata", "Request", @@ -186,10 +240,12 @@ "Result", "RequestParams", "RequestParamsMeta", + "InputResponseRequestParams", "NotificationParams", "PaginatedRequest", "PaginatedRequestParams", "PaginatedResult", + "CacheableResult", "EmptyResult", # Capabilities "ClientCapabilities", @@ -236,27 +292,40 @@ "ResourceTemplateReference", "Root", "SamplingMessage", + "SubscriptionFilter", + "Task", + "TaskMetadata", + "RelatedTaskMetadata", "Tool", "ToolAnnotations", "ToolChoice", + "ToolExecution", # Requests "CallToolRequest", "CallToolRequestParams", "CompleteRequest", "CompleteRequestParams", + "CancelTaskRequest", + "CancelTaskRequestParams", "CreateMessageRequest", "CreateMessageRequestParams", + "DiscoverRequest", "ElicitRequest", "ElicitRequestFormParams", "ElicitRequestURLParams", "GetPromptRequest", "GetPromptRequestParams", + "GetTaskPayloadRequest", + "GetTaskPayloadRequestParams", + "GetTaskRequest", + "GetTaskRequestParams", "InitializeRequest", "InitializeRequestParams", "ListPromptsRequest", "ListResourcesRequest", "ListResourceTemplatesRequest", "ListRootsRequest", + "ListTasksRequest", "ListToolsRequest", "PingRequest", "ReadResourceRequest", @@ -265,23 +334,35 @@ "SetLevelRequestParams", "SubscribeRequest", "SubscribeRequestParams", + "SubscriptionsListenRequest", + "SubscriptionsListenRequestParams", "UnsubscribeRequest", "UnsubscribeRequestParams", # Results "CallToolResult", + "CancelTaskResult", "CompleteResult", "CreateMessageResult", "CreateMessageResultWithTools", + "CreateTaskResult", + "DiscoverResult", "ElicitResult", "ElicitationRequiredErrorData", "GetPromptResult", + "GetTaskPayloadResult", + "GetTaskResult", "InitializeResult", + "InputRequiredResult", "ListPromptsResult", "ListResourcesResult", "ListResourceTemplatesResult", "ListRootsResult", + "ListTasksResult", "ListToolsResult", "ReadResourceResult", + # Error data payloads + "MissingRequiredClientCapabilityErrorData", + "UnsupportedProtocolVersionErrorData", # Notifications "CancelledNotification", "CancelledNotificationParams", @@ -297,6 +378,10 @@ "ResourceUpdatedNotification", "ResourceUpdatedNotificationParams", "RootsListChangedNotification", + "SubscriptionsAcknowledgedNotification", + "SubscriptionsAcknowledgedNotificationParams", + "TaskStatusNotification", + "TaskStatusNotificationParams", "ToolListChangedNotification", # Union types for request/response routing "ClientNotification", @@ -317,9 +402,12 @@ "INTERNAL_ERROR", "INVALID_PARAMS", "INVALID_REQUEST", + "JSONRPC_VERSION", "METHOD_NOT_FOUND", + "MISSING_REQUIRED_CLIENT_CAPABILITY", "PARSE_ERROR", "REQUEST_TIMEOUT", + "UNSUPPORTED_PROTOCOL_VERSION", "URL_ELICITATION_REQUIRED", "ErrorData", "JSONRPCError", diff --git a/src/mcp/types/_types.py b/src/mcp/types/_types.py index e9d39ef6f3..598448f4b2 100644 --- a/src/mcp/types/_types.py +++ b/src/mcp/types/_types.py @@ -1,20 +1,34 @@ +"""Version-superset MCP protocol models. + +One model per protocol construct, carrying every field from every supported +protocol version, so application code sees a single set of types regardless of +the negotiated version. Per-field docstrings note version availability. The +`mcp.types.v*` surface packages carry the schema-exact wire shapes. +""" + from __future__ import annotations -from typing import Annotated, Any, Generic, Literal, TypeAlias, TypeVar +from typing import Annotated, Any, Final, Generic, Literal, TypeAlias, TypeVar -from pydantic import BaseModel, ConfigDict, Field, FileUrl, TypeAdapter +from pydantic import ( + BaseModel, + ConfigDict, + Field, + FileUrl, + TypeAdapter, +) from pydantic.alias_generators import to_camel from typing_extensions import NotRequired, TypedDict from mcp.types.jsonrpc import RequestId -LATEST_PROTOCOL_VERSION = "2025-11-25" -"""The latest version of the Model Context Protocol. +LATEST_PROTOCOL_VERSION: Final[str] = "2025-11-25" +"""The newest protocol version this SDK can negotiate. -You can find the latest specification at https://modelcontextprotocol.io/specification/latest. +See https://modelcontextprotocol.io/specification/latest. """ -DEFAULT_NEGOTIATED_VERSION = "2025-03-26" +DEFAULT_NEGOTIATED_VERSION: Final[str] = "2025-03-26" """The default negotiated version of the Model Context Protocol when no version is specified. We need this to satisfy the MCP specification, which requires the server to assume a specific version if none is @@ -25,9 +39,12 @@ """ ProgressToken = str | int +"""A progress token, used to associate progress notifications with the original request.""" Role = Literal["user", "assistant"] +"""The sender or recipient of messages and data in a conversation.""" IconTheme = Literal["light", "dark"] +"""Theme an icon is designed for. Wire values of `Icon.theme` (2025-11-25+).""" class MCPModel(BaseModel): @@ -38,8 +55,33 @@ class MCPModel(BaseModel): Meta: TypeAlias = dict[str, Any] +PROTOCOL_VERSION_META_KEY = "io.modelcontextprotocol/protocolVersion" +"""Reserved request `_meta` key: the MCP protocol version for this request (2026-07-28). + +SDK-managed; for HTTP its value must match the `MCP-Protocol-Version` header. +""" + +CLIENT_INFO_META_KEY = "io.modelcontextprotocol/clientInfo" +"""Reserved request `_meta` key: the client `Implementation` (2026-07-28). SDK-managed.""" + +CLIENT_CAPABILITIES_META_KEY = "io.modelcontextprotocol/clientCapabilities" +"""Reserved request `_meta` key: per-request `ClientCapabilities` (2026-07-28). SDK-managed.""" + +LOG_LEVEL_META_KEY = "io.modelcontextprotocol/logLevel" +"""Reserved request `_meta` key: desired log level for this request (2026-07-28). + +Deprecated (with the rest of logging) by SEP-2577 in the same revision that +introduces it. If absent, the server must not send log notifications. +""" + class RequestParamsMeta(TypedDict, extra_items=Any): + """The `_meta` object on request params (schema name: `RequestMetaObject`). + + An open map: arbitrary keys round-trip via `extra_items=Any`. Read or set + the reserved `io.modelcontextprotocol/*` keys via the `*_META_KEY` constants. + """ + progress_token: NotRequired[ProgressToken] """ If specified, the caller requests out-of-band progress notifications for @@ -51,6 +93,13 @@ class RequestParamsMeta(TypedDict, extra_items=Any): class RequestParams(MCPModel): meta: RequestParamsMeta | None = Field(alias="_meta", default=None) + """Metadata reserved by MCP for protocol-level concerns (wire name `_meta`). + + Carries the optional progress token and, on 2026-07-28+ sessions, the + reserved `io.modelcontextprotocol/*` keys. Required on the wire for + 2026-07-28+ client requests; the session layer supplies the reserved + entries, so code sending through an SDK session leaves this unset. + """ class PaginatedRequestParams(RequestParams): @@ -75,7 +124,11 @@ class NotificationParams(MCPModel): class Request(MCPModel, Generic[RequestParamsT, MethodT]): - """Base class for JSON-RPC requests.""" + """Base class for JSON-RPC requests. + + The JSON-RPC envelope (`jsonrpc`, `id`) is attached by the session layer + (see `mcp.types.jsonrpc`), not carried here. + """ method: MethodT params: RequestParamsT @@ -85,6 +138,8 @@ class PaginatedRequest(Request[PaginatedRequestParams | None, MethodT], Generic[ """Base class for paginated requests, matching the schema's PaginatedRequest interface.""" params: PaginatedRequestParams | None = None + """Pagination params. Required on the 2026-07-28+ wire (because `_meta` is); + the session layer materializes it there. Optional on earlier versions.""" class Notification(MCPModel, Generic[NotificationParamsT, MethodT]): @@ -94,8 +149,23 @@ class Notification(MCPModel, Generic[NotificationParamsT, MethodT]): params: NotificationParamsT +ResultType = Literal["complete", "input_required"] | str +"""Tags a `Result` so the client knows how to parse it (2026-07-28). + +"complete" means the result is final; "input_required" means it is an +`InputRequiredResult`. The union is open (the tasks extension reserves "task"). +Absent `resultType` is equivalent to "complete". +""" + + class Result(MCPModel): - """Base class for JSON-RPC results.""" + """Base class for JSON-RPC results. + + `result_type` is declared per concrete subclass, not here, because defaults + differ: most results default to "complete", `EmptyResult` defaults to None + (so it dumps as `{}`; some peer SDKs strict-validate empty results), and + `InputRequiredResult` carries a literal. + """ meta: Meta | None = Field(alias="_meta", default=None) """ @@ -112,15 +182,42 @@ class PaginatedResult(Result): """ +class CacheableResult(Result): + """Base class for results that carry client-side caching directives (2026-07-28). + + Both fields are required on the 2026-07-28 wire and always serialized by + this SDK; older peers ignore the extra keys. The defaults are SDK choices + (the schema declares none). + """ + + ttl_ms: Annotated[int, Field(ge=0)] = 0 + """How long (ms) the client MAY cache this response, analogous to HTTP + `Cache-Control: max-age`. 0 means immediately stale.""" + + cache_scope: Literal["public", "private"] = "private" + """Analogous to HTTP `Cache-Control: public` vs `private`: "public" allows + shared caches to serve the response to any user; "private" forbids that.""" + + class EmptyResult(Result): - """A response that indicates success but carries no data.""" + """A result that indicates success but carries no data. + + `result_type` defaults to None so this dumps as `{}`: deployed TypeScript + and Rust SDK clients validate empty results strictly and reject extra keys. + The 2026-07-28 schema requires `resultType`, so code answering an empty + result on a 2026-07-28+ session must pass `result_type="complete"`. + """ + + result_type: ResultType | None = None + """None keeps the dump empty; see the class docstring.""" class BaseMetadata(MCPModel): - """Base class for entities with name and optional title fields.""" + """Base class for entities with a programmatic name and an optional display title.""" name: str - """The programmatic name of the entity.""" + """Intended for programmatic or logical use, but used as a display name in past + specs or fallback (if title isn't present).""" title: str | None = None """ @@ -134,35 +231,30 @@ class BaseMetadata(MCPModel): class Icon(MCPModel): - """An icon for display in user interfaces.""" + """An optionally-sized icon for display in a user interface (2025-11-25+).""" src: str - """URL or data URI for the icon.""" + """A standard URI pointing to an icon resource (`http(s):` or `data:`). + + Consumers SHOULD ensure icon URLs come from a trusted domain and SHOULD + take appropriate precautions when consuming SVGs (which can contain script). + """ mime_type: str | None = None - """Optional MIME type for the icon.""" + """Optional MIME type override if the source MIME type is missing or generic.""" sizes: list[str] | None = None - """Optional list of strings specifying icon dimensions (e.g., ["48x48", "96x96"]).""" + """Optional sizes this icon is available in: WxH (e.g. `"48x48"`) or `"any"`. + If not provided, assume the icon can be used at any size.""" theme: IconTheme | None = None - """Optional theme specifier. - - `"light"` indicates the icon is designed for a light background, `"dark"` indicates the icon - is designed for a dark background. - - See https://modelcontextprotocol.io/specification/2025-11-25/schema#icon for more details. - """ + """The theme this icon is designed for. If not provided, assume any theme.""" class Implementation(BaseMetadata): - """Describes the name and version of an MCP implementation.""" + """Describes the name and version of an MCP implementation (`clientInfo` / `serverInfo`).""" version: str - - title: str | None = None - """An optional human-readable title for this implementation.""" - description: str | None = None """An optional human-readable description of what this implementation does.""" @@ -170,11 +262,15 @@ class Implementation(BaseMetadata): """An optional URL of the website for this implementation.""" icons: list[Icon] | None = None - """An optional list of icons for this implementation.""" + """Optional set of sized icons that the client can display in a user interface.""" class RootsCapability(MCPModel): - """Capability for root operations.""" + """Capability for root operations. + + Deprecated in protocol 2026-07-28 (SEP-2577) but still carried there as an + empty object (`list_changed` exists only through 2025-11-25). + """ list_changed: bool | None = None """Whether the client supports notifications for changes to the roots list.""" @@ -201,7 +297,7 @@ class FormElicitationCapability(MCPModel): class UrlElicitationCapability(MCPModel): - """Capability for URL mode elicitation.""" + """Capability for URL mode elicitation (2025-11-25+).""" class ElicitationCapability(MCPModel): @@ -214,11 +310,11 @@ class ElicitationCapability(MCPModel): """Present if the client supports form mode elicitation.""" url: UrlElicitationCapability | None = None - """Present if the client supports URL mode elicitation.""" + """Present if the client supports URL mode elicitation (2025-11-25 and later).""" class SamplingCapability(MCPModel): - """Sampling capability structure, allowing fine-grained capability advertisement.""" + """Sampling capability structure. Deprecated in 2026-07-28 (SEP-2577); shape unchanged.""" context: SamplingContextCapability | None = None """ @@ -232,8 +328,55 @@ class SamplingCapability(MCPModel): """ +class TasksListCapability(MCPModel): + """Capability for tasks listing operations (2025-11-25 only).""" + + +class TasksCancelCapability(MCPModel): + """Capability for tasks cancel operations (2025-11-25 only).""" + + +class TasksCreateMessageCapability(MCPModel): + """Capability for task-augmented sampling/createMessage requests (2025-11-25 only).""" + + +class TasksSamplingCapability(MCPModel): + """Capability for task-augmented sampling operations (2025-11-25 only).""" + + create_message: TasksCreateMessageCapability | None = None + + +class TasksCreateElicitationCapability(MCPModel): + """Capability for task-augmented elicitation/create requests (2025-11-25 only).""" + + +class TasksElicitationCapability(MCPModel): + """Capability for task-augmented elicitation operations (2025-11-25 only).""" + + create: TasksCreateElicitationCapability | None = None + + +class ClientTasksRequestsCapability(MCPModel): + """Specifies which request types the client can augment with tasks (2025-11-25 only).""" + + sampling: TasksSamplingCapability | None = None + elicitation: TasksElicitationCapability | None = None + + +class ClientTasksCapability(MCPModel): + """Capability for client tasks operations (2025-11-25 only).""" + + list: TasksListCapability | None = None + cancel: TasksCancelCapability | None = None + requests: ClientTasksRequestsCapability | None = None + + class ClientCapabilities(MCPModel): - """Capabilities a client may support.""" + """Capabilities a client may support. + + Not a closed set: any client can define additional capabilities. Sent once in + `initialize` through 2025-11-25; per-request in `_meta` on 2026-07-28. + """ experimental: dict[str, dict[str, Any]] | None = None """Experimental, non-standard capabilities that the client supports.""" @@ -246,6 +389,27 @@ class ClientCapabilities(MCPModel): """Present if the client supports elicitation from the user.""" roots: RootsCapability | None = None """Present if the client supports listing roots.""" + extensions: dict[str, dict[str, Any]] | None = None + """MCP extensions the client supports (2026-07-28). Keys are extension + identifiers; values are per-extension settings (empty object = no settings).""" + tasks: ClientTasksCapability | None = None + """Present if the client supports task-augmented requests (2025-11-25 only).""" + + +class UnsupportedProtocolVersionErrorData(MCPModel): + """Error data for the -32004 unsupported-protocol-version error (2026-07-28).""" + + supported: list[str] + """Protocol versions the server supports; the client should pick one and retry.""" + + requested: str + + +class MissingRequiredClientCapabilityErrorData(MCPModel): + """Error data for the -32003 missing-required-client-capability error (2026-07-28).""" + + required_capabilities: ClientCapabilities + """The capabilities the server requires from the client to process this request.""" class PromptsCapability(MCPModel): @@ -279,14 +443,39 @@ class CompletionsCapability(MCPModel): """Capability for completions operations.""" +class TasksCallCapability(MCPModel): + """Capability for task-augmented tools/call requests (2025-11-25 only).""" + + +class TasksToolsCapability(MCPModel): + """Capability for task-augmented tool operations (2025-11-25 only).""" + + call: TasksCallCapability | None = None + + +class ServerTasksRequestsCapability(MCPModel): + """Specifies which request types the server can augment with tasks (2025-11-25 only).""" + + tools: TasksToolsCapability | None = None + + +class ServerTasksCapability(MCPModel): + """Capability for server tasks operations (2025-11-25 only).""" + + list: TasksListCapability | None = None + cancel: TasksCancelCapability | None = None + requests: ServerTasksRequestsCapability | None = None + + class ServerCapabilities(MCPModel): - """Capabilities that a server may support.""" + """Capabilities that a server may support. Not a closed set.""" experimental: dict[str, dict[str, Any]] | None = None """Experimental, non-standard capabilities that the server supports.""" logging: LoggingCapability | None = None - """Present if the server supports sending log messages to the client.""" + """Present if the server supports sending log messages to the client. + Deprecated in 2026-07-28 (SEP-2577).""" prompts: PromptsCapability | None = None """Present if the server offers any prompt templates.""" @@ -300,9 +489,19 @@ class ServerCapabilities(MCPModel): completions: CompletionsCapability | None = None """Present if the server offers autocompletion suggestions for prompts and resources.""" + extensions: dict[str, dict[str, Any]] | None = None + """MCP extensions the server supports (2026-07-28). Keys are extension + identifiers; values are per-extension settings (empty object = no settings).""" + + tasks: ServerTasksCapability | None = None + """Present if the server supports task-augmented requests (2025-11-25 only).""" + class InitializeRequestParams(RequestParams): - """Parameters for the initialize request.""" + """Parameters for the `initialize` request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + """ protocol_version: str """The latest version of the Model Context Protocol that the client supports.""" @@ -313,6 +512,9 @@ class InitializeRequestParams(RequestParams): class InitializeRequest(Request[InitializeRequestParams, Literal["initialize"]]): """This request is sent from the client to the server when it first connects, asking it to begin initialization. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + On 2026-07-28 the handshake is `server/discover` plus per-request `_meta`. """ method: Literal["initialize"] = "initialize" @@ -320,19 +522,29 @@ class InitializeRequest(Request[InitializeRequestParams, Literal["initialize"]]) class InitializeResult(Result): - """After receiving an initialize request from the client, the server sends this.""" + """After receiving an initialize request from the client, the server sends this response. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + """ protocol_version: str - """The version of the Model Context Protocol that the server wants to use.""" + """The version of the Model Context Protocol that the server wants to use. + If the client cannot support this version, it MUST disconnect.""" capabilities: ServerCapabilities server_info: Implementation instructions: str | None = None - """Instructions describing how to use the server and its features.""" + """Instructions describing how to use the server and its features. + + Clients may use this to improve an LLM's understanding of available tools, + resources, etc., for example by adding it to the system prompt. + """ class InitializedNotification(Notification[NotificationParams | None, Literal["notifications/initialized"]]): """This notification is sent from the client to the server after initialization has finished. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. """ method: Literal["notifications/initialized"] = "notifications/initialized" @@ -341,13 +553,181 @@ class InitializedNotification(Notification[NotificationParams | None, Literal["n class PingRequest(Request[RequestParams | None, Literal["ping"]]): """A ping, issued by either the server or the client, to check that the other party is - still alive. + still alive. The receiver must promptly respond, or else may be disconnected. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. """ method: Literal["ping"] = "ping" params: RequestParams | None = None +class DiscoverRequest(Request[RequestParams | None, Literal["server/discover"]]): + """Asks the server to advertise its supported protocol versions, capabilities, + and other metadata (2026-07-28). + + Servers speaking 2026-07-28 MUST implement this; clients MAY call it but are + not required to (version negotiation can also happen via per-request `_meta`). + """ + + method: Literal["server/discover"] = "server/discover" + params: RequestParams | None = None + """Required on the 2026-07-28 wire (for `_meta`); the session layer materializes it.""" + + +class DiscoverResult(CacheableResult): + """The result returned by the server for a `server/discover` request (2026-07-28).""" + + supported_versions: list[str] + """MCP protocol versions this server supports; the client should pick one for subsequent requests.""" + + capabilities: ServerCapabilities + + server_info: Implementation + + instructions: str | None = None + """Natural-language guidance describing the server and its features, e.g. for + a system prompt. Should not duplicate information already in tool descriptions.""" + + result_type: ResultType = "complete" + """See `ResultType`. Always serialized; required on the 2026-07-28 wire, + ignored by older peers, and defaulted on inbound bodies that omit it.""" + + +# Tasks: introduced in 2025-11-25, removed from the core spec in 2026-07-28 +# (continuing as an extension). Defined here types-only; their methods are not +# in the request/notification unions below, so they are never dispatched. + + +class ToolExecution(MCPModel): + """Execution-related properties for a tool (2025-11-25 only).""" + + task_support: Literal["forbidden", "optional", "required"] | None = None + """Whether this tool supports task-augmented execution. Absent means "forbidden".""" + + +class TaskMetadata(MCPModel): + """Metadata for augmenting a request with task execution (the `task` params field; 2025-11-25 only).""" + + ttl: int | None = None + """Requested duration in milliseconds to retain task from creation.""" + + +class RelatedTaskMetadata(MCPModel): + """Associates a message with a task, via `_meta["io.modelcontextprotocol/related-task"]` (2025-11-25 only).""" + + task_id: str + + +TaskStatus = Literal["working", "input_required", "completed", "failed", "cancelled"] +"""The status of a task (2025-11-25 only).""" + + +class Task(MCPModel): + """Data associated with a task (2025-11-25 only).""" + + task_id: str + + status: TaskStatus + + status_message: str | None = None + """Optional human-readable message describing the current task state.""" + + created_at: str + """ISO 8601 timestamp when the task was created.""" + + last_updated_at: str + """ISO 8601 timestamp when the task was last updated.""" + + ttl: int | None + """Actual retention duration from creation in milliseconds, null for unlimited.""" + + poll_interval: int | None = None + """Suggested polling interval in milliseconds.""" + + +class CreateTaskResult(Result): + """A response to a task-augmented request (2025-11-25 only).""" + + task: Task + + +class GetTaskRequestParams(RequestParams): + task_id: str + + +class GetTaskRequest(Request[GetTaskRequestParams, Literal["tasks/get"]]): + """A request to retrieve the state of a task (2025-11-25 only).""" + + method: Literal["tasks/get"] = "tasks/get" + params: GetTaskRequestParams + + +class GetTaskResult(Result, Task): + """The response to a tasks/get request (2025-11-25 only).""" + + +class CancelTaskRequestParams(RequestParams): + task_id: str + + +class CancelTaskRequest(Request[CancelTaskRequestParams, Literal["tasks/cancel"]]): + """A request to cancel a task (2025-11-25 only).""" + + method: Literal["tasks/cancel"] = "tasks/cancel" + params: CancelTaskRequestParams + + +class CancelTaskResult(Result, Task): + """The response to a tasks/cancel request (2025-11-25 only).""" + + +class TaskStatusNotificationParams(NotificationParams, Task): + """Parameters for a `notifications/tasks/status` notification.""" + + +class TaskStatusNotification(Notification[TaskStatusNotificationParams, Literal["notifications/tasks/status"]]): + """An optional notification informing the requestor that a task's status has changed (2025-11-25 only).""" + + method: Literal["notifications/tasks/status"] = "notifications/tasks/status" + params: TaskStatusNotificationParams + + +class GetTaskPayloadRequestParams(RequestParams): + """Parameters for a tasks/result request.""" + + task_id: str + + +class GetTaskPayloadRequest(Request[GetTaskPayloadRequestParams, Literal["tasks/result"]]): + """A request to retrieve the result of a completed task (2025-11-25 only).""" + + method: Literal["tasks/result"] = "tasks/result" + params: GetTaskPayloadRequestParams + + +class GetTaskPayloadResult(Result): + """The response to a tasks/result request (2025-11-25 only). + + The structure matches the result type of the original request. The payload + arrives as extra wire fields, which `MCPModel` does not retain; validate the + response into the original request's result type (e.g. `CallToolResult`) + instead of this class. + """ + + +class ListTasksRequest(PaginatedRequest[Literal["tasks/list"]]): + """A request to retrieve a list of tasks (2025-11-25 only).""" + + method: Literal["tasks/list"] = "tasks/list" + + +class ListTasksResult(PaginatedResult): + """The response to a tasks/list request (2025-11-25 only).""" + + tasks: list[Task] + + class ProgressNotificationParams(NotificationParams): """Parameters for progress notifications.""" @@ -384,8 +764,17 @@ class ListResourcesRequest(PaginatedRequest[Literal["resources/list"]]): class Annotations(MCPModel): + """Optional annotations the client can use to inform how objects are used or displayed.""" + audience: list[Role] | None = None + """Who the intended audience is, e.g. `["user", "assistant"]`.""" + priority: Annotated[float, Field(ge=0.0, le=1.0)] | None = None + """How important this data is for operating the server: 1 means effectively + required, 0 means entirely optional.""" + + last_modified: str | None = None + """ISO 8601 timestamp of when the item was last modified.""" class Resource(BaseMetadata): @@ -407,15 +796,13 @@ class Resource(BaseMetadata): """ icons: list[Icon] | None = None - """An optional list of icons for this resource.""" + """Optional set of sized icons that the client can display in a user interface.""" annotations: Annotations | None = None + """Optional annotations for the client.""" meta: Meta | None = Field(alias="_meta", default=None) - """ - See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - for notes on _meta usage. - """ + """See the MCP specification for notes on `_meta` usage.""" class ResourceTemplate(BaseMetadata): @@ -425,7 +812,7 @@ class ResourceTemplate(BaseMetadata): """A URI template (according to RFC 6570) that can be used to construct resource URIs.""" description: str | None = None - """A human-readable description of what this template is for.""" + """A description of what this template is for.""" mime_type: str | None = None """The MIME type for all resources that match this template. @@ -434,9 +821,10 @@ class ResourceTemplate(BaseMetadata): """ icons: list[Icon] | None = None - """An optional list of icons for this resource template.""" + """An optional set of sized icons that the client can display in a user interface.""" annotations: Annotations | None = None + """Optional annotations for the client.""" meta: Meta | None = Field(alias="_meta", default=None) """ @@ -445,10 +833,12 @@ class ResourceTemplate(BaseMetadata): """ -class ListResourcesResult(PaginatedResult): +class ListResourcesResult(PaginatedResult, CacheableResult): """The server's response to a resources/list request from the client.""" resources: list[Resource] + result_type: ResultType = "complete" + """See `ResultType`. Always serialized; older peers ignore it.""" class ListResourceTemplatesRequest(PaginatedRequest[Literal["resources/templates/list"]]): @@ -457,19 +847,33 @@ class ListResourceTemplatesRequest(PaginatedRequest[Literal["resources/templates method: Literal["resources/templates/list"] = "resources/templates/list" -class ListResourceTemplatesResult(PaginatedResult): +class ListResourceTemplatesResult(PaginatedResult, CacheableResult): """The server's response to a resources/templates/list request from the client.""" resource_templates: list[ResourceTemplate] + result_type: ResultType = "complete" + """See `ResultType`. Always serialized; older peers ignore it.""" -class ReadResourceRequestParams(RequestParams): - """Parameters for reading a resource.""" +class InputResponseRequestParams(RequestParams): + """Base params for client requests that can carry responses to a server's + input requests (2026-07-28 multi-round-trip flow). + When a request returns an `InputRequiredResult`, the client retries the + original request with these fields populated. + """ + + input_responses: InputResponses | None = None + """Responses to the server's `InputRequiredResult.input_requests`, keyed identically.""" + request_state: str | None = None + """Opaque state from the `InputRequiredResult`, passed back verbatim on retry.""" + + +class ReadResourceRequestParams(InputResponseRequestParams): uri: str """ - The URI of the resource to read. The URI can use any protocol; it is up to the - server how to interpret it. + The URI of the resource. The URI can use any protocol; it is up to the server + how to interpret it. """ @@ -511,10 +915,14 @@ class BlobResourceContents(ResourceContents): """A base64-encoded string representing the binary data of the item.""" -class ReadResourceResult(Result): +class ReadResourceResult(CacheableResult): """The server's response to a resources/read request from the client.""" contents: list[TextResourceContents | BlobResourceContents] + """The contents of the resource or sub-resources that were read.""" + + result_type: ResultType = "complete" + """See `ResultType`. Always serialized; older peers ignore it.""" class ResourceListChangedNotification( @@ -522,6 +930,9 @@ class ResourceListChangedNotification( ): """An optional notification from the server to the client, informing it that the list of resources it can read from has changed. + + May be sent spontaneously through 2025-11-25; on 2026-07-28 sessions the + client must opt in via `subscriptions/listen`. """ method: Literal["notifications/resources/list_changed"] = "notifications/resources/list_changed" @@ -529,7 +940,10 @@ class ResourceListChangedNotification( class SubscribeRequestParams(RequestParams): - """Parameters for subscribing to a resource.""" + """Parameters for subscribing to a resource. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + """ uri: str """ @@ -541,6 +955,9 @@ class SubscribeRequestParams(RequestParams): class SubscribeRequest(Request[SubscribeRequestParams, Literal["resources/subscribe"]]): """Sent from the client to request resources/updated notifications from the server whenever a particular resource changes. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + On 2026-07-28 use `subscriptions/listen` instead. """ method: Literal["resources/subscribe"] = "resources/subscribe" @@ -548,15 +965,21 @@ class SubscribeRequest(Request[SubscribeRequestParams, Literal["resources/subscr class UnsubscribeRequestParams(RequestParams): - """Parameters for unsubscribing from a resource.""" + """Parameters for a resources/unsubscribe request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + """ uri: str """The URI of the resource to unsubscribe from.""" class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/unsubscribe"]]): - """Sent from the client to request cancellation of resources/updated notifications from - the server. + """Sent from the client to request cancellation of resources/updated notifications + from the server. This should follow a previous resources/subscribe request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + On 2026-07-28 use `subscriptions/listen` instead. """ method: Literal["resources/unsubscribe"] = "resources/unsubscribe" @@ -564,8 +987,6 @@ class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/un class ResourceUpdatedNotificationParams(NotificationParams): - """Parameters for resource update notifications.""" - uri: str """ The URI of the resource that has been updated. This might be a sub-resource of the @@ -578,23 +999,82 @@ class ResourceUpdatedNotification( ): """A notification from the server to the client, informing it that a resource has changed and may need to be read again. + + Only sent if the client subscribed: via `resources/subscribe` through + 2025-11-25, or `subscriptions/listen` on 2026-07-28. """ method: Literal["notifications/resources/updated"] = "notifications/resources/updated" params: ResourceUpdatedNotificationParams +class SubscriptionFilter(MCPModel): + """The set of notification types a client opts in to via `subscriptions/listen` (2026-07-28). + + Each type is opt-in; the server MUST NOT send types not requested here. + Echoed back in `notifications/subscriptions/acknowledged` as the subset the + server agreed to honor. Extensions merge additional keys (e.g. `taskIds`), + so unknown keys round-trip. + """ + + model_config = ConfigDict(alias_generator=to_camel, populate_by_name=True, extra="allow") + + tools_list_changed: bool | None = None + """If true, receive notifications/tools/list_changed.""" + + prompts_list_changed: bool | None = None + """If true, receive notifications/prompts/list_changed.""" + + resources_list_changed: bool | None = None + """If true, receive notifications/resources/list_changed.""" + + resource_subscriptions: list[str] | None = None + """Subscribe to notifications/resources/updated for these resource URIs.""" + + +class SubscriptionsListenRequestParams(RequestParams): + notifications: SubscriptionFilter + """The notifications the client opts in to on this stream.""" + + +class SubscriptionsListenRequest(Request[SubscriptionsListenRequestParams, Literal["subscriptions/listen"]]): + """Opens a long-lived channel for receiving notifications outside the context + of a specific request (2026-07-28). + """ + + method: Literal["subscriptions/listen"] = "subscriptions/listen" + params: SubscriptionsListenRequestParams + + +class SubscriptionsAcknowledgedNotificationParams(NotificationParams): + notifications: SubscriptionFilter + """The subset of requested notification types the server agreed to honor. + Unsupported types are omitted.""" + + +class SubscriptionsAcknowledgedNotification( + Notification[ + SubscriptionsAcknowledgedNotificationParams, + Literal["notifications/subscriptions/acknowledged"], + ] +): + """First message on a `subscriptions/listen` stream: acknowledges the + subscription and reports which notification types the server will honor (2026-07-28). + """ + + method: Literal["notifications/subscriptions/acknowledged"] = "notifications/subscriptions/acknowledged" + params: SubscriptionsAcknowledgedNotificationParams + + class ListPromptsRequest(PaginatedRequest[Literal["prompts/list"]]): - """Sent from the client to request a list of prompts and prompt templates.""" + """Sent from the client to request a list of prompts and prompt templates the server has.""" method: Literal["prompts/list"] = "prompts/list" -class PromptArgument(MCPModel): - """An argument for a prompt template.""" +class PromptArgument(BaseMetadata): + """Describes an argument that a prompt can accept.""" - name: str - """The name of the argument.""" description: str | None = None """A human-readable description of the argument.""" required: bool | None = None @@ -617,15 +1097,15 @@ class Prompt(BaseMetadata): """ -class ListPromptsResult(PaginatedResult): +class ListPromptsResult(PaginatedResult, CacheableResult): """The server's response to a prompts/list request from the client.""" prompts: list[Prompt] + result_type: ResultType = "complete" + """See `ResultType`. Always serialized; older peers ignore it.""" -class GetPromptRequestParams(RequestParams): - """Parameters for getting a prompt.""" - +class GetPromptRequestParams(InputResponseRequestParams): name: str """The name of the prompt or prompt template.""" arguments: dict[str, str] | None = None @@ -640,12 +1120,13 @@ class GetPromptRequest(Request[GetPromptRequestParams, Literal["prompts/get"]]): class TextContent(MCPModel): - """Text content for a message.""" + """Text provided to or from an LLM.""" type: Literal["text"] = "text" text: str """The text content of the message.""" annotations: Annotations | None = None + """Optional annotations for the client.""" meta: Meta | None = Field(alias="_meta", default=None) """ See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) @@ -654,7 +1135,7 @@ class TextContent(MCPModel): class ImageContent(MCPModel): - """Image content for a message.""" + """An image provided to or from an LLM.""" type: Literal["image"] = "image" data: str @@ -665,15 +1146,13 @@ class ImageContent(MCPModel): image types. """ annotations: Annotations | None = None + """Optional annotations for the client.""" meta: Meta | None = Field(alias="_meta", default=None) - """ - See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - for notes on _meta usage. - """ + """See the MCP specification's "General fields: _meta" section for notes on _meta usage.""" class AudioContent(MCPModel): - """Audio content for a message.""" + """Audio provided to or from an LLM.""" type: Literal["audio"] = "audio" data: str @@ -684,6 +1163,7 @@ class AudioContent(MCPModel): audio types. """ annotations: Annotations | None = None + """Optional annotations for the client.""" meta: Meta | None = Field(alias="_meta", default=None) """ See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) @@ -692,11 +1172,11 @@ class AudioContent(MCPModel): class ToolUseContent(MCPModel): - """Content representing an assistant's request to invoke a tool. + """An assistant's request to invoke a tool during sampling (2025-11-25+). - This content type appears in assistant messages when the LLM wants to call a tool - during sampling. The server should execute the tool and return a ToolResultContent - in the next user message. + Appears in `sampling/createMessage` results and replayed assistant messages. + The server should execute the tool and return a `ToolResultContent` in the + next user message. Deprecated in 2026-07-28 (SEP-2577). """ type: Literal["tool_use"] = "tool_use" @@ -712,48 +1192,45 @@ class ToolUseContent(MCPModel): """Arguments to pass to the tool. Must conform to the tool's inputSchema.""" meta: Meta | None = Field(alias="_meta", default=None) - """ - See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - for notes on _meta usage. - """ + """Optional metadata. Clients SHOULD preserve this in subsequent sampling + requests to enable caching optimizations.""" class ToolResultContent(MCPModel): - """Content representing the result of a tool execution. + """The result of a tool use, provided by the user back to the assistant (2025-11-25+). - This content type appears in user messages as a response to a ToolUseContent - from the assistant. It contains the output of executing the requested tool. + Appears in sampling messages as a response to a `ToolUseContent` block. + Requires the `sampling.tools` client capability. Deprecated in 2026-07-28 (SEP-2577). """ type: Literal["tool_result"] = "tool_result" """Discriminator for tool result content.""" tool_use_id: str - """The unique identifier that corresponds to the tool call's id field.""" + """The `id` of the `ToolUseContent` this result corresponds to.""" content: list[ContentBlock] = [] - """ - A list of content objects representing the tool result. - Defaults to empty list if not provided. - """ + """The unstructured result content (same format as `CallToolResult.content`).""" - structured_content: dict[str, Any] | None = None - """ - Optional structured tool output that matches the tool's outputSchema (if defined). - """ + structured_content: Any = None + """An optional structured result value. Any JSON value on 2026-07-28; + restricted to a JSON object on 2025-11-25.""" is_error: bool | None = None - """Whether the tool execution resulted in an error.""" + """Whether the tool use resulted in an error. Absent is equivalent to false.""" meta: Meta | None = Field(alias="_meta", default=None) - """ - See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - for notes on _meta usage. - """ + """Optional metadata. Clients SHOULD preserve this in subsequent sampling + requests to enable caching optimizations.""" SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent -"""Content block types allowed in sampling messages.""" +"""Content block types allowed in sampling messages. + +This is the widest (2025-11-25+) membership; older sessions allow only a subset +on the wire. Serialization never narrows a value to fit; version gating is the +session layer's responsibility. Deprecated in 2026-07-28 (SEP-2577). +""" SamplingContent: TypeAlias = TextContent | ImageContent | AudioContent """Basic content types for sampling responses (without tool use). @@ -794,6 +1271,7 @@ class EmbeddedResource(MCPModel): type: Literal["resource"] = "resource" resource: TextResourceContents | BlobResourceContents annotations: Annotations | None = None + """Optional annotations for the client.""" meta: Meta | None = Field(alias="_meta", default=None) """ See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) @@ -815,7 +1293,10 @@ class ResourceLink(Resource): class PromptMessage(MCPModel): - """Describes a message returned as part of a prompt.""" + """Describes a message returned as part of a prompt. + + Similar to `SamplingMessage`, but also supports embedded resources. + """ role: Role content: ContentBlock @@ -827,6 +1308,10 @@ class GetPromptResult(Result): description: str | None = None """An optional description for the prompt.""" messages: list[PromptMessage] + """The messages composing the prompt, in the order they should be presented.""" + + result_type: ResultType = "complete" + """See `ResultType`. Always serialized; older peers ignore it.""" class PromptListChangedNotification( @@ -834,6 +1319,9 @@ class PromptListChangedNotification( ): """An optional notification from the server to the client, informing it that the list of prompts it offers has changed. + + May be sent spontaneously through 2025-11-25; on 2026-07-28 sessions the + client must opt in via `subscriptions/listen`. """ method: Literal["notifications/prompts/list_changed"] = "notifications/prompts/list_changed" @@ -898,34 +1386,43 @@ class Tool(BaseMetadata): description: str | None = None """A human-readable description of the tool.""" input_schema: dict[str, Any] - """A JSON Schema object defining the expected parameters for the tool.""" - output_schema: dict[str, Any] | None = None + """A JSON Schema object defining the expected parameters for the tool. + + `type: "object"` is required at the root. 2026-07-28 allows any JSON Schema + 2020-12 keyword; earlier versions define only `type`/`properties`/`required`. """ - An optional JSON Schema object defining the structure of the tool's output - returned in the structured_content field of a CallToolResult. + execution: ToolExecution | None = None + """Execution-related properties (2025-11-25 only; removed in 2026-07-28).""" + output_schema: dict[str, Any] | None = None + """An optional JSON Schema object defining the structure of the tool's output + returned in the `structured_content` field of a `CallToolResult`. + + Restricted to `type: "object"` at the root through 2025-11-25; any valid + JSON Schema 2020-12 on 2026-07-28. """ icons: list[Icon] | None = None - """An optional list of icons for this tool.""" + """Optional set of sized icons for display (2025-11-25+).""" annotations: ToolAnnotations | None = None - """Optional additional tool information.""" + """Optional additional tool information. + Display-name precedence: `title`, `annotations.title`, then `name`.""" meta: Meta | None = Field(alias="_meta", default=None) - """ - See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - for notes on _meta usage. - """ + """See the MCP specification for notes on `_meta` usage.""" -class ListToolsResult(PaginatedResult): +class ListToolsResult(PaginatedResult, CacheableResult): """The server's response to a tools/list request from the client.""" tools: list[Tool] + result_type: ResultType = "complete" + """See `ResultType`. Always serialized; older peers ignore it.""" -class CallToolRequestParams(RequestParams): - """Parameters for calling a tool.""" +class CallToolRequestParams(InputResponseRequestParams): name: str arguments: dict[str, Any] | None = None + task: TaskMetadata | None = None + """If specified, the caller requests task-augmented execution (2025-11-25 only).""" class CallToolRequest(Request[CallToolRequestParams, Literal["tools/call"]]): @@ -936,17 +1433,35 @@ class CallToolRequest(Request[CallToolRequestParams, Literal["tools/call"]]): class CallToolResult(Result): - """The server's response to a tool call.""" + """The server's response to a tool call. + + Errors that originate from the tool SHOULD be reported inside the result + with `is_error` set to true, not as an MCP protocol-level error, so the LLM + can see and self-correct. Errors in finding the tool, or any other + exceptional condition, should be reported as an MCP error response. + """ content: list[ContentBlock] - structured_content: dict[str, Any] | None = None - """An optional JSON object that represents the structured result of the tool call.""" + """A list of content objects that represent the unstructured result of the tool call.""" + structured_content: Any = None + """An optional JSON value representing the structured result of the tool call. + + Any JSON value on 2026-07-28; restricted to a JSON object on 2025-06-18 and + 2025-11-25. + """ is_error: bool = False + """Whether the tool call ended in an error.""" + + result_type: ResultType = "complete" + """See `ResultType`. Always serialized; older peers ignore it.""" class ToolListChangedNotification(Notification[NotificationParams | None, Literal["notifications/tools/list_changed"]]): """An optional notification from the server to the client, informing it that the list of tools it offers has changed. + + May be sent spontaneously through 2025-11-25; on 2026-07-28 sessions the + client must opt in via `subscriptions/listen`. """ method: Literal["notifications/tools/list_changed"] = "notifications/tools/list_changed" @@ -954,25 +1469,36 @@ class ToolListChangedNotification(Notification[NotificationParams | None, Litera LoggingLevel = Literal["debug", "info", "notice", "warning", "error", "critical", "alert", "emergency"] +"""The severity of a log message. + +These map to syslog severities (RFC-5424 section 6.2.1). Logging is deprecated +in 2026-07-28 (SEP-2577); the level scale is unchanged across versions. +""" class SetLevelRequestParams(RequestParams): - """Parameters for setting the logging level.""" + """Parameters for setting the logging level. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + """ level: LoggingLevel - """The level of logging that the client wants to receive from the server.""" + """The level of logging that the client wants to receive from the server. + The server should send all logs at this level and higher (more severe).""" class SetLevelRequest(Request[SetLevelRequestParams, Literal["logging/setLevel"]]): - """A request from the client to the server, to enable or adjust logging.""" + """A request from the client to the server, to enable or adjust logging. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + On 2026-07-28 the client opts in per-request via `_meta` (`LOG_LEVEL_META_KEY`). + """ method: Literal["logging/setLevel"] = "logging/setLevel" params: SetLevelRequestParams class LoggingMessageNotificationParams(NotificationParams): - """Parameters for logging message notifications.""" - level: LoggingLevel """The severity of this log message.""" logger: str | None = None @@ -985,24 +1511,42 @@ class LoggingMessageNotificationParams(NotificationParams): class LoggingMessageNotification(Notification[LoggingMessageNotificationParams, Literal["notifications/message"]]): - """Notification of a log message passed from server to client.""" + """Notification of a log message passed from server to client. + + Through 2025-11-25 the client subscribes via `logging/setLevel`. On + 2026-07-28 the client opts in per-request via `_meta` (`LOG_LEVEL_META_KEY`) + and the server MUST NOT send this without it. Deprecated in 2026-07-28 (SEP-2577). + """ method: Literal["notifications/message"] = "notifications/message" params: LoggingMessageNotificationParams IncludeContext = Literal["none", "thisServer", "allServers"] +"""Scope of MCP-server context a sampling request asks the client to attach. + +"thisServer" and "allServers" are deprecated (SEP-2596). +""" class ModelHint(MCPModel): - """Hints to use for model selection.""" + """Hints to use for model selection. + + Keys not declared here are up to the client to interpret. Deprecated in + 2026-07-28 (SEP-2577) with the rest of sampling. + """ name: str | None = None - """A hint for a model name.""" + """A hint for a model name. + + The client SHOULD treat this as a substring (e.g. `sonnet` matches + `claude-3-5-sonnet-20241022`) and MAY map it to another provider's model + that fills a similar niche. + """ class ModelPreferences(MCPModel): - """The server's preferences for model selection, requested by the client during + """The server's preferences for model selection, requested of the client during sampling. Because LLMs can vary along multiple dimensions, choosing the "best" model is @@ -1014,6 +1558,8 @@ class ModelPreferences(MCPModel): These preferences are always advisory. The client MAY ignore them. It is also up to the client to decide how to interpret these preferences and how to balance them against other considerations. + + Deprecated in 2026-07-28 (SEP-2577) with the rest of sampling. """ hints: list[ModelHint] | None = None @@ -1050,25 +1596,24 @@ class ModelPreferences(MCPModel): class ToolChoice(MCPModel): - """Controls tool usage behavior during sampling. + """Controls tool selection behavior for sampling requests (2025-11-25+). - Allows the server to specify whether and how the LLM should use tools - in its response. + The client MUST return an error if this is received without the + `sampling.tools` capability. Absent means `{"mode": "auto"}`. """ mode: Literal["auto", "required", "none"] | None = None """ - Controls when tools are used: + Controls the tool use ability of the model: - "auto": Model decides whether to use tools (default) - "required": Model MUST use at least one tool before completing - - "none": Model should not use tools + - "none": Model MUST NOT use any tools """ class CreateMessageRequestParams(RequestParams): - """Parameters for creating a message.""" - messages: list[SamplingMessage] + """The conversation to sample from.""" model_preferences: ModelPreferences | None = None """ The server's preferences for which model to select. The client MAY ignore @@ -1078,35 +1623,44 @@ class CreateMessageRequestParams(RequestParams): """An optional system prompt the server wants to use for sampling.""" include_context: IncludeContext | None = None """ - A request to include context from one or more MCP servers (including the caller), to - be attached to the prompt. + A request to include context from one or more MCP servers (including the + caller), to be attached to the prompt. The client MAY ignore this request. + Default is "none". "thisServer" and "allServers" are deprecated (SEP-2596). """ temperature: float | None = None max_tokens: int """The maximum number of tokens to sample, as requested by the server.""" stop_sequences: list[str] | None = None metadata: dict[str, Any] | None = None - """Optional metadata to pass through to the LLM provider.""" + """Optional metadata to pass through to the LLM provider. Provider-specific.""" tools: list[Tool] | None = None - """ - Tool definitions for the LLM to use during sampling. - Requires clientCapabilities.sampling.tools to be present. - """ + """Tools the model may use during generation (2025-11-25+). Requires the + `sampling.tools` client capability.""" tool_choice: ToolChoice | None = None - """ - Controls tool usage behavior. - Requires clientCapabilities.sampling.tools and the tools parameter to be present. - """ + """Controls how the model uses tools (2025-11-25+). Requires the + `sampling.tools` client capability.""" + task: TaskMetadata | None = None + """If specified, the caller requests task-augmented execution (2025-11-25 only).""" class CreateMessageRequest(Request[CreateMessageRequestParams, Literal["sampling/createMessage"]]): - """A request from the server to sample an LLM via the client.""" + """A request from the server to sample an LLM via the client. + + The client has full discretion over which model to select and should inform + the user before sampling (human in the loop). A standalone JSON-RPC request + through 2025-11-25; on 2026-07-28 it is embedded in + `InputRequiredResult.input_requests` instead. Deprecated in 2026-07-28 (SEP-2577). + """ method: Literal["sampling/createMessage"] = "sampling/createMessage" params: CreateMessageRequestParams StopReason = Literal["endTurn", "stopSequence", "maxTokens", "toolUse"] | str +"""The reason why sampling stopped, if known. + +An open union to allow provider-specific stop reasons. "toolUse" is 2025-11-25+. +""" class CreateMessageResult(Result): @@ -1114,6 +1668,9 @@ class CreateMessageResult(Result): This is the backwards-compatible version that returns single content (no arrays). Used when the request does not include tools. + + On 2026-07-28 this travels embedded in an `InputResponses` map rather than + as a top-level JSON-RPC result. Deprecated in 2026-07-28 (SEP-2577). """ role: Role @@ -1129,7 +1686,7 @@ class CreateMessageResult(Result): class CreateMessageResultWithTools(Result): """The client's response to a sampling/createMessage request when tools were provided. - This version supports array content for tool use flows. + This version supports array content for tool use flows (2025-11-25 and later). """ role: Role @@ -1162,12 +1719,15 @@ class ResourceTemplateReference(MCPModel): """The URI or URI template of the resource.""" +# Not BaseMetadata: inheriting would reorder dump keys for existing callers. class PromptReference(MCPModel): """Identifies a prompt.""" type: Literal["ref/prompt"] = "ref/prompt" name: str """The name of the prompt or prompt template.""" + title: str | None = None + """Human-readable display title. If not provided, `name` should be used for display.""" class CompletionArgument(MCPModel): @@ -1187,9 +1747,8 @@ class CompletionContext(MCPModel): class CompleteRequestParams(RequestParams): - """Parameters for completion requests.""" - ref: ResourceTemplateReference | PromptReference + """The prompt or resource-template reference to complete against.""" argument: CompletionArgument context: CompletionContext | None = None """Additional, optional context for completions.""" @@ -1223,6 +1782,10 @@ class CompleteResult(Result): """The server's response to a completion/complete request.""" completion: Completion + """The completion values, with optional total / has-more pagination hints.""" + + result_type: ResultType = "complete" + """See `ResultType`. Always serialized; older peers ignore it.""" class ListRootsRequest(Request[RequestParams | None, Literal["roots/list"]]): @@ -1233,14 +1796,22 @@ class ListRootsRequest(Request[RequestParams | None, Literal["roots/list"]]): This request is typically used when the server needs to understand the file system structure or access specific locations that the client has permission to read from. + + A standalone JSON-RPC request through 2025-11-25; on 2026-07-28 it is + embedded in `InputRequiredResult.input_requests`. Deprecated in 2026-07-28 (SEP-2577). """ method: Literal["roots/list"] = "roots/list" params: RequestParams | None = None + """Stays optional on 2026-07-28 (reserved client `_meta` keys do not apply + to server-to-client payloads).""" class Root(MCPModel): - """Represents a root directory or file that the server can operate on.""" + """Represents a root directory or file that the server can operate on. + + Deprecated in 2026-07-28 (SEP-2577) with the rest of roots. + """ uri: FileUrl """ @@ -1264,8 +1835,11 @@ class Root(MCPModel): class ListRootsResult(Result): """The client's response to a roots/list request from the server. - This result contains an array of Root objects, each representing a root directory - or file that the server can operate on. + This result contains an array of Root objects, each representing a root + directory or file that the server can operate on. + + On 2026-07-28 this is carried as an `InputResponses` entry, not a JSON-RPC + result. Deprecated in 2026-07-28 (SEP-2577). """ roots: list[Root] @@ -1280,6 +1854,8 @@ class RootsListChangedNotification( This notification should be sent whenever the client adds, removes, or modifies any root. The server should then request an updated list of roots using the ListRootsRequest. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. """ method: Literal["notifications/roots/list_changed"] = "notifications/roots/list_changed" @@ -1287,13 +1863,12 @@ class RootsListChangedNotification( class CancelledNotificationParams(NotificationParams): - """Parameters for cancellation notifications.""" - request_id: RequestId | None = None """ The ID of the request to cancel. This MUST correspond to the ID of a request previously issued in the same direction. + Required on the wire through 2025-06-18; optional from 2025-11-25. """ reason: str | None = None """An optional string describing the reason for the cancellation.""" @@ -1302,6 +1877,10 @@ class CancelledNotificationParams(NotificationParams): class CancelledNotification(Notification[CancelledNotificationParams, Literal["notifications/cancelled"]]): """This notification can be sent by either side to indicate that it is canceling a previously-issued request. + + The request SHOULD still be in-flight, but due to communication latency, it + is always possible that this notification MAY arrive after the request has + already finished. A client MUST NOT attempt to cancel its `initialize` request. """ method: Literal["notifications/cancelled"] = "notifications/cancelled" @@ -1325,39 +1904,17 @@ class ElicitCompleteNotification( URLElicitationRequiredError, update the user interface, or otherwise continue an interaction. However, because delivery of the notification is not guaranteed, clients must not wait indefinitely for a notification from the server. + + New in protocol 2025-11-25 with URL mode itself. """ method: Literal["notifications/elicitation/complete"] = "notifications/elicitation/complete" params: ElicitCompleteNotificationParams -ClientRequest = ( - PingRequest - | InitializeRequest - | CompleteRequest - | SetLevelRequest - | GetPromptRequest - | ListPromptsRequest - | ListResourcesRequest - | ListResourceTemplatesRequest - | ReadResourceRequest - | SubscribeRequest - | UnsubscribeRequest - | CallToolRequest - | ListToolsRequest -) -client_request_adapter = TypeAdapter[ClientRequest](ClientRequest) - - -ClientNotification = ( - CancelledNotification | ProgressNotification | InitializedNotification | RootsListChangedNotification -) -client_notification_adapter = TypeAdapter[ClientNotification](ClientNotification) - - -# Type for elicitation schema - a JSON Schema dict +# Kept as a raw JSON Schema dict so callers can hand it straight to a validator; +# the per-version packages model RequestedSchema/PrimitiveSchemaDefinition strictly. ElicitRequestedSchema: TypeAlias = dict[str, Any] -"""Schema for elicitation requests.""" class ElicitRequestFormParams(RequestParams): @@ -1379,12 +1936,15 @@ class ElicitRequestFormParams(RequestParams): Only top-level properties are allowed, without nesting. """ + task: TaskMetadata | None = None + """If specified, the caller requests task-augmented execution (2025-11-25 only).""" + class ElicitRequestURLParams(RequestParams): """Parameters for URL mode elicitation requests. URL mode directs users to external URLs for sensitive out-of-band interactions - like OAuth flows, credential collection, or payment processing. + like OAuth flows, credential collection, or payment processing. New in 2025-11-25. """ mode: Literal["url"] = "url" @@ -1402,6 +1962,9 @@ class ElicitRequestURLParams(RequestParams): The client MUST treat this ID as an opaque value. """ + task: TaskMetadata | None = None + """If specified, the caller requests task-augmented execution (2025-11-25 only).""" + # Union type for elicitation request parameters ElicitRequestParams: TypeAlias = ElicitRequestURLParams | ElicitRequestFormParams @@ -1409,7 +1972,7 @@ class ElicitRequestURLParams(RequestParams): class ElicitRequest(Request[ElicitRequestParams, Literal["elicitation/create"]]): - """A request from the server to elicit information from the client.""" + """A request from the server to elicit additional information from the user via the client.""" method: Literal["elicitation/create"] = "elicitation/create" params: ElicitRequestParams @@ -1432,25 +1995,130 @@ class ElicitResult(Result): Contains values matching the requested schema. Values can be strings, integers, floats, booleans, arrays of strings, or null. For URL mode, this field is omitted. + + The null value arm is superset leniency; the schema-exact surface types + declare no null arm. """ class ElicitationRequiredErrorData(MCPModel): - """Error data for URLElicitationRequiredError. + """Error data for the -32042 URL-elicitation-required error. Servers return this when a request cannot be processed until one or more URL mode elicitations are completed. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. """ elicitations: list[ElicitRequestURLParams] """List of URL mode elicitations that must be completed.""" +InputRequest: TypeAlias = CreateMessageRequest | ListRootsRequest | ElicitRequest +"""A single server-initiated input request embedded in `InputRequiredResult` (2026-07-28). + +Discriminated by `method`. On 2026-07-28 these embedded payloads take the place +of standalone server-to-client JSON-RPC requests. +""" + +InputRequests: TypeAlias = dict[str, InputRequest] +"""A map of server-initiated requests that the client must fulfill (2026-07-28). + +Keys are server-assigned identifiers. Carried by `InputRequiredResult.input_requests` +and by the tasks extension. +""" + +InputResponse: TypeAlias = CreateMessageResult | CreateMessageResultWithTools | ListRootsResult | ElicitResult +"""A client response to a single server-initiated input request (2026-07-28). + +`CreateMessageResultWithTools` is this SDK's array-content split of the schema's +single `CreateMessageResult` arm; the wire union has three arms. +""" + +InputResponses: TypeAlias = dict[str, InputResponse] +"""A map of client responses to server-initiated input requests (2026-07-28). + +Keys match those of the `InputRequests` map the server sent. Also used by the +tasks extension's `tasks/update` params. +""" + + +class InputRequiredResult(Result): + """The server needs additional input before the original request can complete (2026-07-28). + + Returned in place of the normal result of an interactive client request + (`tools/call`, `prompts/get`, `resources/read`). The client fulfills + `input_requests` and retries the original request, carrying the responses + and the echoed `request_state`. At least one of those two fields is + present on the wire (spec MUST; not enforced by the model). + """ + + result_type: Literal["input_required"] = "input_required" + """Discriminating tag for the dual-result response unions.""" + + input_requests: InputRequests | None = None + """Requests the client must complete before retrying. Keys are server-assigned.""" + + request_state: str | None = None + """Opaque state to pass back verbatim when the client retries the original request.""" + + +# Forward refs to InputResponses; rebuild at import time rather than first use. +InputResponseRequestParams.model_rebuild() +ReadResourceRequestParams.model_rebuild() +GetPromptRequestParams.model_rebuild() +CallToolRequestParams.model_rebuild() + +# Top-level message unions: superset across all supported protocol versions. +# Per-version validity is recorded in `mcp.types.methods`, not enforced here. + +ClientRequest = ( + PingRequest + | InitializeRequest + | CompleteRequest + | SetLevelRequest + | GetPromptRequest + | ListPromptsRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | CallToolRequest + | ListToolsRequest + | DiscoverRequest + | SubscriptionsListenRequest +) +"""Union of client-to-server request payloads across all supported protocol versions. + +The 2025-11-25 task requests are deliberately excluded (types-only). +""" + +client_request_adapter = TypeAdapter[ClientRequest](ClientRequest) + + +ClientNotification = ( + CancelledNotification | ProgressNotification | InitializedNotification | RootsListChangedNotification +) +"""Notifications sent from the client to the server. + +`TaskStatusNotification` is deliberately excluded (types-only). +""" + +client_notification_adapter = TypeAdapter[ClientNotification](ClientNotification) + + ClientResult = EmptyResult | CreateMessageResult | CreateMessageResultWithTools | ListRootsResult | ElicitResult client_result_adapter = TypeAdapter[ClientResult](ClientResult) ServerRequest = PingRequest | CreateMessageRequest | ListRootsRequest | ElicitRequest +"""Union of standalone JSON-RPC requests a server can send to a client. + +Live through 2025-11-25 only: 2026-07-28 has no server-to-client JSON-RPC +requests (these payloads are embedded in `InputRequiredResult` instead). +""" + server_request_adapter = TypeAdapter[ServerRequest](ServerRequest) @@ -1463,13 +2131,20 @@ class ElicitationRequiredErrorData(MCPModel): | ToolListChangedNotification | PromptListChangedNotification | ElicitCompleteNotification + | SubscriptionsAcknowledgedNotification ) +"""Union of server-to-client notification payloads across all supported protocol versions. + +`TaskStatusNotification` is deliberately excluded (types-only). +""" + server_notification_adapter = TypeAdapter[ServerNotification](ServerNotification) ServerResult = ( EmptyResult | InitializeResult + | DiscoverResult | CompleteResult | GetPromptResult | ListPromptsResult @@ -1478,5 +2153,11 @@ class ElicitationRequiredErrorData(MCPModel): | ReadResourceResult | CallToolResult | ListToolsResult + | InputRequiredResult ) +"""Union of every result payload a server can return for a client request. + +`InputRequiredResult` is deliberately last: both of its fields are optional, +so an earlier position would shadow other members during union resolution. +""" server_result_adapter = TypeAdapter[ServerResult](ServerResult) diff --git a/src/mcp/types/_wire_base.py b/src/mcp/types/_wire_base.py new file mode 100644 index 0000000000..d6a2e55f7a --- /dev/null +++ b/src/mcp/types/_wire_base.py @@ -0,0 +1,19 @@ +"""Shared pydantic bases for the `mcp.types.v*` wire-shape packages. + +No alias generator is configured: every wire name is an explicit +`Field(alias=...)` so each surface file shows exactly what goes on the wire. +""" + +from pydantic import BaseModel, ConfigDict + + +class WireModel(BaseModel): + """Base for surface-package models: unknown fields are accepted and dropped.""" + + model_config = ConfigDict(populate_by_name=True, extra="ignore") + + +class OpenWireModel(BaseModel): + """Base for `_meta` carrier models: unknown fields are retained for round-tripping.""" + + model_config = ConfigDict(populate_by_name=True, extra="allow") diff --git a/src/mcp/types/jsonrpc.py b/src/mcp/types/jsonrpc.py index 84304a37c1..990973833b 100644 --- a/src/mcp/types/jsonrpc.py +++ b/src/mcp/types/jsonrpc.py @@ -2,13 +2,16 @@ from __future__ import annotations -from typing import Annotated, Any, Literal +from typing import Annotated, Any, Final, Literal from pydantic import BaseModel, Field, TypeAdapter RequestId = Annotated[int, Field(strict=True)] | str """The ID of a JSON-RPC request.""" +JSONRPC_VERSION: Final[Literal["2.0"]] = "2.0" +"""The JSON-RPC version string carried by every MCP message envelope.""" + class JSONRPCRequest(BaseModel): """A JSON-RPC request that expects a response.""" @@ -27,9 +30,11 @@ class JSONRPCNotification(BaseModel): params: dict[str, Any] | None = None -# TODO(Marcelo): This is actually not correct. A JSONRPCResponse is the union of a successful response and an error. class JSONRPCResponse(BaseModel): - """A successful (non-error) response to a request.""" + """A successful (non-error) response to a request. + + Named `JSONRPCResultResponse` in the 2025-11-25+ schemas; the SDK keeps the original name. + """ jsonrpc: Literal["2.0"] id: RequestId @@ -38,18 +43,41 @@ class JSONRPCResponse(BaseModel): # MCP-specific error codes in the range [-32000, -32099] URL_ELICITATION_REQUIRED = -32042 -"""Error code indicating that a URL mode elicitation is required before the request can be processed.""" +"""A URL-mode elicitation is required before the request can be processed (protocol 2025-11-25 only).""" + +MISSING_REQUIRED_CLIENT_CAPABILITY = -32003 +"""The server requires a client capability the request did not declare (protocol 2026-07-28).""" -# SDK error codes +UNSUPPORTED_PROTOCOL_VERSION = -32004 +"""The request's protocol version is not supported by the server (protocol 2026-07-28).""" + +# SDK error codes: SDK-internal allocations in the JSON-RPC server-error range +# [-32000, -32099]; not defined by the MCP schema. New values must avoid codes +# the spec has allocated above. CONNECTION_CLOSED = -32000 +"""SDK-only: the connection closed before a response arrived; never emitted on the wire.""" + REQUEST_TIMEOUT = -32001 +"""SDK-only: a request timed out waiting for its response.""" # Standard JSON-RPC error codes PARSE_ERROR = -32700 +"""Standard JSON-RPC: invalid JSON was received.""" + INVALID_REQUEST = -32600 +"""Standard JSON-RPC: the message is not a valid request object.""" + METHOD_NOT_FOUND = -32601 +"""Standard JSON-RPC: the requested method does not exist or is not available.""" + INVALID_PARAMS = -32602 +"""Standard JSON-RPC: invalid method parameters.""" + INTERNAL_ERROR = -32603 +"""Standard JSON-RPC: an internal error occurred on the receiver. + +The SDK uses the generic `ErrorData` envelope; the schema's per-code wrapper types are not constructed. +""" class ErrorData(BaseModel): @@ -76,8 +104,15 @@ class JSONRPCError(BaseModel): jsonrpc: Literal["2.0"] id: RequestId | None + """The id of the request this error responds to. + + Required but nullable per JSON-RPC 2.0: `None` encodes `"id": null` (the id could not be determined). + """ + error: ErrorData JSONRPCMessage = JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError +"""Any JSON-RPC envelope that can be decoded off the wire or encoded to be sent.""" + jsonrpc_message_adapter: TypeAdapter[JSONRPCMessage] = TypeAdapter(JSONRPCMessage) diff --git a/src/mcp/types/methods.py b/src/mcp/types/methods.py new file mode 100644 index 0000000000..f3431b84ef --- /dev/null +++ b/src/mcp/types/methods.py @@ -0,0 +1,602 @@ +"""Per-version method maps and parse functions for inbound MCP traffic. + +Surface maps key `(method, version)` to schema-exact types (key absence is the +version gate); monolith maps key `method` to the version-free `mcp.types` models +user code receives. Session-layer wiring is a follow-up.""" + +from __future__ import annotations + +from collections.abc import Mapping +from functools import cache +from types import MappingProxyType, UnionType +from typing import Any, Final, TypeVar + +from pydantic import BaseModel, TypeAdapter + +import mcp.types as types +import mcp.types.v2025_11_25 as v2025 +import mcp.types.v2026_07_28 as v2026 +from mcp.shared.version import KNOWN_PROTOCOL_VERSIONS +from mcp.types._wire_base import WireModel + +__all__ = [ + "CLIENT_NOTIFICATIONS", + "CLIENT_REQUESTS", + "CLIENT_RESULTS", + "MONOLITH_NOTIFICATIONS", + "MONOLITH_REQUESTS", + "MONOLITH_RESULTS", + "SERVER_NOTIFICATIONS", + "SERVER_REQUESTS", + "SERVER_RESULTS", + "parse_client_notification", + "parse_client_request", + "parse_client_result", + "parse_server_notification", + "parse_server_request", + "parse_server_result", +] + + +# --- Surface maps: client-to-server --- + +CLIENT_REQUESTS: Final[Mapping[tuple[str, str], type[WireModel]]] = MappingProxyType( + { + # 2024-11-05 + ("completion/complete", "2024-11-05"): v2025.CompleteRequest, + ("initialize", "2024-11-05"): v2025.InitializeRequest, + ("logging/setLevel", "2024-11-05"): v2025.SetLevelRequest, + ("ping", "2024-11-05"): v2025.PingRequest, + ("prompts/get", "2024-11-05"): v2025.GetPromptRequest, + ("prompts/list", "2024-11-05"): v2025.ListPromptsRequest, + ("resources/list", "2024-11-05"): v2025.ListResourcesRequest, + ("resources/read", "2024-11-05"): v2025.ReadResourceRequest, + ("resources/subscribe", "2024-11-05"): v2025.SubscribeRequest, + ("resources/templates/list", "2024-11-05"): v2025.ListResourceTemplatesRequest, + ("resources/unsubscribe", "2024-11-05"): v2025.UnsubscribeRequest, + ("tools/call", "2024-11-05"): v2025.CallToolRequest, + ("tools/list", "2024-11-05"): v2025.ListToolsRequest, + # 2025-03-26 + ("completion/complete", "2025-03-26"): v2025.CompleteRequest, + ("initialize", "2025-03-26"): v2025.InitializeRequest, + ("logging/setLevel", "2025-03-26"): v2025.SetLevelRequest, + ("ping", "2025-03-26"): v2025.PingRequest, + ("prompts/get", "2025-03-26"): v2025.GetPromptRequest, + ("prompts/list", "2025-03-26"): v2025.ListPromptsRequest, + ("resources/list", "2025-03-26"): v2025.ListResourcesRequest, + ("resources/read", "2025-03-26"): v2025.ReadResourceRequest, + ("resources/subscribe", "2025-03-26"): v2025.SubscribeRequest, + ("resources/templates/list", "2025-03-26"): v2025.ListResourceTemplatesRequest, + ("resources/unsubscribe", "2025-03-26"): v2025.UnsubscribeRequest, + ("tools/call", "2025-03-26"): v2025.CallToolRequest, + ("tools/list", "2025-03-26"): v2025.ListToolsRequest, + # 2025-06-18 + ("completion/complete", "2025-06-18"): v2025.CompleteRequest, + ("initialize", "2025-06-18"): v2025.InitializeRequest, + ("logging/setLevel", "2025-06-18"): v2025.SetLevelRequest, + ("ping", "2025-06-18"): v2025.PingRequest, + ("prompts/get", "2025-06-18"): v2025.GetPromptRequest, + ("prompts/list", "2025-06-18"): v2025.ListPromptsRequest, + ("resources/list", "2025-06-18"): v2025.ListResourcesRequest, + ("resources/read", "2025-06-18"): v2025.ReadResourceRequest, + ("resources/subscribe", "2025-06-18"): v2025.SubscribeRequest, + ("resources/templates/list", "2025-06-18"): v2025.ListResourceTemplatesRequest, + ("resources/unsubscribe", "2025-06-18"): v2025.UnsubscribeRequest, + ("tools/call", "2025-06-18"): v2025.CallToolRequest, + ("tools/list", "2025-06-18"): v2025.ListToolsRequest, + # 2025-11-25 (tasks/* deliberately absent) + ("completion/complete", "2025-11-25"): v2025.CompleteRequest, + ("initialize", "2025-11-25"): v2025.InitializeRequest, + ("logging/setLevel", "2025-11-25"): v2025.SetLevelRequest, + ("ping", "2025-11-25"): v2025.PingRequest, + ("prompts/get", "2025-11-25"): v2025.GetPromptRequest, + ("prompts/list", "2025-11-25"): v2025.ListPromptsRequest, + ("resources/list", "2025-11-25"): v2025.ListResourcesRequest, + ("resources/read", "2025-11-25"): v2025.ReadResourceRequest, + ("resources/subscribe", "2025-11-25"): v2025.SubscribeRequest, + ("resources/templates/list", "2025-11-25"): v2025.ListResourceTemplatesRequest, + ("resources/unsubscribe", "2025-11-25"): v2025.UnsubscribeRequest, + ("tools/call", "2025-11-25"): v2025.CallToolRequest, + ("tools/list", "2025-11-25"): v2025.ListToolsRequest, + # 2026-07-28 (lifecycle, logging, subscribe pair removed; discover/listen added) + ("completion/complete", "2026-07-28"): v2026.CompleteRequest, + ("prompts/get", "2026-07-28"): v2026.GetPromptRequest, + ("prompts/list", "2026-07-28"): v2026.ListPromptsRequest, + ("resources/list", "2026-07-28"): v2026.ListResourcesRequest, + ("resources/read", "2026-07-28"): v2026.ReadResourceRequest, + ("resources/templates/list", "2026-07-28"): v2026.ListResourceTemplatesRequest, + ("server/discover", "2026-07-28"): v2026.DiscoverRequest, + ("subscriptions/listen", "2026-07-28"): v2026.SubscriptionsListenRequest, + ("tools/call", "2026-07-28"): v2026.CallToolRequest, + ("tools/list", "2026-07-28"): v2026.ListToolsRequest, + } +) + +CLIENT_NOTIFICATIONS: Final[Mapping[tuple[str, str], type[WireModel]]] = MappingProxyType( + { + # 2024-11-05 + ("notifications/cancelled", "2024-11-05"): v2025.CancelledNotification, + ("notifications/initialized", "2024-11-05"): v2025.InitializedNotification, + ("notifications/progress", "2024-11-05"): v2025.ProgressNotification, + ("notifications/roots/list_changed", "2024-11-05"): v2025.RootsListChangedNotification, + # 2025-03-26 + ("notifications/cancelled", "2025-03-26"): v2025.CancelledNotification, + ("notifications/initialized", "2025-03-26"): v2025.InitializedNotification, + ("notifications/progress", "2025-03-26"): v2025.ProgressNotification, + ("notifications/roots/list_changed", "2025-03-26"): v2025.RootsListChangedNotification, + # 2025-06-18 + ("notifications/cancelled", "2025-06-18"): v2025.CancelledNotification, + ("notifications/initialized", "2025-06-18"): v2025.InitializedNotification, + ("notifications/progress", "2025-06-18"): v2025.ProgressNotification, + ("notifications/roots/list_changed", "2025-06-18"): v2025.RootsListChangedNotification, + # 2025-11-25 (tasks/status deliberately absent) + ("notifications/cancelled", "2025-11-25"): v2025.CancelledNotification, + ("notifications/initialized", "2025-11-25"): v2025.InitializedNotification, + ("notifications/progress", "2025-11-25"): v2025.ProgressNotification, + ("notifications/roots/list_changed", "2025-11-25"): v2025.RootsListChangedNotification, + # 2026-07-28 (initialized and roots/list_changed removed) + ("notifications/cancelled", "2026-07-28"): v2026.CancelledNotification, + ("notifications/progress", "2026-07-28"): v2026.ProgressNotification, + } +) + + +# --- Surface maps: server-to-client --- + +SERVER_REQUESTS: Final[Mapping[tuple[str, str], type[WireModel]]] = MappingProxyType( + { + # 2024-11-05 + ("ping", "2024-11-05"): v2025.PingRequest, + ("roots/list", "2024-11-05"): v2025.ListRootsRequest, + ("sampling/createMessage", "2024-11-05"): v2025.CreateMessageRequest, + # 2025-03-26 + ("ping", "2025-03-26"): v2025.PingRequest, + ("roots/list", "2025-03-26"): v2025.ListRootsRequest, + ("sampling/createMessage", "2025-03-26"): v2025.CreateMessageRequest, + # 2025-06-18 (adds elicitation/create) + ("elicitation/create", "2025-06-18"): v2025.ElicitRequest, + ("ping", "2025-06-18"): v2025.PingRequest, + ("roots/list", "2025-06-18"): v2025.ListRootsRequest, + ("sampling/createMessage", "2025-06-18"): v2025.CreateMessageRequest, + # 2025-11-25 (tasks/* deliberately absent) + ("elicitation/create", "2025-11-25"): v2025.ElicitRequest, + ("ping", "2025-11-25"): v2025.PingRequest, + ("roots/list", "2025-11-25"): v2025.ListRootsRequest, + ("sampling/createMessage", "2025-11-25"): v2025.CreateMessageRequest, + # 2026-07-28: none (schema defines no ServerRequest union) + } +) + +SERVER_NOTIFICATIONS: Final[Mapping[tuple[str, str], type[WireModel]]] = MappingProxyType( + { + # 2024-11-05 + ("notifications/cancelled", "2024-11-05"): v2025.CancelledNotification, + ("notifications/message", "2024-11-05"): v2025.LoggingMessageNotification, + ("notifications/progress", "2024-11-05"): v2025.ProgressNotification, + ("notifications/prompts/list_changed", "2024-11-05"): v2025.PromptListChangedNotification, + ("notifications/resources/list_changed", "2024-11-05"): v2025.ResourceListChangedNotification, + ("notifications/resources/updated", "2024-11-05"): v2025.ResourceUpdatedNotification, + ("notifications/tools/list_changed", "2024-11-05"): v2025.ToolListChangedNotification, + # 2025-03-26 + ("notifications/cancelled", "2025-03-26"): v2025.CancelledNotification, + ("notifications/message", "2025-03-26"): v2025.LoggingMessageNotification, + ("notifications/progress", "2025-03-26"): v2025.ProgressNotification, + ("notifications/prompts/list_changed", "2025-03-26"): v2025.PromptListChangedNotification, + ("notifications/resources/list_changed", "2025-03-26"): v2025.ResourceListChangedNotification, + ("notifications/resources/updated", "2025-03-26"): v2025.ResourceUpdatedNotification, + ("notifications/tools/list_changed", "2025-03-26"): v2025.ToolListChangedNotification, + # 2025-06-18 + ("notifications/cancelled", "2025-06-18"): v2025.CancelledNotification, + ("notifications/message", "2025-06-18"): v2025.LoggingMessageNotification, + ("notifications/progress", "2025-06-18"): v2025.ProgressNotification, + ("notifications/prompts/list_changed", "2025-06-18"): v2025.PromptListChangedNotification, + ("notifications/resources/list_changed", "2025-06-18"): v2025.ResourceListChangedNotification, + ("notifications/resources/updated", "2025-06-18"): v2025.ResourceUpdatedNotification, + ("notifications/tools/list_changed", "2025-06-18"): v2025.ToolListChangedNotification, + # 2025-11-25 (adds elicitation/complete; tasks/status deliberately absent) + ("notifications/cancelled", "2025-11-25"): v2025.CancelledNotification, + ("notifications/elicitation/complete", "2025-11-25"): v2025.ElicitationCompleteNotification, + ("notifications/message", "2025-11-25"): v2025.LoggingMessageNotification, + ("notifications/progress", "2025-11-25"): v2025.ProgressNotification, + ("notifications/prompts/list_changed", "2025-11-25"): v2025.PromptListChangedNotification, + ("notifications/resources/list_changed", "2025-11-25"): v2025.ResourceListChangedNotification, + ("notifications/resources/updated", "2025-11-25"): v2025.ResourceUpdatedNotification, + ("notifications/tools/list_changed", "2025-11-25"): v2025.ToolListChangedNotification, + # 2026-07-28 (adds subscriptions/acknowledged) + ("notifications/cancelled", "2026-07-28"): v2026.CancelledNotification, + ("notifications/elicitation/complete", "2026-07-28"): v2026.ElicitationCompleteNotification, + ("notifications/message", "2026-07-28"): v2026.LoggingMessageNotification, + ("notifications/progress", "2026-07-28"): v2026.ProgressNotification, + ("notifications/prompts/list_changed", "2026-07-28"): v2026.PromptListChangedNotification, + ("notifications/resources/list_changed", "2026-07-28"): v2026.ResourceListChangedNotification, + ("notifications/resources/updated", "2026-07-28"): v2026.ResourceUpdatedNotification, + ("notifications/subscriptions/acknowledged", "2026-07-28"): v2026.SubscriptionsAcknowledgedNotification, + ("notifications/tools/list_changed", "2026-07-28"): v2026.ToolListChangedNotification, + } +) + + +# --- Surface maps: results --- + +SERVER_RESULTS: Final[Mapping[tuple[str, str], type[WireModel] | UnionType]] = MappingProxyType( + { + # 2024-11-05 + ("completion/complete", "2024-11-05"): v2025.CompleteResult, + ("initialize", "2024-11-05"): v2025.InitializeResult, + ("logging/setLevel", "2024-11-05"): v2025.EmptyResult, + ("ping", "2024-11-05"): v2025.EmptyResult, + ("prompts/get", "2024-11-05"): v2025.GetPromptResult, + ("prompts/list", "2024-11-05"): v2025.ListPromptsResult, + ("resources/list", "2024-11-05"): v2025.ListResourcesResult, + ("resources/read", "2024-11-05"): v2025.ReadResourceResult, + ("resources/subscribe", "2024-11-05"): v2025.EmptyResult, + ("resources/templates/list", "2024-11-05"): v2025.ListResourceTemplatesResult, + ("resources/unsubscribe", "2024-11-05"): v2025.EmptyResult, + ("tools/call", "2024-11-05"): v2025.CallToolResult, + ("tools/list", "2024-11-05"): v2025.ListToolsResult, + # 2025-03-26 + ("completion/complete", "2025-03-26"): v2025.CompleteResult, + ("initialize", "2025-03-26"): v2025.InitializeResult, + ("logging/setLevel", "2025-03-26"): v2025.EmptyResult, + ("ping", "2025-03-26"): v2025.EmptyResult, + ("prompts/get", "2025-03-26"): v2025.GetPromptResult, + ("prompts/list", "2025-03-26"): v2025.ListPromptsResult, + ("resources/list", "2025-03-26"): v2025.ListResourcesResult, + ("resources/read", "2025-03-26"): v2025.ReadResourceResult, + ("resources/subscribe", "2025-03-26"): v2025.EmptyResult, + ("resources/templates/list", "2025-03-26"): v2025.ListResourceTemplatesResult, + ("resources/unsubscribe", "2025-03-26"): v2025.EmptyResult, + ("tools/call", "2025-03-26"): v2025.CallToolResult, + ("tools/list", "2025-03-26"): v2025.ListToolsResult, + # 2025-06-18 + ("completion/complete", "2025-06-18"): v2025.CompleteResult, + ("initialize", "2025-06-18"): v2025.InitializeResult, + ("logging/setLevel", "2025-06-18"): v2025.EmptyResult, + ("ping", "2025-06-18"): v2025.EmptyResult, + ("prompts/get", "2025-06-18"): v2025.GetPromptResult, + ("prompts/list", "2025-06-18"): v2025.ListPromptsResult, + ("resources/list", "2025-06-18"): v2025.ListResourcesResult, + ("resources/read", "2025-06-18"): v2025.ReadResourceResult, + ("resources/subscribe", "2025-06-18"): v2025.EmptyResult, + ("resources/templates/list", "2025-06-18"): v2025.ListResourceTemplatesResult, + ("resources/unsubscribe", "2025-06-18"): v2025.EmptyResult, + ("tools/call", "2025-06-18"): v2025.CallToolResult, + ("tools/list", "2025-06-18"): v2025.ListToolsResult, + # 2025-11-25 + ("completion/complete", "2025-11-25"): v2025.CompleteResult, + ("initialize", "2025-11-25"): v2025.InitializeResult, + ("logging/setLevel", "2025-11-25"): v2025.EmptyResult, + ("ping", "2025-11-25"): v2025.EmptyResult, + ("prompts/get", "2025-11-25"): v2025.GetPromptResult, + ("prompts/list", "2025-11-25"): v2025.ListPromptsResult, + ("resources/list", "2025-11-25"): v2025.ListResourcesResult, + ("resources/read", "2025-11-25"): v2025.ReadResourceResult, + ("resources/subscribe", "2025-11-25"): v2025.EmptyResult, + ("resources/templates/list", "2025-11-25"): v2025.ListResourceTemplatesResult, + ("resources/unsubscribe", "2025-11-25"): v2025.EmptyResult, + ("tools/call", "2025-11-25"): v2025.CallToolResult, + ("tools/list", "2025-11-25"): v2025.ListToolsResult, + # 2026-07-28 (dual-result rows use the version's union aliases) + ("completion/complete", "2026-07-28"): v2026.CompleteResult, + ("prompts/get", "2026-07-28"): v2026.AnyGetPromptResult, + ("prompts/list", "2026-07-28"): v2026.ListPromptsResult, + ("resources/list", "2026-07-28"): v2026.ListResourcesResult, + ("resources/read", "2026-07-28"): v2026.AnyReadResourceResult, + ("resources/templates/list", "2026-07-28"): v2026.ListResourceTemplatesResult, + ("server/discover", "2026-07-28"): v2026.DiscoverResult, + ("subscriptions/listen", "2026-07-28"): v2026.EmptyResult, + ("tools/call", "2026-07-28"): v2026.AnyCallToolResult, + ("tools/list", "2026-07-28"): v2026.ListToolsResult, + } +) +"""Results servers send, keyed by the originating client request's (method, version).""" + +CLIENT_RESULTS: Final[Mapping[tuple[str, str], type[WireModel] | UnionType]] = MappingProxyType( + { + # 2024-11-05 + ("ping", "2024-11-05"): v2025.EmptyResult, + ("roots/list", "2024-11-05"): v2025.ListRootsResult, + ("sampling/createMessage", "2024-11-05"): v2025.CreateMessageResult, + # 2025-03-26 + ("ping", "2025-03-26"): v2025.EmptyResult, + ("roots/list", "2025-03-26"): v2025.ListRootsResult, + ("sampling/createMessage", "2025-03-26"): v2025.CreateMessageResult, + # 2025-06-18 + ("elicitation/create", "2025-06-18"): v2025.ElicitResult, + ("ping", "2025-06-18"): v2025.EmptyResult, + ("roots/list", "2025-06-18"): v2025.ListRootsResult, + ("sampling/createMessage", "2025-06-18"): v2025.CreateMessageResult, + # 2025-11-25 + ("elicitation/create", "2025-11-25"): v2025.ElicitResult, + ("ping", "2025-11-25"): v2025.EmptyResult, + ("roots/list", "2025-11-25"): v2025.ListRootsResult, + ("sampling/createMessage", "2025-11-25"): v2025.CreateMessageResult, + # 2026-07-28: none (no server-to-client requests at this version) + } +) +"""Results clients send, keyed by the originating server request's (method, version).""" + + +# --- Monolith maps --- + +MONOLITH_REQUESTS: Final[Mapping[str, type[types.Request[Any, Any]]]] = MappingProxyType( + { + "completion/complete": types.CompleteRequest, + "elicitation/create": types.ElicitRequest, + "initialize": types.InitializeRequest, + "logging/setLevel": types.SetLevelRequest, + "ping": types.PingRequest, + "prompts/get": types.GetPromptRequest, + "prompts/list": types.ListPromptsRequest, + "resources/list": types.ListResourcesRequest, + "resources/read": types.ReadResourceRequest, + "resources/subscribe": types.SubscribeRequest, + "resources/templates/list": types.ListResourceTemplatesRequest, + "resources/unsubscribe": types.UnsubscribeRequest, + "roots/list": types.ListRootsRequest, + "sampling/createMessage": types.CreateMessageRequest, + "server/discover": types.DiscoverRequest, + "subscriptions/listen": types.SubscriptionsListenRequest, + "tools/call": types.CallToolRequest, + "tools/list": types.ListToolsRequest, + } +) +"""Monolith request model per method, both directions.""" + +MONOLITH_NOTIFICATIONS: Final[Mapping[str, type[types.Notification[Any, Any]]]] = MappingProxyType( + { + "notifications/cancelled": types.CancelledNotification, + "notifications/elicitation/complete": types.ElicitCompleteNotification, + "notifications/initialized": types.InitializedNotification, + "notifications/message": types.LoggingMessageNotification, + "notifications/progress": types.ProgressNotification, + "notifications/prompts/list_changed": types.PromptListChangedNotification, + "notifications/resources/list_changed": types.ResourceListChangedNotification, + "notifications/resources/updated": types.ResourceUpdatedNotification, + "notifications/roots/list_changed": types.RootsListChangedNotification, + "notifications/subscriptions/acknowledged": types.SubscriptionsAcknowledgedNotification, + "notifications/tools/list_changed": types.ToolListChangedNotification, + } +) +"""Monolith notification model per method, both directions.""" + +MONOLITH_RESULTS: Final[Mapping[str, type[types.Result] | UnionType]] = MappingProxyType( + { + "completion/complete": types.CompleteResult, + "elicitation/create": types.ElicitResult, + "initialize": types.InitializeResult, + "logging/setLevel": types.EmptyResult, + "ping": types.EmptyResult, + "prompts/get": types.GetPromptResult | types.InputRequiredResult, + "prompts/list": types.ListPromptsResult, + "resources/list": types.ListResourcesResult, + "resources/read": types.ReadResourceResult | types.InputRequiredResult, + "resources/subscribe": types.EmptyResult, + "resources/templates/list": types.ListResourceTemplatesResult, + "resources/unsubscribe": types.EmptyResult, + "roots/list": types.ListRootsResult, + # Arm order load-bearing: a single-block body satisfies both arms and + # smart-union ties resolve leftmost. Pinned by tests/types/test_methods.py. + "sampling/createMessage": types.CreateMessageResult | types.CreateMessageResultWithTools, + "server/discover": types.DiscoverResult, + "subscriptions/listen": types.EmptyResult, + "tools/call": types.CallToolResult | types.InputRequiredResult, + "tools/list": types.ListToolsResult, + } +) +"""Monolith result model (or two-arm union) per request method.""" + + +# --- Parse functions --- + +# Envelope stubs merged into bodies for surface validation (surface classes are full frames). +_REQUEST_STUB: Final[Mapping[str, Any]] = MappingProxyType({"jsonrpc": "2.0", "id": 0}) +_NOTIFICATION_STUB: Final[Mapping[str, Any]] = MappingProxyType({"jsonrpc": "2.0"}) + + +def _check_known_version(version: str) -> None: + """Raise ValueError for unknown `version` so a typo cannot silently gate every method.""" + if version not in KNOWN_PROTOCOL_VERSIONS: + raise ValueError(f"version must be a known protocol version, got {version!r}") + + +def _body(method: str, params: Mapping[str, Any] | None) -> dict[str, Any]: + """Build a JSON-RPC body, omitting `params` when None.""" + body: dict[str, Any] = {"method": method} + if params is not None: + body["params"] = params + return body + + +@cache +def _adapter(target: type[BaseModel] | UnionType) -> TypeAdapter[Any]: + return TypeAdapter(target) + + +_MonolithT = TypeVar("_MonolithT") + + +def _monolith_row(monolith: Mapping[str, _MonolithT], method: str) -> _MonolithT: + """Look up `method` in `monolith`, raising RuntimeError on miss. + + Not KeyError: the surface row already matched, so a miss is inconsistent + extension maps and must not be caught by the session's `except KeyError` gate. + """ + try: + return monolith[method] + except KeyError: + raise RuntimeError(f"inconsistent extension maps: surface defines {method!r} but monolith does not") from None + + +def parse_client_request( + method: str, + version: str, + params: Mapping[str, Any] | None, + *, + surface: Mapping[tuple[str, str], type[WireModel]] = CLIENT_REQUESTS, + monolith: Mapping[str, type[types.Request[Any, Any]]] = MONOLITH_REQUESTS, +) -> types.Request[Any, Any]: + """Validate a client request against `surface`, then parse and return its `monolith` model. + + Args: + surface: `(method, version)` to schema-exact type map; the version-gate + lookup and shape check run against this. Pass an extended map to + admit custom methods. + monolith: `method` to version-free model map; the returned instance is + parsed from this row. Must cover every method `surface` admits. + + Raises: + ValueError: `version` is not a known protocol version. + KeyError: `(method, version)` is not in `surface` (the version gate). + pydantic.ValidationError: body fails surface or monolith validation. + RuntimeError: surface matched but `method` has no monolith row. + """ + _check_known_version(version) + surface_type = surface[(method, version)] + surface_type.model_validate({**_REQUEST_STUB, **_body(method, params)}, by_name=False) + return _monolith_row(monolith, method).model_validate(_body(method, params), by_name=False) + + +def parse_server_request( + method: str, + version: str, + params: Mapping[str, Any] | None, + *, + surface: Mapping[tuple[str, str], type[WireModel]] = SERVER_REQUESTS, + monolith: Mapping[str, type[types.Request[Any, Any]]] = MONOLITH_REQUESTS, +) -> types.Request[Any, Any]: + """Validate a server request against `surface`, then parse and return its `monolith` model. + + Args: + surface: `(method, version)` to schema-exact type map; the version-gate + lookup and shape check run against this. Pass an extended map to + admit custom methods. + monolith: `method` to version-free model map; the returned instance is + parsed from this row. Must cover every method `surface` admits. + + Raises: + ValueError: `version` is not a known protocol version. + KeyError: `(method, version)` is not in `surface` (the version gate). + pydantic.ValidationError: body fails surface or monolith validation. + RuntimeError: surface matched but `method` has no monolith row. + """ + _check_known_version(version) + surface_type = surface[(method, version)] + surface_type.model_validate({**_REQUEST_STUB, **_body(method, params)}, by_name=False) + return _monolith_row(monolith, method).model_validate(_body(method, params), by_name=False) + + +def parse_client_notification( + method: str, + version: str, + params: Mapping[str, Any] | None, + *, + surface: Mapping[tuple[str, str], type[WireModel]] = CLIENT_NOTIFICATIONS, + monolith: Mapping[str, type[types.Notification[Any, Any]]] = MONOLITH_NOTIFICATIONS, +) -> types.Notification[Any, Any]: + """Validate a client notification against `surface`, then parse and return its `monolith` model. + + Args: + surface: `(method, version)` to schema-exact type map; the version-gate + lookup and shape check run against this. Pass an extended map to + admit custom methods. + monolith: `method` to version-free model map; the returned instance is + parsed from this row. Must cover every method `surface` admits. + + Raises: + ValueError: `version` is not a known protocol version. + KeyError: `(method, version)` is not in `surface`. + pydantic.ValidationError: body fails surface or monolith validation. + RuntimeError: surface matched but `method` has no monolith row. + """ + _check_known_version(version) + surface_type = surface[(method, version)] + surface_type.model_validate({**_NOTIFICATION_STUB, **_body(method, params)}, by_name=False) + return _monolith_row(monolith, method).model_validate(_body(method, params), by_name=False) + + +def parse_server_notification( + method: str, + version: str, + params: Mapping[str, Any] | None, + *, + surface: Mapping[tuple[str, str], type[WireModel]] = SERVER_NOTIFICATIONS, + monolith: Mapping[str, type[types.Notification[Any, Any]]] = MONOLITH_NOTIFICATIONS, +) -> types.Notification[Any, Any]: + """Validate a server notification against `surface`, then parse and return its `monolith` model. + + Args: + surface: `(method, version)` to schema-exact type map; the version-gate + lookup and shape check run against this. Pass an extended map to + admit custom methods. + monolith: `method` to version-free model map; the returned instance is + parsed from this row. Must cover every method `surface` admits. + + Raises: + ValueError: `version` is not a known protocol version. + KeyError: `(method, version)` is not in `surface`. + pydantic.ValidationError: body fails surface or monolith validation. + RuntimeError: surface matched but `method` has no monolith row. + """ + _check_known_version(version) + surface_type = surface[(method, version)] + surface_type.model_validate({**_NOTIFICATION_STUB, **_body(method, params)}, by_name=False) + return _monolith_row(monolith, method).model_validate(_body(method, params), by_name=False) + + +def parse_server_result( + method: str, + version: str, + data: Mapping[str, Any], + *, + surface: Mapping[tuple[str, str], type[WireModel] | UnionType] = SERVER_RESULTS, + monolith: Mapping[str, type[types.Result] | UnionType] = MONOLITH_RESULTS, +) -> types.Result: + """Validate a server result against `surface`, then parse and return its `monolith` model. + + Args: + surface: `(method, version)` to schema-exact type map; the version-gate + lookup and shape check run against this. Pass an extended map to + admit custom methods. + monolith: `method` to version-free model map; the returned instance is + parsed from this row. Must cover every method `surface` admits. + + Raises: + ValueError: `version` is not a known protocol version. + KeyError: `(method, version)` is not in `surface`. + pydantic.ValidationError: result fails surface or monolith validation. + RuntimeError: surface matched but `method` has no monolith row. + """ + _check_known_version(version) + _adapter(surface[(method, version)]).validate_python(data, by_name=False) + result: types.Result = _adapter(_monolith_row(monolith, method)).validate_python(data, by_name=False) + return result + + +def parse_client_result( + method: str, + version: str, + data: Mapping[str, Any], + *, + surface: Mapping[tuple[str, str], type[WireModel] | UnionType] = CLIENT_RESULTS, + monolith: Mapping[str, type[types.Result] | UnionType] = MONOLITH_RESULTS, +) -> types.Result: + """Validate a client result against `surface`, then parse and return its `monolith` model. + + Args: + surface: `(method, version)` to schema-exact type map; the version-gate + lookup and shape check run against this. Pass an extended map to + admit custom methods. + monolith: `method` to version-free model map; the returned instance is + parsed from this row. Must cover every method `surface` admits. + + Raises: + ValueError: `version` is not a known protocol version. + KeyError: `(method, version)` is not in `surface`. + pydantic.ValidationError: result fails surface or monolith validation. + RuntimeError: surface matched but `method` has no monolith row. + """ + _check_known_version(version) + _adapter(surface[(method, version)]).validate_python(data, by_name=False) + result: types.Result = _adapter(_monolith_row(monolith, method)).validate_python(data, by_name=False) + return result diff --git a/src/mcp/types/v2025_11_25/__init__.py b/src/mcp/types/v2025_11_25/__init__.py new file mode 100644 index 0000000000..d67f7caef3 --- /dev/null +++ b/src/mcp/types/v2025_11_25/__init__.py @@ -0,0 +1,1485 @@ +"""Internal wire-shape models for protocol 2025-11-25. Not part of the public API. + +Serves inbound validation for every protocol version through 2025-11-25 (each +earlier schema is a strict subset of this one). Models default to +`extra="ignore"`; the few kept open are commented in place. See +`mcp.types._wire_base` and `mcp.types.methods`. +Pinned to schema/2025-11-25/schema.json @ 6d441518de8a9d5adbab0b10a76a667a63f90665. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import ConfigDict, Field + +from mcp.types._wire_base import OpenWireModel, WireModel + + +class BaseMetadata(WireModel): + """Base interface for metadata with name (identifier) and title (display name).""" + + name: str + """Programmatic identifier; also the display fallback when `title` is absent.""" + title: str | None = None + """Human-readable display name.""" + + +class BlobResourceContents(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + blob: str + """Base64-encoded binary data.""" + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + uri: str + + +class BooleanSchema(WireModel): + default: bool | None = None + description: str | None = None + title: str | None = None + type: Literal["boolean"] + + +class CancelTaskRequestParams(WireModel): + task_id: Annotated[str, Field(alias="taskId")] + + +class Elicitation(WireModel): + """Present if the client supports elicitation from the server.""" + + form: dict[str, Any] | None = None + url: dict[str, Any] | None = None + + +class Roots(WireModel): + """Present if the client supports listing roots.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + + +class Sampling(WireModel): + """Present if the client supports sampling from an LLM.""" + + context: dict[str, Any] | None = None + """Present if the client supports the `includeContext` parameter.""" + tools: dict[str, Any] | None = None + """Present if the client supports the `tools` and `toolChoice` parameters.""" + + +class Elicitation1(WireModel): + """Task support for elicitation-related requests.""" + + create: dict[str, Any] | None = None + + +class Sampling1(WireModel): + """Task support for sampling-related requests.""" + + create_message: Annotated[dict[str, Any] | None, Field(alias="createMessage")] = None + + +class Requests(WireModel): + """Specifies which request types can be augmented with tasks.""" + + elicitation: Elicitation1 | None = None + sampling: Sampling1 | None = None + + +class Tasks(WireModel): + """Present if the client supports task-augmented requests.""" + + cancel: dict[str, Any] | None = None + list: dict[str, Any] | None = None + requests: Requests | None = None + + +class ClientCapabilities(WireModel): + """Capabilities a client may support. Not a closed set.""" + + elicitation: Elicitation | None = None + experimental: dict[str, dict[str, Any]] | None = None + roots: Roots | None = None + sampling: Sampling | None = None + tasks: Tasks | None = None + + +class Argument(WireModel): + """The argument being completed.""" + + name: str + value: str + + +class Context(WireModel): + """Additional context for completions.""" + + arguments: dict[str, str] | None = None + """Already-resolved variables in a URI template or prompt.""" + + +class Completion(WireModel): + has_more: Annotated[bool | None, Field(alias="hasMore")] = None + total: int | None = None + values: list[str] + """Must not exceed 100 items.""" + + +class CompleteResult(WireModel): + """The server's response to a completion/complete request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + completion: Completion + + +Cursor: TypeAlias = str + + +class ElicitResult(WireModel): + """The client's response to an elicitation request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + action: Literal["accept", "cancel", "decline"] + # Deviation: schema.json renders the number arm as "integer" but schema.ts + # types it `number`, so fractional answers are legal. Follow schema.ts. + content: dict[str, list[str] | str | int | float | bool] | None = None + """Submitted form data; only present when action is "accept" and mode was "form".""" + + +class ElicitationCompleteNotificationParams(WireModel): + elicitation_id: Annotated[str, Field(alias="elicitationId")] + + +class ElicitationCompleteNotification(WireModel): + """Server-to-client notification that an out-of-band elicitation completed.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/elicitation/complete"] + params: ElicitationCompleteNotificationParams + + +class Error(WireModel): + code: int + data: Any | None = None + message: str + + +class GetTaskPayloadRequestParams(WireModel): + task_id: Annotated[str, Field(alias="taskId")] + + +class GetTaskPayloadResult(WireModel): + """Response to tasks/result; structure matches the original request's result type.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + + +class GetTaskRequestParams(WireModel): + task_id: Annotated[str, Field(alias="taskId")] + + +class Icon(WireModel): + """An optionally-sized icon that can be displayed in a user interface.""" + + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + sizes: list[str] | None = None + """Each entry is WxH (e.g. "48x48") or "any" for scalable formats.""" + src: str + """HTTP/HTTPS URL or `data:` URI.""" + theme: Literal["dark", "light"] | None = None + + +class Icons(WireModel): + """Base interface adding an `icons` property.""" + + icons: list[Icon] | None = None + + +class Implementation(WireModel): + """Describes the MCP implementation.""" + + description: str | None = None + icons: list[Icon] | None = None + name: str + title: str | None = None + version: str + website_url: Annotated[str | None, Field(alias="websiteUrl")] = None + + +class JSONRPCNotification(WireModel): + """A notification which does not expect a response.""" + + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +class LegacyTitledEnumSchema(WireModel): + """Use TitledSingleSelectEnumSchema instead.""" + + default: str | None = None + description: str | None = None + enum: list[str] + enum_names: Annotated[list[str] | None, Field(alias="enumNames")] = None + """Display names for enum values (legacy, non-standard JSON Schema).""" + title: str | None = None + type: Literal["string"] + + +LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + + +class LoggingMessageNotificationParams(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + data: Any + level: LoggingLevel + logger: str | None = None + + +class ModelHint(WireModel): + """Hints to use for model selection.""" + + name: str | None = None + """Substring of a model name; the client may map it to another provider's equivalent.""" + + +class ModelPreferences(WireModel): + """The server's advisory preferences for model selection during sampling.""" + + cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None + hints: list[ModelHint] | None = None + """Evaluated in order; first match wins.""" + intelligence_priority: Annotated[float | None, Field(alias="intelligencePriority", ge=0.0, le=1.0)] = None + speed_priority: Annotated[float | None, Field(alias="speedPriority", ge=0.0, le=1.0)] = None + + +class Notification(WireModel): + method: str + params: dict[str, Any] | None = None + + +class NotificationParams(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + + +class NumberSchema(WireModel): + # Deviation: schema.json renders these as "integer" but schema.ts types + # them `number` (JSON Schema bounds are numbers). Follow schema.ts. + default: int | float | None = None + description: str | None = None + maximum: int | float | None = None + minimum: int | float | None = None + title: str | None = None + type: Literal["integer", "number"] + + +class PaginatedResult(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """Opaque pagination token; if present, more results may be available.""" + + +ProgressToken: TypeAlias = str | int + + +class PromptArgument(WireModel): + """Describes an argument that a prompt can accept.""" + + description: str | None = None + name: str + required: bool | None = None + title: str | None = None + + +class PromptListChangedNotification(WireModel): + """Server-to-client notification that the prompt list has changed.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/prompts/list_changed"] + params: NotificationParams | None = None + + +class PromptReference(WireModel): + """Identifies a prompt.""" + + name: str + title: str | None = None + type: Literal["ref/prompt"] + + +class Meta(OpenWireModel): + """Request `_meta` object.""" + + progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None + """If set, the caller wants `notifications/progress` for this request, tagged with this token.""" + + +class ReadResourceRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + uri: str + + +class RelatedTaskMetadata(WireModel): + """Associates a message with a task via `_meta["io.modelcontextprotocol/related-task"]`.""" + + task_id: Annotated[str, Field(alias="taskId")] + + +class Request(WireModel): + method: str + params: dict[str, Any] | None = None + + +RequestId: TypeAlias = str | int + + +class RequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + + +class ResourceContents(WireModel): + """The contents of a specific resource or sub-resource.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + uri: str + + +class ResourceListChangedNotification(WireModel): + """Server-to-client notification that the resource list has changed.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/list_changed"] + params: NotificationParams | None = None + + +class ResourceRequestParams(WireModel): + """Common parameters when working with resources.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + uri: str + + +class ResourceTemplateReference(WireModel): + """A reference to a resource or resource template definition.""" + + type: Literal["ref/resource"] + uri: str + """The URI or URI template of the resource.""" + + +class ResourceUpdatedNotificationParams(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + uri: str + """May be a sub-resource of the one the client subscribed to.""" + + +class Result(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + + +Role: TypeAlias = Literal["assistant", "user"] + + +class Root(WireModel): + """A root directory or file that the server can operate on.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + name: str | None = None + uri: str + """Must start with file:// for now.""" + + +class RootsListChangedNotification(WireModel): + """Client-to-server notification that the roots list has changed.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/roots/list_changed"] + params: NotificationParams | None = None + + +class Prompts(WireModel): + """Present if the server offers any prompt templates.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + + +class Resources(WireModel): + """Present if the server offers any resources to read.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + subscribe: bool | None = None + + +class Tools(WireModel): + """Task support for tool-related requests.""" + + call: dict[str, Any] | None = None + + +class Requests1(WireModel): + """Specifies which request types can be augmented with tasks.""" + + tools: Tools | None = None + + +class Tasks1(WireModel): + """Present if the server supports task-augmented requests.""" + + cancel: dict[str, Any] | None = None + list: dict[str, Any] | None = None + requests: Requests1 | None = None + + +class Tools1(WireModel): + """Present if the server offers any tools to call.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + + +class ServerCapabilities(WireModel): + """Capabilities a server may support. Not a closed set.""" + + completions: dict[str, Any] | None = None + experimental: dict[str, dict[str, Any]] | None = None + logging: dict[str, Any] | None = None + prompts: Prompts | None = None + resources: Resources | None = None + tasks: Tasks1 | None = None + tools: Tools1 | None = None + + +class SetLevelRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + level: LoggingLevel + """Minimum severity to send to the client as notifications/message.""" + + +class StringSchema(WireModel): + default: str | None = None + description: str | None = None + format: Literal["date", "date-time", "email", "uri"] | None = None + max_length: Annotated[int | None, Field(alias="maxLength")] = None + min_length: Annotated[int | None, Field(alias="minLength")] = None + title: str | None = None + type: Literal["string"] + + +class SubscribeRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + uri: str + + +class TaskMetadata(WireModel): + """Metadata for augmenting a request with task execution (the `task` param field).""" + + ttl: int | None = None + """Requested retention from creation, in milliseconds.""" + + +TaskStatus: TypeAlias = Literal["cancelled", "completed", "failed", "input_required", "working"] + + +class TextResourceContents(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + text: str + uri: str + + +class AnyOfItem(WireModel): + const: str + title: str + + +class Items(WireModel): + """Schema for array items with enum options and display labels.""" + + any_of: Annotated[list[AnyOfItem], Field(alias="anyOf")] + + +class TitledMultiSelectEnumSchema(WireModel): + """Multiple-selection enum with display titles for each option.""" + + default: list[str] | None = None + description: str | None = None + items: Items + max_items: Annotated[int | None, Field(alias="maxItems")] = None + min_items: Annotated[int | None, Field(alias="minItems")] = None + title: str | None = None + type: Literal["array"] + + +class OneOfItem(WireModel): + const: str + title: str + + +class TitledSingleSelectEnumSchema(WireModel): + """Single-selection enum with display titles for each option.""" + + default: str | None = None + description: str | None = None + one_of: Annotated[list[OneOfItem], Field(alias="oneOf")] + title: str | None = None + type: Literal["string"] + + +class InputSchema(WireModel): + """A JSON Schema object defining the expected parameters for the tool.""" + + # Kept open: arbitrary JSON Schema keywords ride extra fields. + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + properties: dict[str, dict[str, Any]] | None = None + required: list[str] | None = None + type: Literal["object"] + + +class OutputSchema(WireModel): + """A JSON Schema object defining the structure of `CallToolResult.structuredContent`.""" + + # Kept open: arbitrary JSON Schema keywords ride extra fields. + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + properties: dict[str, dict[str, Any]] | None = None + required: list[str] | None = None + type: Literal["object"] + + +class ToolAnnotations(WireModel): + """Untrusted hints describing a tool's behavior to clients.""" + + destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None + """Only meaningful when `readOnlyHint` is false. Default: true.""" + idempotent_hint: Annotated[bool | None, Field(alias="idempotentHint")] = None + """Only meaningful when `readOnlyHint` is false. Default: false.""" + open_world_hint: Annotated[bool | None, Field(alias="openWorldHint")] = None + """Default: true.""" + read_only_hint: Annotated[bool | None, Field(alias="readOnlyHint")] = None + """Default: false.""" + title: str | None = None + + +class ToolChoice(WireModel): + """Controls tool selection behavior for sampling requests.""" + + mode: Literal["auto", "none", "required"] | None = None + + +class ToolExecution(WireModel): + """Execution-related properties for a tool.""" + + task_support: Annotated[Literal["forbidden", "optional", "required"] | None, Field(alias="taskSupport")] = None + """Whether this tool supports task-augmented execution. Default: "forbidden".""" + + +class ToolListChangedNotification(WireModel): + """Server-to-client notification that the tool list has changed.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/tools/list_changed"] + params: NotificationParams | None = None + + +class ToolUseContent(WireModel): + """A request from the assistant to call a tool.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + id: str + """Unique identifier matched against `ToolResultContent.tool_use_id`.""" + input: dict[str, Any] + name: str + type: Literal["tool_use"] + + +class UnsubscribeRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + uri: str + + +class Items1(WireModel): + """Schema for the array items.""" + + enum: list[str] + type: Literal["string"] + + +class UntitledMultiSelectEnumSchema(WireModel): + """Multiple-selection enum without per-option display titles.""" + + default: list[str] | None = None + description: str | None = None + items: Items1 + max_items: Annotated[int | None, Field(alias="maxItems")] = None + min_items: Annotated[int | None, Field(alias="minItems")] = None + title: str | None = None + type: Literal["array"] + + +class UntitledSingleSelectEnumSchema(WireModel): + """Single-selection enum without per-option display titles.""" + + default: str | None = None + description: str | None = None + enum: list[str] + title: str | None = None + type: Literal["string"] + + +class Annotations(WireModel): + """Optional annotations for the client.""" + + audience: list[Role] | None = None + last_modified: Annotated[str | None, Field(alias="lastModified")] = None + """ISO 8601 timestamp.""" + priority: Annotated[float | None, Field(ge=0.0, le=1.0)] = None + """1 means effectively required, 0 means entirely optional.""" + + +class AudioContent(WireModel): + """Audio provided to or from an LLM.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + data: str + """Base64-encoded audio data.""" + mime_type: Annotated[str, Field(alias="mimeType")] + type: Literal["audio"] + + +class CallToolRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + arguments: dict[str, Any] | None = None + name: str + task: TaskMetadata | None = None + """If set, run as a task and return `CreateTaskResult` immediately.""" + + +class CancelTaskRequest(WireModel): + """A request to cancel a task.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/cancel"] + params: CancelTaskRequestParams + + +class CancelledNotificationParams(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + reason: str | None = None + request_id: Annotated[RequestId | None, Field(alias="requestId")] = None + """Required for non-task requests; MUST NOT be used for tasks (use `tasks/cancel`).""" + + +class CompleteRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + argument: Argument + context: Context | None = None + ref: PromptReference | ResourceTemplateReference + + +class ElicitRequestURLParams(WireModel): + """Parameters for a URL-mode elicitation request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + elicitation_id: Annotated[str, Field(alias="elicitationId")] + """Server-unique opaque ID.""" + message: str + mode: Literal["url"] + task: TaskMetadata | None = None + """If set, run as a task and return `CreateTaskResult` immediately.""" + url: str + + +class EmbeddedResource(WireModel): + """The contents of a resource, embedded into a prompt or tool call result.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + resource: TextResourceContents | BlobResourceContents + type: Literal["resource"] + + +EmptyResult: TypeAlias = Result + + +EnumSchema: TypeAlias = ( + UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +class GetPromptRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + arguments: dict[str, str] | None = None + name: str + + +class GetTaskPayloadRequest(WireModel): + """A request to retrieve the result of a completed task.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/result"] + params: GetTaskPayloadRequestParams + + +class GetTaskRequest(WireModel): + """A request to retrieve the state of a task.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/get"] + params: GetTaskRequestParams + + +class ImageContent(WireModel): + """An image provided to or from an LLM.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + data: str + """Base64-encoded image data.""" + mime_type: Annotated[str, Field(alias="mimeType")] + type: Literal["image"] + + +class InitializeRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + capabilities: ClientCapabilities + client_info: Annotated[Implementation, Field(alias="clientInfo")] + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """The latest protocol version the client supports.""" + + +class InitializeResult(WireModel): + """The server's response to an initialize request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + capabilities: ServerCapabilities + instructions: str | None = None + """Instructions describing how to use the server and its features.""" + protocol_version: Annotated[str, Field(alias="protocolVersion")] + """The protocol version the server wants to use; the client MUST disconnect if unsupported.""" + server_info: Annotated[Implementation, Field(alias="serverInfo")] + + +class InitializedNotification(WireModel): + """Sent from the client to the server after initialization has finished.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/initialized"] + params: NotificationParams | None = None + + +class JSONRPCErrorResponse(WireModel): + """A response to a request that indicates an error occurred.""" + + error: Error + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class JSONRPCRequest(WireModel): + """A request that expects a response.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +class JSONRPCResultResponse(WireModel): + """A successful (non-error) response to a request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: Result + + +class ListRootsRequest(WireModel): + """Sent from the server to request a list of root URIs from the client.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["roots/list"] + params: RequestParams | None = None + + +class ListRootsResult(WireModel): + """The client's response to a roots/list request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + roots: list[Root] + + +class LoggingMessageNotification(WireModel): + """A log message passed from server to client.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/message"] + params: LoggingMessageNotificationParams + + +MultiSelectEnumSchema: TypeAlias = UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema + + +class PaginatedRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + cursor: str | None = None + """Opaque pagination token; results start after this position.""" + + +class PingRequest(WireModel): + """A ping, issued by either side, to check that the other party is still alive.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["ping"] + params: RequestParams | None = None + + +PrimitiveSchemaDefinition: TypeAlias = ( + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +class ProgressNotificationParams(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + message: str | None = None + progress: float + """Monotonically increasing, even if `total` is unknown.""" + progress_token: Annotated[ProgressToken, Field(alias="progressToken")] + """The token from the originating request's `_meta.progressToken`.""" + total: float | None = None + + +class Prompt(WireModel): + """A prompt or prompt template that the server offers.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + arguments: list[PromptArgument] | None = None + description: str | None = None + icons: list[Icon] | None = None + name: str + title: str | None = None + + +class ReadResourceRequest(WireModel): + """Sent from the client to read a specific resource URI.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/read"] + params: ReadResourceRequestParams + + +class ReadResourceResult(WireModel): + """The server's response to a resources/read request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + contents: list[TextResourceContents | BlobResourceContents] + + +class Resource(WireModel): + """A known resource that the server is capable of reading.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + description: str | None = None + icons: list[Icon] | None = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + name: str + size: int | None = None + """Raw content size in bytes (before base64 encoding), if known.""" + title: str | None = None + uri: str + + +class ResourceLink(WireModel): + """A resource link included in a prompt or tool call result.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + description: str | None = None + icons: list[Icon] | None = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + name: str + size: int | None = None + """Raw content size in bytes (before base64 encoding), if known.""" + title: str | None = None + type: Literal["resource_link"] + uri: str + + +class ResourceTemplate(WireModel): + """A template description for resources available on the server.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + description: str | None = None + icons: list[Icon] | None = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """Only set if all resources matching this template share the same type.""" + name: str + title: str | None = None + uri_template: Annotated[str, Field(alias="uriTemplate")] + """RFC 6570 URI template.""" + + +class ResourceUpdatedNotification(WireModel): + """Server-to-client notification that a subscribed resource has changed.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/updated"] + params: ResourceUpdatedNotificationParams + + +class SetLevelRequest(WireModel): + """A request from the client to enable or adjust logging.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["logging/setLevel"] + params: SetLevelRequestParams + + +SingleSelectEnumSchema: TypeAlias = UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema + + +class SubscribeRequest(WireModel): + """Sent from the client to request resources/updated notifications for a resource.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/subscribe"] + params: SubscribeRequestParams + + +class Task(WireModel): + """Data associated with a task.""" + + created_at: Annotated[str, Field(alias="createdAt")] + """ISO 8601 timestamp.""" + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + """ISO 8601 timestamp.""" + poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None + """Suggested polling interval in milliseconds.""" + status: TaskStatus + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + task_id: Annotated[str, Field(alias="taskId")] + ttl: int | None + """Actual retention from creation in milliseconds; null means unlimited.""" + + +class TaskAugmentedRequestParams(WireModel): + """Common params for any task-augmented request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + task: TaskMetadata | None = None + """If set, run as a task and return `CreateTaskResult` immediately.""" + + +class TaskStatusNotificationParams(WireModel): + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + created_at: Annotated[str, Field(alias="createdAt")] + """ISO 8601 timestamp.""" + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + """ISO 8601 timestamp.""" + poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None + """Suggested polling interval in milliseconds.""" + status: TaskStatus + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + task_id: Annotated[str, Field(alias="taskId")] + ttl: int | None + """Actual retention from creation in milliseconds; null means unlimited.""" + + +class TextContent(WireModel): + """Text provided to or from an LLM.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + text: str + type: Literal["text"] + + +class Tool(WireModel): + """Definition for a tool the client can call.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + annotations: ToolAnnotations | None = None + description: str | None = None + execution: ToolExecution | None = None + icons: list[Icon] | None = None + input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + name: str + output_schema: Annotated[OutputSchema | None, Field(alias="outputSchema")] = None + title: str | None = None + + +class Data(WireModel): + """Error data carrying pending URL elicitations.""" + + elicitations: list[ElicitRequestURLParams] + + +class Error1(WireModel): + code: Literal[-32042] + data: Data + message: str + + +class URLElicitationRequiredError(WireModel): + """Error response indicating the server requires a URL-mode elicitation.""" + + error: Error1 + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class UnsubscribeRequest(WireModel): + """Sent from the client to cancel resources/updated notifications for a resource.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/unsubscribe"] + params: UnsubscribeRequestParams + + +class CallToolRequest(WireModel): + """Used by the client to invoke a tool provided by the server.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/call"] + params: CallToolRequestParams + + +class CancelTaskResult(WireModel): + """The response to a tasks/cancel request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + created_at: Annotated[str, Field(alias="createdAt")] + """ISO 8601 timestamp.""" + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + """ISO 8601 timestamp.""" + poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None + """Suggested polling interval in milliseconds.""" + status: TaskStatus + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + task_id: Annotated[str, Field(alias="taskId")] + ttl: int | None + """Actual retention from creation in milliseconds; null means unlimited.""" + + +class CancelledNotification(WireModel): + """Sent by either side to cancel an in-flight request (not for tasks; use `tasks/cancel`).""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/cancelled"] + params: CancelledNotificationParams + + +class CompleteRequest(WireModel): + """A request from the client to ask for completion options.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["completion/complete"] + params: CompleteRequestParams + + +ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource + + +class CreateTaskResult(WireModel): + """A response to a task-augmented request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + task: Task + + +class RequestedSchema(WireModel): + """A restricted JSON Schema subset: top-level properties only, no nesting.""" + + schema_: Annotated[str | None, Field(alias="$schema")] = None + properties: dict[str, PrimitiveSchemaDefinition] + required: list[str] | None = None + type: Literal["object"] + + +class ElicitRequestFormParams(WireModel): + """Parameters for a form-mode elicitation request.""" + + meta: Annotated[Meta | None, Field(alias="_meta")] = None + message: str + mode: Literal["form"] = "form" + requested_schema: Annotated[RequestedSchema, Field(alias="requestedSchema")] + task: TaskMetadata | None = None + """If set, run as a task and return `CreateTaskResult` immediately.""" + + +ElicitRequestParams: TypeAlias = ElicitRequestURLParams | ElicitRequestFormParams + + +class GetPromptRequest(WireModel): + """Used by the client to get a prompt provided by the server.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/get"] + params: GetPromptRequestParams + + +class GetTaskResult(WireModel): + """The response to a tasks/get request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + created_at: Annotated[str, Field(alias="createdAt")] + """ISO 8601 timestamp.""" + last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] + """ISO 8601 timestamp.""" + poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None + """Suggested polling interval in milliseconds.""" + status: TaskStatus + status_message: Annotated[str | None, Field(alias="statusMessage")] = None + task_id: Annotated[str, Field(alias="taskId")] + ttl: int | None + """Actual retention from creation in milliseconds; null means unlimited.""" + + +class InitializeRequest(WireModel): + """Sent from the client to the server when it first connects.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["initialize"] + params: InitializeRequestParams + + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse + + +JSONRPCResponse: TypeAlias = JSONRPCResultResponse | JSONRPCErrorResponse + + +class ListPromptsRequest(WireModel): + """Sent from the client to request a list of prompts and prompt templates.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/list"] + params: PaginatedRequestParams | None = None + + +class ListPromptsResult(WireModel): + """The server's response to a prompts/list request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + prompts: list[Prompt] + + +class ListResourceTemplatesRequest(WireModel): + """Sent from the client to request a list of resource templates.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/templates/list"] + params: PaginatedRequestParams | None = None + + +class ListResourceTemplatesResult(WireModel): + """The server's response to a resources/templates/list request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] + + +class ListResourcesRequest(WireModel): + """Sent from the client to request a list of resources.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/list"] + params: PaginatedRequestParams | None = None + + +class ListResourcesResult(WireModel): + """The server's response to a resources/list request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + resources: list[Resource] + + +class ListTasksRequest(WireModel): + """A request to retrieve a list of tasks.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/list"] + params: PaginatedRequestParams | None = None + + +class ListTasksResult(WireModel): + """The response to a tasks/list request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + tasks: list[Task] + + +class ListToolsRequest(WireModel): + """Sent from the client to request a list of tools.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/list"] + params: PaginatedRequestParams | None = None + + +class ListToolsResult(WireModel): + """The server's response to a tools/list request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + tools: list[Tool] + + +class PaginatedRequest(WireModel): + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: PaginatedRequestParams | None = None + + +class ProgressNotification(WireModel): + """An out-of-band progress update for a long-running request.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/progress"] + params: ProgressNotificationParams + + +class PromptMessage(WireModel): + """A message returned as part of a prompt; like `SamplingMessage` but allows embedded resources.""" + + content: ContentBlock + role: Role + + +class TaskStatusNotification(WireModel): + """An optional notification that a task's status has changed.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/tasks/status"] + params: TaskStatusNotificationParams + + +class ToolResultContent(WireModel): + """The result of a tool use, provided by the user back to the assistant.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + content: list[ContentBlock] + is_error: Annotated[bool | None, Field(alias="isError")] = None + """Default: false.""" + # 2025-11-25 schema: object-only; 2026-07-28 widens to unknown + structured_content: Annotated[dict[str, Any] | None, Field(alias="structuredContent")] = None + tool_use_id: Annotated[str, Field(alias="toolUseId")] + """Must match the `id` of an earlier `ToolUseContent`.""" + type: Literal["tool_result"] + + +class CallToolResult(WireModel): + """The server's response to a tool call.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + content: list[ContentBlock] + is_error: Annotated[bool | None, Field(alias="isError")] = None + """Tool errors should set this true (not raise a protocol error). Default: false.""" + structured_content: Annotated[dict[str, Any] | None, Field(alias="structuredContent")] = None + + +ClientNotification: TypeAlias = ( + CancelledNotification + | InitializedNotification + | ProgressNotification + | TaskStatusNotification + | RootsListChangedNotification +) + + +ClientRequest: TypeAlias = ( + InitializeRequest + | PingRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | GetTaskRequest + | GetTaskPayloadRequest + | CancelTaskRequest + | ListTasksRequest + | SetLevelRequest + | CompleteRequest +) + + +class ElicitRequest(WireModel): + """A request from the server to elicit additional information from the user.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["elicitation/create"] + params: ElicitRequestParams + + +class GetPromptResult(WireModel): + """The server's response to a prompts/get request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + description: str | None = None + messages: list[PromptMessage] + + +SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent + + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | TaskStatusNotification + | LoggingMessageNotification + | ElicitationCompleteNotification +) + + +ServerResult: TypeAlias = ( + Result + | InitializeResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | GetTaskResult + | GetTaskPayloadResult + | CancelTaskResult + | ListTasksResult + | CompleteResult +) + + +class CreateMessageResult(WireModel): + """The client's response to a sampling/createMessage request.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + content: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + model: str + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """Standard values: "endTurn", "stopSequence", "maxTokens", "toolUse"; open string.""" + + +class SamplingMessage(WireModel): + """A message issued to or received from an LLM API.""" + + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + content: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + role: Role + + +ClientResult: TypeAlias = ( + Result + | GetTaskResult + | GetTaskPayloadResult + | CancelTaskResult + | ListTasksResult + | CreateMessageResult + | ListRootsResult + | ElicitResult +) + + +class CreateMessageRequestParams(WireModel): + meta: Annotated[Meta | None, Field(alias="_meta")] = None + include_context: Annotated[ + Literal["allServers", "none", "thisServer"] | None, + Field(alias="includeContext"), + ] = None + """Default "none"; "thisServer"/"allServers" are soft-deprecated.""" + max_tokens: Annotated[int, Field(alias="maxTokens")] + messages: list[SamplingMessage] + metadata: dict[str, Any] | None = None + """Provider-specific passthrough.""" + model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None + system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + task: TaskMetadata | None = None + """If set, run as a task and return `CreateTaskResult` immediately.""" + temperature: float | None = None + tool_choice: Annotated[ToolChoice | None, Field(alias="toolChoice")] = None + tools: list[Tool] | None = None + + +class CreateMessageRequest(WireModel): + """A request from the server to sample an LLM via the client.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["sampling/createMessage"] + params: CreateMessageRequestParams + + +ServerRequest: TypeAlias = ( + PingRequest + | GetTaskRequest + | GetTaskPayloadRequest + | CancelTaskRequest + | ListTasksRequest + | CreateMessageRequest + | ListRootsRequest + | ElicitRequest +) diff --git a/src/mcp/types/v2026_07_28/__init__.py b/src/mcp/types/v2026_07_28/__init__.py new file mode 100644 index 0000000000..8a970b4f23 --- /dev/null +++ b/src/mcp/types/v2026_07_28/__init__.py @@ -0,0 +1,1492 @@ +"""Internal wire-shape models for protocol 2026-07-28. Not part of the public API. + +Schema-exact validators that the wire-method maps in `mcp.types.methods` point +inbound 2026-07-28 validation at. Generated from schema/draft/schema.json +@ 6d441518de8a9d5adbab0b10a76a667a63f90665 and hand-maintained against that +revision. Models use `extra="ignore"` (unknown keys accepted and dropped) unless +commented otherwise; see `mcp.types._wire_base`. +""" + +from __future__ import annotations + +from typing import Annotated, Any, Literal, TypeAlias + +from pydantic import ConfigDict, Field +from typing_extensions import TypeAliasType + +from mcp.types._wire_base import OpenWireModel, WireModel + +# Deviates from schema.json (renders only string|integer|boolean); follows +# schema.ts, which defines all six JSON types, so floats and null validate. +JSONValue = TypeAliasType("JSONValue", "JSONObject | list[JSONValue] | str | int | float | bool | None") + + +JSONObject = TypeAliasType("JSONObject", dict[str, "JSONValue"]) + + +class BaseMetadata(WireModel): + """Base interface for metadata with name (identifier) and title (display name).""" + + name: str + """Programmatic identifier; also the display fallback when `title` is absent.""" + title: str | None = None + """Human-readable display name.""" + + +class BooleanSchema(WireModel): + default: bool | None = None + description: str | None = None + title: str | None = None + type: Literal["boolean"] + + +class Argument(WireModel): + """The argument being completed.""" + + name: str + value: str + """Value to use for completion matching.""" + + +class Context(WireModel): + """Additional context for completions.""" + + arguments: dict[str, str] | None = None + """Already-resolved variables in a URI template or prompt.""" + + +class Completion(WireModel): + has_more: Annotated[bool | None, Field(alias="hasMore")] = None + """Whether more options exist beyond this response, even if `total` is unknown.""" + total: int | None = None + """Total options available; can exceed `len(values)`.""" + values: Annotated[list[str], Field(max_length=100)] + """Completion values; at most 100 items.""" + + +Cursor: TypeAlias = str + + +class ElicitRequestURLParams(WireModel): + """Parameters for a URL-mode `elicitation/create` request.""" + + elicitation_id: Annotated[str, Field(alias="elicitationId")] + """Server-unique opaque ID for this elicitation.""" + message: str + mode: Literal["url"] + url: str + """URL the user should navigate to.""" + + +class ElicitResult(WireModel): + """Client's result for an `elicitation/create` request.""" + + action: Literal["accept", "cancel", "decline"] + """`accept` = submitted, `decline` = explicit no, `cancel` = dismissed.""" + # Deviates from schema.json (renders number arm as integer); follows + # schema.ts (string|number|boolean|string[]) so float answers validate. + content: dict[str, list[str] | str | int | float | bool] | None = None + """Submitted form data; only present when `action == "accept"` and mode was `"form"`.""" + + +class ElicitationCompleteNotificationParams(WireModel): + elicitation_id: Annotated[str, Field(alias="elicitationId")] + """ID of the elicitation that completed.""" + + +class ElicitationCompleteNotification(WireModel): + """Server-to-client: an out-of-band elicitation has completed.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/elicitation/complete"] + params: ElicitationCompleteNotificationParams + + +class Error(WireModel): + code: int + data: Any | None = None + message: str + + +class Icon(WireModel): + """An optionally-sized icon for display in a UI.""" + + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + sizes: list[str] | None = None + """Sizes the icon supports, each as `"WxH"` or `"any"`; absent means any size.""" + src: str + """HTTP(S) or `data:` URI; consumers should vet origin and sandbox SVG.""" + theme: Literal["dark", "light"] | None = None + """Background theme this icon is designed for; absent means any theme.""" + + +class Icons(WireModel): + """Mixin adding the `icons` property.""" + + icons: list[Icon] | None = None + + +class Implementation(WireModel): + """Describes an MCP implementation.""" + + description: str | None = None + icons: list[Icon] | None = None + name: str + title: str | None = None + version: str + website_url: Annotated[str | None, Field(alias="websiteUrl")] = None + + +class InternalError(WireModel): + """JSON-RPC: internal error on the receiver.""" + + code: Literal[-32603] + data: Any | None = None + message: str + + +class InvalidParamsError(WireModel): + """JSON-RPC: method parameters are invalid or malformed.""" + + code: Literal[-32602] + data: Any | None = None + message: str + + +class InvalidRequestError(WireModel): + """JSON-RPC: request object does not conform to JSON-RPC 2.0.""" + + code: Literal[-32600] + data: Any | None = None + message: str + + +class JSONRPCNotification(WireModel): + """A notification, which does not expect a response.""" + + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +class LegacyTitledEnumSchema(WireModel): + """Deprecated; use `TitledSingleSelectEnumSchema`.""" + + default: str | None = None + description: str | None = None + enum: list[str] + enum_names: Annotated[list[str] | None, Field(alias="enumNames")] = None + """Display names for enum values (non-standard for JSON Schema 2020-12).""" + title: str | None = None + type: Literal["string"] + + +LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + + +class MetaObject(OpenWireModel): + """Contents of a `_meta` field; see the schema for key naming and reservation rules.""" + + +class MethodNotFoundError(WireModel): + """JSON-RPC: requested method does not exist or is not available.""" + + code: Literal[-32601] + data: Any | None = None + message: str + + +class ModelHint(WireModel): + """Hints for model selection; undeclared keys are client-defined.""" + + name: str | None = None + """Model-name substring hint; the client may also map it to an equivalent from another provider.""" + + +class ModelPreferences(WireModel): + """Server's advisory preferences for model selection during sampling.""" + + cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None + hints: list[ModelHint] | None = None + """Evaluated in order (first match wins); should outweigh the numeric priorities.""" + intelligence_priority: Annotated[float | None, Field(alias="intelligencePriority", ge=0.0, le=1.0)] = None + speed_priority: Annotated[float | None, Field(alias="speedPriority", ge=0.0, le=1.0)] = None + + +class Notification(WireModel): + method: str + params: dict[str, Any] | None = None + + +class NotificationParams(WireModel): + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + + +class NumberSchema(WireModel): + default: float | None = None + description: str | None = None + maximum: float | None = None + minimum: float | None = None + title: str | None = None + type: Literal["integer", "number"] + + +class PaginatedResult(WireModel): + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """Opaque pagination position; if present, more results may be available.""" + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + + +class ParseError(WireModel): + """JSON-RPC: invalid JSON received.""" + + code: Literal[-32700] + data: Any | None = None + message: str + + +ProgressToken: TypeAlias = str | int + + +class PromptArgument(WireModel): + """Describes an argument that a prompt can accept.""" + + description: str | None = None + name: str + required: bool | None = None + title: str | None = None + + +class PromptListChangedNotification(WireModel): + """Server-to-client: the prompt list has changed.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/prompts/list_changed"] + params: NotificationParams | None = None + + +class PromptReference(WireModel): + """Identifies a prompt.""" + + name: str + title: str | None = None + type: Literal["ref/prompt"] + + +class Request(WireModel): + method: str + params: dict[str, Any] | None = None + + +RequestId: TypeAlias = str | int + + +class ResourceContents(WireModel): + """The contents of a specific resource or sub-resource.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + uri: str + + +class ResourceListChangedNotification(WireModel): + """Server-to-client: the resource list has changed.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/list_changed"] + params: NotificationParams | None = None + + +class ResourceTemplateReference(WireModel): + """A reference to a resource or resource template definition.""" + + type: Literal["ref/resource"] + uri: str + """URI or URI template of the resource.""" + + +class ResourceUpdatedNotificationParams(WireModel): + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + uri: str + """URI of the updated resource; may be a sub-resource of the subscription URI.""" + + +class Result(WireModel): + """Common result fields.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + + +ResultType: TypeAlias = str + + +Role: TypeAlias = Literal["assistant", "user"] + + +class Root(WireModel): + """A root directory or file the server can operate on.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + name: str | None = None + uri: str + """Must start with `file://` for now.""" + + +class Prompts(WireModel): + """Present if the server offers any prompt templates.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """Whether the server supports prompt-list-changed notifications.""" + + +class Resources(WireModel): + """Present if the server offers any resources to read.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """Whether the server supports resource-list-changed notifications.""" + subscribe: bool | None = None + """Whether the server supports subscribing to resource updates.""" + + +class Tools(WireModel): + """Present if the server offers any tools to call.""" + + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """Whether the server supports tool-list-changed notifications.""" + + +class StringSchema(WireModel): + default: str | None = None + description: str | None = None + format: Literal["date", "date-time", "email", "uri"] | None = None + max_length: Annotated[int | None, Field(alias="maxLength")] = None + min_length: Annotated[int | None, Field(alias="minLength")] = None + title: str | None = None + type: Literal["string"] + + +class SubscriptionFilter(WireModel): + """Notification types a client opts in to on `subscriptions/listen`; each is opt-in.""" + + # Stays open: filter contents are extensible on the wire. + model_config = ConfigDict( + extra="allow", + ) + prompts_list_changed: Annotated[bool | None, Field(alias="promptsListChanged")] = None + """Receive `notifications/prompts/list_changed`.""" + resource_subscriptions: Annotated[list[str] | None, Field(alias="resourceSubscriptions")] = None + """Receive `notifications/resources/updated` for these resource URIs.""" + resources_list_changed: Annotated[bool | None, Field(alias="resourcesListChanged")] = None + """Receive `notifications/resources/list_changed`.""" + tools_list_changed: Annotated[bool | None, Field(alias="toolsListChanged")] = None + """Receive `notifications/tools/list_changed`.""" + + +class SubscriptionsAcknowledgedNotificationParams(WireModel): + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + notifications: SubscriptionFilter + """Subset of requested notification types the server agreed to honor.""" + + +class TextResourceContents(WireModel): + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + text: str + uri: str + + +class AnyOfItem(WireModel): + const: str + title: str + + +class Items(WireModel): + """Array-item schema with enum options and display labels.""" + + any_of: Annotated[list[AnyOfItem], Field(alias="anyOf")] + + +class TitledMultiSelectEnumSchema(WireModel): + """Multi-select enum schema with per-option display titles.""" + + default: list[str] | None = None + description: str | None = None + items: Items + max_items: Annotated[int | None, Field(alias="maxItems")] = None + min_items: Annotated[int | None, Field(alias="minItems")] = None + title: str | None = None + type: Literal["array"] + + +class OneOfItem(WireModel): + const: str + title: str + + +class TitledSingleSelectEnumSchema(WireModel): + """Single-select enum schema with per-option display titles.""" + + default: str | None = None + description: str | None = None + one_of: Annotated[list[OneOfItem], Field(alias="oneOf")] + title: str | None = None + type: Literal["string"] + + +class InputSchema(WireModel): + """JSON Schema for tool parameters; root must be `type: "object"`, defaulting to 2020-12.""" + + # Stays open: arbitrary JSON Schema keywords ride extra fields. + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + type: Literal["object"] + + +class OutputSchema(WireModel): + """JSON Schema for a tool's `structuredContent` output; defaults to 2020-12.""" + + # Stays open: arbitrary JSON Schema keywords ride extra fields. + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + + +class ToolAnnotations(WireModel): + """Tool hints; not guaranteed faithful, never trust from untrusted servers.""" + + destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None + """May perform destructive updates (only meaningful when not read-only); default true.""" + idempotent_hint: Annotated[bool | None, Field(alias="idempotentHint")] = None + """Repeat calls with same args have no additional effect; default false.""" + open_world_hint: Annotated[bool | None, Field(alias="openWorldHint")] = None + """Interacts with an open world of external entities; default true.""" + read_only_hint: Annotated[bool | None, Field(alias="readOnlyHint")] = None + """Does not modify its environment; default false.""" + title: str | None = None + + +class ToolChoice(WireModel): + """Controls tool-selection behavior for sampling requests.""" + + mode: Literal["auto", "none", "required"] | None = None + """`auto` (default) = model decides, `required` = must use a tool, `none` = must not.""" + + +class ToolListChangedNotification(WireModel): + """Server-to-client: the tool list has changed.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/tools/list_changed"] + params: NotificationParams | None = None + + +class ToolUseContent(WireModel): + """A request from the assistant to call a tool.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + id: str + """Unique ID matching this tool use to its result.""" + input: dict[str, Any] + """Arguments conforming to the tool's input schema.""" + name: str + type: Literal["tool_use"] + + +class Data1(WireModel): + requested: str + """Protocol version the client requested.""" + supported: list[str] + """Protocol versions the server supports; the client should retry with one of these.""" + + +class Error2(WireModel): + code: Literal[-32004] + data: Data1 + message: str + + +class UnsupportedProtocolVersionError(WireModel): + """The requested protocol version is unknown or unsupported (HTTP: `400 Bad Request`).""" + + error: Error2 + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class Items1(WireModel): + """Array-item schema.""" + + enum: list[str] + type: Literal["string"] + + +class UntitledMultiSelectEnumSchema(WireModel): + """Multi-select enum schema without per-option display titles.""" + + default: list[str] | None = None + description: str | None = None + items: Items1 + max_items: Annotated[int | None, Field(alias="maxItems")] = None + min_items: Annotated[int | None, Field(alias="minItems")] = None + title: str | None = None + type: Literal["array"] + + +class UntitledSingleSelectEnumSchema(WireModel): + """Single-select enum schema without per-option display titles.""" + + default: str | None = None + description: str | None = None + enum: list[str] + title: str | None = None + type: Literal["string"] + + +class Annotations(WireModel): + """Client-facing annotations informing how objects are used or displayed.""" + + audience: list[Role] | None = None + """Intended audience(s) of this object or data.""" + last_modified: Annotated[str | None, Field(alias="lastModified")] = None + """ISO 8601 timestamp of last modification.""" + priority: Annotated[float | None, Field(ge=0.0, le=1.0)] = None + """Importance for operating the server: 1 = effectively required, 0 = entirely optional.""" + + +class AudioContent(WireModel): + """Audio provided to or from an LLM.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + data: str + """Base64-encoded audio data.""" + mime_type: Annotated[str, Field(alias="mimeType")] + type: Literal["audio"] + + +class BlobResourceContents(WireModel): + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + blob: str + """Base64-encoded binary data.""" + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + uri: str + + +class CacheableResult(WireModel): + """A result carrying a TTL hint for client-side caching.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + + +class CancelledNotificationParams(WireModel): + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + reason: str | None = None + request_id: Annotated[RequestId | None, Field(alias="requestId")] = None + """ID of a request previously issued in the same direction.""" + + +ClientResult: TypeAlias = Result + + +class CompleteResult(WireModel): + """Server's result for a `completion/complete` request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + completion: Completion + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + + +class CompleteResultResponse(WireModel): + """Successful response to a `completion/complete` request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: CompleteResult + + +class EmbeddedResource(WireModel): + """Resource contents embedded in a prompt or tool-call result.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + resource: TextResourceContents | BlobResourceContents + type: Literal["resource"] + + +EmptyResult: TypeAlias = Result + + +EnumSchema: TypeAlias = ( + UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +class ImageContent(WireModel): + """An image provided to or from an LLM.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + data: str + """Base64-encoded image data.""" + mime_type: Annotated[str, Field(alias="mimeType")] + type: Literal["image"] + + +class JSONRPCErrorResponse(WireModel): + """An error response to a request.""" + + error: Error + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class JSONRPCRequest(WireModel): + """A request that expects a response.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +class JSONRPCResultResponse(WireModel): + """A successful (non-error) response to a request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: Result + + +class ListRootsResult(WireModel): + """Client's result for a `roots/list` request.""" + + roots: list[Root] + + +class LoggingMessageNotificationParams(WireModel): + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + data: Any + """Any JSON-serializable log payload.""" + level: LoggingLevel + logger: str | None = None + + +MultiSelectEnumSchema: TypeAlias = UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema + + +PrimitiveSchemaDefinition: TypeAlias = ( + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +class ProgressNotificationParams(WireModel): + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + message: str | None = None + progress: float + """Monotonically increasing progress value; `total` may be unknown.""" + progress_token: Annotated[ProgressToken, Field(alias="progressToken")] + """Token from the originating request, associating this notification with it.""" + total: float | None = None + + +class Prompt(WireModel): + """A prompt or prompt template the server offers.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + arguments: list[PromptArgument] | None = None + description: str | None = None + icons: list[Icon] | None = None + name: str + title: str | None = None + + +class ReadResourceResult(WireModel): + """Server's result for a `resources/read` request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + contents: list[TextResourceContents | BlobResourceContents] + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + + +class Resource(WireModel): + """A known resource the server is capable of reading.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + description: str | None = None + icons: list[Icon] | None = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + name: str + size: int | None = None + """Raw content size in bytes (before base64), if known.""" + title: str | None = None + uri: str + + +class ResourceLink(WireModel): + """A resource reference in a prompt or tool-call result; not guaranteed to appear in `resources/list`.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + description: str | None = None + icons: list[Icon] | None = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + name: str + size: int | None = None + """Raw content size in bytes (before base64), if known.""" + title: str | None = None + type: Literal["resource_link"] + uri: str + + +class ResourceTemplate(WireModel): + """A template description for resources available on the server.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + description: str | None = None + icons: list[Icon] | None = None + mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """MIME type for all matching resources; include only if uniform across matches.""" + name: str + title: str | None = None + uri_template: Annotated[str, Field(alias="uriTemplate")] + """RFC 6570 URI template.""" + + +class ResourceUpdatedNotification(WireModel): + """Server-to-client: a resource the client subscribed to via `subscriptions/listen` has changed.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/updated"] + params: ResourceUpdatedNotificationParams + + +SingleSelectEnumSchema: TypeAlias = UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema + + +class SubscriptionsAcknowledgedNotification(WireModel): + """First message on a `subscriptions/listen` stream, reporting which notifications the server honors.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/subscriptions/acknowledged"] + params: SubscriptionsAcknowledgedNotificationParams + + +class TextContent(WireModel): + """Text provided to or from an LLM.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + text: str + type: Literal["text"] + + +class Tool(WireModel): + """Definition for a tool the client can call.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + annotations: ToolAnnotations | None = None + """Display-name precedence: `title`, `annotations.title`, then `name`.""" + description: str | None = None + icons: list[Icon] | None = None + input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + name: str + output_schema: Annotated[OutputSchema | None, Field(alias="outputSchema")] = None + title: str | None = None + + +class CancelledNotification(WireModel): + """Either side cancelling a previously-issued request; the result will be unused.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/cancelled"] + params: CancelledNotificationParams + + +ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource + + +class RequestedSchema(WireModel): + """Restricted JSON Schema subset: top-level properties only, no nesting.""" + + schema_: Annotated[str | None, Field(alias="$schema")] = None + properties: dict[str, PrimitiveSchemaDefinition] + required: list[str] | None = None + type: Literal["object"] + + +class ElicitRequestFormParams(WireModel): + """Parameters for a form-mode `elicitation/create` request.""" + + message: str + mode: Literal["form"] = "form" + requested_schema: Annotated[RequestedSchema, Field(alias="requestedSchema")] + + +ElicitRequestParams: TypeAlias = ElicitRequestFormParams | ElicitRequestURLParams + + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse + + +JSONRPCResponse: TypeAlias = JSONRPCResultResponse | JSONRPCErrorResponse + + +class ListPromptsResult(WireModel): + """Server's result for a `prompts/list` request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """Opaque pagination position; if present, more results may be available.""" + prompts: list[Prompt] + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + + +class ListPromptsResultResponse(WireModel): + """Successful response to a `prompts/list` request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListPromptsResult + + +class ListResourceTemplatesResult(WireModel): + """Server's result for a `resources/templates/list` request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """Opaque pagination position; if present, more results may be available.""" + resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + + +class ListResourceTemplatesResultResponse(WireModel): + """Successful response to a `resources/templates/list` request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListResourceTemplatesResult + + +class ListResourcesResult(WireModel): + """Server's result for a `resources/list` request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """Opaque pagination position; if present, more results may be available.""" + resources: list[Resource] + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + + +class ListResourcesResultResponse(WireModel): + """Successful response to a `resources/list` request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListResourcesResult + + +class ListToolsResult(WireModel): + """Server's result for a `tools/list` request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """Opaque pagination position; if present, more results may be available.""" + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + tools: list[Tool] + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + + +class ListToolsResultResponse(WireModel): + """Successful response to a `tools/list` request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListToolsResult + + +class LoggingMessageNotification(WireModel): + """Server-to-client log message; opted in via `io.modelcontextprotocol/logLevel` in request `_meta`.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/message"] + params: LoggingMessageNotificationParams + + +class ProgressNotification(WireModel): + """Out-of-band progress update for a long-running request.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/progress"] + params: ProgressNotificationParams + + +class PromptMessage(WireModel): + """A message returned as part of a prompt; like `SamplingMessage` but supports embedded resources.""" + + content: ContentBlock + role: Role + + +ServerNotification: TypeAlias = ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | SubscriptionsAcknowledgedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | LoggingMessageNotification + | ElicitationCompleteNotification +) + + +class ToolResultContent(WireModel): + """The result of a tool use, provided by the user back to the assistant.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + content: list[ContentBlock] + """Unstructured result; same shape as `CallToolResult.content`.""" + is_error: Annotated[bool | None, Field(alias="isError")] = None + """Default false.""" + structured_content: Annotated[Any | None, Field(alias="structuredContent")] = None + """Any JSON value; should conform to the tool's `outputSchema` if defined.""" + tool_use_id: Annotated[str, Field(alias="toolUseId")] + """ID of the corresponding `ToolUseContent`.""" + type: Literal["tool_result"] + + +class CallToolResult(WireModel): + """Server's result for a `tools/call` request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + content: list[ContentBlock] + """Unstructured result of the tool call.""" + is_error: Annotated[bool | None, Field(alias="isError")] = None + """Default false; tool-level errors go here, not as protocol-level errors, so the LLM can see them.""" + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + structured_content: Annotated[Any | None, Field(alias="structuredContent")] = None + """Any JSON value conforming to the tool's `outputSchema` if defined.""" + + +ClientNotification: TypeAlias = CancelledNotification | ProgressNotification + + +class ElicitRequest(WireModel): + """Server request to elicit additional information from the user via the client.""" + + method: Literal["elicitation/create"] + params: ElicitRequestParams + + +class GetPromptResult(WireModel): + """Server's result for a `prompts/get` request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + description: str | None = None + messages: list[PromptMessage] + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + + +SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent + + +class CreateMessageResult(WireModel): + """Client's result for a `sampling/createMessage` request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + content: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + model: str + """Name of the model that generated the message.""" + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """Open string; standard values are `"endTurn"`, `"stopSequence"`, `"maxTokens"`, `"toolUse"`.""" + + +InputResponse: TypeAlias = CreateMessageResult | ListRootsResult | ElicitResult + + +InputResponses: TypeAlias = dict[str, InputResponse] +"""Client responses to server-initiated requests, keyed by the matching `InputRequests` key.""" + + +class SamplingMessage(WireModel): + """A message issued to or received from an LLM API.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + content: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + role: Role + + +class CallToolRequest(WireModel): + """Client request to invoke a tool provided by the server.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/call"] + params: CallToolRequestParams + + +class CallToolRequestParams(WireModel): + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + arguments: dict[str, Any] | None = None + input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None + name: str + request_state: Annotated[str | None, Field(alias="requestState")] = None + + +class CallToolResultResponse(WireModel): + """Successful response to a `tools/call` request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: InputRequiredResult | CallToolResult + + +class Elicitation(WireModel): + """Present if the client supports elicitation from the server.""" + + form: JSONObject | None = None + url: JSONObject | None = None + + +class Sampling(WireModel): + """Present if the client supports sampling from an LLM.""" + + context: JSONObject | None = None + """Declares support for context inclusion via `includeContext`.""" + tools: JSONObject | None = None + """Declares support for `tools` and `toolChoice`.""" + + +class ClientCapabilities(WireModel): + """Capabilities a client may support; not a closed set.""" + + elicitation: Elicitation | None = None + experimental: dict[str, JSONObject] | None = None + extensions: dict[str, JSONObject] | None = None + """Supported MCP extensions, keyed by extension identifier.""" + roots: dict[str, Any] | None = None + sampling: Sampling | None = None + + +class CompleteRequest(WireModel): + """Client request for completion options.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["completion/complete"] + params: CompleteRequestParams + + +class CompleteRequestParams(WireModel): + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + argument: Argument + context: Context | None = None + ref: PromptReference | ResourceTemplateReference + + +class CreateMessageRequest(WireModel): + """Server request for the client to sample an LLM (with human-in-the-loop approval).""" + + method: Literal["sampling/createMessage"] + params: CreateMessageRequestParams + + +class CreateMessageRequestParams(WireModel): + include_context: Annotated[ + Literal["allServers", "none", "thisServer"] | None, + Field(alias="includeContext"), + ] = None + """Default `"none"`; `"thisServer"`/`"allServers"` are deprecated (SEP-2596).""" + max_tokens: Annotated[int, Field(alias="maxTokens")] + """Requested cap; the client may sample fewer.""" + messages: list[SamplingMessage] + metadata: JSONObject | None = None + """Provider-specific passthrough metadata.""" + model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None + system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + temperature: float | None = None + tool_choice: Annotated[ToolChoice | None, Field(alias="toolChoice")] = None + """Error if set without `ClientCapabilities.sampling.tools`; default `{mode: "auto"}`.""" + tools: list[Tool] | None = None + """Error if set without `ClientCapabilities.sampling.tools`.""" + + +class DiscoverRequest(WireModel): + """Client request for the server's supported versions, capabilities, and metadata; servers must implement.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["server/discover"] + params: RequestParams + + +class DiscoverResult(WireModel): + """Server's result for a `server/discover` request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + capabilities: ServerCapabilities + instructions: str | None = None + """Natural-language guidance for the LLM; should not duplicate tool descriptions.""" + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + server_info: Annotated[Implementation, Field(alias="serverInfo")] + supported_versions: Annotated[list[str], Field(alias="supportedVersions")] + """Protocol versions this server supports.""" + ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] + """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + + +class DiscoverResultResponse(WireModel): + """Successful response to a `server/discover` request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: DiscoverResult + + +class GetPromptRequest(WireModel): + """Client request for a prompt provided by the server.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/get"] + params: GetPromptRequestParams + + +class GetPromptRequestParams(WireModel): + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + arguments: dict[str, str] | None = None + input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None + name: str + request_state: Annotated[str | None, Field(alias="requestState")] = None + + +class GetPromptResultResponse(WireModel): + """Successful response to a `prompts/get` request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: InputRequiredResult | GetPromptResult + + +class InputRequiredResult(WireModel): + """Server signals that more input is needed; at least one of `inputRequests` or `requestState` must be present.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + input_requests: Annotated[InputRequests | None, Field(alias="inputRequests")] = None + request_state: Annotated[str | None, Field(alias="requestState")] = None + result_type: Annotated[str, Field(alias="resultType")] + """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + + +class InputResponseRequestParams(WireModel): + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None + request_state: Annotated[str | None, Field(alias="requestState")] = None + + +class ListPromptsRequest(WireModel): + """Client request for the server's prompts and prompt templates.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/list"] + params: PaginatedRequestParams + + +class ListResourceTemplatesRequest(WireModel): + """Client request for the server's resource templates.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/templates/list"] + params: PaginatedRequestParams + + +class ListResourcesRequest(WireModel): + """Client request for the server's resources.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/list"] + params: PaginatedRequestParams + + +class ListRootsRequest(WireModel): + """Server request for the client's root URIs.""" + + method: Literal["roots/list"] + params: RequestParams | None = None + + +class ListToolsRequest(WireModel): + """Client request for the server's tools.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/list"] + params: PaginatedRequestParams + + +class Data(WireModel): + required_capabilities: Annotated[ClientCapabilities, Field(alias="requiredCapabilities")] + """Capabilities the server requires from the client to process the request.""" + + +class Error1(WireModel): + code: Literal[-32003] + data: Data + message: str + + +class MissingRequiredClientCapabilityError(WireModel): + """The request requires a client capability not declared in `clientCapabilities` (HTTP: `400 Bad Request`).""" + + error: Error1 + id: RequestId | None = None + jsonrpc: Literal["2.0"] + + +class PaginatedRequest(WireModel): + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: PaginatedRequestParams + + +class PaginatedRequestParams(WireModel): + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + cursor: str | None = None + """Opaque pagination position; results start after this cursor.""" + + +class ReadResourceRequest(WireModel): + """Client request to read a specific resource URI.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/read"] + params: ReadResourceRequestParams + + +class ReadResourceRequestParams(WireModel): + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None + request_state: Annotated[str | None, Field(alias="requestState")] = None + uri: str + """Any URI scheme; interpretation is server-defined.""" + + +class ReadResourceResultResponse(WireModel): + """Successful response to a `resources/read` request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: InputRequiredResult | ReadResourceResult + + +class RequestMetaObject(OpenWireModel): + """Extends `MetaObject` with request-specific reserved keys; same key naming rules apply.""" + + io_modelcontextprotocol_client_capabilities: Annotated[ + ClientCapabilities, Field(alias="io.modelcontextprotocol/clientCapabilities") + ] + """Per-request client capabilities; servers must not infer from prior requests.""" + io_modelcontextprotocol_client_info: Annotated[Implementation, Field(alias="io.modelcontextprotocol/clientInfo")] + """Identifies the client software making the request.""" + io_modelcontextprotocol_log_level: Annotated[ + LoggingLevel | None, Field(alias="io.modelcontextprotocol/logLevel") + ] = None + """Log level for this request; absent means no `notifications/message` may be sent.""" + io_modelcontextprotocol_protocol_version: Annotated[str, Field(alias="io.modelcontextprotocol/protocolVersion")] + """Protocol version for this request; over HTTP, must match the `MCP-Protocol-Version` header.""" + progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None + """Opaque token opting in to `notifications/progress` for this request.""" + + +class RequestParams(WireModel): + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + + +class ResourceRequestParams(WireModel): + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + uri: str + """Any URI scheme; interpretation is server-defined.""" + + +class ServerCapabilities(WireModel): + """Capabilities a server may support; not a closed set.""" + + completions: JSONObject | None = None + experimental: dict[str, JSONObject] | None = None + extensions: dict[str, JSONObject] | None = None + """Supported MCP extensions, keyed by extension identifier.""" + logging: JSONObject | None = None + prompts: Prompts | None = None + resources: Resources | None = None + tools: Tools | None = None + + +class SubscriptionsListenRequest(WireModel): + """Client request to open a long-lived channel for receiving notifications outside any specific request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["subscriptions/listen"] + params: SubscriptionsListenRequestParams + + +class SubscriptionsListenRequestParams(WireModel): + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + notifications: SubscriptionFilter + """Notification types the client opts in to on this stream.""" + + +AnyCallToolResult: TypeAlias = CallToolResult | InputRequiredResult +"""Named alias for `CallToolResultResponse.result` so the wire-method maps can reference it as a value.""" + +AnyGetPromptResult: TypeAlias = GetPromptResult | InputRequiredResult +"""Everything a `prompts/get` response's `result` may be at this version.""" + +AnyReadResourceResult: TypeAlias = ReadResourceResult | InputRequiredResult +"""Everything a `resources/read` response's `result` may be at this version.""" + + +ServerResult: TypeAlias = ( + Result + | InputRequiredResult + | DiscoverResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | CompleteResult +) + + +InputRequest: TypeAlias = CreateMessageRequest | ListRootsRequest | ElicitRequest + + +ClientRequest: TypeAlias = ( + DiscoverRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscriptionsListenRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | CompleteRequest +) + + +InputRequests: TypeAlias = dict[str, InputRequest] +"""Server-initiated requests the client must fulfill, keyed by server-assigned identifier.""" + + +JSONArray: TypeAlias = list["JSONValue"] + + +CallToolRequest.model_rebuild() +CallToolRequestParams.model_rebuild() +CallToolResultResponse.model_rebuild() +Elicitation.model_rebuild() +Sampling.model_rebuild() +ClientCapabilities.model_rebuild() +CompleteRequest.model_rebuild() +CompleteRequestParams.model_rebuild() +CreateMessageRequest.model_rebuild() +CreateMessageRequestParams.model_rebuild() +DiscoverRequest.model_rebuild() +DiscoverResult.model_rebuild() +GetPromptRequest.model_rebuild() +GetPromptRequestParams.model_rebuild() +GetPromptResultResponse.model_rebuild() +InputRequiredResult.model_rebuild() +InputResponseRequestParams.model_rebuild() +ListPromptsRequest.model_rebuild() +ListResourceTemplatesRequest.model_rebuild() +ListResourcesRequest.model_rebuild() +ListRootsRequest.model_rebuild() +ListToolsRequest.model_rebuild() +PaginatedRequest.model_rebuild() +PaginatedRequestParams.model_rebuild() +ReadResourceRequest.model_rebuild() +ReadResourceRequestParams.model_rebuild() +ServerCapabilities.model_rebuild() +SubscriptionsListenRequest.model_rebuild() diff --git a/tests/interaction/lowlevel/test_resources.py b/tests/interaction/lowlevel/test_resources.py index e42b69d7ec..a5a4bdc14d 100644 --- a/tests/interaction/lowlevel/test_resources.py +++ b/tests/interaction/lowlevel/test_resources.py @@ -81,7 +81,9 @@ async def list_resources( description="The project's front page.", mime_type="text/markdown", size=1024, - annotations=Annotations(audience=["user", "assistant"], priority=0.8), + annotations=Annotations( + audience=["user", "assistant"], priority=0.8, last_modified="2025-01-01T00:00:00Z" + ), icons=[Icon(src="https://example.com/readme.png", mime_type="image/png", sizes=["48x48"])], ), ] diff --git a/tests/interaction/transports/test_hosting_resume.py b/tests/interaction/transports/test_hosting_resume.py index c7945d56c3..b835c78024 100644 --- a/tests/interaction/transports/test_hosting_resume.py +++ b/tests/interaction/transports/test_hosting_resume.py @@ -108,6 +108,7 @@ async def test_a_post_sse_stream_begins_with_a_priming_event_and_stamps_every_ev "content": [{"type": "text", "text": "counted to 2"}], "structuredContent": {"result": "counted to 2"}, "isError": False, + "resultType": "complete", }, ) ) diff --git a/tests/server/test_session.py b/tests/server/test_session.py index 86ec507574..44aab6b33b 100644 --- a/tests/server/test_session.py +++ b/tests/server/test_session.py @@ -219,6 +219,6 @@ async def test_protocol_version_is_none_on_stateless_connection(): seen: list[str | None] = [] async with connected_runner(_runner_server(seen), initialized=False, stateless=True) as (client, runner): result = await client.send_raw_request("tools/list", None) - assert result == {"tools": []} + assert result == {"tools": [], "resultType": "complete", "ttlMs": 0, "cacheScope": "private"} assert seen == [None] assert runner.session.protocol_version is None diff --git a/tests/test_types.py b/tests/test_types.py index f424efdbf7..d604ef1bba 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -1,20 +1,34 @@ from typing import Any import pytest +from inline_snapshot import snapshot from mcp.types import ( LATEST_PROTOCOL_VERSION, + CallToolResult, ClientCapabilities, + CompleteResult, + Completion, CreateMessageRequestParams, CreateMessageResult, CreateMessageResultWithTools, + DiscoverResult, + EmptyResult, + GetPromptResult, Implementation, InitializeRequest, InitializeRequestParams, + InputRequiredResult, JSONRPCRequest, + ListPromptsResult, + ListResourcesResult, + ListResourceTemplatesResult, ListToolsResult, + ReadResourceResult, + Result, SamplingCapability, SamplingMessage, + ServerCapabilities, TextContent, Tool, ToolChoice, @@ -360,3 +374,61 @@ def test_list_tools_result_preserves_json_schema_2020_12_fields(): assert tool.input_schema["$schema"] == "https://json-schema.org/draft/2020-12/schema" assert "$defs" in tool.input_schema assert tool.input_schema["additionalProperties"] is False + + +def _wire_dump(result: Result) -> dict[str, Any]: + return result.model_dump(by_alias=True, mode="json", exclude_none=True) + + +def test_concrete_wire_results_always_dump_result_type_complete(): + """Required by 2026-07-28; older peers tolerate the extra key.""" + carriers: list[Result] = [ + CompleteResult(completion=Completion(values=[])), + GetPromptResult(messages=[]), + CallToolResult(content=[]), + ReadResourceResult(contents=[]), + ListPromptsResult(prompts=[]), + ListResourcesResult(resources=[]), + ListResourceTemplatesResult(resource_templates=[]), + ListToolsResult(tools=[]), + DiscoverResult( + supported_versions=["2026-07-28"], + capabilities=ServerCapabilities(), + server_info=Implementation(name="server", version="1.0"), + ), + ] + for result in carriers: + assert _wire_dump(result)["resultType"] == "complete", type(result).__name__ + + +def test_cacheable_results_always_dump_their_caching_directives(): + """Required by 2026-07-28; older peers tolerate the extra keys.""" + cacheable: list[Result] = [ + ReadResourceResult(contents=[]), + ListPromptsResult(prompts=[]), + ListResourceTemplatesResult(resource_templates=[]), + ListResourcesResult(resources=[]), + ListToolsResult(tools=[]), + DiscoverResult( + supported_versions=["2026-07-28"], + capabilities=ServerCapabilities(), + server_info=Implementation(name="server", version="1.0"), + ), + ] + for result in cacheable: + dumped = _wire_dump(result) + assert dumped["ttlMs"] == 0, type(result).__name__ + assert dumped["cacheScope"] == "private", type(result).__name__ + + +def test_empty_result_dumps_no_fields_by_default(): + """Deployed peers reject extra keys on empty results, so resultType is never volunteered.""" + assert _wire_dump(EmptyResult()) == snapshot({}) + + +def test_empty_result_dumps_result_type_only_when_explicitly_tagged(): + assert _wire_dump(EmptyResult(result_type="complete")) == snapshot({"resultType": "complete"}) + + +def test_input_required_result_dumps_its_discriminating_tag(): + assert _wire_dump(InputRequiredResult()) == snapshot({"resultType": "input_required"}) diff --git a/tests/types/__init__.py b/tests/types/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/types/test_methods.py b/tests/types/test_methods.py new file mode 100644 index 0000000000..423bafaf47 --- /dev/null +++ b/tests/types/test_methods.py @@ -0,0 +1,742 @@ +"""Tests for the wire-method maps and two-step parse functions in `mcp.types.methods`.""" + +import importlib.util +from collections.abc import Mapping +from types import MappingProxyType, UnionType +from typing import Any, get_args + +import pydantic +import pytest + +import mcp.types as types +import mcp.types.v2025_11_25 as v2025 +import mcp.types.v2026_07_28 as v2026 +from mcp.shared.version import KNOWN_PROTOCOL_VERSIONS +from mcp.types import methods +from mcp.types._wire_base import WireModel + +# Transcribed from each schema's ClientRequest/ServerRequest/ClientNotification/ +# ServerNotification unions, minus the tasks/* family (extensions register those). +EXPECTED_METHODS: dict[str, dict[str, frozenset[str]]] = { + "2024-11-05": { + "CLIENT_REQUESTS": frozenset( + { + "completion/complete", + "initialize", + "logging/setLevel", + "ping", + "prompts/get", + "prompts/list", + "resources/list", + "resources/read", + "resources/subscribe", + "resources/templates/list", + "resources/unsubscribe", + "tools/call", + "tools/list", + } + ), + "CLIENT_NOTIFICATIONS": frozenset( + { + "notifications/cancelled", + "notifications/initialized", + "notifications/progress", + "notifications/roots/list_changed", + } + ), + "SERVER_REQUESTS": frozenset({"ping", "roots/list", "sampling/createMessage"}), + "SERVER_NOTIFICATIONS": frozenset( + { + "notifications/cancelled", + "notifications/message", + "notifications/progress", + "notifications/prompts/list_changed", + "notifications/resources/list_changed", + "notifications/resources/updated", + "notifications/tools/list_changed", + } + ), + }, + "2025-03-26": { + "CLIENT_REQUESTS": frozenset( + { + "completion/complete", + "initialize", + "logging/setLevel", + "ping", + "prompts/get", + "prompts/list", + "resources/list", + "resources/read", + "resources/subscribe", + "resources/templates/list", + "resources/unsubscribe", + "tools/call", + "tools/list", + } + ), + "CLIENT_NOTIFICATIONS": frozenset( + { + "notifications/cancelled", + "notifications/initialized", + "notifications/progress", + "notifications/roots/list_changed", + } + ), + "SERVER_REQUESTS": frozenset({"ping", "roots/list", "sampling/createMessage"}), + "SERVER_NOTIFICATIONS": frozenset( + { + "notifications/cancelled", + "notifications/message", + "notifications/progress", + "notifications/prompts/list_changed", + "notifications/resources/list_changed", + "notifications/resources/updated", + "notifications/tools/list_changed", + } + ), + }, + "2025-06-18": { + "CLIENT_REQUESTS": frozenset( + { + "completion/complete", + "initialize", + "logging/setLevel", + "ping", + "prompts/get", + "prompts/list", + "resources/list", + "resources/read", + "resources/subscribe", + "resources/templates/list", + "resources/unsubscribe", + "tools/call", + "tools/list", + } + ), + "CLIENT_NOTIFICATIONS": frozenset( + { + "notifications/cancelled", + "notifications/initialized", + "notifications/progress", + "notifications/roots/list_changed", + } + ), + "SERVER_REQUESTS": frozenset({"elicitation/create", "ping", "roots/list", "sampling/createMessage"}), + "SERVER_NOTIFICATIONS": frozenset( + { + "notifications/cancelled", + "notifications/message", + "notifications/progress", + "notifications/prompts/list_changed", + "notifications/resources/list_changed", + "notifications/resources/updated", + "notifications/tools/list_changed", + } + ), + }, + "2025-11-25": { + "CLIENT_REQUESTS": frozenset( + { + "completion/complete", + "initialize", + "logging/setLevel", + "ping", + "prompts/get", + "prompts/list", + "resources/list", + "resources/read", + "resources/subscribe", + "resources/templates/list", + "resources/unsubscribe", + "tools/call", + "tools/list", + } + ), + "CLIENT_NOTIFICATIONS": frozenset( + { + "notifications/cancelled", + "notifications/initialized", + "notifications/progress", + "notifications/roots/list_changed", + } + ), + "SERVER_REQUESTS": frozenset({"elicitation/create", "ping", "roots/list", "sampling/createMessage"}), + "SERVER_NOTIFICATIONS": frozenset( + { + "notifications/cancelled", + "notifications/elicitation/complete", + "notifications/message", + "notifications/progress", + "notifications/prompts/list_changed", + "notifications/resources/list_changed", + "notifications/resources/updated", + "notifications/tools/list_changed", + } + ), + }, + "2026-07-28": { + "CLIENT_REQUESTS": frozenset( + { + "completion/complete", + "prompts/get", + "prompts/list", + "resources/list", + "resources/read", + "resources/templates/list", + "server/discover", + "subscriptions/listen", + "tools/call", + "tools/list", + } + ), + "CLIENT_NOTIFICATIONS": frozenset({"notifications/cancelled", "notifications/progress"}), + # No standalone server-to-client request channel at this version. + "SERVER_REQUESTS": frozenset(), + "SERVER_NOTIFICATIONS": frozenset( + { + "notifications/cancelled", + "notifications/elicitation/complete", + "notifications/message", + "notifications/progress", + "notifications/prompts/list_changed", + "notifications/resources/list_changed", + "notifications/resources/updated", + "notifications/subscriptions/acknowledged", + "notifications/tools/list_changed", + } + ), + }, +} + +# Pinned per (method, version): class identity, or exact arm tuple for unions. +EXPECTED_SERVER_RESULTS: dict[tuple[str, str], type[WireModel] | tuple[type[WireModel], ...]] = { + ("completion/complete", "2024-11-05"): v2025.CompleteResult, + ("initialize", "2024-11-05"): v2025.InitializeResult, + ("logging/setLevel", "2024-11-05"): v2025.EmptyResult, + ("ping", "2024-11-05"): v2025.EmptyResult, + ("prompts/get", "2024-11-05"): v2025.GetPromptResult, + ("prompts/list", "2024-11-05"): v2025.ListPromptsResult, + ("resources/list", "2024-11-05"): v2025.ListResourcesResult, + ("resources/read", "2024-11-05"): v2025.ReadResourceResult, + ("resources/subscribe", "2024-11-05"): v2025.EmptyResult, + ("resources/templates/list", "2024-11-05"): v2025.ListResourceTemplatesResult, + ("resources/unsubscribe", "2024-11-05"): v2025.EmptyResult, + ("tools/call", "2024-11-05"): v2025.CallToolResult, + ("tools/list", "2024-11-05"): v2025.ListToolsResult, + ("completion/complete", "2025-03-26"): v2025.CompleteResult, + ("initialize", "2025-03-26"): v2025.InitializeResult, + ("logging/setLevel", "2025-03-26"): v2025.EmptyResult, + ("ping", "2025-03-26"): v2025.EmptyResult, + ("prompts/get", "2025-03-26"): v2025.GetPromptResult, + ("prompts/list", "2025-03-26"): v2025.ListPromptsResult, + ("resources/list", "2025-03-26"): v2025.ListResourcesResult, + ("resources/read", "2025-03-26"): v2025.ReadResourceResult, + ("resources/subscribe", "2025-03-26"): v2025.EmptyResult, + ("resources/templates/list", "2025-03-26"): v2025.ListResourceTemplatesResult, + ("resources/unsubscribe", "2025-03-26"): v2025.EmptyResult, + ("tools/call", "2025-03-26"): v2025.CallToolResult, + ("tools/list", "2025-03-26"): v2025.ListToolsResult, + ("completion/complete", "2025-06-18"): v2025.CompleteResult, + ("initialize", "2025-06-18"): v2025.InitializeResult, + ("logging/setLevel", "2025-06-18"): v2025.EmptyResult, + ("ping", "2025-06-18"): v2025.EmptyResult, + ("prompts/get", "2025-06-18"): v2025.GetPromptResult, + ("prompts/list", "2025-06-18"): v2025.ListPromptsResult, + ("resources/list", "2025-06-18"): v2025.ListResourcesResult, + ("resources/read", "2025-06-18"): v2025.ReadResourceResult, + ("resources/subscribe", "2025-06-18"): v2025.EmptyResult, + ("resources/templates/list", "2025-06-18"): v2025.ListResourceTemplatesResult, + ("resources/unsubscribe", "2025-06-18"): v2025.EmptyResult, + ("tools/call", "2025-06-18"): v2025.CallToolResult, + ("tools/list", "2025-06-18"): v2025.ListToolsResult, + ("completion/complete", "2025-11-25"): v2025.CompleteResult, + ("initialize", "2025-11-25"): v2025.InitializeResult, + ("logging/setLevel", "2025-11-25"): v2025.EmptyResult, + ("ping", "2025-11-25"): v2025.EmptyResult, + ("prompts/get", "2025-11-25"): v2025.GetPromptResult, + ("prompts/list", "2025-11-25"): v2025.ListPromptsResult, + ("resources/list", "2025-11-25"): v2025.ListResourcesResult, + ("resources/read", "2025-11-25"): v2025.ReadResourceResult, + ("resources/subscribe", "2025-11-25"): v2025.EmptyResult, + ("resources/templates/list", "2025-11-25"): v2025.ListResourceTemplatesResult, + ("resources/unsubscribe", "2025-11-25"): v2025.EmptyResult, + ("tools/call", "2025-11-25"): v2025.CallToolResult, + ("tools/list", "2025-11-25"): v2025.ListToolsResult, + ("completion/complete", "2026-07-28"): v2026.CompleteResult, + ("prompts/get", "2026-07-28"): (v2026.GetPromptResult, v2026.InputRequiredResult), + ("prompts/list", "2026-07-28"): v2026.ListPromptsResult, + ("resources/list", "2026-07-28"): v2026.ListResourcesResult, + ("resources/read", "2026-07-28"): (v2026.ReadResourceResult, v2026.InputRequiredResult), + ("resources/templates/list", "2026-07-28"): v2026.ListResourceTemplatesResult, + ("server/discover", "2026-07-28"): v2026.DiscoverResult, + ("subscriptions/listen", "2026-07-28"): v2026.EmptyResult, + ("tools/call", "2026-07-28"): (v2026.CallToolResult, v2026.InputRequiredResult), + ("tools/list", "2026-07-28"): v2026.ListToolsResult, +} + +EXPECTED_CLIENT_RESULTS: dict[tuple[str, str], type[WireModel] | tuple[type[WireModel], ...]] = { + ("ping", "2024-11-05"): v2025.EmptyResult, + ("roots/list", "2024-11-05"): v2025.ListRootsResult, + ("sampling/createMessage", "2024-11-05"): v2025.CreateMessageResult, + ("ping", "2025-03-26"): v2025.EmptyResult, + ("roots/list", "2025-03-26"): v2025.ListRootsResult, + ("sampling/createMessage", "2025-03-26"): v2025.CreateMessageResult, + ("elicitation/create", "2025-06-18"): v2025.ElicitResult, + ("ping", "2025-06-18"): v2025.EmptyResult, + ("roots/list", "2025-06-18"): v2025.ListRootsResult, + ("sampling/createMessage", "2025-06-18"): v2025.CreateMessageResult, + ("elicitation/create", "2025-11-25"): v2025.ElicitResult, + ("ping", "2025-11-25"): v2025.EmptyResult, + ("roots/list", "2025-11-25"): v2025.ListRootsResult, + ("sampling/createMessage", "2025-11-25"): v2025.CreateMessageResult, +} + +EMPTY_SERVER_RESPONSE_METHODS = frozenset( + {"logging/setLevel", "ping", "resources/subscribe", "resources/unsubscribe", "subscriptions/listen"} +) +EMPTY_CLIENT_RESPONSE_METHODS = frozenset({"ping"}) + +# Pre-2026 versions share the 2025-11-25 surface package. +PACKAGE_BY_VERSION = { + "2024-11-05": "mcp.types.v2025_11_25", + "2025-03-26": "mcp.types.v2025_11_25", + "2025-06-18": "mcp.types.v2025_11_25", + "2025-11-25": "mcp.types.v2025_11_25", + "2026-07-28": "mcp.types.v2026_07_28", +} + +# The three reserved `params._meta` entries the 2026 surface requires on every request. +META_TRIPLE: dict[str, Any] = { + "io.modelcontextprotocol/protocolVersion": "2026-07-28", + "io.modelcontextprotocol/clientInfo": {"name": "client", "version": "1.0"}, + "io.modelcontextprotocol/clientCapabilities": {}, +} + +# One minimal valid params mapping per surface request class. +REQUEST_PARAMS_FIXTURES: dict[type[WireModel], dict[str, Any] | None] = { + v2025.CallToolRequest: {"name": "echo"}, + v2025.CompleteRequest: {"ref": {"type": "ref/prompt", "name": "p"}, "argument": {"name": "a", "value": "v"}}, + v2025.CreateMessageRequest: { + "messages": [{"role": "user", "content": {"type": "text", "text": "hi"}}], + "maxTokens": 100, + }, + v2025.ElicitRequest: {"message": "m", "requestedSchema": {"type": "object", "properties": {}}}, + v2025.GetPromptRequest: {"name": "greeting"}, + v2025.InitializeRequest: { + "protocolVersion": "2025-11-25", + "capabilities": {}, + "clientInfo": {"name": "client", "version": "1.0"}, + }, + v2025.ListPromptsRequest: None, + v2025.ListResourcesRequest: None, + v2025.ListResourceTemplatesRequest: None, + v2025.ListRootsRequest: None, + v2025.ListToolsRequest: None, + v2025.PingRequest: None, + v2025.ReadResourceRequest: {"uri": "https://example.com/resource"}, + v2025.SetLevelRequest: {"level": "info"}, + v2025.SubscribeRequest: {"uri": "https://example.com/resource"}, + v2025.UnsubscribeRequest: {"uri": "https://example.com/resource"}, + v2026.CallToolRequest: {"_meta": META_TRIPLE, "name": "echo"}, + v2026.CompleteRequest: { + "_meta": META_TRIPLE, + "ref": {"type": "ref/prompt", "name": "p"}, + "argument": {"name": "a", "value": "v"}, + }, + v2026.DiscoverRequest: {"_meta": META_TRIPLE}, + v2026.GetPromptRequest: {"_meta": META_TRIPLE, "name": "greeting"}, + v2026.ListPromptsRequest: {"_meta": META_TRIPLE}, + v2026.ListResourcesRequest: {"_meta": META_TRIPLE}, + v2026.ListResourceTemplatesRequest: {"_meta": META_TRIPLE}, + v2026.ListToolsRequest: {"_meta": META_TRIPLE}, + v2026.ReadResourceRequest: {"_meta": META_TRIPLE, "uri": "https://example.com/resource"}, + v2026.SubscriptionsListenRequest: {"_meta": META_TRIPLE, "notifications": {}}, +} + +NOTIFICATION_PARAMS_FIXTURES: dict[type[WireModel], dict[str, Any] | None] = { + v2025.CancelledNotification: {"requestId": 1}, + v2025.ElicitationCompleteNotification: {"elicitationId": "e1"}, + v2025.InitializedNotification: None, + v2025.LoggingMessageNotification: {"level": "info", "data": "x"}, + v2025.ProgressNotification: {"progressToken": 1, "progress": 0.5}, + v2025.PromptListChangedNotification: None, + v2025.ResourceListChangedNotification: None, + v2025.ResourceUpdatedNotification: {"uri": "https://example.com/resource"}, + v2025.RootsListChangedNotification: None, + v2025.ToolListChangedNotification: None, + v2026.CancelledNotification: {"requestId": 1}, + v2026.ElicitationCompleteNotification: {"elicitationId": "e1"}, + v2026.LoggingMessageNotification: {"level": "info", "data": "x"}, + v2026.ProgressNotification: {"progressToken": 1, "progress": 0.5}, + v2026.PromptListChangedNotification: None, + v2026.ResourceListChangedNotification: None, + v2026.ResourceUpdatedNotification: {"uri": "https://example.com/resource"}, + v2026.SubscriptionsAcknowledgedNotification: {"notifications": {}}, + v2026.ToolListChangedNotification: None, +} + +# One minimal valid result body per response row value (class or union alias). +RESULT_BODY_FIXTURES: dict[type[WireModel] | UnionType, dict[str, Any]] = { + v2025.CallToolResult: {"content": []}, + v2025.CompleteResult: {"completion": {"values": []}}, + v2025.CreateMessageResult: {"role": "assistant", "content": {"type": "text", "text": "hi"}, "model": "m"}, + v2025.ElicitResult: {"action": "accept"}, + v2025.EmptyResult: {}, + v2025.GetPromptResult: {"messages": []}, + v2025.InitializeResult: { + "protocolVersion": "2025-11-25", + "capabilities": {}, + "serverInfo": {"name": "server", "version": "1.0"}, + }, + v2025.ListPromptsResult: {"prompts": []}, + v2025.ListResourcesResult: {"resources": []}, + v2025.ListResourceTemplatesResult: {"resourceTemplates": []}, + v2025.ListRootsResult: {"roots": [{"uri": "file:///workspace"}]}, + v2025.ListToolsResult: {"tools": []}, + v2025.ReadResourceResult: {"contents": []}, + v2026.AnyCallToolResult: {"content": [], "resultType": "complete"}, + v2026.AnyGetPromptResult: {"messages": [], "resultType": "complete"}, + v2026.AnyReadResourceResult: {"contents": [], "resultType": "complete", "ttlMs": 0, "cacheScope": "private"}, + v2026.CompleteResult: {"completion": {"values": []}, "resultType": "complete"}, + v2026.DiscoverResult: { + "supportedVersions": ["2026-07-28"], + "capabilities": {}, + "serverInfo": {"name": "server", "version": "1.0"}, + "resultType": "complete", + "ttlMs": 0, + "cacheScope": "private", + }, + v2026.EmptyResult: {"resultType": "complete"}, + v2026.ListPromptsResult: {"prompts": [], "resultType": "complete", "ttlMs": 0, "cacheScope": "private"}, + v2026.ListResourcesResult: {"resources": [], "resultType": "complete", "ttlMs": 0, "cacheScope": "private"}, + v2026.ListResourceTemplatesResult: { + "resourceTemplates": [], + "resultType": "complete", + "ttlMs": 0, + "cacheScope": "private", + }, + v2026.ListToolsResult: {"tools": [], "resultType": "complete", "ttlMs": 0, "cacheScope": "private"}, +} + + +def test_maps_define_exactly_the_expected_methods_for_every_known_version(): + # Derive the version axis from KNOWN_PROTOCOL_VERSIONS so a new version + # without map rows fails here rather than gating every method at runtime. + assert set(EXPECTED_METHODS) == set(KNOWN_PROTOCOL_VERSIONS) + surface_maps: dict[str, Mapping[tuple[str, str], object]] = { + "CLIENT_REQUESTS": methods.CLIENT_REQUESTS, + "CLIENT_NOTIFICATIONS": methods.CLIENT_NOTIFICATIONS, + "SERVER_REQUESTS": methods.SERVER_REQUESTS, + "SERVER_NOTIFICATIONS": methods.SERVER_NOTIFICATIONS, + } + for version in KNOWN_PROTOCOL_VERSIONS: + for map_name, surface_map in surface_maps.items(): + derived = {method for (method, row_version) in surface_map if row_version == version} + assert derived == EXPECTED_METHODS[version][map_name], f"{map_name} at {version}" + + +def test_response_map_keys_mirror_the_request_map_keys(): + assert set(methods.SERVER_RESULTS) == set(methods.CLIENT_REQUESTS) + assert set(methods.CLIENT_RESULTS) == set(methods.SERVER_REQUESTS) + + +def test_response_row_values_match_the_pinned_classes_and_unions(): + """Only the known empty-response methods may be valued by the bare `EmptyResult`.""" + assert set(EXPECTED_SERVER_RESULTS) == set(methods.SERVER_RESULTS) + assert set(EXPECTED_CLIENT_RESULTS) == set(methods.CLIENT_RESULTS) + pinned = [ + (methods.SERVER_RESULTS, EXPECTED_SERVER_RESULTS, EMPTY_SERVER_RESPONSE_METHODS), + (methods.CLIENT_RESULTS, EXPECTED_CLIENT_RESULTS, EMPTY_CLIENT_RESPONSE_METHODS), + ] + for response_map, expected_rows, empty_methods in pinned: + for (method, version), expected in expected_rows.items(): + actual = response_map[(method, version)] + if isinstance(expected, tuple): + assert get_args(actual) == expected, f"{method} at {version}" + else: + assert actual is expected, f"{method} at {version}" + if method not in empty_methods: + assert actual is not v2025.EmptyResult, f"{method} at {version}" + assert actual is not v2026.EmptyResult, f"{method} at {version}" + + +def test_surface_keys_agree_with_their_classes_and_the_monolith_maps(): + """Each surface key's method matches its class's method literal, its monolith row, and its version's package.""" + request_maps: list[Mapping[tuple[str, str], type[WireModel]]] = [ + methods.CLIENT_REQUESTS, + methods.SERVER_REQUESTS, + ] + notification_maps: list[Mapping[tuple[str, str], type[WireModel]]] = [ + methods.CLIENT_NOTIFICATIONS, + methods.SERVER_NOTIFICATIONS, + ] + for surface_maps, monolith_map in ( + (request_maps, methods.MONOLITH_REQUESTS), + (notification_maps, methods.MONOLITH_NOTIFICATIONS), + ): + for surface_map in surface_maps: + for (method, version), surface_type in surface_map.items(): + assert method in monolith_map, f"{method} has no monolith row" + assert get_args(surface_type.model_fields["method"].annotation) == (method,) + assert get_args(monolith_map[method].model_fields["method"].annotation) == (method,) + assert surface_type.__module__ == PACKAGE_BY_VERSION[version], f"{method} at {version}" + for response_map in (methods.SERVER_RESULTS, methods.CLIENT_RESULTS): + for (method, version), row in response_map.items(): + assert method in methods.MONOLITH_RESULTS, f"{method} has no monolith row" + for arm in get_args(row) or (row,): + assert arm.__module__ == PACKAGE_BY_VERSION[version], f"{method} at {version}" + + +def _assign_item(mapping: Any) -> None: + mapping["new-key"] = None + + +def test_built_in_maps_are_immutable(): + map_names = [ + "CLIENT_NOTIFICATIONS", + "CLIENT_REQUESTS", + "CLIENT_RESULTS", + "MONOLITH_NOTIFICATIONS", + "MONOLITH_REQUESTS", + "MONOLITH_RESULTS", + "SERVER_NOTIFICATIONS", + "SERVER_REQUESTS", + "SERVER_RESULTS", + ] + for map_name in map_names: + built_in = getattr(methods, map_name) + assert isinstance(built_in, MappingProxyType), map_name + with pytest.raises(TypeError): + _assign_item(built_in) + + +def test_minimal_request_bodies_parse_through_every_request_row(): + for (method, version), surface_type in methods.CLIENT_REQUESTS.items(): + parsed = methods.parse_client_request(method, version, REQUEST_PARAMS_FIXTURES[surface_type]) + assert isinstance(parsed, types.Request), f"{method} at {version}" + for (method, version), surface_type in methods.SERVER_REQUESTS.items(): + parsed = methods.parse_server_request(method, version, REQUEST_PARAMS_FIXTURES[surface_type]) + assert isinstance(parsed, types.Request), f"{method} at {version}" + + +def test_minimal_notification_bodies_parse_through_every_notification_row(): + for (method, version), surface_type in methods.CLIENT_NOTIFICATIONS.items(): + parsed = methods.parse_client_notification(method, version, NOTIFICATION_PARAMS_FIXTURES[surface_type]) + assert isinstance(parsed, types.Notification), f"{method} at {version}" + for (method, version), surface_type in methods.SERVER_NOTIFICATIONS.items(): + parsed = methods.parse_server_notification(method, version, NOTIFICATION_PARAMS_FIXTURES[surface_type]) + assert isinstance(parsed, types.Notification), f"{method} at {version}" + + +def test_minimal_result_bodies_parse_through_every_result_row(): + for (method, version), row in methods.SERVER_RESULTS.items(): + parsed = methods.parse_server_result(method, version, RESULT_BODY_FIXTURES[row]) + assert isinstance(parsed, types.Result), f"{method} at {version}" + for (method, version), row in methods.CLIENT_RESULTS.items(): + parsed = methods.parse_client_result(method, version, RESULT_BODY_FIXTURES[row]) + assert isinstance(parsed, types.Result), f"{method} at {version}" + + +def test_non_file_root_uri_passes_the_surface_step_and_rejects_at_the_monolith_step(): + """The monolith's `Root.uri` is file-scheme only; the surfaces declare a plain string.""" + non_file_roots = {"roots": [{"uri": "https://example.com/x"}]} + # Surface step admits the body, so the two-step parse fails at the monolith step. + pydantic.TypeAdapter(v2025.ListRootsResult).validate_python(non_file_roots) + with pytest.raises(pydantic.ValidationError): + methods.parse_client_result("roots/list", "2025-11-25", non_file_roots) + + # Same divergence on the 2026 path that embeds a roots response. + retry_params = {"_meta": META_TRIPLE, "name": "echo", "inputResponses": {"r1": non_file_roots}} + frame = {"jsonrpc": "2.0", "id": 0, "method": "tools/call", "params": retry_params} + v2026.CallToolRequest.model_validate(frame, by_name=False) + with pytest.raises(pydantic.ValidationError): + methods.parse_client_request("tools/call", "2026-07-28", retry_params) + + file_roots = {"roots": [{"uri": "file:///workspace"}]} + assert isinstance(methods.parse_client_result("roots/list", "2025-11-25", file_roots), types.ListRootsResult) + retried = methods.parse_client_request( + "tools/call", "2026-07-28", {"_meta": META_TRIPLE, "name": "echo", "inputResponses": {"r1": file_roots}} + ) + assert isinstance(retried, types.CallToolRequest) + + +def test_absent_map_keys_raise_key_error_for_every_gate_shape(): + """Key absence is the version gate; the session layer maps it to `METHOD_NOT_FOUND`.""" + gated = [ + ("resources/subscribe", "2026-07-28"), # removed at this version + ("server/discover", "2025-11-25"), # not yet at this version + ("tasks/get", "2025-11-25"), # never built-in + ("sampling/createMessage", "2025-11-25"), # wrong direction + ] + for method, version in gated: + with pytest.raises(KeyError): + methods.parse_client_request(method, version, None) + with pytest.raises(KeyError): + methods.parse_server_request("ping", "2026-07-28", None) + + +def test_unknown_version_strings_raise_value_error_on_every_parse_function(): + body_parsers = [ + methods.parse_client_request, + methods.parse_server_request, + methods.parse_client_notification, + methods.parse_server_notification, + ] + for body_parser in body_parsers: + with pytest.raises(ValueError) as excinfo: + body_parser("ping", "2099-01-01", None) + assert "2099-01-01" in str(excinfo.value) + result_parsers = [methods.parse_server_result, methods.parse_client_result] + for result_parser in result_parsers: + with pytest.raises(ValueError) as excinfo: + result_parser("ping", "2099-01-01", {}) + assert "2099-01-01" in str(excinfo.value) + + +def test_2026_07_28_requests_missing_a_reserved_meta_entry_reject_as_missing(): + for absent_key in META_TRIPLE: + partial_meta = {key: value for key, value in META_TRIPLE.items() if key != absent_key} + with pytest.raises(pydantic.ValidationError) as excinfo: + methods.parse_client_request("tools/list", "2026-07-28", {"_meta": partial_meta}) + assert [error["loc"] for error in excinfo.value.errors() if error["type"] == "missing"] == [ + ("params", "_meta", absent_key) + ] + + +def test_2026_07_28_results_require_result_type(): + with pytest.raises(pydantic.ValidationError): + methods.parse_server_result("tools/call", "2026-07-28", {"content": []}) + with pytest.raises(pydantic.ValidationError): + methods.parse_server_result("subscriptions/listen", "2026-07-28", {}) + + +def test_empty_result_body_parses_at_versions_that_define_it(): + parsed = methods.parse_server_result("ping", "2025-11-25", {}) + assert isinstance(parsed, types.EmptyResult) + + +def test_2026_07_28_shaped_result_extras_pass_at_earlier_versions(): + """The earlier surface ignores unknown keys; the monolith preserves them on fields it declares.""" + parsed = methods.parse_server_result( + "tools/list", "2025-11-25", {"tools": [], "resultType": "complete", "ttlMs": 5, "cacheScope": "public"} + ) + assert isinstance(parsed, types.ListToolsResult) + assert parsed.result_type == "complete" + assert parsed.ttl_ms == 5 + assert parsed.cache_scope == "public" + + +def test_embedded_input_request_entries_without_method_reject_at_the_surface_step(): + """The monolith's embedded request classes default `method`, so only the surface step rejects this.""" + body = {"resultType": "input_required", "inputRequests": {"r1": {"params": None}}} + monolith_row = methods.MONOLITH_RESULTS["tools/call"] + monolith_only: types.Result = pydantic.TypeAdapter[Any](monolith_row).validate_python(body) + assert isinstance(monolith_only, types.InputRequiredResult) + with pytest.raises(pydantic.ValidationError): + methods.parse_server_result("tools/call", "2026-07-28", body) + + +def test_none_params_omit_the_key_so_required_params_reject(): + with pytest.raises(pydantic.ValidationError) as excinfo: + methods.parse_client_request("tools/call", "2025-11-25", None) + assert [error["loc"] for error in excinfo.value.errors() if error["type"] == "missing"] == [("params",)] + assert isinstance(methods.parse_client_request("ping", "2025-11-25", None), types.PingRequest) + + +def test_snake_case_spellings_of_required_aliased_fields_reject_as_missing(): + """Wire parsing is alias-only (`by_name=False`), at both the surface and monolith steps.""" + snake_params = {"messages": [{"role": "user", "content": {"type": "text", "text": "hi"}}], "max_tokens": 100} + with pytest.raises(pydantic.ValidationError) as excinfo: + methods.parse_server_request("sampling/createMessage", "2025-11-25", snake_params) + assert [error["loc"] for error in excinfo.value.errors() if error["type"] == "missing"] == [("params", "maxTokens")] + with pytest.raises(pydantic.ValidationError): + types.CreateMessageRequest.model_validate( + {"method": "sampling/createMessage", "params": snake_params}, by_name=False + ) + + +def test_extension_map_rows_parse_through_the_same_functions(): + extended_surface = {**methods.CLIENT_REQUESTS, ("tasks/get", "2025-11-25"): v2025.GetTaskRequest} + extended_monolith = {**methods.MONOLITH_REQUESTS, "tasks/get": types.GetTaskRequest} + parsed = methods.parse_client_request( + "tasks/get", "2025-11-25", {"taskId": "t1"}, surface=extended_surface, monolith=extended_monolith + ) + assert isinstance(parsed, types.GetTaskRequest) + assert parsed.params.task_id == "t1" + + +def test_inconsistent_extension_maps_raise_runtime_error_after_the_surface_hit(): + """Must not raise `KeyError`: the session layer treats that as the version gate.""" + extended_surface = {**methods.CLIENT_REQUESTS, ("tasks/get", "2025-11-25"): v2025.GetTaskRequest} + with pytest.raises(RuntimeError, match="inconsistent extension maps"): + methods.parse_client_request("tasks/get", "2025-11-25", {"taskId": "t1"}, surface=extended_surface) + + +def test_input_required_unions_discriminate_identically_in_both_arm_orders(): + complete_bodies: dict[str, dict[str, Any]] = { + "tools/call": {"content": []}, + "prompts/get": {"messages": []}, + "resources/read": {"contents": []}, + } + shared_bodies: list[dict[str, Any]] = [ + {"resultType": "input_required", "inputRequests": {"r1": {"method": "roots/list"}}}, + {"resultType": "input_required", "requestState": "blob"}, + ] + for method, complete_body in complete_bodies.items(): + row = methods.MONOLITH_RESULTS[method] + complete_arm, input_required_arm = get_args(row) + assert input_required_arm is types.InputRequiredResult + bodies: list[dict[str, Any]] = [ + complete_body, + {**complete_body, "resultType": "complete"}, + *shared_bodies, + {**complete_body, "resultType": "task"}, # open tag is preserved + {**complete_body, "resultType": "input_required"}, # complete shape plus the tag + ] + for body in bodies: + forward = pydantic.TypeAdapter[Any](complete_arm | input_required_arm).validate_python(body) + reversed_order = pydantic.TypeAdapter[Any](input_required_arm | complete_arm).validate_python(body) + assert type(forward) is type(reversed_order), f"{method}: {body}" + assert forward.result_type == reversed_order.result_type + through_row = pydantic.TypeAdapter[Any](row).validate_python(complete_body) + assert isinstance(through_row, complete_arm) + open_tagged = pydantic.TypeAdapter[Any](row).validate_python({**complete_body, "resultType": "task"}) + assert open_tagged.result_type == "task" + + +def test_sampling_union_keeps_the_complete_arm_first_because_order_is_load_bearing(): + """A single-block body satisfies both arms; smart-union ties resolve leftmost.""" + assert get_args(methods.MONOLITH_RESULTS["sampling/createMessage"]) == ( + types.CreateMessageResult, + types.CreateMessageResultWithTools, + ) + single_block: dict[str, Any] = {"role": "assistant", "content": {"type": "text", "text": "hi"}, "model": "m"} + through_row = methods.parse_client_result("sampling/createMessage", "2025-11-25", single_block) + assert type(through_row) is types.CreateMessageResult + reversed_union = pydantic.TypeAdapter[Any](types.CreateMessageResultWithTools | types.CreateMessageResult) + assert type(reversed_union.validate_python(single_block)) is types.CreateMessageResultWithTools + + array_body: dict[str, Any] = {"role": "assistant", "content": [{"type": "text", "text": "hi"}], "model": "m"} + tool_use_body: dict[str, Any] = { + "role": "assistant", + "content": {"type": "tool_use", "name": "t", "id": "c1", "input": {}}, + "model": "m", + } + for body in (array_body, tool_use_body): + parsed = methods.parse_client_result("sampling/createMessage", "2025-11-25", body) + assert type(parsed) is types.CreateMessageResultWithTools + + +def test_importing_the_module_builds_no_adapters_and_identical_rows_share_one(): + # Execute a fresh copy so the cache assertion is order-independent. + spec = importlib.util.find_spec("mcp.types.methods") + assert spec is not None and spec.loader is not None + fresh = importlib.util.module_from_spec(spec) + spec.loader.exec_module(fresh) + assert fresh._adapter.cache_info().currsize == 0 + fresh.parse_server_result("ping", "2025-11-25", {}) + assert fresh._adapter.cache_info().currsize == 2 + # Identical row values at another version: no new adapters. + fresh.parse_server_result("ping", "2024-11-05", {}) + assert fresh._adapter.cache_info().currsize == 2 diff --git a/tests/types/test_parity.py b/tests/types/test_parity.py new file mode 100644 index 0000000000..9212d038dc --- /dev/null +++ b/tests/types/test_parity.py @@ -0,0 +1,192 @@ +"""Assert every per-version surface model's wire fields are a subset of its `mcp.types` superset counterpart.""" + +from __future__ import annotations + +import inspect +from types import ModuleType + +import pytest +from pydantic import BaseModel + +import mcp.types as monolith +import mcp.types._types as _types +import mcp.types.v2025_11_25 as v2025_11_25 +import mcp.types.v2026_07_28 as v2026_07_28 + +SURFACES: tuple[ModuleType, ...] = (v2025_11_25, v2026_07_28) + +# Envelope fields the monolith models on `mcp.types.jsonrpc` instead of on each request/notification. +ENVELOPE_FIELDS: frozenset[str] = frozenset({"jsonrpc", "id"}) + +# Surface classes whose monolith counterpart has a different name (key: "."). +NAME_MAP: dict[str, type[BaseModel]] = { + # v2025_11_25 + "v2025_11_25.Argument": monolith.CompletionArgument, + "v2025_11_25.Context": monolith.CompletionContext, + "v2025_11_25.Data": monolith.ElicitationRequiredErrorData, + "v2025_11_25.Elicitation": monolith.ElicitationCapability, + "v2025_11_25.Elicitation1": _types.TasksElicitationCapability, + "v2025_11_25.ElicitationCompleteNotification": monolith.ElicitCompleteNotification, + "v2025_11_25.ElicitationCompleteNotificationParams": monolith.ElicitCompleteNotificationParams, + "v2025_11_25.Error": monolith.ErrorData, + "v2025_11_25.JSONRPCErrorResponse": monolith.JSONRPCError, + "v2025_11_25.JSONRPCResultResponse": monolith.JSONRPCResponse, + "v2025_11_25.Prompts": monolith.PromptsCapability, + "v2025_11_25.Requests": _types.ClientTasksRequestsCapability, + "v2025_11_25.Requests1": _types.ServerTasksRequestsCapability, + "v2025_11_25.Resources": monolith.ResourcesCapability, + "v2025_11_25.Roots": monolith.RootsCapability, + "v2025_11_25.Sampling": monolith.SamplingCapability, + "v2025_11_25.Sampling1": _types.TasksSamplingCapability, + "v2025_11_25.Tasks": _types.ClientTasksCapability, + "v2025_11_25.Tasks1": _types.ServerTasksCapability, + "v2025_11_25.Tools": _types.TasksToolsCapability, + "v2025_11_25.Tools1": monolith.ToolsCapability, + # v2026_07_28 + "v2026_07_28.Argument": monolith.CompletionArgument, + "v2026_07_28.Context": monolith.CompletionContext, + "v2026_07_28.Data": monolith.MissingRequiredClientCapabilityErrorData, + "v2026_07_28.Data1": monolith.UnsupportedProtocolVersionErrorData, + "v2026_07_28.Elicitation": monolith.ElicitationCapability, + "v2026_07_28.ElicitationCompleteNotification": monolith.ElicitCompleteNotification, + "v2026_07_28.ElicitationCompleteNotificationParams": monolith.ElicitCompleteNotificationParams, + "v2026_07_28.Error": monolith.ErrorData, + "v2026_07_28.JSONRPCErrorResponse": monolith.JSONRPCError, + "v2026_07_28.JSONRPCResultResponse": monolith.JSONRPCResponse, + "v2026_07_28.Prompts": monolith.PromptsCapability, + "v2026_07_28.Resources": monolith.ResourcesCapability, + "v2026_07_28.Sampling": monolith.SamplingCapability, + "v2026_07_28.Tools": monolith.ToolsCapability, +} + +# Surface classes with no monolith equivalent (envelope wrappers, JSON-Schema fragments modelled as `dict`). +SKIP: frozenset[str] = frozenset( + { + # v2025_11_25 + "v2025_11_25.AnyOfItem", + "v2025_11_25.BooleanSchema", + "v2025_11_25.Error1", + "v2025_11_25.Icons", + "v2025_11_25.InputSchema", + "v2025_11_25.Items", + "v2025_11_25.Items1", + "v2025_11_25.LegacyTitledEnumSchema", + "v2025_11_25.Meta", + "v2025_11_25.NumberSchema", + "v2025_11_25.OneOfItem", + "v2025_11_25.OutputSchema", + "v2025_11_25.RequestedSchema", + "v2025_11_25.ResourceRequestParams", + "v2025_11_25.StringSchema", + "v2025_11_25.TaskAugmentedRequestParams", + "v2025_11_25.TitledMultiSelectEnumSchema", + "v2025_11_25.TitledSingleSelectEnumSchema", + "v2025_11_25.URLElicitationRequiredError", + "v2025_11_25.UntitledMultiSelectEnumSchema", + "v2025_11_25.UntitledSingleSelectEnumSchema", + # v2026_07_28 + "v2026_07_28.AnyOfItem", + "v2026_07_28.BooleanSchema", + "v2026_07_28.CallToolResultResponse", + "v2026_07_28.CompleteResultResponse", + "v2026_07_28.DiscoverResultResponse", + "v2026_07_28.Error1", + "v2026_07_28.Error2", + "v2026_07_28.GetPromptResultResponse", + "v2026_07_28.Icons", + "v2026_07_28.InputSchema", + "v2026_07_28.InternalError", + "v2026_07_28.InvalidParamsError", + "v2026_07_28.InvalidRequestError", + "v2026_07_28.Items", + "v2026_07_28.Items1", + "v2026_07_28.LegacyTitledEnumSchema", + "v2026_07_28.ListPromptsResultResponse", + "v2026_07_28.ListResourceTemplatesResultResponse", + "v2026_07_28.ListResourcesResultResponse", + "v2026_07_28.ListToolsResultResponse", + "v2026_07_28.MetaObject", + "v2026_07_28.MethodNotFoundError", + "v2026_07_28.MissingRequiredClientCapabilityError", + "v2026_07_28.NumberSchema", + "v2026_07_28.OneOfItem", + "v2026_07_28.OutputSchema", + "v2026_07_28.ParseError", + "v2026_07_28.ReadResourceResultResponse", + "v2026_07_28.RequestMetaObject", + "v2026_07_28.RequestedSchema", + "v2026_07_28.ResourceRequestParams", + "v2026_07_28.StringSchema", + "v2026_07_28.TitledMultiSelectEnumSchema", + "v2026_07_28.TitledSingleSelectEnumSchema", + "v2026_07_28.UnsupportedProtocolVersionError", + "v2026_07_28.UntitledMultiSelectEnumSchema", + "v2026_07_28.UntitledSingleSelectEnumSchema", + } +) + +# Intentional gaps: (surface class, wire alias) -> reason the monolith omits the field. +_RESULT_TYPE_REASON = "resultType is declared on each concrete Result subclass, not the base" +FIELD_EXCEPTIONS: dict[tuple[type[BaseModel], str], str] = { + (v2026_07_28.Result, "resultType"): _RESULT_TYPE_REASON, + (v2026_07_28.PaginatedResult, "resultType"): _RESULT_TYPE_REASON, + (v2026_07_28.CacheableResult, "resultType"): _RESULT_TYPE_REASON, +} + + +def _wire_aliases(model: type[BaseModel]) -> set[str]: + return {field.alias or name for name, field in model.model_fields.items()} + + +def _surface_classes(module: ModuleType) -> list[tuple[str, type[BaseModel]]]: + tail = module.__name__.rsplit(".", 1)[-1] + out: list[tuple[str, type[BaseModel]]] = [] + for name, obj in vars(module).items(): + if not (inspect.isclass(obj) and issubclass(obj, BaseModel)): + continue + if obj.__module__ != module.__name__ or obj.__name__ != name: + continue # re-export or alias to another model + out.append((f"{tail}.{name}", obj)) + return out + + +def _matched_pairs() -> list[tuple[str, type[BaseModel], type[BaseModel]]]: + pairs: list[tuple[str, type[BaseModel], type[BaseModel]]] = [] + for module in SURFACES: + for qualname, surface_cls in _surface_classes(module): + if qualname in SKIP: + continue + mono_cls = ( + NAME_MAP.get(qualname) + or getattr(monolith, surface_cls.__name__, None) + or getattr(_types, surface_cls.__name__, None) + ) + assert isinstance(mono_cls, type) and issubclass(mono_cls, BaseModel), qualname + pairs.append((qualname, surface_cls, mono_cls)) + return pairs + + +@pytest.mark.parametrize( + "qualname,surface_cls,mono_cls", _matched_pairs(), ids=lambda v: v if isinstance(v, str) else "" +) +def test_monolith_is_superset_of_surface_fields( + qualname: str, surface_cls: type[BaseModel], mono_cls: type[BaseModel] +) -> None: + surface_fields = _wire_aliases(surface_cls) - ENVELOPE_FIELDS + excused = {alias for (cls, alias) in FIELD_EXCEPTIONS if cls is surface_cls} + missing = surface_fields - _wire_aliases(mono_cls) - excused + assert not missing, f"{qualname}: monolith {mono_cls.__name__} missing wire fields {sorted(missing)}" + + +def test_every_surface_class_is_accounted_for() -> None: + monolith_models = { + name + for name, obj in (vars(monolith) | vars(_types)).items() + if inspect.isclass(obj) and issubclass(obj, BaseModel) + } + surface = {q: cls.__name__ for module in SURFACES for q, cls in _surface_classes(module)} + auto_matched = {q for q, name in surface.items() if name in monolith_models} + unmapped = surface.keys() - auto_matched - NAME_MAP.keys() - SKIP + assert not unmapped, f"surface classes with no mapping: {sorted(unmapped)}" + stale = (NAME_MAP.keys() | SKIP) - surface.keys() + assert not stale, f"stale NAME_MAP/SKIP entries: {sorted(stale)}" diff --git a/tests/types/test_wire_frames.py b/tests/types/test_wire_frames.py new file mode 100644 index 0000000000..4badda6c31 --- /dev/null +++ b/tests/types/test_wire_frames.py @@ -0,0 +1,87 @@ +"""Snapshot pins for outbound JSON-RPC frames; a diff is a wire-visible change needing a deliberate decision.""" + +from typing import Any + +from inline_snapshot import snapshot +from pydantic import BaseModel + +from mcp.types import ( + METHOD_NOT_FOUND, + CallToolRequest, + CallToolRequestParams, + CallToolResult, + EmptyResult, + ErrorData, + InputRequiredResult, + JSONRPCError, + JSONRPCNotification, + JSONRPCRequest, + JSONRPCResponse, + ListRootsRequest, + ListToolsResult, + ProgressNotification, + ProgressNotificationParams, + TextContent, + Tool, +) + + +def _body(model: BaseModel) -> dict[str, Any]: + """Mirror the session layer's outbound payload dump.""" + return model.model_dump(by_alias=True, mode="json", exclude_none=True) + + +def _frame(envelope: BaseModel) -> str: + """Mirror the transports' frame serialization.""" + return envelope.model_dump_json(by_alias=True, exclude_unset=True) + + +def test_request_frame_carries_the_envelope_and_the_dumped_request_body(): + request = CallToolRequest(params=CallToolRequestParams(name="echo", arguments={"text": "hi"})) + frame = JSONRPCRequest(jsonrpc="2.0", id=1, **_body(request)) + assert _frame(frame) == snapshot( + '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"echo","arguments":{"text":"hi"}}}' + ) + + +def test_notification_frame_has_no_id_and_carries_the_dumped_params(): + notification = ProgressNotification(params=ProgressNotificationParams(progress_token="t1", progress=0.5)) + frame = JSONRPCNotification(jsonrpc="2.0", **_body(notification)) + assert _frame(frame) == snapshot( + '{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"t1","progress":0.5}}' + ) + + +def test_non_empty_result_frame_always_dumps_result_type_complete(): + result = CallToolResult(content=[TextContent(text="ok")]) + frame = JSONRPCResponse(jsonrpc="2.0", id=1, result=_body(result)) + assert _frame(frame) == snapshot( + '{"jsonrpc":"2.0","id":1,"result":{"content":[{"type":"text","text":"ok"}],"isError":false,"resultType":"complete"}}' + ) + + +def test_cacheable_list_result_frame_always_dumps_its_caching_directives(): + result = ListToolsResult(tools=[Tool(name="echo", input_schema={"type": "object"})]) + frame = JSONRPCResponse(jsonrpc="2.0", id=2, result=_body(result)) + assert _frame(frame) == snapshot( + '{"jsonrpc":"2.0","id":2,"result":{"ttlMs":0,"cacheScope":"private","tools":[{"name":"echo","inputSchema":{"type":"object"}}],"resultType":"complete"}}' + ) + + +def test_empty_result_frame_dumps_an_empty_result_object(): + """Deployed peers reject extra keys on empty results, so the SDK omits resultType here.""" + frame = JSONRPCResponse(jsonrpc="2.0", id=3, result=_body(EmptyResult())) + assert _frame(frame) == snapshot('{"jsonrpc":"2.0","id":3,"result":{}}') + + +def test_input_required_result_frame_carries_the_tag_and_the_embedded_requests(): + result = InputRequiredResult(input_requests={"r1": ListRootsRequest()}, request_state="s1") + frame = JSONRPCResponse(jsonrpc="2.0", id=4, result=_body(result)) + assert _frame(frame) == snapshot( + '{"jsonrpc":"2.0","id":4,"result":{"resultType":"input_required","inputRequests":{"r1":{"method":"roots/list"}},"requestState":"s1"}}' + ) + + +def test_error_frame_wraps_error_data_in_the_jsonrpc_envelope(): + frame = JSONRPCError(jsonrpc="2.0", id=5, error=ErrorData(code=METHOD_NOT_FOUND, message="Method not found")) + assert _frame(frame) == snapshot('{"jsonrpc":"2.0","id":5,"error":{"code":-32601,"message":"Method not found"}}') From 408c56e24d769679862b8bccad4774f98d5e53bd Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Mon, 15 Jun 2026 17:07:07 +0000 Subject: [PATCH 2/7] Generate per-version packages from vendored schemas; add --check to CI Vendor schema/2025-11-25.json and schema/2026-07-28.json (pinned to spec SHA 6d441518), restore scripts/gen_surface_types.py to regenerate both src/mcp/types/v* packages from them, and run --check in CI so the committed files cannot drift from the schemas. --- .github/workflows/shared.yml | 5 + pyproject.toml | 1 + schema/2025-11-25.json | 4057 +++++++++++++++++++++++++ schema/2026-07-28.json | 3873 +++++++++++++++++++++++ schema/PINNED.json | 14 + schema/README.md | 12 + scripts/gen_surface_types.py | 972 ++++++ src/mcp/types/v2025_11_25/__init__.py | 1646 +++++++++- src/mcp/types/v2026_07_28/__init__.py | 1839 +++++++++-- uv.lock | 85 + 10 files changed, 12130 insertions(+), 374 deletions(-) create mode 100644 schema/2025-11-25.json create mode 100644 schema/2026-07-28.json create mode 100644 schema/PINNED.json create mode 100644 schema/README.md create mode 100644 scripts/gen_surface_types.py diff --git a/.github/workflows/shared.yml b/.github/workflows/shared.yml index 852ffc3850..efea5a7463 100644 --- a/.github/workflows/shared.yml +++ b/.github/workflows/shared.yml @@ -30,6 +30,11 @@ jobs: env: SKIP: no-commit-to-branch,readme-v1-frozen + - name: Surface types match vendored schema + run: | + uv sync --group codegen --frozen + uv run --group codegen python scripts/gen_surface_types.py --check + # TODO(Max): Drop this in v2. Deliberate updates (e.g. the v2 status # banner) go through the 'override-readme-freeze' label. - name: Check README.md is not modified diff --git a/pyproject.toml b/pyproject.toml index 749af47ab6..2ea5cf1a08 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,6 +104,7 @@ docs = [ "mkdocs-material[imaging]>=9.5.45", "mkdocstrings-python>=2.0.1", ] +codegen = ["datamodel-code-generator==0.57.0"] [build-system] requires = ["hatchling", "uv-dynamic-versioning"] diff --git a/schema/2025-11-25.json b/schema/2025-11-25.json new file mode 100644 index 0000000000..4956c09fef --- /dev/null +++ b/schema/2025-11-25.json @@ -0,0 +1,4057 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "description": "Describes who the intended audience of this object or data is.\n\nIt can include multiple entries to indicate content useful for multiple audiences (e.g., `[\"user\", \"assistant\"]`).", + "items": { + "$ref": "#/$defs/Role" + }, + "type": "array" + }, + "lastModified": { + "description": "The moment the resource was last modified, as an ISO 8601 formatted string.\n\nShould be an ISO 8601 formatted string (e.g., \"2025-01-12T15:00:58Z\").\n\nExamples: last activity timestamp in an open file, timestamp when the resource\nwas attached, etc.", + "type": "string" + }, + "priority": { + "description": "Describes how important this data is for operating the server.\n\nA value of 1 means \"most important,\" and indicates that the data is\neffectively required, while 0 means \"least important,\" and indicates that\nthe data is entirely optional.", + "maximum": 1, + "minimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "data": { + "description": "The base64-encoded audio data.", + "format": "byte", + "type": "string" + }, + "mimeType": { + "description": "The MIME type of the audio. Different providers may support different audio types.", + "type": "string" + }, + "type": { + "const": "audio", + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BaseMetadata": { + "description": "Base interface for metadata with name (identifier) and title (display name) properties.", + "properties": { + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "blob": { + "description": "A base64-encoded string representing the binary data of the item.", + "format": "byte", + "type": "string" + }, + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "BooleanSchema": { + "properties": { + "default": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "const": "boolean", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "CallToolRequest": { + "description": "Used by the client to invoke a tool provided by the server.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tools/call", + "type": "string" + }, + "params": { + "$ref": "#/$defs/CallToolRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "CallToolRequestParams": { + "description": "Parameters for a `tools/call` request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "arguments": { + "additionalProperties": {}, + "description": "Arguments to use for the tool call.", + "type": "object" + }, + "name": { + "description": "The name of the tool.", + "type": "string" + }, + "task": { + "$ref": "#/$defs/TaskMetadata", + "description": "If specified, the caller is requesting task-augmented execution for this request.\nThe request will return a CreateTaskResult immediately, and the actual result can be\nretrieved later via tasks/result.\n\nTask augmentation is subject to capability negotiation - receivers MUST declare support\nfor task augmentation of specific request types in their capabilities." + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "CallToolResult": { + "description": "The server's response to a tool call.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "content": { + "description": "A list of content objects that represent the unstructured result of the tool call.", + "items": { + "$ref": "#/$defs/ContentBlock" + }, + "type": "array" + }, + "isError": { + "description": "Whether the tool call ended in an error.\n\nIf not set, this is assumed to be false (the call was successful).\n\nAny errors that originate from the tool SHOULD be reported inside the result\nobject, with `isError` set to true, _not_ as an MCP protocol-level error\nresponse. Otherwise, the LLM would not be able to see that an error occurred\nand self-correct.\n\nHowever, any errors in _finding_ the tool, an error indicating that the\nserver does not support tool calls, or any other exceptional conditions,\nshould be reported as an MCP error response.", + "type": "boolean" + }, + "structuredContent": { + "additionalProperties": {}, + "description": "An optional JSON object that represents the structured result of the tool call.", + "type": "object" + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "CancelTaskRequest": { + "description": "A request to cancel a task.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tasks/cancel", + "type": "string" + }, + "params": { + "properties": { + "taskId": { + "description": "The task identifier to cancel.", + "type": "string" + } + }, + "required": [ + "taskId" + ], + "type": "object" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "CancelTaskResult": { + "allOf": [ + { + "$ref": "#/$defs/Result" + }, + { + "$ref": "#/$defs/Task" + } + ], + "description": "The response to a tasks/cancel request." + }, + "CancelledNotification": { + "description": "This notification can be sent by either side to indicate that it is cancelling a previously-issued request.\n\nThe request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished.\n\nThis notification indicates that the result will be unused, so any associated processing SHOULD cease.\n\nA client MUST NOT attempt to cancel its `initialize` request.\n\nFor task cancellation, use the `tasks/cancel` request instead of this notification.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/cancelled", + "type": "string" + }, + "params": { + "$ref": "#/$defs/CancelledNotificationParams" + } + }, + "required": [ + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "CancelledNotificationParams": { + "description": "Parameters for a `notifications/cancelled` notification.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "reason": { + "description": "An optional string describing the reason for the cancellation. This MAY be logged or presented to the user.", + "type": "string" + }, + "requestId": { + "$ref": "#/$defs/RequestId", + "description": "The ID of the request to cancel.\n\nThis MUST correspond to the ID of a request previously issued in the same direction.\nThis MUST be provided for cancelling non-task requests.\nThis MUST NOT be used for cancelling tasks (use the `tasks/cancel` request instead)." + } + }, + "type": "object" + }, + "ClientCapabilities": { + "description": "Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.", + "properties": { + "elicitation": { + "description": "Present if the client supports elicitation from the server.", + "properties": { + "form": { + "additionalProperties": true, + "properties": {}, + "type": "object" + }, + "url": { + "additionalProperties": true, + "properties": {}, + "type": "object" + } + }, + "type": "object" + }, + "experimental": { + "additionalProperties": { + "additionalProperties": true, + "properties": {}, + "type": "object" + }, + "description": "Experimental, non-standard capabilities that the client supports.", + "type": "object" + }, + "roots": { + "description": "Present if the client supports listing roots.", + "properties": { + "listChanged": { + "description": "Whether the client supports notifications for changes to the roots list.", + "type": "boolean" + } + }, + "type": "object" + }, + "sampling": { + "description": "Present if the client supports sampling from an LLM.", + "properties": { + "context": { + "additionalProperties": true, + "description": "Whether the client supports context inclusion via includeContext parameter.\nIf not declared, servers SHOULD only use `includeContext: \"none\"` (or omit it).", + "properties": {}, + "type": "object" + }, + "tools": { + "additionalProperties": true, + "description": "Whether the client supports tool use via tools and toolChoice parameters.", + "properties": {}, + "type": "object" + } + }, + "type": "object" + }, + "tasks": { + "description": "Present if the client supports task-augmented requests.", + "properties": { + "cancel": { + "additionalProperties": true, + "description": "Whether this client supports tasks/cancel.", + "properties": {}, + "type": "object" + }, + "list": { + "additionalProperties": true, + "description": "Whether this client supports tasks/list.", + "properties": {}, + "type": "object" + }, + "requests": { + "description": "Specifies which request types can be augmented with tasks.", + "properties": { + "elicitation": { + "description": "Task support for elicitation-related requests.", + "properties": { + "create": { + "additionalProperties": true, + "description": "Whether the client supports task-augmented elicitation/create requests.", + "properties": {}, + "type": "object" + } + }, + "type": "object" + }, + "sampling": { + "description": "Task support for sampling-related requests.", + "properties": { + "createMessage": { + "additionalProperties": true, + "description": "Whether the client supports task-augmented sampling/createMessage requests.", + "properties": {}, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "ClientNotification": { + "anyOf": [ + { + "$ref": "#/$defs/CancelledNotification" + }, + { + "$ref": "#/$defs/InitializedNotification" + }, + { + "$ref": "#/$defs/ProgressNotification" + }, + { + "$ref": "#/$defs/TaskStatusNotification" + }, + { + "$ref": "#/$defs/RootsListChangedNotification" + } + ] + }, + "ClientRequest": { + "anyOf": [ + { + "$ref": "#/$defs/InitializeRequest" + }, + { + "$ref": "#/$defs/PingRequest" + }, + { + "$ref": "#/$defs/ListResourcesRequest" + }, + { + "$ref": "#/$defs/ListResourceTemplatesRequest" + }, + { + "$ref": "#/$defs/ReadResourceRequest" + }, + { + "$ref": "#/$defs/SubscribeRequest" + }, + { + "$ref": "#/$defs/UnsubscribeRequest" + }, + { + "$ref": "#/$defs/ListPromptsRequest" + }, + { + "$ref": "#/$defs/GetPromptRequest" + }, + { + "$ref": "#/$defs/ListToolsRequest" + }, + { + "$ref": "#/$defs/CallToolRequest" + }, + { + "$ref": "#/$defs/GetTaskRequest" + }, + { + "$ref": "#/$defs/GetTaskPayloadRequest" + }, + { + "$ref": "#/$defs/CancelTaskRequest" + }, + { + "$ref": "#/$defs/ListTasksRequest" + }, + { + "$ref": "#/$defs/SetLevelRequest" + }, + { + "$ref": "#/$defs/CompleteRequest" + } + ] + }, + "ClientResult": { + "anyOf": [ + { + "$ref": "#/$defs/Result" + }, + { + "$ref": "#/$defs/GetTaskResult", + "description": "The response to a tasks/get request." + }, + { + "$ref": "#/$defs/GetTaskPayloadResult" + }, + { + "$ref": "#/$defs/CancelTaskResult", + "description": "The response to a tasks/cancel request." + }, + { + "$ref": "#/$defs/ListTasksResult" + }, + { + "$ref": "#/$defs/CreateMessageResult" + }, + { + "$ref": "#/$defs/ListRootsResult" + }, + { + "$ref": "#/$defs/ElicitResult" + } + ] + }, + "CompleteRequest": { + "description": "A request from the client to the server, to ask for completion options.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "completion/complete", + "type": "string" + }, + "params": { + "$ref": "#/$defs/CompleteRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "CompleteRequestParams": { + "description": "Parameters for a `completion/complete` request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "argument": { + "description": "The argument's information", + "properties": { + "name": { + "description": "The name of the argument", + "type": "string" + }, + "value": { + "description": "The value of the argument to use for completion matching.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "context": { + "description": "Additional, optional context for completions", + "properties": { + "arguments": { + "additionalProperties": { + "type": "string" + }, + "description": "Previously-resolved variables in a URI template or prompt.", + "type": "object" + } + }, + "type": "object" + }, + "ref": { + "anyOf": [ + { + "$ref": "#/$defs/PromptReference" + }, + { + "$ref": "#/$defs/ResourceTemplateReference" + } + ] + } + }, + "required": [ + "argument", + "ref" + ], + "type": "object" + }, + "CompleteResult": { + "description": "The server's response to a completion/complete request", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "completion": { + "properties": { + "hasMore": { + "description": "Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.", + "type": "boolean" + }, + "total": { + "description": "The total number of completion options available. This can exceed the number of values actually sent in the response.", + "type": "integer" + }, + "values": { + "description": "An array of completion values. Must not exceed 100 items.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "values" + ], + "type": "object" + } + }, + "required": [ + "completion" + ], + "type": "object" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/$defs/TextContent" + }, + { + "$ref": "#/$defs/ImageContent" + }, + { + "$ref": "#/$defs/AudioContent" + }, + { + "$ref": "#/$defs/ResourceLink" + }, + { + "$ref": "#/$defs/EmbeddedResource" + } + ] + }, + "CreateMessageRequest": { + "description": "A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "sampling/createMessage", + "type": "string" + }, + "params": { + "$ref": "#/$defs/CreateMessageRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "CreateMessageRequestParams": { + "description": "Parameters for a `sampling/createMessage` request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "includeContext": { + "description": "A request to include context from one or more MCP servers (including the caller), to be attached to the prompt.\nThe client MAY ignore this request.\n\nDefault is \"none\". Values \"thisServer\" and \"allServers\" are soft-deprecated. Servers SHOULD only use these values if the client\ndeclares ClientCapabilities.sampling.context. These values may be removed in future spec releases.", + "enum": [ + "allServers", + "none", + "thisServer" + ], + "type": "string" + }, + "maxTokens": { + "description": "The requested maximum number of tokens to sample (to prevent runaway completions).\n\nThe client MAY choose to sample fewer tokens than the requested maximum.", + "type": "integer" + }, + "messages": { + "items": { + "$ref": "#/$defs/SamplingMessage" + }, + "type": "array" + }, + "metadata": { + "additionalProperties": true, + "description": "Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific.", + "properties": {}, + "type": "object" + }, + "modelPreferences": { + "$ref": "#/$defs/ModelPreferences", + "description": "The server's preferences for which model to select. The client MAY ignore these preferences." + }, + "stopSequences": { + "items": { + "type": "string" + }, + "type": "array" + }, + "systemPrompt": { + "description": "An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt.", + "type": "string" + }, + "task": { + "$ref": "#/$defs/TaskMetadata", + "description": "If specified, the caller is requesting task-augmented execution for this request.\nThe request will return a CreateTaskResult immediately, and the actual result can be\nretrieved later via tasks/result.\n\nTask augmentation is subject to capability negotiation - receivers MUST declare support\nfor task augmentation of specific request types in their capabilities." + }, + "temperature": { + "type": "number" + }, + "toolChoice": { + "$ref": "#/$defs/ToolChoice", + "description": "Controls how the model uses tools.\nThe client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared.\nDefault is `{ mode: \"auto\" }`." + }, + "tools": { + "description": "Tools that the model may use during generation.\nThe client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared.", + "items": { + "$ref": "#/$defs/Tool" + }, + "type": "array" + } + }, + "required": [ + "maxTokens", + "messages" + ], + "type": "object" + }, + "CreateMessageResult": { + "description": "The client's response to a sampling/createMessage request from the server.\nThe client should inform the user before returning the sampled message, to allow them\nto inspect the response (human in the loop) and decide whether to allow the server to see it.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "content": { + "anyOf": [ + { + "$ref": "#/$defs/TextContent" + }, + { + "$ref": "#/$defs/ImageContent" + }, + { + "$ref": "#/$defs/AudioContent" + }, + { + "$ref": "#/$defs/ToolUseContent" + }, + { + "$ref": "#/$defs/ToolResultContent" + }, + { + "items": { + "$ref": "#/$defs/SamplingMessageContentBlock" + }, + "type": "array" + } + ] + }, + "model": { + "description": "The name of the model that generated the message.", + "type": "string" + }, + "role": { + "$ref": "#/$defs/Role" + }, + "stopReason": { + "description": "The reason why sampling stopped, if known.\n\nStandard values:\n- \"endTurn\": Natural end of the assistant's turn\n- \"stopSequence\": A stop sequence was encountered\n- \"maxTokens\": Maximum token limit was reached\n- \"toolUse\": The model wants to use one or more tools\n\nThis field is an open string to allow for provider-specific stop reasons.", + "type": "string" + } + }, + "required": [ + "content", + "model", + "role" + ], + "type": "object" + }, + "CreateTaskResult": { + "description": "A response to a task-augmented request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "task": { + "$ref": "#/$defs/Task" + } + }, + "required": [ + "task" + ], + "type": "object" + }, + "Cursor": { + "description": "An opaque token used to represent a cursor for pagination.", + "type": "string" + }, + "ElicitRequest": { + "description": "A request from the server to elicit additional information from the user via the client.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "elicitation/create", + "type": "string" + }, + "params": { + "$ref": "#/$defs/ElicitRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "ElicitRequestFormParams": { + "description": "The parameters for a request to elicit non-sensitive information from the user via a form in the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "message": { + "description": "The message to present to the user describing what information is being requested.", + "type": "string" + }, + "mode": { + "const": "form", + "description": "The elicitation mode.", + "type": "string" + }, + "requestedSchema": { + "description": "A restricted subset of JSON Schema.\nOnly top-level properties are allowed, without nesting.", + "properties": { + "$schema": { + "type": "string" + }, + "properties": { + "additionalProperties": { + "$ref": "#/$defs/PrimitiveSchemaDefinition" + }, + "type": "object" + }, + "required": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "const": "object", + "type": "string" + } + }, + "required": [ + "properties", + "type" + ], + "type": "object" + }, + "task": { + "$ref": "#/$defs/TaskMetadata", + "description": "If specified, the caller is requesting task-augmented execution for this request.\nThe request will return a CreateTaskResult immediately, and the actual result can be\nretrieved later via tasks/result.\n\nTask augmentation is subject to capability negotiation - receivers MUST declare support\nfor task augmentation of specific request types in their capabilities." + } + }, + "required": [ + "message", + "requestedSchema" + ], + "type": "object" + }, + "ElicitRequestParams": { + "anyOf": [ + { + "$ref": "#/$defs/ElicitRequestURLParams" + }, + { + "$ref": "#/$defs/ElicitRequestFormParams" + } + ], + "description": "The parameters for a request to elicit additional information from the user via the client." + }, + "ElicitRequestURLParams": { + "description": "The parameters for a request to elicit information from the user via a URL in the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "elicitationId": { + "description": "The ID of the elicitation, which must be unique within the context of the server.\nThe client MUST treat this ID as an opaque value.", + "type": "string" + }, + "message": { + "description": "The message to present to the user explaining why the interaction is needed.", + "type": "string" + }, + "mode": { + "const": "url", + "description": "The elicitation mode.", + "type": "string" + }, + "task": { + "$ref": "#/$defs/TaskMetadata", + "description": "If specified, the caller is requesting task-augmented execution for this request.\nThe request will return a CreateTaskResult immediately, and the actual result can be\nretrieved later via tasks/result.\n\nTask augmentation is subject to capability negotiation - receivers MUST declare support\nfor task augmentation of specific request types in their capabilities." + }, + "url": { + "description": "The URL that the user should navigate to.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "elicitationId", + "message", + "mode", + "url" + ], + "type": "object" + }, + "ElicitResult": { + "description": "The client's response to an elicitation request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "action": { + "description": "The user action in response to the elicitation.\n- \"accept\": User submitted the form/confirmed the action\n- \"decline\": User explicitly decline the action\n- \"cancel\": User dismissed without making an explicit choice", + "enum": [ + "accept", + "cancel", + "decline" + ], + "type": "string" + }, + "content": { + "additionalProperties": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": [ + "string", + "integer", + "boolean" + ] + } + ] + }, + "description": "The submitted form data, only present when action is \"accept\" and mode was \"form\".\nContains values matching the requested schema.\nOmitted for out-of-band mode responses.", + "type": "object" + } + }, + "required": [ + "action" + ], + "type": "object" + }, + "ElicitationCompleteNotification": { + "description": "An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/elicitation/complete", + "type": "string" + }, + "params": { + "properties": { + "elicitationId": { + "description": "The ID of the elicitation that completed.", + "type": "string" + } + }, + "required": [ + "elicitationId" + ], + "type": "object" + } + }, + "required": [ + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit\nof the LLM and/or the user.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "resource": { + "anyOf": [ + { + "$ref": "#/$defs/TextResourceContents" + }, + { + "$ref": "#/$defs/BlobResourceContents" + } + ] + }, + "type": { + "const": "resource", + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmptyResult": { + "$ref": "#/$defs/Result" + }, + "EnumSchema": { + "anyOf": [ + { + "$ref": "#/$defs/UntitledSingleSelectEnumSchema" + }, + { + "$ref": "#/$defs/TitledSingleSelectEnumSchema" + }, + { + "$ref": "#/$defs/UntitledMultiSelectEnumSchema" + }, + { + "$ref": "#/$defs/TitledMultiSelectEnumSchema" + }, + { + "$ref": "#/$defs/LegacyTitledEnumSchema" + } + ] + }, + "Error": { + "properties": { + "code": { + "description": "The error type that occurred.", + "type": "integer" + }, + "data": { + "description": "Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.)." + }, + "message": { + "description": "A short description of the error. The message SHOULD be limited to a concise single sentence.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" + }, + "GetPromptRequest": { + "description": "Used by the client to get a prompt provided by the server.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "prompts/get", + "type": "string" + }, + "params": { + "$ref": "#/$defs/GetPromptRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "GetPromptRequestParams": { + "description": "Parameters for a `prompts/get` request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "arguments": { + "additionalProperties": { + "type": "string" + }, + "description": "Arguments to use for templating the prompt.", + "type": "object" + }, + "name": { + "description": "The name of the prompt or prompt template.", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "GetPromptResult": { + "description": "The server's response to a prompts/get request from the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "description": { + "description": "An optional description for the prompt.", + "type": "string" + }, + "messages": { + "items": { + "$ref": "#/$defs/PromptMessage" + }, + "type": "array" + } + }, + "required": [ + "messages" + ], + "type": "object" + }, + "GetTaskPayloadRequest": { + "description": "A request to retrieve the result of a completed task.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tasks/result", + "type": "string" + }, + "params": { + "properties": { + "taskId": { + "description": "The task identifier to retrieve results for.", + "type": "string" + } + }, + "required": [ + "taskId" + ], + "type": "object" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "GetTaskPayloadResult": { + "additionalProperties": {}, + "description": "The response to a tasks/result request.\nThe structure matches the result type of the original request.\nFor example, a tools/call task would return the CallToolResult structure.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + } + }, + "type": "object" + }, + "GetTaskRequest": { + "description": "A request to retrieve the state of a task.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tasks/get", + "type": "string" + }, + "params": { + "properties": { + "taskId": { + "description": "The task identifier to query.", + "type": "string" + } + }, + "required": [ + "taskId" + ], + "type": "object" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "GetTaskResult": { + "allOf": [ + { + "$ref": "#/$defs/Result" + }, + { + "$ref": "#/$defs/Task" + } + ], + "description": "The response to a tasks/get request." + }, + "Icon": { + "description": "An optionally-sized icon that can be displayed in a user interface.", + "properties": { + "mimeType": { + "description": "Optional MIME type override if the source MIME type is missing or generic.\nFor example: `\"image/png\"`, `\"image/jpeg\"`, or `\"image/svg+xml\"`.", + "type": "string" + }, + "sizes": { + "description": "Optional array of strings that specify sizes at which the icon can be used.\nEach string should be in WxH format (e.g., `\"48x48\"`, `\"96x96\"`) or `\"any\"` for scalable formats like SVG.\n\nIf not provided, the client should assume that the icon can be used at any size.", + "items": { + "type": "string" + }, + "type": "array" + }, + "src": { + "description": "A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a\n`data:` URI with Base64-encoded image data.\n\nConsumers SHOULD takes steps to ensure URLs serving icons are from the\nsame domain as the client/server or a trusted domain.\n\nConsumers SHOULD take appropriate precautions when consuming SVGs as they can contain\nexecutable JavaScript.", + "format": "uri", + "type": "string" + }, + "theme": { + "description": "Optional specifier for the theme this icon is designed for. `light` indicates\nthe icon is designed to be used with a light background, and `dark` indicates\nthe icon is designed to be used with a dark background.\n\nIf not provided, the client should assume the icon can be used with any theme.", + "enum": [ + "dark", + "light" + ], + "type": "string" + } + }, + "required": [ + "src" + ], + "type": "object" + }, + "Icons": { + "description": "Base interface to add `icons` property.", + "properties": { + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + } + }, + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "data": { + "description": "The base64-encoded image data.", + "format": "byte", + "type": "string" + }, + "mimeType": { + "description": "The MIME type of the image. Different providers may support different image types.", + "type": "string" + }, + "type": { + "const": "image", + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "Implementation": { + "description": "Describes the MCP implementation.", + "properties": { + "description": { + "description": "An optional human-readable description of what this implementation does.\n\nThis can be used by clients or servers to provide context about their purpose\nand capabilities. For example, a server might describe the types of resources\nor tools it provides, while a client might describe its intended use case.", + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + }, + "version": { + "type": "string" + }, + "websiteUrl": { + "description": "An optional URL of the website for this implementation.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + }, + "InitializeRequest": { + "description": "This request is sent from the client to the server when it first connects, asking it to begin initialization.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "initialize", + "type": "string" + }, + "params": { + "$ref": "#/$defs/InitializeRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "InitializeRequestParams": { + "description": "Parameters for an `initialize` request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "capabilities": { + "$ref": "#/$defs/ClientCapabilities" + }, + "clientInfo": { + "$ref": "#/$defs/Implementation" + }, + "protocolVersion": { + "description": "The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well.", + "type": "string" + } + }, + "required": [ + "capabilities", + "clientInfo", + "protocolVersion" + ], + "type": "object" + }, + "InitializeResult": { + "description": "After receiving an initialize request from the client, the server sends this response.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "capabilities": { + "$ref": "#/$defs/ServerCapabilities" + }, + "instructions": { + "description": "Instructions describing how to use the server and its features.\n\nThis can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a \"hint\" to the model. For example, this information MAY be added to the system prompt.", + "type": "string" + }, + "protocolVersion": { + "description": "The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect.", + "type": "string" + }, + "serverInfo": { + "$ref": "#/$defs/Implementation" + } + }, + "required": [ + "capabilities", + "protocolVersion", + "serverInfo" + ], + "type": "object" + }, + "InitializedNotification": { + "description": "This notification is sent from the client to the server after initialization has finished.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/initialized", + "type": "string" + }, + "params": { + "$ref": "#/$defs/NotificationParams" + } + }, + "required": [ + "jsonrpc", + "method" + ], + "type": "object" + }, + "JSONRPCErrorResponse": { + "description": "A response to a request that indicates an error occurred.", + "properties": { + "error": { + "$ref": "#/$defs/Error" + }, + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + } + }, + "required": [ + "error", + "jsonrpc" + ], + "type": "object" + }, + "JSONRPCMessage": { + "anyOf": [ + { + "$ref": "#/$defs/JSONRPCRequest" + }, + { + "$ref": "#/$defs/JSONRPCNotification" + }, + { + "$ref": "#/$defs/JSONRPCResultResponse" + }, + { + "$ref": "#/$defs/JSONRPCErrorResponse" + } + ], + "description": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent." + }, + "JSONRPCNotification": { + "description": "A notification which does not expect a response.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "type": "string" + }, + "params": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "jsonrpc", + "method" + ], + "type": "object" + }, + "JSONRPCRequest": { + "description": "A request that expects a response.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "type": "string" + }, + "params": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "id", + "jsonrpc", + "method" + ], + "type": "object" + }, + "JSONRPCResponse": { + "anyOf": [ + { + "$ref": "#/$defs/JSONRPCResultResponse" + }, + { + "$ref": "#/$defs/JSONRPCErrorResponse" + } + ], + "description": "A response to a request, containing either the result or error." + }, + "JSONRPCResultResponse": { + "description": "A successful (non-error) response to a request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "$ref": "#/$defs/Result" + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, + "LegacyTitledEnumSchema": { + "description": "Use TitledSingleSelectEnumSchema instead.\nThis interface will be removed in a future version.", + "properties": { + "default": { + "type": "string" + }, + "description": { + "type": "string" + }, + "enum": { + "items": { + "type": "string" + }, + "type": "array" + }, + "enumNames": { + "description": "(Legacy) Display names for enum values.\nNon-standard according to JSON schema 2020-12.", + "items": { + "type": "string" + }, + "type": "array" + }, + "title": { + "type": "string" + }, + "type": { + "const": "string", + "type": "string" + } + }, + "required": [ + "enum", + "type" + ], + "type": "object" + }, + "ListPromptsRequest": { + "description": "Sent from the client to request a list of prompts and prompt templates the server has.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "prompts/list", + "type": "string" + }, + "params": { + "$ref": "#/$defs/PaginatedRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method" + ], + "type": "object" + }, + "ListPromptsResult": { + "description": "The server's response to a prompts/list request from the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "prompts": { + "items": { + "$ref": "#/$defs/Prompt" + }, + "type": "array" + } + }, + "required": [ + "prompts" + ], + "type": "object" + }, + "ListResourceTemplatesRequest": { + "description": "Sent from the client to request a list of resource templates the server has.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "resources/templates/list", + "type": "string" + }, + "params": { + "$ref": "#/$defs/PaginatedRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method" + ], + "type": "object" + }, + "ListResourceTemplatesResult": { + "description": "The server's response to a resources/templates/list request from the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "resourceTemplates": { + "items": { + "$ref": "#/$defs/ResourceTemplate" + }, + "type": "array" + } + }, + "required": [ + "resourceTemplates" + ], + "type": "object" + }, + "ListResourcesRequest": { + "description": "Sent from the client to request a list of resources the server has.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "resources/list", + "type": "string" + }, + "params": { + "$ref": "#/$defs/PaginatedRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method" + ], + "type": "object" + }, + "ListResourcesResult": { + "description": "The server's response to a resources/list request from the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "resources": { + "items": { + "$ref": "#/$defs/Resource" + }, + "type": "array" + } + }, + "required": [ + "resources" + ], + "type": "object" + }, + "ListRootsRequest": { + "description": "Sent from the server to request a list of root URIs from the client. Roots allow\nservers to ask for specific directories or files to operate on. A common example\nfor roots is providing a set of repositories or directories a server should operate\non.\n\nThis request is typically used when the server needs to understand the file system\nstructure or access specific locations that the client has permission to read from.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "roots/list", + "type": "string" + }, + "params": { + "$ref": "#/$defs/RequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method" + ], + "type": "object" + }, + "ListRootsResult": { + "description": "The client's response to a roots/list request from the server.\nThis result contains an array of Root objects, each representing a root directory\nor file that the server can operate on.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "roots": { + "items": { + "$ref": "#/$defs/Root" + }, + "type": "array" + } + }, + "required": [ + "roots" + ], + "type": "object" + }, + "ListTasksRequest": { + "description": "A request to retrieve a list of tasks.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tasks/list", + "type": "string" + }, + "params": { + "$ref": "#/$defs/PaginatedRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method" + ], + "type": "object" + }, + "ListTasksResult": { + "description": "The response to a tasks/list request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "tasks": { + "items": { + "$ref": "#/$defs/Task" + }, + "type": "array" + } + }, + "required": [ + "tasks" + ], + "type": "object" + }, + "ListToolsRequest": { + "description": "Sent from the client to request a list of tools the server has.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tools/list", + "type": "string" + }, + "params": { + "$ref": "#/$defs/PaginatedRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method" + ], + "type": "object" + }, + "ListToolsResult": { + "description": "The server's response to a tools/list request from the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "tools": { + "items": { + "$ref": "#/$defs/Tool" + }, + "type": "array" + } + }, + "required": [ + "tools" + ], + "type": "object" + }, + "LoggingLevel": { + "description": "The severity of a log message.\n\nThese map to syslog message severities, as specified in RFC-5424:\nhttps://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1", + "enum": [ + "alert", + "critical", + "debug", + "emergency", + "error", + "info", + "notice", + "warning" + ], + "type": "string" + }, + "LoggingMessageNotification": { + "description": "JSONRPCNotification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/message", + "type": "string" + }, + "params": { + "$ref": "#/$defs/LoggingMessageNotificationParams" + } + }, + "required": [ + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "LoggingMessageNotificationParams": { + "description": "Parameters for a `notifications/message` notification.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "data": { + "description": "The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here." + }, + "level": { + "$ref": "#/$defs/LoggingLevel", + "description": "The severity of this log message." + }, + "logger": { + "description": "An optional name of the logger issuing this message.", + "type": "string" + } + }, + "required": [ + "data", + "level" + ], + "type": "object" + }, + "ModelHint": { + "description": "Hints to use for model selection.\n\nKeys not declared here are currently left unspecified by the spec and are up\nto the client to interpret.", + "properties": { + "name": { + "description": "A hint for a model name.\n\nThe client SHOULD treat this as a substring of a model name; for example:\n - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022`\n - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc.\n - `claude` should match any Claude model\n\nThe client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example:\n - `gemini-1.5-flash` could match `claude-3-haiku-20240307`", + "type": "string" + } + }, + "type": "object" + }, + "ModelPreferences": { + "description": "The server's preferences for model selection, requested of the client during sampling.\n\nBecause LLMs can vary along multiple dimensions, choosing the \"best\" model is\nrarely straightforward. Different models excel in different areas—some are\nfaster but less capable, others are more capable but more expensive, and so\non. This interface allows servers to express their priorities across multiple\ndimensions to help clients make an appropriate selection for their use case.\n\nThese preferences are always advisory. The client MAY ignore them. It is also\nup to the client to decide how to interpret these preferences and how to\nbalance them against other considerations.", + "properties": { + "costPriority": { + "description": "How much to prioritize cost when selecting a model. A value of 0 means cost\nis not important, while a value of 1 means cost is the most important\nfactor.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "hints": { + "description": "Optional hints to use for model selection.\n\nIf multiple hints are specified, the client MUST evaluate them in order\n(such that the first match is taken).\n\nThe client SHOULD prioritize these hints over the numeric priorities, but\nMAY still use the priorities to select from ambiguous matches.", + "items": { + "$ref": "#/$defs/ModelHint" + }, + "type": "array" + }, + "intelligencePriority": { + "description": "How much to prioritize intelligence and capabilities when selecting a\nmodel. A value of 0 means intelligence is not important, while a value of 1\nmeans intelligence is the most important factor.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "speedPriority": { + "description": "How much to prioritize sampling speed (latency) when selecting a model. A\nvalue of 0 means speed is not important, while a value of 1 means speed is\nthe most important factor.", + "maximum": 1, + "minimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "MultiSelectEnumSchema": { + "anyOf": [ + { + "$ref": "#/$defs/UntitledMultiSelectEnumSchema" + }, + { + "$ref": "#/$defs/TitledMultiSelectEnumSchema" + } + ] + }, + "Notification": { + "properties": { + "method": { + "type": "string" + }, + "params": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "NotificationParams": { + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + } + }, + "type": "object" + }, + "NumberSchema": { + "properties": { + "default": { + "type": "integer" + }, + "description": { + "type": "string" + }, + "maximum": { + "type": "integer" + }, + "minimum": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "type": { + "enum": [ + "integer", + "number" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "PaginatedRequest": { + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "type": "string" + }, + "params": { + "$ref": "#/$defs/PaginatedRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method" + ], + "type": "object" + }, + "PaginatedRequestParams": { + "description": "Common parameters for paginated requests.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "cursor": { + "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", + "type": "string" + } + }, + "type": "object" + }, + "PaginatedResult": { + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + } + }, + "type": "object" + }, + "PingRequest": { + "description": "A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "ping", + "type": "string" + }, + "params": { + "$ref": "#/$defs/RequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method" + ], + "type": "object" + }, + "PrimitiveSchemaDefinition": { + "anyOf": [ + { + "$ref": "#/$defs/StringSchema" + }, + { + "$ref": "#/$defs/NumberSchema" + }, + { + "$ref": "#/$defs/BooleanSchema" + }, + { + "$ref": "#/$defs/UntitledSingleSelectEnumSchema" + }, + { + "$ref": "#/$defs/TitledSingleSelectEnumSchema" + }, + { + "$ref": "#/$defs/UntitledMultiSelectEnumSchema" + }, + { + "$ref": "#/$defs/TitledMultiSelectEnumSchema" + }, + { + "$ref": "#/$defs/LegacyTitledEnumSchema" + } + ], + "description": "Restricted schema definitions that only allow primitive types\nwithout nested objects or arrays." + }, + "ProgressNotification": { + "description": "An out-of-band notification used to inform the receiver of a progress update for a long-running request.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/progress", + "type": "string" + }, + "params": { + "$ref": "#/$defs/ProgressNotificationParams" + } + }, + "required": [ + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "ProgressNotificationParams": { + "description": "Parameters for a `notifications/progress` notification.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "message": { + "description": "An optional message describing the current progress.", + "type": "string" + }, + "progress": { + "description": "The progress thus far. This should increase every time progress is made, even if the total is unknown.", + "type": "number" + }, + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "The progress token which was given in the initial request, used to associate this notification with the request that is proceeding." + }, + "total": { + "description": "Total number of items to process (or total progress required), if known.", + "type": "number" + } + }, + "required": [ + "progress", + "progressToken" + ], + "type": "object" + }, + "ProgressToken": { + "description": "A progress token, used to associate progress notifications with the original request.", + "type": [ + "string", + "integer" + ] + }, + "Prompt": { + "description": "A prompt or prompt template that the server offers.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "arguments": { + "description": "A list of arguments to use for templating the prompt.", + "items": { + "$ref": "#/$defs/PromptArgument" + }, + "type": "array" + }, + "description": { + "description": "An optional description of what this prompt provides", + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "PromptArgument": { + "description": "Describes an argument that a prompt can accept.", + "properties": { + "description": { + "description": "A human-readable description of the argument.", + "type": "string" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "required": { + "description": "Whether this argument must be provided.", + "type": "boolean" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "PromptListChangedNotification": { + "description": "An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/prompts/list_changed", + "type": "string" + }, + "params": { + "$ref": "#/$defs/NotificationParams" + } + }, + "required": [ + "jsonrpc", + "method" + ], + "type": "object" + }, + "PromptMessage": { + "description": "Describes a message returned as part of a prompt.\n\nThis is similar to `SamplingMessage`, but also supports the embedding of\nresources from the MCP server.", + "properties": { + "content": { + "$ref": "#/$defs/ContentBlock" + }, + "role": { + "$ref": "#/$defs/Role" + } + }, + "required": [ + "content", + "role" + ], + "type": "object" + }, + "PromptReference": { + "description": "Identifies a prompt.", + "properties": { + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + }, + "type": { + "const": "ref/prompt", + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object" + }, + "ReadResourceRequest": { + "description": "Sent from the client to the server, to read a specific resource URI.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "resources/read", + "type": "string" + }, + "params": { + "$ref": "#/$defs/ReadResourceRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "ReadResourceRequestParams": { + "description": "Parameters for a `resources/read` request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "uri": { + "description": "The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "ReadResourceResult": { + "description": "The server's response to a resources/read request from the client.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "contents": { + "items": { + "anyOf": [ + { + "$ref": "#/$defs/TextResourceContents" + }, + { + "$ref": "#/$defs/BlobResourceContents" + } + ] + }, + "type": "array" + } + }, + "required": [ + "contents" + ], + "type": "object" + }, + "RelatedTaskMetadata": { + "description": "Metadata for associating messages with a task.\nInclude this in the `_meta` field under the key `io.modelcontextprotocol/related-task`.", + "properties": { + "taskId": { + "description": "The task identifier this message is associated with.", + "type": "string" + } + }, + "required": [ + "taskId" + ], + "type": "object" + }, + "Request": { + "properties": { + "method": { + "type": "string" + }, + "params": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "RequestId": { + "description": "A uniquely identifying ID for a request in JSON-RPC.", + "type": [ + "string", + "integer" + ] + }, + "RequestParams": { + "description": "Common params for any request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + } + }, + "type": "object" + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "description": { + "description": "A description of what this resource represents.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "size": { + "description": "The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.\n\nThis can be used by Hosts to display file sizes and estimate context window usage.", + "type": "integer" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "name", + "uri" + ], + "type": "object" + }, + "ResourceContents": { + "description": "The contents of a specific resource or sub-resource.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "description": { + "description": "A description of what this resource represents.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "size": { + "description": "The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.\n\nThis can be used by Hosts to display file sizes and estimate context window usage.", + "type": "integer" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + }, + "type": { + "const": "resource_link", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "ResourceListChangedNotification": { + "description": "An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/resources/list_changed", + "type": "string" + }, + "params": { + "$ref": "#/$defs/NotificationParams" + } + }, + "required": [ + "jsonrpc", + "method" + ], + "type": "object" + }, + "ResourceRequestParams": { + "description": "Common parameters when working with resources.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "uri": { + "description": "The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "description": { + "description": "A description of what this template is for.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "mimeType": { + "description": "The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.", + "type": "string" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + }, + "uriTemplate": { + "description": "A URI template (according to RFC 6570) that can be used to construct resource URIs.", + "format": "uri-template", + "type": "string" + } + }, + "required": [ + "name", + "uriTemplate" + ], + "type": "object" + }, + "ResourceTemplateReference": { + "description": "A reference to a resource or resource template definition.", + "properties": { + "type": { + "const": "ref/resource", + "type": "string" + }, + "uri": { + "description": "The URI or URI template of the resource.", + "format": "uri-template", + "type": "string" + } + }, + "required": [ + "type", + "uri" + ], + "type": "object" + }, + "ResourceUpdatedNotification": { + "description": "A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/resources/updated", + "type": "string" + }, + "params": { + "$ref": "#/$defs/ResourceUpdatedNotificationParams" + } + }, + "required": [ + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "ResourceUpdatedNotificationParams": { + "description": "Parameters for a `notifications/resources/updated` notification.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "uri": { + "description": "The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "Result": { + "additionalProperties": {}, + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + } + }, + "type": "object" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "Root": { + "description": "Represents a root directory or file that the server can operate on.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "name": { + "description": "An optional name for the root. This can be used to provide a human-readable\nidentifier for the root, which may be useful for display purposes or for\nreferencing the root in other parts of the application.", + "type": "string" + }, + "uri": { + "description": "The URI identifying the root. This *must* start with file:// for now.\nThis restriction may be relaxed in future versions of the protocol to allow\nother URI schemes.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "RootsListChangedNotification": { + "description": "A notification from the client to the server, informing it that the list of roots has changed.\nThis notification should be sent whenever the client adds, removes, or modifies any root.\nThe server should then request an updated list of roots using the ListRootsRequest.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/roots/list_changed", + "type": "string" + }, + "params": { + "$ref": "#/$defs/NotificationParams" + } + }, + "required": [ + "jsonrpc", + "method" + ], + "type": "object" + }, + "SamplingMessage": { + "description": "Describes a message issued to or received from an LLM API.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "content": { + "anyOf": [ + { + "$ref": "#/$defs/TextContent" + }, + { + "$ref": "#/$defs/ImageContent" + }, + { + "$ref": "#/$defs/AudioContent" + }, + { + "$ref": "#/$defs/ToolUseContent" + }, + { + "$ref": "#/$defs/ToolResultContent" + }, + { + "items": { + "$ref": "#/$defs/SamplingMessageContentBlock" + }, + "type": "array" + } + ] + }, + "role": { + "$ref": "#/$defs/Role" + } + }, + "required": [ + "content", + "role" + ], + "type": "object" + }, + "SamplingMessageContentBlock": { + "anyOf": [ + { + "$ref": "#/$defs/TextContent" + }, + { + "$ref": "#/$defs/ImageContent" + }, + { + "$ref": "#/$defs/AudioContent" + }, + { + "$ref": "#/$defs/ToolUseContent" + }, + { + "$ref": "#/$defs/ToolResultContent" + } + ] + }, + "ServerCapabilities": { + "description": "Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.", + "properties": { + "completions": { + "additionalProperties": true, + "description": "Present if the server supports argument autocompletion suggestions.", + "properties": {}, + "type": "object" + }, + "experimental": { + "additionalProperties": { + "additionalProperties": true, + "properties": {}, + "type": "object" + }, + "description": "Experimental, non-standard capabilities that the server supports.", + "type": "object" + }, + "logging": { + "additionalProperties": true, + "description": "Present if the server supports sending log messages to the client.", + "properties": {}, + "type": "object" + }, + "prompts": { + "description": "Present if the server offers any prompt templates.", + "properties": { + "listChanged": { + "description": "Whether this server supports notifications for changes to the prompt list.", + "type": "boolean" + } + }, + "type": "object" + }, + "resources": { + "description": "Present if the server offers any resources to read.", + "properties": { + "listChanged": { + "description": "Whether this server supports notifications for changes to the resource list.", + "type": "boolean" + }, + "subscribe": { + "description": "Whether this server supports subscribing to resource updates.", + "type": "boolean" + } + }, + "type": "object" + }, + "tasks": { + "description": "Present if the server supports task-augmented requests.", + "properties": { + "cancel": { + "additionalProperties": true, + "description": "Whether this server supports tasks/cancel.", + "properties": {}, + "type": "object" + }, + "list": { + "additionalProperties": true, + "description": "Whether this server supports tasks/list.", + "properties": {}, + "type": "object" + }, + "requests": { + "description": "Specifies which request types can be augmented with tasks.", + "properties": { + "tools": { + "description": "Task support for tool-related requests.", + "properties": { + "call": { + "additionalProperties": true, + "description": "Whether the server supports task-augmented tools/call requests.", + "properties": {}, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "tools": { + "description": "Present if the server offers any tools to call.", + "properties": { + "listChanged": { + "description": "Whether this server supports notifications for changes to the tool list.", + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "ServerNotification": { + "anyOf": [ + { + "$ref": "#/$defs/CancelledNotification" + }, + { + "$ref": "#/$defs/ProgressNotification" + }, + { + "$ref": "#/$defs/ResourceListChangedNotification" + }, + { + "$ref": "#/$defs/ResourceUpdatedNotification" + }, + { + "$ref": "#/$defs/PromptListChangedNotification" + }, + { + "$ref": "#/$defs/ToolListChangedNotification" + }, + { + "$ref": "#/$defs/TaskStatusNotification" + }, + { + "$ref": "#/$defs/LoggingMessageNotification" + }, + { + "$ref": "#/$defs/ElicitationCompleteNotification" + } + ] + }, + "ServerRequest": { + "anyOf": [ + { + "$ref": "#/$defs/PingRequest" + }, + { + "$ref": "#/$defs/GetTaskRequest" + }, + { + "$ref": "#/$defs/GetTaskPayloadRequest" + }, + { + "$ref": "#/$defs/CancelTaskRequest" + }, + { + "$ref": "#/$defs/ListTasksRequest" + }, + { + "$ref": "#/$defs/CreateMessageRequest" + }, + { + "$ref": "#/$defs/ListRootsRequest" + }, + { + "$ref": "#/$defs/ElicitRequest" + } + ] + }, + "ServerResult": { + "anyOf": [ + { + "$ref": "#/$defs/Result" + }, + { + "$ref": "#/$defs/InitializeResult" + }, + { + "$ref": "#/$defs/ListResourcesResult" + }, + { + "$ref": "#/$defs/ListResourceTemplatesResult" + }, + { + "$ref": "#/$defs/ReadResourceResult" + }, + { + "$ref": "#/$defs/ListPromptsResult" + }, + { + "$ref": "#/$defs/GetPromptResult" + }, + { + "$ref": "#/$defs/ListToolsResult" + }, + { + "$ref": "#/$defs/CallToolResult" + }, + { + "$ref": "#/$defs/GetTaskResult", + "description": "The response to a tasks/get request." + }, + { + "$ref": "#/$defs/GetTaskPayloadResult" + }, + { + "$ref": "#/$defs/CancelTaskResult", + "description": "The response to a tasks/cancel request." + }, + { + "$ref": "#/$defs/ListTasksResult" + }, + { + "$ref": "#/$defs/CompleteResult" + } + ] + }, + "SetLevelRequest": { + "description": "A request from the client to the server, to enable or adjust logging.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "logging/setLevel", + "type": "string" + }, + "params": { + "$ref": "#/$defs/SetLevelRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "SetLevelRequestParams": { + "description": "Parameters for a `logging/setLevel` request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "level": { + "$ref": "#/$defs/LoggingLevel", + "description": "The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message." + } + }, + "required": [ + "level" + ], + "type": "object" + }, + "SingleSelectEnumSchema": { + "anyOf": [ + { + "$ref": "#/$defs/UntitledSingleSelectEnumSchema" + }, + { + "$ref": "#/$defs/TitledSingleSelectEnumSchema" + } + ] + }, + "StringSchema": { + "properties": { + "default": { + "type": "string" + }, + "description": { + "type": "string" + }, + "format": { + "enum": [ + "date", + "date-time", + "email", + "uri" + ], + "type": "string" + }, + "maxLength": { + "type": "integer" + }, + "minLength": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "type": { + "const": "string", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "SubscribeRequest": { + "description": "Sent from the client to request resources/updated notifications from the server whenever a particular resource changes.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "resources/subscribe", + "type": "string" + }, + "params": { + "$ref": "#/$defs/SubscribeRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "SubscribeRequestParams": { + "description": "Parameters for a `resources/subscribe` request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "uri": { + "description": "The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "Task": { + "description": "Data associated with a task.", + "properties": { + "createdAt": { + "description": "ISO 8601 timestamp when the task was created.", + "type": "string" + }, + "lastUpdatedAt": { + "description": "ISO 8601 timestamp when the task was last updated.", + "type": "string" + }, + "pollInterval": { + "description": "Suggested polling interval in milliseconds.", + "type": "integer" + }, + "status": { + "$ref": "#/$defs/TaskStatus", + "description": "Current task state." + }, + "statusMessage": { + "description": "Optional human-readable message describing the current task state.\nThis can provide context for any status, including:\n- Reasons for \"cancelled\" status\n- Summaries for \"completed\" status\n- Diagnostic information for \"failed\" status (e.g., error details, what went wrong)", + "type": "string" + }, + "taskId": { + "description": "The task identifier.", + "type": "string" + }, + "ttl": { + "description": "Actual retention duration from creation in milliseconds, null for unlimited.", + "type": [ + "integer", + "null" + ] + } + }, + "required": [ + "createdAt", + "lastUpdatedAt", + "status", + "taskId", + "ttl" + ], + "type": "object" + }, + "TaskAugmentedRequestParams": { + "description": "Common params for any task-augmented request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "task": { + "$ref": "#/$defs/TaskMetadata", + "description": "If specified, the caller is requesting task-augmented execution for this request.\nThe request will return a CreateTaskResult immediately, and the actual result can be\nretrieved later via tasks/result.\n\nTask augmentation is subject to capability negotiation - receivers MUST declare support\nfor task augmentation of specific request types in their capabilities." + } + }, + "type": "object" + }, + "TaskMetadata": { + "description": "Metadata for augmenting a request with task execution.\nInclude this in the `task` field of the request parameters.", + "properties": { + "ttl": { + "description": "Requested duration in milliseconds to retain task from creation.", + "type": "integer" + } + }, + "type": "object" + }, + "TaskStatus": { + "description": "The status of a task.", + "enum": [ + "cancelled", + "completed", + "failed", + "input_required", + "working" + ], + "type": "string" + }, + "TaskStatusNotification": { + "description": "An optional notification from the receiver to the requestor, informing them that a task's status has changed. Receivers are not required to send these notifications.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/tasks/status", + "type": "string" + }, + "params": { + "$ref": "#/$defs/TaskStatusNotificationParams" + } + }, + "required": [ + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "TaskStatusNotificationParams": { + "allOf": [ + { + "$ref": "#/$defs/NotificationParams" + }, + { + "$ref": "#/$defs/Task" + } + ], + "description": "Parameters for a `notifications/tasks/status` notification." + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "text": { + "description": "The text content of the message.", + "type": "string" + }, + "type": { + "const": "text", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "text": { + "description": "The text of the item. This must only be set if the item can actually be represented as text (not binary data).", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "TitledMultiSelectEnumSchema": { + "description": "Schema for multiple-selection enumeration with display titles for each option.", + "properties": { + "default": { + "description": "Optional default value.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": { + "description": "Optional description for the enum field.", + "type": "string" + }, + "items": { + "description": "Schema for array items with enum options and display labels.", + "properties": { + "anyOf": { + "description": "Array of enum options with values and display labels.", + "items": { + "properties": { + "const": { + "description": "The constant enum value.", + "type": "string" + }, + "title": { + "description": "Display title for this option.", + "type": "string" + } + }, + "required": [ + "const", + "title" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "anyOf" + ], + "type": "object" + }, + "maxItems": { + "description": "Maximum number of items to select.", + "type": "integer" + }, + "minItems": { + "description": "Minimum number of items to select.", + "type": "integer" + }, + "title": { + "description": "Optional title for the enum field.", + "type": "string" + }, + "type": { + "const": "array", + "type": "string" + } + }, + "required": [ + "items", + "type" + ], + "type": "object" + }, + "TitledSingleSelectEnumSchema": { + "description": "Schema for single-selection enumeration with display titles for each option.", + "properties": { + "default": { + "description": "Optional default value.", + "type": "string" + }, + "description": { + "description": "Optional description for the enum field.", + "type": "string" + }, + "oneOf": { + "description": "Array of enum options with values and display labels.", + "items": { + "properties": { + "const": { + "description": "The enum value.", + "type": "string" + }, + "title": { + "description": "Display label for this option.", + "type": "string" + } + }, + "required": [ + "const", + "title" + ], + "type": "object" + }, + "type": "array" + }, + "title": { + "description": "Optional title for the enum field.", + "type": "string" + }, + "type": { + "const": "string", + "type": "string" + } + }, + "required": [ + "oneOf", + "type" + ], + "type": "object" + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "annotations": { + "$ref": "#/$defs/ToolAnnotations", + "description": "Optional additional tool information.\n\nDisplay name precedence order is: title, annotations.title, then name." + }, + "description": { + "description": "A human-readable description of the tool.\n\nThis can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a \"hint\" to the model.", + "type": "string" + }, + "execution": { + "$ref": "#/$defs/ToolExecution", + "description": "Execution-related properties for this tool." + }, + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "inputSchema": { + "description": "A JSON Schema object defining the expected parameters for the tool.", + "properties": { + "$schema": { + "type": "string" + }, + "properties": { + "additionalProperties": { + "additionalProperties": true, + "properties": {}, + "type": "object" + }, + "type": "object" + }, + "required": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "const": "object", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "outputSchema": { + "description": "An optional JSON Schema object defining the structure of the tool's output returned in\nthe structuredContent field of a CallToolResult.\n\nDefaults to JSON Schema 2020-12 when no explicit $schema is provided.\nCurrently restricted to type: \"object\" at the root level.", + "properties": { + "$schema": { + "type": "string" + }, + "properties": { + "additionalProperties": { + "additionalProperties": true, + "properties": {}, + "type": "object" + }, + "type": "object" + }, + "required": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "const": "object", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for Tool,\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + } + }, + "required": [ + "inputSchema", + "name" + ], + "type": "object" + }, + "ToolAnnotations": { + "description": "Additional properties describing a Tool to clients.\n\nNOTE: all properties in ToolAnnotations are **hints**.\nThey are not guaranteed to provide a faithful description of\ntool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on ToolAnnotations\nreceived from untrusted servers.", + "properties": { + "destructiveHint": { + "description": "If true, the tool may perform destructive updates to its environment.\nIf false, the tool performs only additive updates.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: true", + "type": "boolean" + }, + "idempotentHint": { + "description": "If true, calling the tool repeatedly with the same arguments\nwill have no additional effect on its environment.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: false", + "type": "boolean" + }, + "openWorldHint": { + "description": "If true, this tool may interact with an \"open world\" of external\nentities. If false, the tool's domain of interaction is closed.\nFor example, the world of a web search tool is open, whereas that\nof a memory tool is not.\n\nDefault: true", + "type": "boolean" + }, + "readOnlyHint": { + "description": "If true, the tool does not modify its environment.\n\nDefault: false", + "type": "boolean" + }, + "title": { + "description": "A human-readable title for the tool.", + "type": "string" + } + }, + "type": "object" + }, + "ToolChoice": { + "description": "Controls tool selection behavior for sampling requests.", + "properties": { + "mode": { + "description": "Controls the tool use ability of the model:\n- \"auto\": Model decides whether to use tools (default)\n- \"required\": Model MUST use at least one tool before completing\n- \"none\": Model MUST NOT use any tools", + "enum": [ + "auto", + "none", + "required" + ], + "type": "string" + } + }, + "type": "object" + }, + "ToolExecution": { + "description": "Execution-related properties for a tool.", + "properties": { + "taskSupport": { + "description": "Indicates whether this tool supports task-augmented execution.\nThis allows clients to handle long-running operations through polling\nthe task system.\n\n- \"forbidden\": Tool does not support task-augmented execution (default when absent)\n- \"optional\": Tool may support task-augmented execution\n- \"required\": Tool requires task-augmented execution\n\nDefault: \"forbidden\"", + "enum": [ + "forbidden", + "optional", + "required" + ], + "type": "string" + } + }, + "type": "object" + }, + "ToolListChangedNotification": { + "description": "An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/tools/list_changed", + "type": "string" + }, + "params": { + "$ref": "#/$defs/NotificationParams" + } + }, + "required": [ + "jsonrpc", + "method" + ], + "type": "object" + }, + "ToolResultContent": { + "description": "The result of a tool use, provided by the user back to the assistant.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "Optional metadata about the tool result. Clients SHOULD preserve this field when\nincluding tool results in subsequent sampling requests to enable caching optimizations.\n\nSee [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "content": { + "description": "The unstructured result content of the tool use.\n\nThis has the same format as CallToolResult.content and can include text, images,\naudio, resource links, and embedded resources.", + "items": { + "$ref": "#/$defs/ContentBlock" + }, + "type": "array" + }, + "isError": { + "description": "Whether the tool use resulted in an error.\n\nIf true, the content typically describes the error that occurred.\nDefault: false", + "type": "boolean" + }, + "structuredContent": { + "additionalProperties": {}, + "description": "An optional structured result object.\n\nIf the tool defined an outputSchema, this SHOULD conform to that schema.", + "type": "object" + }, + "toolUseId": { + "description": "The ID of the tool use this result corresponds to.\n\nThis MUST match the ID from a previous ToolUseContent.", + "type": "string" + }, + "type": { + "const": "tool_result", + "type": "string" + } + }, + "required": [ + "content", + "toolUseId", + "type" + ], + "type": "object" + }, + "ToolUseContent": { + "description": "A request from the assistant to call a tool.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "Optional metadata about the tool use. Clients SHOULD preserve this field when\nincluding tool uses in subsequent sampling requests to enable caching optimizations.\n\nSee [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "type": "object" + }, + "id": { + "description": "A unique identifier for this tool use.\n\nThis ID is used to match tool results to their corresponding tool uses.", + "type": "string" + }, + "input": { + "additionalProperties": {}, + "description": "The arguments to pass to the tool, conforming to the tool's input schema.", + "type": "object" + }, + "name": { + "description": "The name of the tool to call.", + "type": "string" + }, + "type": { + "const": "tool_use", + "type": "string" + } + }, + "required": [ + "id", + "input", + "name", + "type" + ], + "type": "object" + }, + "URLElicitationRequiredError": { + "description": "An error response that indicates that the server requires the client to provide additional information via an elicitation request.", + "properties": { + "error": { + "allOf": [ + { + "$ref": "#/$defs/Error" + }, + { + "properties": { + "code": { + "const": -32042, + "type": "integer" + }, + "data": { + "additionalProperties": {}, + "properties": { + "elicitations": { + "items": { + "$ref": "#/$defs/ElicitRequestURLParams" + }, + "type": "array" + } + }, + "required": [ + "elicitations" + ], + "type": "object" + } + }, + "required": [ + "code", + "data" + ], + "type": "object" + } + ] + }, + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + } + }, + "required": [ + "error", + "jsonrpc" + ], + "type": "object" + }, + "UnsubscribeRequest": { + "description": "Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "resources/unsubscribe", + "type": "string" + }, + "params": { + "$ref": "#/$defs/UnsubscribeRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "UnsubscribeRequestParams": { + "description": "Parameters for a `resources/unsubscribe` request.", + "properties": { + "_meta": { + "additionalProperties": {}, + "description": "See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.", + "properties": { + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "type": "object" + }, + "uri": { + "description": "The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "UntitledMultiSelectEnumSchema": { + "description": "Schema for multiple-selection enumeration without display titles for options.", + "properties": { + "default": { + "description": "Optional default value.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": { + "description": "Optional description for the enum field.", + "type": "string" + }, + "items": { + "description": "Schema for the array items.", + "properties": { + "enum": { + "description": "Array of enum values to choose from.", + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "const": "string", + "type": "string" + } + }, + "required": [ + "enum", + "type" + ], + "type": "object" + }, + "maxItems": { + "description": "Maximum number of items to select.", + "type": "integer" + }, + "minItems": { + "description": "Minimum number of items to select.", + "type": "integer" + }, + "title": { + "description": "Optional title for the enum field.", + "type": "string" + }, + "type": { + "const": "array", + "type": "string" + } + }, + "required": [ + "items", + "type" + ], + "type": "object" + }, + "UntitledSingleSelectEnumSchema": { + "description": "Schema for single-selection enumeration without display titles for options.", + "properties": { + "default": { + "description": "Optional default value.", + "type": "string" + }, + "description": { + "description": "Optional description for the enum field.", + "type": "string" + }, + "enum": { + "description": "Array of enum values to choose from.", + "items": { + "type": "string" + }, + "type": "array" + }, + "title": { + "description": "Optional title for the enum field.", + "type": "string" + }, + "type": { + "const": "string", + "type": "string" + } + }, + "required": [ + "enum", + "type" + ], + "type": "object" + } + } +} diff --git a/schema/2026-07-28.json b/schema/2026-07-28.json new file mode 100644 index 0000000000..2bfbcfa233 --- /dev/null +++ b/schema/2026-07-28.json @@ -0,0 +1,3873 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$defs": { + "Annotations": { + "description": "Optional annotations for the client. The client can use annotations to inform how objects are used or displayed", + "properties": { + "audience": { + "description": "Describes who the intended audience of this object or data is.\n\nIt can include multiple entries to indicate content useful for multiple audiences (e.g., `[\"user\", \"assistant\"]`).", + "items": { + "$ref": "#/$defs/Role" + }, + "type": "array" + }, + "lastModified": { + "description": "The moment the resource was last modified, as an ISO 8601 formatted string.\n\nShould be an ISO 8601 formatted string (e.g., \"2025-01-12T15:00:58Z\").\n\nExamples: last activity timestamp in an open file, timestamp when the resource\nwas attached, etc.", + "type": "string" + }, + "priority": { + "description": "Describes how important this data is for operating the server.\n\nA value of 1 means \"most important,\" and indicates that the data is\neffectively required, while 0 means \"least important,\" and indicates that\nthe data is entirely optional.", + "maximum": 1, + "minimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "AudioContent": { + "description": "Audio provided to or from an LLM.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "data": { + "description": "The base64-encoded audio data.", + "format": "byte", + "type": "string" + }, + "mimeType": { + "description": "The MIME type of the audio. Different providers may support different audio types.", + "type": "string" + }, + "type": { + "const": "audio", + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "BaseMetadata": { + "description": "Base interface for metadata with name (identifier) and title (display name) properties.", + "properties": { + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for {@link Tool},\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "BlobResourceContents": { + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "blob": { + "description": "A base64-encoded string representing the binary data of the item.", + "format": "byte", + "type": "string" + }, + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "blob", + "uri" + ], + "type": "object" + }, + "BooleanSchema": { + "properties": { + "default": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "title": { + "type": "string" + }, + "type": { + "const": "boolean", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "CacheableResult": { + "description": "A result that supports a time-to-live (TTL) hint for client-side caching.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "cacheScope": { + "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.", + "enum": [ + "private", + "public" + ], + "type": "string" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + }, + "ttlMs": { + "description": "A hint from the server indicating how long (in milliseconds) the\nclient MAY cache this response before re-fetching. Semantics are\nanalogous to HTTP Cache-Control max-age.\n\n- If 0, The response SHOULD be considered immediately stale,\n The client MAY re-fetch every time the result is needed.\n- If positive, the client SHOULD consider the result fresh for this many\n milliseconds after receiving the response.", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "cacheScope", + "resultType", + "ttlMs" + ], + "type": "object" + }, + "CallToolRequest": { + "description": "Used by the client to invoke a tool provided by the server.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tools/call", + "type": "string" + }, + "params": { + "$ref": "#/$defs/CallToolRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "CallToolRequestParams": { + "description": "Parameters for a `tools/call` request.", + "properties": { + "_meta": { + "$ref": "#/$defs/RequestMetaObject" + }, + "arguments": { + "additionalProperties": {}, + "description": "Arguments to use for the tool call.", + "type": "object" + }, + "inputResponses": { + "$ref": "#/$defs/InputResponses" + }, + "name": { + "description": "The name of the tool.", + "type": "string" + }, + "requestState": { + "type": "string" + } + }, + "required": [ + "_meta", + "name" + ], + "type": "object" + }, + "CallToolResult": { + "description": "The result returned by the server for a {@link CallToolRequesttools/call} request.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "content": { + "description": "A list of content objects that represent the unstructured result of the tool call.", + "items": { + "$ref": "#/$defs/ContentBlock" + }, + "type": "array" + }, + "isError": { + "description": "Whether the tool call ended in an error.\n\nIf not set, this is assumed to be false (the call was successful).\n\nAny errors that originate from the tool SHOULD be reported inside the result\nobject, with `isError` set to true, _not_ as an MCP protocol-level error\nresponse. Otherwise, the LLM would not be able to see that an error occurred\nand self-correct.\n\nHowever, any errors in _finding_ the tool, an error indicating that the\nserver does not support tool calls, or any other exceptional conditions,\nshould be reported as an MCP error response.", + "type": "boolean" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + }, + "structuredContent": { + "description": "An optional JSON value that represents the structured result of the tool call.\n\nThis can be any JSON value (object, array, string, number, boolean, or null)\nthat conforms to the tool's outputSchema if one is defined." + } + }, + "required": [ + "content", + "resultType" + ], + "type": "object" + }, + "CallToolResultResponse": { + "description": "A successful response from the server for a {@link CallToolRequesttools/call} request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/$defs/InputRequiredResult" + }, + { + "$ref": "#/$defs/CallToolResult" + } + ] + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, + "CancelledNotification": { + "description": "This notification can be sent by either side to indicate that it is cancelling a previously-issued request.\n\nThe request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished.\n\nThis notification indicates that the result will be unused, so any associated processing SHOULD cease.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/cancelled", + "type": "string" + }, + "params": { + "$ref": "#/$defs/CancelledNotificationParams" + } + }, + "required": [ + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "CancelledNotificationParams": { + "description": "Parameters for a `notifications/cancelled` notification.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "reason": { + "description": "An optional string describing the reason for the cancellation. This MAY be logged or presented to the user.", + "type": "string" + }, + "requestId": { + "$ref": "#/$defs/RequestId", + "description": "The ID of the request to cancel.\n\nThis MUST correspond to the ID of a request previously issued in the same direction." + } + }, + "type": "object" + }, + "ClientCapabilities": { + "description": "Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities.", + "properties": { + "elicitation": { + "description": "Present if the client supports elicitation from the server.", + "properties": { + "form": { + "$ref": "#/$defs/JSONObject" + }, + "url": { + "$ref": "#/$defs/JSONObject" + } + }, + "type": "object" + }, + "experimental": { + "additionalProperties": { + "$ref": "#/$defs/JSONObject" + }, + "description": "Experimental, non-standard capabilities that the client supports.", + "type": "object" + }, + "extensions": { + "additionalProperties": { + "$ref": "#/$defs/JSONObject" + }, + "description": "Optional MCP extensions that the client supports. Keys are extension identifiers\n(e.g., \"io.modelcontextprotocol/oauth-client-credentials\"), and values are\nper-extension settings objects. An empty object indicates support with no settings.", + "type": "object" + }, + "roots": { + "description": "Present if the client supports listing roots.", + "properties": {}, + "type": "object" + }, + "sampling": { + "description": "Present if the client supports sampling from an LLM.", + "properties": { + "context": { + "$ref": "#/$defs/JSONObject", + "description": "Whether the client supports context inclusion via `includeContext` parameter.\nIf not declared, servers SHOULD only use `includeContext: \"none\"` (or omit it)." + }, + "tools": { + "$ref": "#/$defs/JSONObject", + "description": "Whether the client supports tool use via `tools` and `toolChoice` parameters." + } + }, + "type": "object" + } + }, + "type": "object" + }, + "ClientNotification": { + "anyOf": [ + { + "$ref": "#/$defs/CancelledNotification" + }, + { + "$ref": "#/$defs/ProgressNotification" + } + ] + }, + "ClientRequest": { + "anyOf": [ + { + "$ref": "#/$defs/DiscoverRequest" + }, + { + "$ref": "#/$defs/ListResourcesRequest" + }, + { + "$ref": "#/$defs/ListResourceTemplatesRequest" + }, + { + "$ref": "#/$defs/ReadResourceRequest" + }, + { + "$ref": "#/$defs/SubscriptionsListenRequest" + }, + { + "$ref": "#/$defs/ListPromptsRequest" + }, + { + "$ref": "#/$defs/GetPromptRequest" + }, + { + "$ref": "#/$defs/ListToolsRequest" + }, + { + "$ref": "#/$defs/CallToolRequest" + }, + { + "$ref": "#/$defs/CompleteRequest" + } + ] + }, + "ClientResult": { + "$ref": "#/$defs/Result", + "description": "Common result fields." + }, + "CompleteRequest": { + "description": "A request from the client to the server, to ask for completion options.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "completion/complete", + "type": "string" + }, + "params": { + "$ref": "#/$defs/CompleteRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "CompleteRequestParams": { + "description": "Parameters for a `completion/complete` request.", + "properties": { + "_meta": { + "$ref": "#/$defs/RequestMetaObject" + }, + "argument": { + "description": "The argument's information", + "properties": { + "name": { + "description": "The name of the argument", + "type": "string" + }, + "value": { + "description": "The value of the argument to use for completion matching.", + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, + "context": { + "description": "Additional, optional context for completions", + "properties": { + "arguments": { + "additionalProperties": { + "type": "string" + }, + "description": "Previously-resolved variables in a URI template or prompt.", + "type": "object" + } + }, + "type": "object" + }, + "ref": { + "anyOf": [ + { + "$ref": "#/$defs/PromptReference" + }, + { + "$ref": "#/$defs/ResourceTemplateReference" + } + ] + } + }, + "required": [ + "_meta", + "argument", + "ref" + ], + "type": "object" + }, + "CompleteResult": { + "description": "The result returned by the server for a {@link CompleteRequestcompletion/complete} request.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "completion": { + "properties": { + "hasMore": { + "description": "Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown.", + "type": "boolean" + }, + "total": { + "description": "The total number of completion options available. This can exceed the number of values actually sent in the response.", + "type": "integer" + }, + "values": { + "description": "An array of completion values. Must not exceed 100 items.", + "items": { + "type": "string" + }, + "maxItems": 100, + "type": "array" + } + }, + "required": [ + "values" + ], + "type": "object" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + } + }, + "required": [ + "completion", + "resultType" + ], + "type": "object" + }, + "CompleteResultResponse": { + "description": "A successful response from the server for a {@link CompleteRequestcompletion/complete} request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "$ref": "#/$defs/CompleteResult" + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, + "ContentBlock": { + "anyOf": [ + { + "$ref": "#/$defs/TextContent" + }, + { + "$ref": "#/$defs/ImageContent" + }, + { + "$ref": "#/$defs/AudioContent" + }, + { + "$ref": "#/$defs/ResourceLink" + }, + { + "$ref": "#/$defs/EmbeddedResource" + } + ] + }, + "CreateMessageRequest": { + "description": "A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it.", + "properties": { + "method": { + "const": "sampling/createMessage", + "type": "string" + }, + "params": { + "$ref": "#/$defs/CreateMessageRequestParams" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "CreateMessageRequestParams": { + "description": "Parameters for a `sampling/createMessage` request.", + "properties": { + "includeContext": { + "description": "A request to include context from one or more MCP servers (including the caller), to be attached to the prompt.\nThe client MAY ignore this request.\n\nDefault is `\"none\"`. The values `\"thisServer\"` and `\"allServers\"` are deprecated (SEP-2596): servers SHOULD\nomit this field or use `\"none\"`, and SHOULD only use the deprecated values if the client declares\n{@link ClientCapabilities.sampling.context}.", + "enum": [ + "allServers", + "none", + "thisServer" + ], + "type": "string" + }, + "maxTokens": { + "description": "The requested maximum number of tokens to sample (to prevent runaway completions).\n\nThe client MAY choose to sample fewer tokens than the requested maximum.", + "type": "integer" + }, + "messages": { + "items": { + "$ref": "#/$defs/SamplingMessage" + }, + "type": "array" + }, + "metadata": { + "$ref": "#/$defs/JSONObject", + "description": "Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific." + }, + "modelPreferences": { + "$ref": "#/$defs/ModelPreferences", + "description": "The server's preferences for which model to select. The client MAY ignore these preferences." + }, + "stopSequences": { + "items": { + "type": "string" + }, + "type": "array" + }, + "systemPrompt": { + "description": "An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt.", + "type": "string" + }, + "temperature": { + "type": "number" + }, + "toolChoice": { + "$ref": "#/$defs/ToolChoice", + "description": "Controls how the model uses tools.\nThe client MUST return an error if this field is provided but {@link ClientCapabilities.sampling.tools} is not declared.\nDefault is `{ mode: \"auto\" }`." + }, + "tools": { + "description": "Tools that the model may use during generation.\nThe client MUST return an error if this field is provided but {@link ClientCapabilities.sampling.tools} is not declared.", + "items": { + "$ref": "#/$defs/Tool" + }, + "type": "array" + } + }, + "required": [ + "maxTokens", + "messages" + ], + "type": "object" + }, + "CreateMessageResult": { + "description": "The result returned by the client for a {@link CreateMessageRequestsampling/createMessage} request.\nThe client should inform the user before returning the sampled message, to allow them\nto inspect the response (human in the loop) and decide whether to allow the server to see it.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "content": { + "anyOf": [ + { + "$ref": "#/$defs/TextContent" + }, + { + "$ref": "#/$defs/ImageContent" + }, + { + "$ref": "#/$defs/AudioContent" + }, + { + "$ref": "#/$defs/ToolUseContent" + }, + { + "$ref": "#/$defs/ToolResultContent" + }, + { + "items": { + "$ref": "#/$defs/SamplingMessageContentBlock" + }, + "type": "array" + } + ] + }, + "model": { + "description": "The name of the model that generated the message.", + "type": "string" + }, + "role": { + "$ref": "#/$defs/Role" + }, + "stopReason": { + "description": "The reason why sampling stopped, if known.\n\nStandard values:\n- `\"endTurn\"`: Natural end of the assistant's turn\n- `\"stopSequence\"`: A stop sequence was encountered\n- `\"maxTokens\"`: Maximum token limit was reached\n- `\"toolUse\"`: The model wants to use one or more tools\n\nThis field is an open string to allow for provider-specific stop reasons.", + "type": "string" + } + }, + "required": [ + "content", + "model", + "role" + ], + "type": "object" + }, + "Cursor": { + "description": "An opaque token used to represent a cursor for pagination.", + "type": "string" + }, + "DiscoverRequest": { + "description": "A request from the client asking the server to advertise its supported\nprotocol versions, capabilities, and other metadata. Servers **MUST**\nimplement `server/discover`. Clients **MAY** call it but are not required\nto — version negotiation can also happen inline via per-request `_meta`.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "server/discover", + "type": "string" + }, + "params": { + "$ref": "#/$defs/RequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "DiscoverResult": { + "description": "The result returned by the server for a {@link DiscoverRequestserver/discover} request.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "cacheScope": { + "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.", + "enum": [ + "private", + "public" + ], + "type": "string" + }, + "capabilities": { + "$ref": "#/$defs/ServerCapabilities", + "description": "The capabilities of the server." + }, + "instructions": { + "description": "Natural-language guidance describing the server and its features.\n\nThis can be used by clients to improve an LLM's understanding of\navailable tools (e.g., by including it in a system prompt). It should\nfocus on information that helps the model use the server effectively\nand should not duplicate information already in tool descriptions.", + "type": "string" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + }, + "serverInfo": { + "$ref": "#/$defs/Implementation", + "description": "Information about the server software implementation." + }, + "supportedVersions": { + "description": "MCP Protocol Versions this server supports. The client should choose a\nversion from this list for use in subsequent requests.", + "items": { + "type": "string" + }, + "type": "array" + }, + "ttlMs": { + "description": "A hint from the server indicating how long (in milliseconds) the\nclient MAY cache this response before re-fetching. Semantics are\nanalogous to HTTP Cache-Control max-age.\n\n- If 0, The response SHOULD be considered immediately stale,\n The client MAY re-fetch every time the result is needed.\n- If positive, the client SHOULD consider the result fresh for this many\n milliseconds after receiving the response.", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "cacheScope", + "capabilities", + "resultType", + "serverInfo", + "supportedVersions", + "ttlMs" + ], + "type": "object" + }, + "DiscoverResultResponse": { + "description": "A successful response from the server for a {@link DiscoverRequestserver/discover} request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "$ref": "#/$defs/DiscoverResult" + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, + "ElicitRequest": { + "description": "A request from the server to elicit additional information from the user via the client.", + "properties": { + "method": { + "const": "elicitation/create", + "type": "string" + }, + "params": { + "$ref": "#/$defs/ElicitRequestParams" + } + }, + "required": [ + "method", + "params" + ], + "type": "object" + }, + "ElicitRequestFormParams": { + "description": "The parameters for a request to elicit non-sensitive information from the user via a form in the client.", + "properties": { + "message": { + "description": "The message to present to the user describing what information is being requested.", + "type": "string" + }, + "mode": { + "const": "form", + "description": "The elicitation mode.", + "type": "string" + }, + "requestedSchema": { + "description": "A restricted subset of JSON Schema.\nOnly top-level properties are allowed, without nesting.", + "properties": { + "$schema": { + "type": "string" + }, + "properties": { + "additionalProperties": { + "$ref": "#/$defs/PrimitiveSchemaDefinition" + }, + "type": "object" + }, + "required": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "const": "object", + "type": "string" + } + }, + "required": [ + "properties", + "type" + ], + "type": "object" + } + }, + "required": [ + "message", + "requestedSchema" + ], + "type": "object" + }, + "ElicitRequestParams": { + "anyOf": [ + { + "$ref": "#/$defs/ElicitRequestFormParams" + }, + { + "$ref": "#/$defs/ElicitRequestURLParams" + } + ], + "description": "The parameters for a request to elicit additional information from the user via the client." + }, + "ElicitRequestURLParams": { + "description": "The parameters for a request to elicit information from the user via a URL in the client.", + "properties": { + "elicitationId": { + "description": "The ID of the elicitation, which must be unique within the context of the server.\nThe client MUST treat this ID as an opaque value.", + "type": "string" + }, + "message": { + "description": "The message to present to the user explaining why the interaction is needed.", + "type": "string" + }, + "mode": { + "const": "url", + "description": "The elicitation mode.", + "type": "string" + }, + "url": { + "description": "The URL that the user should navigate to.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "elicitationId", + "message", + "mode", + "url" + ], + "type": "object" + }, + "ElicitResult": { + "description": "The result returned by the client for an {@link ElicitRequestelicitation/create} request.", + "properties": { + "action": { + "description": "The user action in response to the elicitation.\n- `\"accept\"`: User submitted the form/confirmed the action\n- `\"decline\"`: User explicitly declined the action\n- `\"cancel\"`: User dismissed without making an explicit choice", + "enum": [ + "accept", + "cancel", + "decline" + ], + "type": "string" + }, + "content": { + "additionalProperties": { + "anyOf": [ + { + "items": { + "type": "string" + }, + "type": "array" + }, + { + "type": [ + "string", + "integer", + "boolean" + ] + } + ] + }, + "description": "The submitted form data, only present when action is `\"accept\"` and mode was `\"form\"`.\nContains values matching the requested schema.\nOmitted for out-of-band mode responses.", + "type": "object" + } + }, + "required": [ + "action" + ], + "type": "object" + }, + "ElicitationCompleteNotification": { + "description": "An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/elicitation/complete", + "type": "string" + }, + "params": { + "properties": { + "elicitationId": { + "description": "The ID of the elicitation that completed.", + "type": "string" + } + }, + "required": [ + "elicitationId" + ], + "type": "object" + } + }, + "required": [ + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "EmbeddedResource": { + "description": "The contents of a resource, embedded into a prompt or tool call result.\n\nIt is up to the client how best to render embedded resources for the benefit\nof the LLM and/or the user.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "resource": { + "anyOf": [ + { + "$ref": "#/$defs/TextResourceContents" + }, + { + "$ref": "#/$defs/BlobResourceContents" + } + ] + }, + "type": { + "const": "resource", + "type": "string" + } + }, + "required": [ + "resource", + "type" + ], + "type": "object" + }, + "EmptyResult": { + "$ref": "#/$defs/Result", + "description": "Common result fields." + }, + "EnumSchema": { + "anyOf": [ + { + "$ref": "#/$defs/UntitledSingleSelectEnumSchema" + }, + { + "$ref": "#/$defs/TitledSingleSelectEnumSchema" + }, + { + "$ref": "#/$defs/UntitledMultiSelectEnumSchema" + }, + { + "$ref": "#/$defs/TitledMultiSelectEnumSchema" + }, + { + "$ref": "#/$defs/LegacyTitledEnumSchema" + } + ] + }, + "Error": { + "properties": { + "code": { + "description": "The error type that occurred.", + "type": "integer" + }, + "data": { + "description": "Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.)." + }, + "message": { + "description": "A short description of the error. The message SHOULD be limited to a concise single sentence.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" + }, + "GetPromptRequest": { + "description": "Used by the client to get a prompt provided by the server.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "prompts/get", + "type": "string" + }, + "params": { + "$ref": "#/$defs/GetPromptRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "GetPromptRequestParams": { + "description": "Parameters for a `prompts/get` request.", + "properties": { + "_meta": { + "$ref": "#/$defs/RequestMetaObject" + }, + "arguments": { + "additionalProperties": { + "type": "string" + }, + "description": "Arguments to use for templating the prompt.", + "type": "object" + }, + "inputResponses": { + "$ref": "#/$defs/InputResponses" + }, + "name": { + "description": "The name of the prompt or prompt template.", + "type": "string" + }, + "requestState": { + "type": "string" + } + }, + "required": [ + "_meta", + "name" + ], + "type": "object" + }, + "GetPromptResult": { + "description": "The result returned by the server for a {@link GetPromptRequestprompts/get} request.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "description": { + "description": "An optional description for the prompt.", + "type": "string" + }, + "messages": { + "items": { + "$ref": "#/$defs/PromptMessage" + }, + "type": "array" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + } + }, + "required": [ + "messages", + "resultType" + ], + "type": "object" + }, + "GetPromptResultResponse": { + "description": "A successful response from the server for a {@link GetPromptRequestprompts/get} request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/$defs/InputRequiredResult" + }, + { + "$ref": "#/$defs/GetPromptResult" + } + ] + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, + "Icon": { + "description": "An optionally-sized icon that can be displayed in a user interface.", + "properties": { + "mimeType": { + "description": "Optional MIME type override if the source MIME type is missing or generic.\nFor example: `\"image/png\"`, `\"image/jpeg\"`, or `\"image/svg+xml\"`.", + "type": "string" + }, + "sizes": { + "description": "Optional array of strings that specify sizes at which the icon can be used.\nEach string should be in WxH format (e.g., `\"48x48\"`, `\"96x96\"`) or `\"any\"` for scalable formats like SVG.\n\nIf not provided, the client should assume that the icon can be used at any size.", + "items": { + "type": "string" + }, + "type": "array" + }, + "src": { + "description": "A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a\n`data:` URI with Base64-encoded image data.\n\nConsumers SHOULD take steps to ensure URLs serving icons are from the\nsame domain as the client/server or a trusted domain.\n\nConsumers SHOULD take appropriate precautions when consuming SVGs as they can contain\nexecutable JavaScript.", + "format": "uri", + "type": "string" + }, + "theme": { + "description": "Optional specifier for the theme this icon is designed for. `\"light\"` indicates\nthe icon is designed to be used with a light background, and `\"dark\"` indicates\nthe icon is designed to be used with a dark background.\n\nIf not provided, the client should assume the icon can be used with any theme.", + "enum": [ + "dark", + "light" + ], + "type": "string" + } + }, + "required": [ + "src" + ], + "type": "object" + }, + "Icons": { + "description": "Base interface to add `icons` property.", + "properties": { + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + } + }, + "type": "object" + }, + "ImageContent": { + "description": "An image provided to or from an LLM.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "data": { + "description": "The base64-encoded image data.", + "format": "byte", + "type": "string" + }, + "mimeType": { + "description": "The MIME type of the image. Different providers may support different image types.", + "type": "string" + }, + "type": { + "const": "image", + "type": "string" + } + }, + "required": [ + "data", + "mimeType", + "type" + ], + "type": "object" + }, + "Implementation": { + "description": "Describes the MCP implementation.", + "properties": { + "description": { + "description": "An optional human-readable description of what this implementation does.\n\nThis can be used by clients or servers to provide context about their purpose\nand capabilities. For example, a server might describe the types of resources\nor tools it provides, while a client might describe its intended use case.", + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for {@link Tool},\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + }, + "version": { + "description": "The version of this implementation.", + "type": "string" + }, + "websiteUrl": { + "description": "An optional URL of the website for this implementation.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "name", + "version" + ], + "type": "object" + }, + "InputRequest": { + "anyOf": [ + { + "$ref": "#/$defs/CreateMessageRequest" + }, + { + "$ref": "#/$defs/ListRootsRequest" + }, + { + "$ref": "#/$defs/ElicitRequest" + } + ] + }, + "InputRequests": { + "additionalProperties": { + "$ref": "#/$defs/InputRequest" + }, + "description": "A map of server-initiated requests that the client must fulfill.\nKeys are server-assigned identifiers; values are the request objects.", + "type": "object" + }, + "InputRequiredResult": { + "description": "An InputRequiredResult sent by the server to indicate that additional input is needed\nbefore the request can be completed.\n\nAt least one of `inputRequests` or `requestState` MUST be present.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "inputRequests": { + "$ref": "#/$defs/InputRequests" + }, + "requestState": { + "type": "string" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + } + }, + "required": [ + "resultType" + ], + "type": "object" + }, + "InputResponse": { + "anyOf": [ + { + "$ref": "#/$defs/CreateMessageResult" + }, + { + "$ref": "#/$defs/ListRootsResult" + }, + { + "$ref": "#/$defs/ElicitResult" + } + ] + }, + "InputResponseRequestParams": { + "properties": { + "_meta": { + "$ref": "#/$defs/RequestMetaObject" + }, + "inputResponses": { + "$ref": "#/$defs/InputResponses" + }, + "requestState": { + "type": "string" + } + }, + "required": [ + "_meta" + ], + "type": "object" + }, + "InputResponses": { + "additionalProperties": { + "$ref": "#/$defs/InputResponse" + }, + "description": "A map of client responses to server-initiated requests.\nKeys correspond to the keys in the {@link InputRequests} map;\nvalues are the client's result for each request.", + "type": "object" + }, + "InternalError": { + "description": "A JSON-RPC error indicating that an internal error occurred on the receiver. This error is returned when the receiver encounters an unexpected condition that prevents it from fulfilling the request.", + "properties": { + "code": { + "const": -32603, + "description": "The error type that occurred.", + "type": "integer" + }, + "data": { + "description": "Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.)." + }, + "message": { + "description": "A short description of the error. The message SHOULD be limited to a concise single sentence.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" + }, + "InvalidParamsError": { + "description": "A JSON-RPC error indicating that the method parameters are invalid or malformed.\n\nIn MCP, this error is returned in various contexts when request parameters fail validation:\n\n- **Tools**: Unknown tool name or invalid tool arguments\n- **Prompts**: Unknown prompt name or missing required arguments\n- **Pagination**: Invalid or expired cursor values\n- **Logging**: Invalid log level\n- **Elicitation**: Server requests an elicitation mode not declared in client capabilities\n- **Sampling**: Missing tool result or tool results mixed with other content", + "properties": { + "code": { + "const": -32602, + "description": "The error type that occurred.", + "type": "integer" + }, + "data": { + "description": "Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.)." + }, + "message": { + "description": "A short description of the error. The message SHOULD be limited to a concise single sentence.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" + }, + "InvalidRequestError": { + "description": "A JSON-RPC error indicating that the request is not a valid request object. This error is returned when the message structure does not conform to the JSON-RPC 2.0 specification requirements for a request (e.g., missing required fields like `jsonrpc` or `method`, or using invalid types for these fields).", + "properties": { + "code": { + "const": -32600, + "description": "The error type that occurred.", + "type": "integer" + }, + "data": { + "description": "Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.)." + }, + "message": { + "description": "A short description of the error. The message SHOULD be limited to a concise single sentence.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" + }, + "JSONArray": { + "items": { + "$ref": "#/$defs/JSONValue" + }, + "type": "array" + }, + "JSONObject": { + "additionalProperties": { + "$ref": "#/$defs/JSONValue" + }, + "type": "object" + }, + "JSONRPCErrorResponse": { + "description": "A response to a request that indicates an error occurred.", + "properties": { + "error": { + "$ref": "#/$defs/Error" + }, + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + } + }, + "required": [ + "error", + "jsonrpc" + ], + "type": "object" + }, + "JSONRPCMessage": { + "anyOf": [ + { + "$ref": "#/$defs/JSONRPCRequest" + }, + { + "$ref": "#/$defs/JSONRPCNotification" + }, + { + "$ref": "#/$defs/JSONRPCResultResponse" + }, + { + "$ref": "#/$defs/JSONRPCErrorResponse" + } + ], + "description": "Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent." + }, + "JSONRPCNotification": { + "description": "A notification which does not expect a response.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "type": "string" + }, + "params": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "jsonrpc", + "method" + ], + "type": "object" + }, + "JSONRPCRequest": { + "description": "A request that expects a response.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "type": "string" + }, + "params": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "id", + "jsonrpc", + "method" + ], + "type": "object" + }, + "JSONRPCResponse": { + "anyOf": [ + { + "$ref": "#/$defs/JSONRPCResultResponse" + }, + { + "$ref": "#/$defs/JSONRPCErrorResponse" + } + ], + "description": "A response to a request, containing either the result or error." + }, + "JSONRPCResultResponse": { + "description": "A successful (non-error) response to a request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "$ref": "#/$defs/Result" + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, + "JSONValue": { + "anyOf": [ + { + "$ref": "#/$defs/JSONObject" + }, + { + "items": { + "$ref": "#/$defs/JSONValue" + }, + "type": "array" + }, + { + "type": [ + "string", + "integer", + "boolean" + ] + } + ] + }, + "LegacyTitledEnumSchema": { + "description": "Use {@link TitledSingleSelectEnumSchema} instead.\nThis interface will be removed in a future version.", + "properties": { + "default": { + "type": "string" + }, + "description": { + "type": "string" + }, + "enum": { + "items": { + "type": "string" + }, + "type": "array" + }, + "enumNames": { + "description": "(Legacy) Display names for enum values.\nNon-standard according to JSON schema 2020-12.", + "items": { + "type": "string" + }, + "type": "array" + }, + "title": { + "type": "string" + }, + "type": { + "const": "string", + "type": "string" + } + }, + "required": [ + "enum", + "type" + ], + "type": "object" + }, + "ListPromptsRequest": { + "description": "Sent from the client to request a list of prompts and prompt templates the server has.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "prompts/list", + "type": "string" + }, + "params": { + "$ref": "#/$defs/PaginatedRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "ListPromptsResult": { + "description": "The result returned by the server for a {@link ListPromptsRequestprompts/list} request.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "cacheScope": { + "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.", + "enum": [ + "private", + "public" + ], + "type": "string" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "prompts": { + "items": { + "$ref": "#/$defs/Prompt" + }, + "type": "array" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + }, + "ttlMs": { + "description": "A hint from the server indicating how long (in milliseconds) the\nclient MAY cache this response before re-fetching. Semantics are\nanalogous to HTTP Cache-Control max-age.\n\n- If 0, The response SHOULD be considered immediately stale,\n The client MAY re-fetch every time the result is needed.\n- If positive, the client SHOULD consider the result fresh for this many\n milliseconds after receiving the response.", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "cacheScope", + "prompts", + "resultType", + "ttlMs" + ], + "type": "object" + }, + "ListPromptsResultResponse": { + "description": "A successful response from the server for a {@link ListPromptsRequestprompts/list} request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "$ref": "#/$defs/ListPromptsResult" + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, + "ListResourceTemplatesRequest": { + "description": "Sent from the client to request a list of resource templates the server has.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "resources/templates/list", + "type": "string" + }, + "params": { + "$ref": "#/$defs/PaginatedRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "ListResourceTemplatesResult": { + "description": "The result returned by the server for a {@link ListResourceTemplatesRequestresources/templates/list} request.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "cacheScope": { + "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.", + "enum": [ + "private", + "public" + ], + "type": "string" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "resourceTemplates": { + "items": { + "$ref": "#/$defs/ResourceTemplate" + }, + "type": "array" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + }, + "ttlMs": { + "description": "A hint from the server indicating how long (in milliseconds) the\nclient MAY cache this response before re-fetching. Semantics are\nanalogous to HTTP Cache-Control max-age.\n\n- If 0, The response SHOULD be considered immediately stale,\n The client MAY re-fetch every time the result is needed.\n- If positive, the client SHOULD consider the result fresh for this many\n milliseconds after receiving the response.", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "cacheScope", + "resourceTemplates", + "resultType", + "ttlMs" + ], + "type": "object" + }, + "ListResourceTemplatesResultResponse": { + "description": "A successful response from the server for a {@link ListResourceTemplatesRequestresources/templates/list} request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "$ref": "#/$defs/ListResourceTemplatesResult" + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, + "ListResourcesRequest": { + "description": "Sent from the client to request a list of resources the server has.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "resources/list", + "type": "string" + }, + "params": { + "$ref": "#/$defs/PaginatedRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "ListResourcesResult": { + "description": "The result returned by the server for a {@link ListResourcesRequestresources/list} request.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "cacheScope": { + "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.", + "enum": [ + "private", + "public" + ], + "type": "string" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "resources": { + "items": { + "$ref": "#/$defs/Resource" + }, + "type": "array" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + }, + "ttlMs": { + "description": "A hint from the server indicating how long (in milliseconds) the\nclient MAY cache this response before re-fetching. Semantics are\nanalogous to HTTP Cache-Control max-age.\n\n- If 0, The response SHOULD be considered immediately stale,\n The client MAY re-fetch every time the result is needed.\n- If positive, the client SHOULD consider the result fresh for this many\n milliseconds after receiving the response.", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "cacheScope", + "resources", + "resultType", + "ttlMs" + ], + "type": "object" + }, + "ListResourcesResultResponse": { + "description": "A successful response from the server for a {@link ListResourcesRequestresources/list} request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "$ref": "#/$defs/ListResourcesResult" + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, + "ListRootsRequest": { + "description": "Sent from the server to request a list of root URIs from the client. Roots allow\nservers to ask for specific directories or files to operate on. A common example\nfor roots is providing a set of repositories or directories a server should operate\non.\n\nThis request is typically used when the server needs to understand the file system\nstructure or access specific locations that the client has permission to read from.", + "properties": { + "method": { + "const": "roots/list", + "type": "string" + }, + "params": { + "$ref": "#/$defs/RequestParams" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "ListRootsResult": { + "description": "The result returned by the client for a {@link ListRootsRequestroots/list} request.\nThis result contains an array of {@link Root} objects, each representing a root directory\nor file that the server can operate on.", + "properties": { + "roots": { + "items": { + "$ref": "#/$defs/Root" + }, + "type": "array" + } + }, + "required": [ + "roots" + ], + "type": "object" + }, + "ListToolsRequest": { + "description": "Sent from the client to request a list of tools the server has.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "tools/list", + "type": "string" + }, + "params": { + "$ref": "#/$defs/PaginatedRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "ListToolsResult": { + "description": "The result returned by the server for a {@link ListToolsRequesttools/list} request.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "cacheScope": { + "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.", + "enum": [ + "private", + "public" + ], + "type": "string" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + }, + "tools": { + "items": { + "$ref": "#/$defs/Tool" + }, + "type": "array" + }, + "ttlMs": { + "description": "A hint from the server indicating how long (in milliseconds) the\nclient MAY cache this response before re-fetching. Semantics are\nanalogous to HTTP Cache-Control max-age.\n\n- If 0, The response SHOULD be considered immediately stale,\n The client MAY re-fetch every time the result is needed.\n- If positive, the client SHOULD consider the result fresh for this many\n milliseconds after receiving the response.", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "cacheScope", + "resultType", + "tools", + "ttlMs" + ], + "type": "object" + }, + "ListToolsResultResponse": { + "description": "A successful response from the server for a {@link ListToolsRequesttools/list} request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "$ref": "#/$defs/ListToolsResult" + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, + "LoggingLevel": { + "description": "The severity of a log message.\n\nThese map to syslog message severities, as specified in RFC-5424:\nhttps://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1", + "enum": [ + "alert", + "critical", + "debug", + "emergency", + "error", + "info", + "notice", + "warning" + ], + "type": "string" + }, + "LoggingMessageNotification": { + "description": "JSONRPCNotification of a log message passed from server to client. The client opts in by setting `\"io.modelcontextprotocol/logLevel\"` in a request's `_meta`.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/message", + "type": "string" + }, + "params": { + "$ref": "#/$defs/LoggingMessageNotificationParams" + } + }, + "required": [ + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "LoggingMessageNotificationParams": { + "description": "Parameters for a `notifications/message` notification.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "data": { + "description": "The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here." + }, + "level": { + "$ref": "#/$defs/LoggingLevel", + "description": "The severity of this log message." + }, + "logger": { + "description": "An optional name of the logger issuing this message.", + "type": "string" + } + }, + "required": [ + "data", + "level" + ], + "type": "object" + }, + "MetaObject": { + "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- Implementations SHOULD use reverse DNS notation (e.g., `com.example/` rather than `example.com/`).\n- Any prefix where the second label is `modelcontextprotocol` or `mcp` is **reserved** for MCP use. For example: `io.modelcontextprotocol/`, `dev.mcp/`, `org.modelcontextprotocol.api/`, and `com.mcp.tools/` are all reserved. However, `com.example.mcp/` is NOT reserved, as the second label is `example`.\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" + }, + "MethodNotFoundError": { + "description": "A JSON-RPC error indicating that the requested method does not exist or is not available.\n\nIn MCP, a server returns this error when a client invokes a method the server does not implement — either a genuinely unknown method, or one gated behind a server capability the server did not advertise (e.g., calling `prompts/list` when the `prompts` capability was not advertised).\n\nA request that requires a client capability the client did not declare is signalled instead by {@link MissingRequiredClientCapabilityError} (`-32003`).", + "properties": { + "code": { + "const": -32601, + "description": "The error type that occurred.", + "type": "integer" + }, + "data": { + "description": "Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.)." + }, + "message": { + "description": "A short description of the error. The message SHOULD be limited to a concise single sentence.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" + }, + "MissingRequiredClientCapabilityError": { + "description": "Returned when processing a request requires a capability the client did not\ndeclare in `clientCapabilities`. For HTTP, the response status code MUST be\n`400 Bad Request`.", + "properties": { + "error": { + "allOf": [ + { + "$ref": "#/$defs/Error" + }, + { + "properties": { + "code": { + "const": -32003, + "type": "integer" + }, + "data": { + "properties": { + "requiredCapabilities": { + "$ref": "#/$defs/ClientCapabilities", + "description": "The capabilities the server requires from the client to process this request." + } + }, + "required": [ + "requiredCapabilities" + ], + "type": "object" + } + }, + "required": [ + "code", + "data" + ], + "type": "object" + } + ] + }, + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + } + }, + "required": [ + "error", + "jsonrpc" + ], + "type": "object" + }, + "ModelHint": { + "description": "Hints to use for model selection.\n\nKeys not declared here are currently left unspecified by the spec and are up\nto the client to interpret.", + "properties": { + "name": { + "description": "A hint for a model name.\n\nThe client SHOULD treat this as a substring of a model name; for example:\n - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022`\n - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc.\n - `claude` should match any Claude model\n\nThe client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example:\n - `gemini-1.5-flash` could match `claude-3-haiku-20240307`", + "type": "string" + } + }, + "type": "object" + }, + "ModelPreferences": { + "description": "The server's preferences for model selection, requested of the client during sampling.\n\nBecause LLMs can vary along multiple dimensions, choosing the \"best\" model is\nrarely straightforward. Different models excel in different areas—some are\nfaster but less capable, others are more capable but more expensive, and so\non. This interface allows servers to express their priorities across multiple\ndimensions to help clients make an appropriate selection for their use case.\n\nThese preferences are always advisory. The client MAY ignore them. It is also\nup to the client to decide how to interpret these preferences and how to\nbalance them against other considerations.", + "properties": { + "costPriority": { + "description": "How much to prioritize cost when selecting a model. A value of 0 means cost\nis not important, while a value of 1 means cost is the most important\nfactor.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "hints": { + "description": "Optional hints to use for model selection.\n\nIf multiple hints are specified, the client MUST evaluate them in order\n(such that the first match is taken).\n\nThe client SHOULD prioritize these hints over the numeric priorities, but\nMAY still use the priorities to select from ambiguous matches.", + "items": { + "$ref": "#/$defs/ModelHint" + }, + "type": "array" + }, + "intelligencePriority": { + "description": "How much to prioritize intelligence and capabilities when selecting a\nmodel. A value of 0 means intelligence is not important, while a value of 1\nmeans intelligence is the most important factor.", + "maximum": 1, + "minimum": 0, + "type": "number" + }, + "speedPriority": { + "description": "How much to prioritize sampling speed (latency) when selecting a model. A\nvalue of 0 means speed is not important, while a value of 1 means speed is\nthe most important factor.", + "maximum": 1, + "minimum": 0, + "type": "number" + } + }, + "type": "object" + }, + "MultiSelectEnumSchema": { + "anyOf": [ + { + "$ref": "#/$defs/UntitledMultiSelectEnumSchema" + }, + { + "$ref": "#/$defs/TitledMultiSelectEnumSchema" + } + ] + }, + "Notification": { + "properties": { + "method": { + "type": "string" + }, + "params": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "NotificationParams": { + "description": "Common params for any notification.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + } + }, + "type": "object" + }, + "NumberSchema": { + "properties": { + "default": { + "type": "number" + }, + "description": { + "type": "string" + }, + "maximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "title": { + "type": "string" + }, + "type": { + "enum": [ + "integer", + "number" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "PaginatedRequest": { + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "type": "string" + }, + "params": { + "$ref": "#/$defs/PaginatedRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "PaginatedRequestParams": { + "description": "Common params for paginated requests.", + "properties": { + "_meta": { + "$ref": "#/$defs/RequestMetaObject" + }, + "cursor": { + "description": "An opaque token representing the current pagination position.\nIf provided, the server should return results starting after this cursor.", + "type": "string" + } + }, + "required": [ + "_meta" + ], + "type": "object" + }, + "PaginatedResult": { + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "nextCursor": { + "description": "An opaque token representing the pagination position after the last returned result.\nIf present, there may be more results available.", + "type": "string" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + } + }, + "required": [ + "resultType" + ], + "type": "object" + }, + "ParseError": { + "description": "A JSON-RPC error indicating that invalid JSON was received by the server. This error is returned when the server cannot parse the JSON text of a message.", + "properties": { + "code": { + "const": -32700, + "description": "The error type that occurred.", + "type": "integer" + }, + "data": { + "description": "Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.)." + }, + "message": { + "description": "A short description of the error. The message SHOULD be limited to a concise single sentence.", + "type": "string" + } + }, + "required": [ + "code", + "message" + ], + "type": "object" + }, + "PrimitiveSchemaDefinition": { + "anyOf": [ + { + "$ref": "#/$defs/StringSchema" + }, + { + "$ref": "#/$defs/NumberSchema" + }, + { + "$ref": "#/$defs/BooleanSchema" + }, + { + "$ref": "#/$defs/UntitledSingleSelectEnumSchema" + }, + { + "$ref": "#/$defs/TitledSingleSelectEnumSchema" + }, + { + "$ref": "#/$defs/UntitledMultiSelectEnumSchema" + }, + { + "$ref": "#/$defs/TitledMultiSelectEnumSchema" + }, + { + "$ref": "#/$defs/LegacyTitledEnumSchema" + } + ], + "description": "Restricted schema definitions that only allow primitive types\nwithout nested objects or arrays." + }, + "ProgressNotification": { + "description": "An out-of-band notification used to inform the receiver of a progress update for a long-running request.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/progress", + "type": "string" + }, + "params": { + "$ref": "#/$defs/ProgressNotificationParams" + } + }, + "required": [ + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "ProgressNotificationParams": { + "description": "Parameters for a {@link ProgressNotificationnotifications/progress} notification.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "message": { + "description": "An optional message describing the current progress.", + "type": "string" + }, + "progress": { + "description": "The progress thus far. This should increase every time progress is made, even if the total is unknown.", + "type": "number" + }, + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "The progress token which was given in the initial request, used to associate this notification with the request that is proceeding." + }, + "total": { + "description": "Total number of items to process (or total progress required), if known.", + "type": "number" + } + }, + "required": [ + "progress", + "progressToken" + ], + "type": "object" + }, + "ProgressToken": { + "description": "A progress token, used to associate progress notifications with the original request.", + "type": [ + "string", + "integer" + ] + }, + "Prompt": { + "description": "A prompt or prompt template that the server offers.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "arguments": { + "description": "A list of arguments to use for templating the prompt.", + "items": { + "$ref": "#/$defs/PromptArgument" + }, + "type": "array" + }, + "description": { + "description": "An optional description of what this prompt provides", + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for {@link Tool},\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "PromptArgument": { + "description": "Describes an argument that a prompt can accept.", + "properties": { + "description": { + "description": "A human-readable description of the argument.", + "type": "string" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "required": { + "description": "Whether this argument must be provided.", + "type": "boolean" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for {@link Tool},\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "PromptListChangedNotification": { + "description": "An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/prompts/list_changed", + "type": "string" + }, + "params": { + "$ref": "#/$defs/NotificationParams" + } + }, + "required": [ + "jsonrpc", + "method" + ], + "type": "object" + }, + "PromptMessage": { + "description": "Describes a message returned as part of a prompt.\n\nThis is similar to {@link SamplingMessage}, but also supports the embedding of\nresources from the MCP server.", + "properties": { + "content": { + "$ref": "#/$defs/ContentBlock" + }, + "role": { + "$ref": "#/$defs/Role" + } + }, + "required": [ + "content", + "role" + ], + "type": "object" + }, + "PromptReference": { + "description": "Identifies a prompt.", + "properties": { + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for {@link Tool},\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + }, + "type": { + "const": "ref/prompt", + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object" + }, + "ReadResourceRequest": { + "description": "Sent from the client to the server, to read a specific resource URI.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "resources/read", + "type": "string" + }, + "params": { + "$ref": "#/$defs/ReadResourceRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "ReadResourceRequestParams": { + "description": "Parameters for a `resources/read` request.", + "properties": { + "_meta": { + "$ref": "#/$defs/RequestMetaObject" + }, + "inputResponses": { + "$ref": "#/$defs/InputResponses" + }, + "requestState": { + "type": "string" + }, + "uri": { + "description": "The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "_meta", + "uri" + ], + "type": "object" + }, + "ReadResourceResult": { + "description": "The result returned by the server for a {@link ReadResourceRequestresources/read} request.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "cacheScope": { + "description": "Indicates the intended scope of the cached response, analogous to HTTP\n`Cache-Control: public` vs `Cache-Control: private`.\n\n- `\"public\"`: Any client or intermediary (e.g., shared gateway, proxy)\n MAY cache the response and serve it to any user.\n- `\"private\"`: Only the requesting user's client MAY cache the response.\n Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached\n copy to a different user.", + "enum": [ + "private", + "public" + ], + "type": "string" + }, + "contents": { + "items": { + "anyOf": [ + { + "$ref": "#/$defs/TextResourceContents" + }, + { + "$ref": "#/$defs/BlobResourceContents" + } + ] + }, + "type": "array" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + }, + "ttlMs": { + "description": "A hint from the server indicating how long (in milliseconds) the\nclient MAY cache this response before re-fetching. Semantics are\nanalogous to HTTP Cache-Control max-age.\n\n- If 0, The response SHOULD be considered immediately stale,\n The client MAY re-fetch every time the result is needed.\n- If positive, the client SHOULD consider the result fresh for this many\n milliseconds after receiving the response.", + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "cacheScope", + "contents", + "resultType", + "ttlMs" + ], + "type": "object" + }, + "ReadResourceResultResponse": { + "description": "A successful response from the server for a {@link ReadResourceRequestresources/read} request.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "result": { + "anyOf": [ + { + "$ref": "#/$defs/InputRequiredResult" + }, + { + "$ref": "#/$defs/ReadResourceResult" + } + ] + } + }, + "required": [ + "id", + "jsonrpc", + "result" + ], + "type": "object" + }, + "Request": { + "properties": { + "method": { + "type": "string" + }, + "params": { + "additionalProperties": {}, + "type": "object" + } + }, + "required": [ + "method" + ], + "type": "object" + }, + "RequestId": { + "description": "A uniquely identifying ID for a request in JSON-RPC.", + "type": [ + "string", + "integer" + ] + }, + "RequestMetaObject": { + "description": "Extends {@link MetaObject} with additional request-specific fields. All key naming rules from `MetaObject` apply.", + "properties": { + "io.modelcontextprotocol/clientCapabilities": { + "$ref": "#/$defs/ClientCapabilities", + "description": "The client's capabilities for this specific request. Required.\n\nCapabilities are declared per-request rather than once at initialization;\nan empty object means the client supports no optional capabilities.\nServers MUST NOT infer capabilities from prior requests." + }, + "io.modelcontextprotocol/clientInfo": { + "$ref": "#/$defs/Implementation", + "description": "Identifies the client software making the request. Required.\n\nThe {@link Implementation} schema requires `name` and `version`; other\nfields are optional." + }, + "io.modelcontextprotocol/logLevel": { + "$ref": "#/$defs/LoggingLevel", + "description": "The desired log level for this request. Optional.\n\nIf absent, the server MUST NOT send any {@link LoggingMessageNotificationnotifications/message}\nnotifications for this request. The client opts in to log messages by\nexplicitly setting a level. Replaces the former `logging/setLevel` RPC." + }, + "io.modelcontextprotocol/protocolVersion": { + "description": "The MCP Protocol Version being used for this request. Required.\n\nFor the HTTP transport, this value MUST match the `MCP-Protocol-Version`\nheader; otherwise the server MUST return a `400 Bad Request`. If the\nserver does not support the requested version, it MUST return an\n{@link UnsupportedProtocolVersionError}.", + "type": "string" + }, + "progressToken": { + "$ref": "#/$defs/ProgressToken", + "description": "If specified, the caller is requesting out-of-band progress notifications for this request (as represented by {@link ProgressNotificationnotifications/progress}). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications." + } + }, + "required": [ + "io.modelcontextprotocol/clientCapabilities", + "io.modelcontextprotocol/clientInfo", + "io.modelcontextprotocol/protocolVersion" + ], + "type": "object" + }, + "RequestParams": { + "description": "Common params for any request.", + "properties": { + "_meta": { + "$ref": "#/$defs/RequestMetaObject" + } + }, + "required": [ + "_meta" + ], + "type": "object" + }, + "Resource": { + "description": "A known resource that the server is capable of reading.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "description": { + "description": "A description of what this resource represents.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "size": { + "description": "The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.\n\nThis can be used by Hosts to display file sizes and estimate context window usage.", + "type": "integer" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for {@link Tool},\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "name", + "uri" + ], + "type": "object" + }, + "ResourceContents": { + "description": "The contents of a specific resource or sub-resource.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "ResourceLink": { + "description": "A resource that the server is capable of reading, included in a prompt or tool call result.\n\nNote: resource links returned by tools are not guaranteed to appear in the results of {@link ListResourcesRequestresources/list} requests.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "description": { + "description": "A description of what this resource represents.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "size": { + "description": "The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known.\n\nThis can be used by Hosts to display file sizes and estimate context window usage.", + "type": "integer" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for {@link Tool},\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + }, + "type": { + "const": "resource_link", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "name", + "type", + "uri" + ], + "type": "object" + }, + "ResourceListChangedNotification": { + "description": "An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/resources/list_changed", + "type": "string" + }, + "params": { + "$ref": "#/$defs/NotificationParams" + } + }, + "required": [ + "jsonrpc", + "method" + ], + "type": "object" + }, + "ResourceRequestParams": { + "description": "Common params for resource-related requests.", + "properties": { + "_meta": { + "$ref": "#/$defs/RequestMetaObject" + }, + "uri": { + "description": "The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "_meta", + "uri" + ], + "type": "object" + }, + "ResourceTemplate": { + "description": "A template description for resources available on the server.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "description": { + "description": "A description of what this template is for.\n\nThis can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a \"hint\" to the model.", + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "mimeType": { + "description": "The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type.", + "type": "string" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for {@link Tool},\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + }, + "uriTemplate": { + "description": "A URI template (according to RFC 6570) that can be used to construct resource URIs.", + "format": "uri-template", + "type": "string" + } + }, + "required": [ + "name", + "uriTemplate" + ], + "type": "object" + }, + "ResourceTemplateReference": { + "description": "A reference to a resource or resource template definition.", + "properties": { + "type": { + "const": "ref/resource", + "type": "string" + }, + "uri": { + "description": "The URI or URI template of the resource.", + "format": "uri-template", + "type": "string" + } + }, + "required": [ + "type", + "uri" + ], + "type": "object" + }, + "ResourceUpdatedNotification": { + "description": "A notification from the server to the client, informing it that a resource has changed and may need to be read again. This is only sent for resources the client opted in to via the `resourceSubscriptions` field of a {@link SubscriptionsListenRequestsubscriptions/listen} request.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/resources/updated", + "type": "string" + }, + "params": { + "$ref": "#/$defs/ResourceUpdatedNotificationParams" + } + }, + "required": [ + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "ResourceUpdatedNotificationParams": { + "description": "Parameters for a `notifications/resources/updated` notification.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "uri": { + "description": "The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "Result": { + "additionalProperties": {}, + "description": "Common result fields.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "resultType": { + "description": "Indicates the type of the result, which allows the client to determine\nhow to parse the result object.\n\nServers implementing this protocol version MUST include this field.\nFor backward compatibility, when a client receives a result from a\nserver implementing an earlier protocol version (which does not include\n`resultType`), the client MUST treat the absent field as `\"complete\"`.", + "type": "string" + } + }, + "required": [ + "resultType" + ], + "type": "object" + }, + "ResultType": { + "description": "Indicates the type of a {@link Result} object, allowing the client to\ndetermine how to parse the response.\n\ncomplete - the request completed successfully and the result contains the final content.\ninput_required - the request requires additional input and the result contains an {@link InputRequiredResult} object with instructions for the client to provide additional input before retrying the original request.", + "type": "string" + }, + "Role": { + "description": "The sender or recipient of messages and data in a conversation.", + "enum": [ + "assistant", + "user" + ], + "type": "string" + }, + "Root": { + "description": "Represents a root directory or file that the server can operate on.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "name": { + "description": "An optional name for the root. This can be used to provide a human-readable\nidentifier for the root, which may be useful for display purposes or for\nreferencing the root in other parts of the application.", + "type": "string" + }, + "uri": { + "description": "The URI identifying the root. This *must* start with `file://` for now.\nThis restriction may be relaxed in future versions of the protocol to allow\nother URI schemes.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "uri" + ], + "type": "object" + }, + "SamplingMessage": { + "description": "Describes a message issued to or received from an LLM API.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "content": { + "anyOf": [ + { + "$ref": "#/$defs/TextContent" + }, + { + "$ref": "#/$defs/ImageContent" + }, + { + "$ref": "#/$defs/AudioContent" + }, + { + "$ref": "#/$defs/ToolUseContent" + }, + { + "$ref": "#/$defs/ToolResultContent" + }, + { + "items": { + "$ref": "#/$defs/SamplingMessageContentBlock" + }, + "type": "array" + } + ] + }, + "role": { + "$ref": "#/$defs/Role" + } + }, + "required": [ + "content", + "role" + ], + "type": "object" + }, + "SamplingMessageContentBlock": { + "anyOf": [ + { + "$ref": "#/$defs/TextContent" + }, + { + "$ref": "#/$defs/ImageContent" + }, + { + "$ref": "#/$defs/AudioContent" + }, + { + "$ref": "#/$defs/ToolUseContent" + }, + { + "$ref": "#/$defs/ToolResultContent" + } + ] + }, + "ServerCapabilities": { + "description": "Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities.", + "properties": { + "completions": { + "$ref": "#/$defs/JSONObject", + "description": "Present if the server supports argument autocompletion suggestions." + }, + "experimental": { + "additionalProperties": { + "$ref": "#/$defs/JSONObject" + }, + "description": "Experimental, non-standard capabilities that the server supports.", + "type": "object" + }, + "extensions": { + "additionalProperties": { + "$ref": "#/$defs/JSONObject" + }, + "description": "Optional MCP extensions that the server supports. Keys are extension identifiers\n(e.g., \"io.modelcontextprotocol/tasks\"), and values are per-extension settings\nobjects. An empty object indicates support with no settings.", + "type": "object" + }, + "logging": { + "$ref": "#/$defs/JSONObject", + "description": "Present if the server supports sending log messages to the client." + }, + "prompts": { + "description": "Present if the server offers any prompt templates.", + "properties": { + "listChanged": { + "description": "Whether this server supports notifications for changes to the prompt list.", + "type": "boolean" + } + }, + "type": "object" + }, + "resources": { + "description": "Present if the server offers any resources to read.", + "properties": { + "listChanged": { + "description": "Whether this server supports notifications for changes to the resource list.", + "type": "boolean" + }, + "subscribe": { + "description": "Whether this server supports subscribing to resource updates.", + "type": "boolean" + } + }, + "type": "object" + }, + "tools": { + "description": "Present if the server offers any tools to call.", + "properties": { + "listChanged": { + "description": "Whether this server supports notifications for changes to the tool list.", + "type": "boolean" + } + }, + "type": "object" + } + }, + "type": "object" + }, + "ServerNotification": { + "anyOf": [ + { + "$ref": "#/$defs/CancelledNotification" + }, + { + "$ref": "#/$defs/ProgressNotification" + }, + { + "$ref": "#/$defs/ResourceListChangedNotification" + }, + { + "$ref": "#/$defs/SubscriptionsAcknowledgedNotification" + }, + { + "$ref": "#/$defs/ResourceUpdatedNotification" + }, + { + "$ref": "#/$defs/PromptListChangedNotification" + }, + { + "$ref": "#/$defs/ToolListChangedNotification" + }, + { + "$ref": "#/$defs/LoggingMessageNotification" + }, + { + "$ref": "#/$defs/ElicitationCompleteNotification" + } + ] + }, + "ServerResult": { + "anyOf": [ + { + "$ref": "#/$defs/Result" + }, + { + "$ref": "#/$defs/InputRequiredResult" + }, + { + "$ref": "#/$defs/DiscoverResult" + }, + { + "$ref": "#/$defs/ListResourcesResult" + }, + { + "$ref": "#/$defs/ListResourceTemplatesResult" + }, + { + "$ref": "#/$defs/ReadResourceResult" + }, + { + "$ref": "#/$defs/ListPromptsResult" + }, + { + "$ref": "#/$defs/GetPromptResult" + }, + { + "$ref": "#/$defs/ListToolsResult" + }, + { + "$ref": "#/$defs/CallToolResult" + }, + { + "$ref": "#/$defs/CompleteResult" + } + ] + }, + "SingleSelectEnumSchema": { + "anyOf": [ + { + "$ref": "#/$defs/UntitledSingleSelectEnumSchema" + }, + { + "$ref": "#/$defs/TitledSingleSelectEnumSchema" + } + ] + }, + "StringSchema": { + "properties": { + "default": { + "type": "string" + }, + "description": { + "type": "string" + }, + "format": { + "enum": [ + "date", + "date-time", + "email", + "uri" + ], + "type": "string" + }, + "maxLength": { + "type": "integer" + }, + "minLength": { + "type": "integer" + }, + "title": { + "type": "string" + }, + "type": { + "const": "string", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "SubscriptionFilter": { + "description": "The set of notification types a client may opt in to on a\n{@link SubscriptionsListenRequestsubscriptions/listen} request.\n\nEach notification type is **opt-in**; the server **MUST NOT** send\nnotification types the client has not explicitly requested here.", + "properties": { + "promptsListChanged": { + "description": "If true, receive {@link PromptListChangedNotificationnotifications/prompts/list_changed}.", + "type": "boolean" + }, + "resourceSubscriptions": { + "description": "Subscribe to {@link ResourceUpdatedNotificationnotifications/resources/updated} for these resource URIs.\nReplaces the former `resources/subscribe` RPC.", + "items": { + "type": "string" + }, + "type": "array" + }, + "resourcesListChanged": { + "description": "If true, receive {@link ResourceListChangedNotificationnotifications/resources/list_changed}.", + "type": "boolean" + }, + "toolsListChanged": { + "description": "If true, receive {@link ToolListChangedNotificationnotifications/tools/list_changed}.", + "type": "boolean" + } + }, + "type": "object" + }, + "SubscriptionsAcknowledgedNotification": { + "description": "Sent by the server as the first message on a\n{@link SubscriptionsListenRequestsubscriptions/listen} stream to acknowledge\nthat the subscription has been established and to report which notification\ntypes it agreed to honor.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/subscriptions/acknowledged", + "type": "string" + }, + "params": { + "$ref": "#/$defs/SubscriptionsAcknowledgedNotificationParams" + } + }, + "required": [ + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "SubscriptionsAcknowledgedNotificationParams": { + "description": "Parameters for a {@link SubscriptionsAcknowledgedNotificationnotifications/subscriptions/acknowledged} notification.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "notifications": { + "$ref": "#/$defs/SubscriptionFilter", + "description": "The subset of requested notification types the server agreed to honor.\nOnly includes notification types the server actually supports; if the\nclient requested an unsupported type (e.g., `promptsListChanged` when\nthe server has no prompts), it is omitted from this set." + } + }, + "required": [ + "notifications" + ], + "type": "object" + }, + "SubscriptionsListenRequest": { + "description": "Sent from the client to open a long-lived channel for receiving notifications\noutside the context of a specific request. Replaces the previous HTTP GET\nendpoint and ensures consistent behavior between HTTP and STDIO.", + "properties": { + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "subscriptions/listen", + "type": "string" + }, + "params": { + "$ref": "#/$defs/SubscriptionsListenRequestParams" + } + }, + "required": [ + "id", + "jsonrpc", + "method", + "params" + ], + "type": "object" + }, + "SubscriptionsListenRequestParams": { + "description": "Parameters for a {@link SubscriptionsListenRequestsubscriptions/listen} request.", + "properties": { + "_meta": { + "$ref": "#/$defs/RequestMetaObject" + }, + "notifications": { + "$ref": "#/$defs/SubscriptionFilter", + "description": "The notifications the client opts in to on this stream. The server\n**MUST NOT** send notification types the client has not explicitly\nrequested." + } + }, + "required": [ + "_meta", + "notifications" + ], + "type": "object" + }, + "TextContent": { + "description": "Text provided to or from an LLM.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "annotations": { + "$ref": "#/$defs/Annotations", + "description": "Optional annotations for the client." + }, + "text": { + "description": "The text content of the message.", + "type": "string" + }, + "type": { + "const": "text", + "type": "string" + } + }, + "required": [ + "text", + "type" + ], + "type": "object" + }, + "TextResourceContents": { + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "mimeType": { + "description": "The MIME type of this resource, if known.", + "type": "string" + }, + "text": { + "description": "The text of the item. This must only be set if the item can actually be represented as text (not binary data).", + "type": "string" + }, + "uri": { + "description": "The URI of this resource.", + "format": "uri", + "type": "string" + } + }, + "required": [ + "text", + "uri" + ], + "type": "object" + }, + "TitledMultiSelectEnumSchema": { + "description": "Schema for multiple-selection enumeration with display titles for each option.", + "properties": { + "default": { + "description": "Optional default value.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": { + "description": "Optional description for the enum field.", + "type": "string" + }, + "items": { + "description": "Schema for array items with enum options and display labels.", + "properties": { + "anyOf": { + "description": "Array of enum options with values and display labels.", + "items": { + "properties": { + "const": { + "description": "The constant enum value.", + "type": "string" + }, + "title": { + "description": "Display title for this option.", + "type": "string" + } + }, + "required": [ + "const", + "title" + ], + "type": "object" + }, + "type": "array" + } + }, + "required": [ + "anyOf" + ], + "type": "object" + }, + "maxItems": { + "description": "Maximum number of items to select.", + "type": "integer" + }, + "minItems": { + "description": "Minimum number of items to select.", + "type": "integer" + }, + "title": { + "description": "Optional title for the enum field.", + "type": "string" + }, + "type": { + "const": "array", + "type": "string" + } + }, + "required": [ + "items", + "type" + ], + "type": "object" + }, + "TitledSingleSelectEnumSchema": { + "description": "Schema for single-selection enumeration with display titles for each option.", + "properties": { + "default": { + "description": "Optional default value.", + "type": "string" + }, + "description": { + "description": "Optional description for the enum field.", + "type": "string" + }, + "oneOf": { + "description": "Array of enum options with values and display labels.", + "items": { + "properties": { + "const": { + "description": "The enum value.", + "type": "string" + }, + "title": { + "description": "Display label for this option.", + "type": "string" + } + }, + "required": [ + "const", + "title" + ], + "type": "object" + }, + "type": "array" + }, + "title": { + "description": "Optional title for the enum field.", + "type": "string" + }, + "type": { + "const": "string", + "type": "string" + } + }, + "required": [ + "oneOf", + "type" + ], + "type": "object" + }, + "Tool": { + "description": "Definition for a tool the client can call.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject" + }, + "annotations": { + "$ref": "#/$defs/ToolAnnotations", + "description": "Optional additional tool information.\n\nDisplay name precedence order is: `title`, `annotations.title`, then `name`." + }, + "description": { + "description": "A human-readable description of the tool.\n\nThis can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a \"hint\" to the model.", + "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 MIME types:\n- `image/png` - PNG images (safe, universal compatibility)\n- `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility)\n\nClients that support rendering icons SHOULD also support:\n- `image/svg+xml` - SVG images (scalable but requires security precautions)\n- `image/webp` - WebP images (modern, efficient format)", + "items": { + "$ref": "#/$defs/Icon" + }, + "type": "array" + }, + "inputSchema": { + "additionalProperties": {}, + "description": "A JSON Schema object defining the expected parameters for the tool.\n\nTool arguments are always JSON objects, so `type: \"object\"` is required at the root.\nBeyond that, any JSON Schema 2020-12 keyword may appear alongside `type` — including\ncomposition keywords (`oneOf`, `anyOf`, `allOf`, `not`), conditional keywords\n(`if`/`then`/`else`), reference keywords (`$ref`, `$defs`, `$anchor`), and any other\nstandard validation or annotation keywords.\n\nDefaults to JSON Schema 2020-12 when no explicit `$schema` is provided.", + "properties": { + "$schema": { + "type": "string" + }, + "type": { + "const": "object", + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "name": { + "description": "Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present).", + "type": "string" + }, + "outputSchema": { + "additionalProperties": {}, + "description": "An optional JSON Schema object defining the structure of the tool's output returned in\nthe structuredContent field of a {@link CallToolResult}. This can be any valid JSON Schema 2020-12.\n\nDefaults to JSON Schema 2020-12 when no explicit `$schema` is provided.", + "properties": { + "$schema": { + "type": "string" + } + }, + "type": "object" + }, + "title": { + "description": "Intended for UI and end-user contexts — optimized to be human-readable and easily understood,\neven by those unfamiliar with domain-specific terminology.\n\nIf not provided, the name should be used for display (except for {@link Tool},\nwhere `annotations.title` should be given precedence over using `name`,\nif present).", + "type": "string" + } + }, + "required": [ + "inputSchema", + "name" + ], + "type": "object" + }, + "ToolAnnotations": { + "description": "Additional properties describing a {@link Tool} to clients.\n\nNOTE: all properties in `ToolAnnotations` are **hints**.\nThey are not guaranteed to provide a faithful description of\ntool behavior (including descriptive properties like `title`).\n\nClients should never make tool use decisions based on `ToolAnnotations`\nreceived from untrusted servers.", + "properties": { + "destructiveHint": { + "description": "If true, the tool may perform destructive updates to its environment.\nIf false, the tool performs only additive updates.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: true", + "type": "boolean" + }, + "idempotentHint": { + "description": "If true, calling the tool repeatedly with the same arguments\nwill have no additional effect on its environment.\n\n(This property is meaningful only when `readOnlyHint == false`)\n\nDefault: false", + "type": "boolean" + }, + "openWorldHint": { + "description": "If true, this tool may interact with an \"open world\" of external\nentities. If false, the tool's domain of interaction is closed.\nFor example, the world of a web search tool is open, whereas that\nof a memory tool is not.\n\nDefault: true", + "type": "boolean" + }, + "readOnlyHint": { + "description": "If true, the tool does not modify its environment.\n\nDefault: false", + "type": "boolean" + }, + "title": { + "description": "A human-readable title for the tool.", + "type": "string" + } + }, + "type": "object" + }, + "ToolChoice": { + "description": "Controls tool selection behavior for sampling requests.", + "properties": { + "mode": { + "description": "Controls the tool use ability of the model:\n- `\"auto\"`: Model decides whether to use tools (default)\n- `\"required\"`: Model MUST use at least one tool before completing\n- `\"none\"`: Model MUST NOT use any tools", + "enum": [ + "auto", + "none", + "required" + ], + "type": "string" + } + }, + "type": "object" + }, + "ToolListChangedNotification": { + "description": "An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client.", + "properties": { + "jsonrpc": { + "const": "2.0", + "type": "string" + }, + "method": { + "const": "notifications/tools/list_changed", + "type": "string" + }, + "params": { + "$ref": "#/$defs/NotificationParams" + } + }, + "required": [ + "jsonrpc", + "method" + ], + "type": "object" + }, + "ToolResultContent": { + "description": "The result of a tool use, provided by the user back to the assistant.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject", + "description": "Optional metadata about the tool result. Clients SHOULD preserve this field when\nincluding tool results in subsequent sampling requests to enable caching optimizations." + }, + "content": { + "description": "The unstructured result content of the tool use.\n\nThis has the same format as {@link CallToolResult.content} and can include text, images,\naudio, resource links, and embedded resources.", + "items": { + "$ref": "#/$defs/ContentBlock" + }, + "type": "array" + }, + "isError": { + "description": "Whether the tool use resulted in an error.\n\nIf true, the content typically describes the error that occurred.\nDefault: false", + "type": "boolean" + }, + "structuredContent": { + "description": "An optional structured result value.\n\nThis can be any JSON value (object, array, string, number, boolean, or null).\nIf the tool defined an {@link Tool.outputSchema}, this SHOULD conform to that schema." + }, + "toolUseId": { + "description": "The ID of the tool use this result corresponds to.\n\nThis MUST match the ID from a previous {@link ToolUseContent}.", + "type": "string" + }, + "type": { + "const": "tool_result", + "type": "string" + } + }, + "required": [ + "content", + "toolUseId", + "type" + ], + "type": "object" + }, + "ToolUseContent": { + "description": "A request from the assistant to call a tool.", + "properties": { + "_meta": { + "$ref": "#/$defs/MetaObject", + "description": "Optional metadata about the tool use. Clients SHOULD preserve this field when\nincluding tool uses in subsequent sampling requests to enable caching optimizations." + }, + "id": { + "description": "A unique identifier for this tool use.\n\nThis ID is used to match tool results to their corresponding tool uses.", + "type": "string" + }, + "input": { + "additionalProperties": {}, + "description": "The arguments to pass to the tool, conforming to the tool's input schema.", + "type": "object" + }, + "name": { + "description": "The name of the tool to call.", + "type": "string" + }, + "type": { + "const": "tool_use", + "type": "string" + } + }, + "required": [ + "id", + "input", + "name", + "type" + ], + "type": "object" + }, + "UnsupportedProtocolVersionError": { + "description": "Returned when the request's protocol version is unknown to the server or\nunsupported (e.g., a known experimental or draft version the server has\nchosen not to implement). For HTTP, the response status code MUST be\n`400 Bad Request`.", + "properties": { + "error": { + "allOf": [ + { + "$ref": "#/$defs/Error" + }, + { + "properties": { + "code": { + "const": -32004, + "type": "integer" + }, + "data": { + "properties": { + "requested": { + "description": "The protocol version that was requested by the client.", + "type": "string" + }, + "supported": { + "description": "Protocol versions the server supports. The client should choose a\nmutually supported version from this list and retry.", + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "requested", + "supported" + ], + "type": "object" + } + }, + "required": [ + "code", + "data" + ], + "type": "object" + } + ] + }, + "id": { + "$ref": "#/$defs/RequestId" + }, + "jsonrpc": { + "const": "2.0", + "type": "string" + } + }, + "required": [ + "error", + "jsonrpc" + ], + "type": "object" + }, + "UntitledMultiSelectEnumSchema": { + "description": "Schema for multiple-selection enumeration without display titles for options.", + "properties": { + "default": { + "description": "Optional default value.", + "items": { + "type": "string" + }, + "type": "array" + }, + "description": { + "description": "Optional description for the enum field.", + "type": "string" + }, + "items": { + "description": "Schema for the array items.", + "properties": { + "enum": { + "description": "Array of enum values to choose from.", + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "const": "string", + "type": "string" + } + }, + "required": [ + "enum", + "type" + ], + "type": "object" + }, + "maxItems": { + "description": "Maximum number of items to select.", + "type": "integer" + }, + "minItems": { + "description": "Minimum number of items to select.", + "type": "integer" + }, + "title": { + "description": "Optional title for the enum field.", + "type": "string" + }, + "type": { + "const": "array", + "type": "string" + } + }, + "required": [ + "items", + "type" + ], + "type": "object" + }, + "UntitledSingleSelectEnumSchema": { + "description": "Schema for single-selection enumeration without display titles for options.", + "properties": { + "default": { + "description": "Optional default value.", + "type": "string" + }, + "description": { + "description": "Optional description for the enum field.", + "type": "string" + }, + "enum": { + "description": "Array of enum values to choose from.", + "items": { + "type": "string" + }, + "type": "array" + }, + "title": { + "description": "Optional title for the enum field.", + "type": "string" + }, + "type": { + "const": "string", + "type": "string" + } + }, + "required": [ + "enum", + "type" + ], + "type": "object" + } + } +} diff --git a/schema/PINNED.json b/schema/PINNED.json new file mode 100644 index 0000000000..f99c75abf0 --- /dev/null +++ b/schema/PINNED.json @@ -0,0 +1,14 @@ +[ + { + "protocol_version": "2025-11-25", + "source_path_in_spec_repo": "schema/2025-11-25/schema.json", + "spec_commit": "6d441518de8a9d5adbab0b10a76a667a63f90665", + "sha256": "4e01628360a2149892eab8f298ceee626d24a58862184eb8ec85d95b8f353e31" + }, + { + "protocol_version": "2026-07-28", + "source_path_in_spec_repo": "schema/draft/schema.json", + "spec_commit": "6d441518de8a9d5adbab0b10a76a667a63f90665", + "sha256": "bce2e7c9622bb0b449475ba6d8d80228c71190a09250e75dabd502b280ecf3cb" + } +] diff --git a/schema/README.md b/schema/README.md new file mode 100644 index 0000000000..829a0109f1 --- /dev/null +++ b/schema/README.md @@ -0,0 +1,12 @@ +# Vendored protocol schemas + +JSON Schema files for each protocol version the SDK has a wire-shape surface +package for, vendored from the [spec repository] at the commit recorded in +`PINNED.json`. `scripts/gen_surface_types.py` reads these to regenerate +`src/mcp/types/v/__init__.py`; CI runs the generator with `--check`. + +To bump: drop the new `schema.json` here as `.json`, update +the matching entry in `PINNED.json` (commit + sha256), and run +`uv run --group codegen python scripts/gen_surface_types.py`. + +[spec repository]: https://github.com/modelcontextprotocol/modelcontextprotocol diff --git a/scripts/gen_surface_types.py b/scripts/gen_surface_types.py new file mode 100644 index 0000000000..5125933e4c --- /dev/null +++ b/scripts/gen_surface_types.py @@ -0,0 +1,972 @@ +"""Regenerate the per-version wire-shape surface packages from vendored schemas. + +The two surfaces under ``src/mcp/types/v/__init__.py`` are pure +generator output: this script is the only writer. For each entry of +``schema/PINNED.json`` it verifies the vendored ``schema/.json`` +against the recorded sha256, runs ``datamodel-code-generator`` (installed via +the ``codegen`` dependency group), applies the post-processing passes +documented on each pass function below, runs ruff, imports the candidate to +audit definition names / field requiredness / ``_meta`` openness, and writes +the result. + +Usage (from the repo root):: + + uv run --group codegen python scripts/gen_surface_types.py [--check] + +``--check`` regenerates to a temp location and diffs against the committed +files; non-zero exit (with a unified diff) on any drift. +""" + +from __future__ import annotations + +import argparse +import ast +import difflib +import hashlib +import importlib.util +import json +import re +import shutil +import subprocess +import sys +import tempfile +import textwrap +import typing +from dataclasses import dataclass +from pathlib import Path +from types import ModuleType +from typing import Any + +from pydantic import BaseModel + +REPO_ROOT = Path(__file__).resolve().parent.parent +SCHEMA_DIR = REPO_ROOT / "schema" +PINNED_PATH = SCHEMA_DIR / "PINNED.json" +SRC_TYPES_DIR = REPO_ROOT / "src" / "mcp" / "types" + +_SRC_BASE_CLASS = "mcp.types._wire_base.WireModel" + +# {@link Target} or {@link Target | label}: emit the label when present. +_JSDOC_LINK_RE = re.compile(r"\{@link\s+(?:[^}|]+?\s*\|\s*)?([^}|]+?)\s*\}") +_GENERATOR_BANNER_RE = re.compile(r"\A# generated by datamodel-codegen:\n(# filename: [^\n]*\n)?\n*") +_FIELD_ALIAS_RE = re.compile(r"alias=['\"]([^'\"]+)['\"]") + +# Wire names whose referenced classes stay extra="allow": schema-open +# pass-through interiors whose unknown keys must survive revalidation. +_OPEN_INTERIOR_FIELD_ALIASES = frozenset({"inputSchema", "outputSchema"}) +_OPEN_INTERIOR_COMMENT = " # Stays open: schema keywords beyond the declared properties ride extra fields.\n" +_SUBSCRIPTION_FILTER_COMMENT = " # Stays open: filter contents are extensible on the wire.\n" + +# Names a relocated cyclic-alias block may reference besides its own members: +# anything else means relocation could break runtime evaluation order. +_RELOCATABLE_NAMES = frozenset( + {"Annotated", "Any", "Literal", "None", "Optional", "Union"} + | {"bool", "bytes", "dict", "float", "frozenset", "int", "list", "set", "str", "tuple"} +) + +# Per-version module docstrings (the generator emits these verbatim). +SRC_HEADERS: dict[str, str] = { + "2025-11-25": '''\ +"""Internal wire-shape models for protocol 2025-11-25. Not part of the public API. + +Generated by `scripts/gen_surface_types.py` from `schema/2025-11-25.json` +@ {spec_commit}. Do not edit; regenerate. Serves inbound validation for every +protocol version through 2025-11-25 (each earlier schema is a strict subset of +this one). Models default to `extra="ignore"`; the few kept open are commented +in place. See `mcp.types._wire_base` and `mcp.types.methods`. +""" +''', + "2026-07-28": '''\ +"""Internal wire-shape models for protocol 2026-07-28. Not part of the public API. + +Generated by `scripts/gen_surface_types.py` from `schema/2026-07-28.json` +@ {spec_commit}. Do not edit; regenerate. Schema-exact validators that the +wire-method maps in `mcp.types.methods` point inbound 2026-07-28 validation at. +Models use `extra="ignore"` (unknown keys accepted and dropped) unless commented +otherwise; see `mcp.types._wire_base`. +""" +''', +} + +# schema.ts -> schema.json renders TypeScript ``number`` as JSON Schema +# ``integer`` at these sites, so the generator emits ``int``-only annotations +# that reject the floats schema.ts permits. Each entry rewrites one field's +# annotation back to what schema.ts declares. +_NUMBER_PATCH_NOTE = " # schema.json renders this `number` as integer; widen per schema.ts so floats validate.\n" +FIELD_TYPE_PATCHES: dict[str, list[tuple[str, str, str]]] = { + "2025-11-25": [ + ("ElicitResult", "content", "dict[str, list[str] | str | int | float | bool] | None"), + ("NumberSchema", "default", "int | float | None"), + ("NumberSchema", "maximum", "int | float | None"), + ("NumberSchema", "minimum", "int | float | None"), + ], + "2026-07-28": [ + ("ElicitResult", "content", "dict[str, list[str] | str | int | float | bool] | None"), + ], +} + +# Same defect for the recursive JSONValue alias (2026 only): schema.json's +# primitive arm is string|integer|boolean where schema.ts has all six JSON +# types. Rewrites the TypeAliasType value string. +ALIAS_TYPE_PATCHES: dict[str, dict[str, str]] = { + "2026-07-28": {"JSONValue": "JSONObject | list[JSONValue] | str | int | float | bool | None"}, +} + +# Hand-written union aliases the wire-method maps reference by value; the +# schema has no named definition for "everything tools/call may return", so the +# generator emits the union inline on the response model and these name it. +EPILOGUES: dict[str, str] = { + "2026-07-28": '''\ +AnyCallToolResult: TypeAlias = CallToolResult | InputRequiredResult +"""Named alias for `CallToolResultResponse.result` so the wire-method maps can reference it as a value.""" + +AnyGetPromptResult: TypeAlias = GetPromptResult | InputRequiredResult +"""Everything a `prompts/get` response's `result` may be at this version.""" + +AnyReadResourceResult: TypeAlias = ReadResourceResult | InputRequiredResult +"""Everything a `resources/read` response's `result` may be at this version.""" +''', +} + + +class PipelineError(Exception): + """A stage of the pipeline failed; the message says which and why.""" + + +@dataclass +class Entry: + """One PINNED.json entry.""" + + protocol_version: str + source_path_in_spec_repo: str + spec_commit: str + sha256: str + + @property + def module(self) -> str: + return "v" + self.protocol_version.replace("-", "_") + + @property + def schema_file(self) -> Path: + return SCHEMA_DIR / f"{self.protocol_version}.json" + + @property + def target(self) -> Path: + return SRC_TYPES_DIR / self.module / "__init__.py" + + +def load_pinned() -> list[Entry]: + """Read schema/PINNED.json and verify each vendored file's sha256.""" + with PINNED_PATH.open() as f: + raw: list[dict[str, str]] = json.load(f) + entries = [Entry(**item) for item in raw] + for entry in entries: + if not entry.schema_file.is_file(): + raise PipelineError(f"{entry.protocol_version}: vendored schema {entry.schema_file} is missing") + actual = hashlib.sha256(entry.schema_file.read_bytes()).hexdigest() + if actual != entry.sha256: + raise PipelineError( + f"{entry.protocol_version}: sha256 mismatch for {entry.schema_file.name} " + f"(PINNED.json={entry.sha256}, on disk={actual})" + ) + return entries + + +def run_generator(schema_path: Path, output_path: Path) -> None: + """Run datamodel-code-generator (from the ``codegen`` dependency group).""" + exe = shutil.which("datamodel-codegen") + cmd = [exe] if exe else ["uv", "run", "--group", "codegen", "datamodel-codegen"] + result = subprocess.run( + [ + *cmd, + "--input", + str(schema_path), + "--input-file-type", + "jsonschema", + "--output", + str(output_path), + "--output-model-type", + "pydantic_v2.BaseModel", + "--target-python-version", + "3.10", + "--base-class", + _SRC_BASE_CLASS, + "--snake-case-field", + "--remove-special-field-name-prefix", + "--use-annotated", + "--use-field-description", + "--use-schema-description", + "--enum-field-as-literal", + "all", + "--use-union-operator", + "--use-double-quotes", + "--extra-fields", + "allow", + "--disable-timestamp", + ], + capture_output=True, + text=True, + ) + if result.returncode != 0 or not output_path.is_file(): + raise PipelineError(f"datamodel-codegen failed:\n{result.stderr.strip()}") + + +def _end_lineno(node: ast.stmt) -> int: + """End line of a statement (ast fills end_lineno for parsed code).""" + end = node.end_lineno + if end is None: + raise PipelineError(f"ast node at line {node.lineno} has no end_lineno") + return end + + +def _collect_rootmodel_aliases(tree: ast.Module) -> dict[str, tuple[ast.ClassDef, str]]: + """Map class name -> (node, unparsed RootModel type argument) for RootModel subclasses.""" + aliases: dict[str, tuple[ast.ClassDef, str]] = {} + for node in tree.body: + if not isinstance(node, ast.ClassDef) or len(node.bases) != 1: + continue + base = node.bases[0] + if isinstance(base, ast.Subscript) and isinstance(base.value, ast.Name) and base.value.id == "RootModel": + aliases[node.name] = (node, ast.unparse(base.slice)) + return aliases + + +def _find_recursive_aliases(aliases: dict[str, tuple[ast.ClassDef, str]]) -> set[str]: + """Names of alias classes that participate in a reference cycle.""" + + def refs(type_expr: str) -> set[str]: + bare = type_expr.replace("'", " ").replace('"', " ") + return {name for name in aliases if re.search(rf"\b{name}\b", bare)} + + cyclic: set[str] = set() + for name, (_, type_expr) in aliases.items(): + seen: set[str] = set() + stack = list(refs(type_expr)) + while stack: + current = stack.pop() + if current == name: + cyclic.add(name) + break + if current in seen or current not in aliases: + continue + seen.add(current) + stack.extend(refs(aliases[current][1])) + return cyclic + + +def _alias_statement(name: str, node: ast.ClassDef, alias_type: str, *, string_form: bool) -> str: + """One converted alias definition (docstring preserved as a trailing string).""" + if string_form: + value = alias_type.replace("'", "").replace('"', "") + text = f'{name} = TypeAliasType("{name}", "{value}")\n' + else: + text = f'{name} = TypeAliasType("{name}", {alias_type})\n' + docstring = ast.get_docstring(node) + if docstring and '"""' not in docstring: + text += '"""' + docstring + '"""\n' + return text + + +def _emit_cyclic_block( + ordered: list[str], aliases: dict[str, tuple[ast.ClassDef, str]], alias_patches: dict[str, str] +) -> str: + """Emit a group of mutually recursive aliases in the one form pyright accepts. + + Self-referencing members go first as a whole-string value (lazily parsed), + the rest follow in expression form so quoted references point backwards; + the caller relocates the whole block to just after the imports. Any + ``alias_patches`` entry overrides the generator's value (always emitted + string-form, with a leading comment). + """ + + def has_self_ref(name: str) -> bool: + bare = aliases[name][1].replace("'", " ").replace('"', " ") + return re.search(rf"\b{name}\b", bare) is not None + + string_members = [n for n in ordered if has_self_ref(n)] + expr_members = [n for n in ordered if not has_self_ref(n)] + parts: list[str] = [] + for name in [*string_members, *expr_members]: + node, generated = aliases[name] + if name in alias_patches: + parts.append( + "# schema.json renders the primitive arm without float/null; widen per schema.ts.\n" + + _alias_statement(name, node, alias_patches[name], string_form=True) + ) + else: + parts.append(_alias_statement(name, node, generated, string_form=name in string_members)) + return "\n\n".join(parts) + + +def _group_is_self_contained(cyclic: set[str], aliases: dict[str, tuple[ast.ClassDef, str]]) -> bool: + """True if the cyclic group references only its own members and builtins.""" + allowed = cyclic | _RELOCATABLE_NAMES + for name in cyclic: + bare = aliases[name][1].replace("'", " ").replace('"', " ") + identifiers = set(re.findall(r"[A-Za-z_][A-Za-z0-9_]*", bare)) + if not identifiers <= allowed: + return False + return True + + +def postprocess(source: str, alias_patches: dict[str, str]) -> tuple[str, list[str]]: + """Convert RootModel wrappers to aliases and clean up generator artifacts. + + Returns the rewritten source and the sorted recursive-alias names (for the + follow-up assertion that the relocated block survived). + """ + source = _GENERATOR_BANNER_RE.sub("", source) + tree = ast.parse(source) + lines = source.splitlines(keepends=True) + + aliases = _collect_rootmodel_aliases(tree) + cyclic = _find_recursive_aliases(aliases) + + converted = 0 + cyclic_in_order = [name for name in aliases if name in cyclic] + relocate = bool(cyclic) and _group_is_self_contained(cyclic, aliases) + replacements: list[tuple[int, int, str]] = [] # 1-based inclusive line ranges + for name, (node, alias_type) in aliases.items(): + if name == "Model" and alias_type == "Any": + # The generator's synthetic artifact for the schema's empty root. + replacements.append((node.lineno, _end_lineno(node), "")) + continue + if name in cyclic: + # The whole group is emitted as one block (see _emit_cyclic_block): + # relocated to just after the imports when self-contained, else at + # the first member's position. + if relocate or name != cyclic_in_order[0]: + new = "" + else: + new = _emit_cyclic_block(cyclic_in_order, aliases, alias_patches) + else: + new = f"{name}: TypeAlias = {alias_type}\n" + docstring = ast.get_docstring(node) + if docstring and '"""' not in docstring: + new += '"""' + docstring + '"""\n' + replacements.append((node.lineno, _end_lineno(node), new)) + converted += 1 + if relocate: + last_import_end = max(_end_lineno(node) for node in tree.body if isinstance(node, ast.Import | ast.ImportFrom)) + block = _emit_cyclic_block(cyclic_in_order, aliases, alias_patches) + # Pure insertion AFTER the last import line (start = end + 1), never a + # replacement of it: the import line itself may carry its own + # replacement (dropping RootModel), and two edits to one line range + # silently clobber each other. + replacements.append((last_import_end + 1, last_import_end, "\n" + block)) + + # Import fixes, on the same parsed tree: drop RootModel from the pydantic + # import, add TypeAlias / TypeAliasType where conversions need them. + need_type_alias = converted > len(cyclic) + need_type_alias_type = bool(cyclic) + typing_handled = False + for node in tree.body: + if not isinstance(node, ast.ImportFrom): + continue + if node.module == "pydantic": + names = [alias for alias in node.names if alias.name != "RootModel"] + text = ast.unparse(ast.ImportFrom(module="pydantic", names=names, level=0)) + "\n" if names else "" + replacements.append((node.lineno, _end_lineno(node), text)) + elif node.module == "typing": + typing_handled = True + names = list(node.names) + if need_type_alias and not any(alias.name == "TypeAlias" for alias in names): + names.append(ast.alias(name="TypeAlias")) + text = ast.unparse(ast.ImportFrom(module="typing", names=names, level=0)) + "\n" + if need_type_alias_type: + text += "from typing_extensions import TypeAliasType\n" + replacements.append((node.lineno, _end_lineno(node), text)) + if not typing_handled and (need_type_alias or need_type_alias_type): + extra = "from typing import TypeAlias\n" if need_type_alias else "" + if need_type_alias_type: + extra += "from typing_extensions import TypeAliasType\n" + for node in tree.body: + if isinstance(node, ast.ImportFrom) and node.module == "__future__": + replacements.append((node.lineno, _end_lineno(node), lines[node.lineno - 1] + extra)) + break + + for start, end, new in sorted(replacements, reverse=True): + lines[start - 1 : end] = [new] + source = "".join(lines) + + # model_rebuild() is only valid on classes; drop calls for converted names. + for name in [*aliases, "Model"]: + source = re.sub(rf"^{name}\.model_rebuild\(\)\n", "", source, flags=re.MULTILINE) + + source = _JSDOC_LINK_RE.sub(r"\1", source) + return source, sorted(cyclic) + + +def assert_alias_blocks_present(source: str, recursive: list[str]) -> None: + """Assert every recursive alias survived post-processing into the output.""" + for name in recursive: + if not re.search(rf'^{name} = TypeAliasType\("{name}"', source, flags=re.MULTILINE): + raise PipelineError(f"recursive alias {name!r} is missing from the post-processed output") + + +def downgrade_value_transforms(source: str) -> str: + """Replace value-transforming pydantic types with plain ``str``. + + URL normalization and base64 re-encoding change bytes on a validate -> + re-dump round trip; the wire models must reproduce input bytes exactly. + """ + source = re.sub(r"\b(AnyUrl|FileUrl|Base64Str)\b", "str", source) + match = re.search(r"^from pydantic import (.+)$", source, flags=re.MULTILINE) + if match: + names = [n.strip() for n in match.group(1).split(",")] + kept = [n for n in names if n != "str"] + source = source.replace(match.group(0), f"from pydantic import {', '.join(kept)}") + return source + + +def widen_structured_content(source: str) -> str: + """Widen typed ``structuredContent`` fields to ``Any`` (2026-07-28 only). + + The 2026-07-28 schema types the field as any JSON value; the 2025-11-25 + surface keeps the schema's ``dict[str, Any]`` so this pass is gated. + """ + tree = ast.parse(source) + lines = source.splitlines(keepends=True) + statements = [ + stmt + for node in tree.body + if isinstance(node, ast.ClassDef) + for stmt in node.body + if isinstance(stmt, ast.AnnAssign) + and isinstance(stmt.target, ast.Name) + and stmt.target.id == "structured_content" + ] + for stmt in sorted(statements, key=lambda s: s.lineno, reverse=True): + start, end = stmt.lineno, _end_lineno(stmt) + segment = "".join(lines[start - 1 : end]) + lines[start - 1 : end] = [segment.replace("dict[str, Any]", "Any")] + return "".join(lines) + + +def _class_field_aliases(node: ast.ClassDef) -> list[tuple[ast.AnnAssign, str, str]]: + """(statement, field name, wire alias) for every annotated field of a class.""" + out: list[tuple[ast.AnnAssign, str, str]] = [] + for stmt in node.body: + if not isinstance(stmt, ast.AnnAssign) or not isinstance(stmt.target, ast.Name): + continue + annotation_text = ast.unparse(stmt.annotation) + alias_match = _FIELD_ALIAS_RE.search(annotation_text) + alias = alias_match.group(1) if alias_match else stmt.target.id + out.append((stmt, stmt.target.id, alias)) + return out + + +def apply_field_type_patches(source: str, patches: list[tuple[str, str, str]]) -> str: + """Rewrite the annotations listed in ``FIELD_TYPE_PATCHES`` for one version. + + Each patch names a (class, field) and the replacement annotation; the + generator's annotation is found via AST and the new one is spliced into the + same line range with a one-line provenance comment above it. + """ + if not patches: + return source + tree = ast.parse(source) + lines = source.splitlines(keepends=True) + classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} + replacements: list[tuple[int, int, str]] = [] + for cls_name, field_name, new_annotation in patches: + node = classes.get(cls_name) + if node is None: + raise PipelineError(f"field-type patch targets missing class {cls_name}") + stmt = next( + (s for s, name, _ in _class_field_aliases(node) if name == field_name), + None, + ) + if stmt is None: + raise PipelineError(f"field-type patch targets missing field {cls_name}.{field_name}") + default = f" = {ast.unparse(stmt.value)}" if stmt.value is not None else "" + new = f"{_NUMBER_PATCH_NOTE} {field_name}: {new_annotation}{default}\n" + replacements.append((stmt.lineno, _end_lineno(stmt), new)) + for start, end, new in sorted(replacements, reverse=True): + lines[start - 1 : end] = [new] + return "".join(lines) + + +def flatten_inheritance(source: str) -> str: + """Flatten classes that subclass other generated classes. + + The generator emits subclassing where one definition narrows another; + pyright's dataclass-transform rules reject the narrowed overrides, and the + committed packages carry no suppressions. Flattening re-declares inherited + fields directly: base fields in base order, overrides in place, own fields + appended. + """ + tree = ast.parse(source) + lines = source.splitlines(keepends=True) + classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} + + def field_segments(node: ast.ClassDef) -> list[tuple[str, str]]: + segments: list[tuple[str, str]] = [] + body = list(node.body) + for index, stmt in enumerate(body): + if not isinstance(stmt, ast.AnnAssign) or not isinstance(stmt.target, ast.Name): + continue + end = _end_lineno(stmt) + if index + 1 < len(body): + nxt = body[index + 1] + if isinstance(nxt, ast.Expr) and isinstance(nxt.value, ast.Constant): + if isinstance(nxt.value.value, str): + end = _end_lineno(nxt) + segments.append((stmt.target.id, "".join(lines[stmt.lineno - 1 : end]))) + return segments + + resolved: dict[str, list[tuple[str, str]]] = {} + + def resolve(name: str) -> list[tuple[str, str]]: + if name in resolved: + return resolved[name] + node = classes[name] + base_names = [b.id for b in node.bases if isinstance(b, ast.Name) and b.id in classes] + merged: list[tuple[str, str]] = [] + for base in base_names: + for field_name, text in resolve(base): + if field_name not in {n for n, _ in merged}: + merged.append((field_name, text)) + for field_name, text in field_segments(node): + existing = [i for i, (n, _) in enumerate(merged) if n == field_name] + if existing: + merged[existing[0]] = (field_name, text) + else: + merged.append((field_name, text)) + resolved[name] = merged + return merged + + replacements: list[tuple[int, int, str]] = [] + for name, node in classes.items(): + generated_bases = [b.id for b in node.bases if isinstance(b, ast.Name) and b.id in classes] + if not generated_bases: + continue + parts: list[str] = [f"class {name}(WireModel):\n"] + if ast.get_docstring(node) is not None: + stmt = node.body[0] + parts.append("".join(lines[stmt.lineno - 1 : _end_lineno(stmt)])) + parts.extend( + "".join(lines[stmt.lineno - 1 : _end_lineno(stmt)]) + for stmt in node.body + if isinstance(stmt, ast.Assign) + and any(isinstance(t, ast.Name) and t.id == "model_config" for t in stmt.targets) + ) + parts.extend(text for _, text in resolve(name)) + replacements.append((node.lineno, _end_lineno(node), "".join(parts))) + + for start, end, new in sorted(replacements, reverse=True): + lines[start - 1 : end] = [new] + return "".join(lines) + + +def rebase_meta_carriers(source: str) -> tuple[str, list[str]]: + """Rebase classes referenced from ``_meta`` fields onto ``OpenWireModel``.""" + tree = ast.parse(source) + classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} + carriers: set[str] = set() + for node in classes.values(): + for stmt, _, alias in _class_field_aliases(node): + if alias != "_meta": + continue + annotation_text = ast.unparse(stmt.annotation) + carriers.update(name for name in classes if re.search(rf"\b{name}\b", annotation_text)) + for name in sorted(carriers): + source = re.sub( + rf"^class {name}\(WireModel\):$", + f"class {name}(OpenWireModel):", + source, + flags=re.MULTILINE, + ) + return source, sorted(carriers) + + +def apply_extra_policy(source: str, carriers: list[str]) -> str: + """Force ``extra="ignore"`` everywhere except the enumerated open classes.""" + tree = ast.parse(source) + lines = source.splitlines(keepends=True) + classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} + + keep_allow: set[str] = set() + for node in classes.values(): + for stmt, _, alias in _class_field_aliases(node): + if alias in _OPEN_INTERIOR_FIELD_ALIASES: + annotation_text = ast.unparse(stmt.annotation) + keep_allow.update(name for name in classes if re.search(rf"\b{name}\b", annotation_text)) + if "SubscriptionFilter" in classes: + keep_allow.add("SubscriptionFilter") + + replacements: list[tuple[int, int, str]] = [] + for name, node in classes.items(): + config_stmt = next( + ( + stmt + for stmt in node.body + if isinstance(stmt, ast.Assign) + and any(isinstance(t, ast.Name) and t.id == "model_config" for t in stmt.targets) + ), + None, + ) + if config_stmt is None: + continue + value = config_stmt.value + is_plain_allow = ( + isinstance(value, ast.Call) + and isinstance(value.func, ast.Name) + and value.func.id == "ConfigDict" + and len(value.keywords) == 1 + and value.keywords[0].arg == "extra" + and isinstance(value.keywords[0].value, ast.Constant) + and value.keywords[0].value.value == "allow" + ) + if not is_plain_allow: + raise PipelineError(f"unexpected model_config on {name}: {ast.unparse(config_stmt)}") + if name in keep_allow and name not in carriers: + comment = _SUBSCRIPTION_FILTER_COMMENT if name == "SubscriptionFilter" else _OPEN_INTERIOR_COMMENT + replacements.append((config_stmt.lineno, config_stmt.lineno - 1, comment)) + continue + remaining = [stmt for stmt in node.body if stmt is not config_stmt] + new = "" if remaining else " pass\n" + replacements.append((config_stmt.lineno, _end_lineno(config_stmt), new)) + + for start, end, new in sorted(replacements, reverse=True): + lines[start - 1 : end] = [new] + return "".join(lines) + + +def deterministic_synthetic_names(source: str) -> str: + """Rename counter-based synthetic classes after their single owner.""" + tree = ast.parse(source) + classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} + synthetic = {name for name in classes if re.fullmatch(r"(?:Params|Meta)\d*", name)} + references: dict[str, list[tuple[str, str]]] = {name: [] for name in synthetic} + for node in classes.values(): + for stmt, field_name, _ in _class_field_aliases(node): + annotation_text = ast.unparse(stmt.annotation) + for name in synthetic: + if re.search(rf"\b{name}\b", annotation_text): + references[name].append((node.name, field_name)) + + renames: dict[str, str] = {} + taken = set(classes) + for name in sorted(synthetic): + refs = references[name] + if len(refs) != 1: + continue + owner, field_name = refs[0] + suffix = {"params": "Params", "meta": "Meta"}.get(field_name) + if suffix is None: + continue + target = f"{owner}{suffix}" + if target in taken: + continue + renames[name] = target + taken.add(target) + + if renames: + pattern = re.compile(r"\b(" + "|".join(sorted(renames, key=len, reverse=True)) + r")\b") + source = pattern.sub(lambda m: renames[m.group(1)], source) + return source + + +def _join_class_docstring_summaries(source: str) -> str: + """Move each class docstring summary onto the opening quote line. + + The D212 fix, applied first so the long-line wrapping below stays in + charge of the resulting line length. + """ + tree = ast.parse(source) + lines = source.splitlines(keepends=True) + for node in tree.body: + if not isinstance(node, ast.ClassDef) or ast.get_docstring(node) is None: + continue + stmt = node.body[0] + first = lines[stmt.lineno - 1] + if first.strip() != '"""' or _end_lineno(stmt) == stmt.lineno: + continue + summary = lines[stmt.lineno].strip() + single_content = _end_lineno(stmt) == stmt.lineno + 2 + indent = first[: len(first) - len(first.lstrip())] + if single_content: + if len(indent) + len(summary) + 6 <= 120: + lines[stmt.lineno - 1] = f'{indent}"""{summary}"""\n' + lines[stmt.lineno] = "" + lines[stmt.lineno + 1] = "" + else: + wrapped = textwrap.wrap( + summary, + width=117, + initial_indent=indent + '"""', + subsequent_indent=indent, + break_long_words=False, + break_on_hyphens=False, + ) + lines[stmt.lineno - 1] = "".join(part + "\n" for part in wrapped) + lines[stmt.lineno] = "" + continue + lines[stmt.lineno - 1] = first.rstrip("\n") + summary + "\n" + lines[stmt.lineno] = "" + return "".join(lines) + + +def wrap_docstrings(source: str) -> str: + """Wrap docstring lines to the repo line length (no per-file E501 exemption).""" + source = _join_class_docstring_summaries(source) + out: list[str] = [] + in_docstring = False + for line in source.splitlines(keepends=True): + quotes = line.count('"""') + long = len(line.rstrip("\n")) > 120 + indent = line[: len(line) - len(line.lstrip())] + if not in_docstring: + if quotes == 1: + in_docstring = True + if long: + out.extend( + part + "\n" + for part in textwrap.wrap( + line.strip().removeprefix('"""'), + width=120, + initial_indent=indent + '"""', + subsequent_indent=indent, + break_long_words=False, + break_on_hyphens=False, + ) + ) + continue + out.append(line) + continue + if quotes: + in_docstring = False + out.append(line) + continue + if not long: + out.append(line) + continue + out.extend( + part + "\n" + for part in textwrap.wrap( + line.strip(), + width=120, + initial_indent=indent, + subsequent_indent=indent, + break_long_words=False, + break_on_hyphens=False, + ) + ) + return "".join(out) + + +def assert_only_wire_bases(source: str) -> None: + """Assert every class in the emitted package sits on one of the two bases.""" + tree = ast.parse(source) + for node in tree.body: + if not isinstance(node, ast.ClassDef): + continue + base_names = [ast.unparse(b) for b in node.bases] + if base_names not in (["WireModel"], ["OpenWireModel"]): + raise PipelineError(f"class {node.name} has unexpected bases {base_names} after the src passes") + + +def run_ruff(path: Path) -> None: + """ruff check --fix + format with the repo config; abort on any residual finding.""" + common = ["uv", "run", "--frozen", "ruff"] + fix = subprocess.run( + [*common, "check", "--no-cache", "--fix", "--exit-zero", str(path)], + cwd=REPO_ROOT, + capture_output=True, + text=True, + ) + if fix.returncode != 0: + raise PipelineError(f"ruff check --fix failed:\n{fix.stderr.strip()}") + fmt = subprocess.run([*common, "format", "--no-cache", str(path)], cwd=REPO_ROOT, capture_output=True, text=True) + if fmt.returncode != 0: + raise PipelineError(f"ruff format failed:\n{fmt.stderr.strip()}") + report = subprocess.run( + [*common, "check", "--no-cache", "--output-format", "json", "--exit-zero", str(path)], + cwd=REPO_ROOT, + capture_output=True, + text=True, + ) + if report.returncode != 0: + raise PipelineError(f"ruff check (report) failed:\n{report.stderr.strip()}") + findings: list[dict[str, Any]] = json.loads(report.stdout) + if findings: + details = "\n".join( + f" {f.get('code')}: {f.get('message')} (line {f.get('location', {}).get('row')})" for f in findings + ) + raise PipelineError(f"ruff residual findings in {path.name}:\n{details}") + + +def schema_defs(schema: dict[str, Any]) -> dict[str, Any]: + """The definitions table of a schema ($defs in 2020-12, definitions in draft-07).""" + defs = schema.get("$defs") or schema.get("definitions") + if not defs: + raise PipelineError("schema has no $defs/definitions table") + return defs + + +def import_candidate(path: Path, module: str) -> ModuleType: + """Import a staged candidate module under a private name.""" + name = f"_candidate_{module}" + spec = importlib.util.spec_from_file_location(name, path) + if spec is None or spec.loader is None: + raise PipelineError(f"cannot load candidate module from {path}") + mod = importlib.util.module_from_spec(spec) + sys.modules[name] = mod + spec.loader.exec_module(mod) + return mod + + +def audit_candidate(mod: ModuleType, schema: dict[str, Any], entry: Entry) -> None: + """Def-name + alias + requiredness audit; raises on any failure.""" + problems: list[str] = [] + for def_name, def_schema in schema_defs(schema).items(): + obj = getattr(mod, def_name, None) + if obj is None: + problems.append(f"def {def_name!r} does not resolve to a module attribute") + continue + if not (isinstance(obj, type) and issubclass(obj, BaseModel)): + continue + properties: dict[str, Any] = def_schema.get("properties", {}) + required: list[str] = def_schema.get("required", []) + fields_by_wire_name = { + (info.serialization_alias or info.alias or field_name): info + for field_name, info in obj.model_fields.items() + } + for prop_name in properties: + info = fields_by_wire_name.get(prop_name) + if info is None: + problems.append(f"{def_name}: no field with wire name {prop_name!r}") + continue + if info.is_required() != (prop_name in required): + problems.append( + f"{def_name}.{prop_name}: requiredness mismatch " + f"(model={info.is_required()}, schema={prop_name in required})" + ) + if problems: + raise PipelineError(f"{entry.protocol_version}: audit failed:\n" + "\n".join(f" - {p}" for p in problems)) + + +def _annotation_models(annotation: Any) -> list[type[BaseModel]]: + """Model classes appearing anywhere in an annotation.""" + if isinstance(annotation, type) and issubclass(annotation, BaseModel): + return [annotation] + found: list[type[BaseModel]] = [] + for arg in typing.get_args(annotation): + found.extend(_annotation_models(arg)) + return found + + +def audit_meta_carriers(mod: ModuleType, entry: Entry) -> None: + """Assert every ``_meta`` field is open: a dict, or an extra='allow' model.""" + problems: list[str] = [] + for name in dir(mod): + obj = getattr(mod, name) + if not (isinstance(obj, type) and issubclass(obj, BaseModel)): + continue + for field_name, info in obj.model_fields.items(): + if (info.serialization_alias or info.alias or field_name) != "_meta": + continue + problems.extend( + f"{name}._meta references {referenced.__name__}, which is not extra='allow'" + for referenced in _annotation_models(info.annotation) + if referenced.model_config.get("extra") != "allow" + ) + if problems: + raise PipelineError( + f"{entry.protocol_version}: _meta carrier audit failed:\n" + "\n".join(f" - {p}" for p in problems) + ) + + +def build_candidate(entry: Entry) -> str: + """Full pipeline for one entry; returns the formatted, audited source text.""" + schema: dict[str, Any] = json.loads(entry.schema_file.read_text()) + + with tempfile.TemporaryDirectory() as tmp: + raw_file = Path(tmp) / "raw.py" + run_generator(entry.schema_file, raw_file) + source, recursive = postprocess(raw_file.read_text(), ALIAS_TYPE_PATCHES.get(entry.protocol_version, {})) + assert_alias_blocks_present(source, recursive) + + source = downgrade_value_transforms(source) + if entry.protocol_version == "2026-07-28": + source = widen_structured_content(source) + source = flatten_inheritance(source) + source, carriers = rebase_meta_carriers(source) + if carriers: + source = source.replace( + "from mcp.types._wire_base import WireModel", + "from mcp.types._wire_base import OpenWireModel, WireModel", + ) + source = apply_extra_policy(source, carriers) + source = deterministic_synthetic_names(source) + source = apply_field_type_patches(source, FIELD_TYPE_PATCHES.get(entry.protocol_version, [])) + source = wrap_docstrings(source) + + epilogue = EPILOGUES.get(entry.protocol_version, "") + if epilogue: + # Before model_rebuild(), not after: pyright's evaluation order for the + # recursive TypeAliasType block at the top is sensitive to where the + # last cross-module-referenced symbol sits. + match = re.search(r"^\w+\.model_rebuild\(\)$", source, flags=re.MULTILINE) + cut = match.start() if match else len(source) + source = source[:cut] + epilogue + "\n\n" + source[cut:] + header = SRC_HEADERS[entry.protocol_version].format(spec_commit=entry.spec_commit) + + staging = SRC_TYPES_DIR / f"_staging_{entry.module}.py" + try: + staging.write_text(header + source) + run_ruff(staging) + candidate = staging.read_text() + assert_only_wire_bases(candidate) + mod = import_candidate(staging, entry.module) + audit_candidate(mod, schema, entry) + audit_meta_carriers(mod, entry) + finally: + staging.unlink(missing_ok=True) + return candidate + + +def main(argv: list[str] | None = None) -> int: + """CLI entry point.""" + parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument("--check", action="store_true", help="diff regenerated output against committed files") + args = parser.parse_args(argv) + + if importlib.util.find_spec("mcp.types._wire_base") is None: + raise PipelineError("src/mcp/types/_wire_base.py (WireModel/OpenWireModel) is required") + + entries = load_pinned() + drifted: list[str] = [] + for entry in entries: + candidate = build_candidate(entry) + if args.check: + committed = entry.target.read_text() if entry.target.is_file() else "" + if committed == candidate: + print(f"{entry.protocol_version}: ok ({entry.target.relative_to(REPO_ROOT)})") + else: + drifted.append(entry.protocol_version) + diff = difflib.unified_diff( + committed.splitlines(keepends=True), + candidate.splitlines(keepends=True), + fromfile=str(entry.target.relative_to(REPO_ROOT)), + tofile="", + ) + sys.stderr.writelines(diff) + else: + entry.target.parent.mkdir(parents=True, exist_ok=True) + entry.target.write_text(candidate) + print(f"{entry.protocol_version}: wrote {entry.target.relative_to(REPO_ROOT)} ({len(candidate)} bytes)") + + if drifted: + print( + f"DRIFT: {drifted} differ from committed output; " + "run `uv run --group codegen python scripts/gen_surface_types.py`", + file=sys.stderr, + ) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/src/mcp/types/v2025_11_25/__init__.py b/src/mcp/types/v2025_11_25/__init__.py index d67f7caef3..aeddcba6a6 100644 --- a/src/mcp/types/v2025_11_25/__init__.py +++ b/src/mcp/types/v2025_11_25/__init__.py @@ -1,10 +1,10 @@ """Internal wire-shape models for protocol 2025-11-25. Not part of the public API. -Serves inbound validation for every protocol version through 2025-11-25 (each -earlier schema is a strict subset of this one). Models default to -`extra="ignore"`; the few kept open are commented in place. See -`mcp.types._wire_base` and `mcp.types.methods`. -Pinned to schema/2025-11-25/schema.json @ 6d441518de8a9d5adbab0b10a76a667a63f90665. +Generated by `scripts/gen_surface_types.py` from `schema/2025-11-25.json` +@ 6d441518de8a9d5adbab0b10a76a667a63f90665. Do not edit; regenerate. Serves inbound validation for every +protocol version through 2025-11-25 (each earlier schema is a strict subset of +this one). Models default to `extra="ignore"`; the few kept open are commented +in place. See `mcp.types._wire_base` and `mcp.types.methods`. """ from __future__ import annotations @@ -17,20 +17,41 @@ class BaseMetadata(WireModel): - """Base interface for metadata with name (identifier) and title (display name).""" + """Base interface for metadata with name (identifier) and title (display name) properties.""" name: str - """Programmatic identifier; also the display fallback when `title` is absent.""" + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ title: str | None = None - """Human-readable display name.""" + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ class BlobResourceContents(WireModel): meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ blob: str - """Base64-encoded binary data.""" + """ + A base64-encoded string representing the binary data of the item. + """ mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ uri: str + """ + The URI of this resource. + """ class BooleanSchema(WireModel): @@ -42,6 +63,9 @@ class BooleanSchema(WireModel): class CancelTaskRequestParams(WireModel): task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier to cancel. + """ class Elicitation(WireModel): @@ -55,79 +79,146 @@ class Roots(WireModel): """Present if the client supports listing roots.""" list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether the client supports notifications for changes to the roots list. + """ class Sampling(WireModel): """Present if the client supports sampling from an LLM.""" context: dict[str, Any] | None = None - """Present if the client supports the `includeContext` parameter.""" + """ + Whether the client supports context inclusion via includeContext parameter. + If not declared, servers SHOULD only use `includeContext: "none"` (or omit it). + """ tools: dict[str, Any] | None = None - """Present if the client supports the `tools` and `toolChoice` parameters.""" + """ + Whether the client supports tool use via tools and toolChoice parameters. + """ class Elicitation1(WireModel): """Task support for elicitation-related requests.""" create: dict[str, Any] | None = None + """ + Whether the client supports task-augmented elicitation/create requests. + """ class Sampling1(WireModel): """Task support for sampling-related requests.""" create_message: Annotated[dict[str, Any] | None, Field(alias="createMessage")] = None + """ + Whether the client supports task-augmented sampling/createMessage requests. + """ class Requests(WireModel): """Specifies which request types can be augmented with tasks.""" elicitation: Elicitation1 | None = None + """ + Task support for elicitation-related requests. + """ sampling: Sampling1 | None = None + """ + Task support for sampling-related requests. + """ class Tasks(WireModel): """Present if the client supports task-augmented requests.""" cancel: dict[str, Any] | None = None + """ + Whether this client supports tasks/cancel. + """ list: dict[str, Any] | None = None + """ + Whether this client supports tasks/list. + """ requests: Requests | None = None + """ + Specifies which request types can be augmented with tasks. + """ class ClientCapabilities(WireModel): - """Capabilities a client may support. Not a closed set.""" + """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a + closed set: any client can define its own, additional capabilities. + """ elicitation: Elicitation | None = None + """ + Present if the client supports elicitation from the server. + """ experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the client supports. + """ roots: Roots | None = None + """ + Present if the client supports listing roots. + """ sampling: Sampling | None = None + """ + Present if the client supports sampling from an LLM. + """ tasks: Tasks | None = None + """ + Present if the client supports task-augmented requests. + """ class Argument(WireModel): - """The argument being completed.""" + """The argument's information""" name: str + """ + The name of the argument + """ value: str + """ + The value of the argument to use for completion matching. + """ class Context(WireModel): - """Additional context for completions.""" + """Additional, optional context for completions""" arguments: dict[str, str] | None = None - """Already-resolved variables in a URI template or prompt.""" + """ + Previously-resolved variables in a URI template or prompt. + """ class Completion(WireModel): has_more: Annotated[bool | None, Field(alias="hasMore")] = None + """ + Indicates whether there are additional completion options beyond those provided in the current response, even if the + exact total is unknown. + """ total: int | None = None + """ + The total number of completion options available. This can exceed the number of values actually sent in the + response. + """ values: list[str] - """Must not exceed 100 items.""" + """ + An array of completion values. Must not exceed 100 items. + """ class CompleteResult(WireModel): - """The server's response to a completion/complete request.""" + """The server's response to a completion/complete request""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ completion: Completion @@ -138,19 +229,36 @@ class ElicitResult(WireModel): """The client's response to an elicitation request.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ action: Literal["accept", "cancel", "decline"] - # Deviation: schema.json renders the number arm as "integer" but schema.ts - # types it `number`, so fractional answers are legal. Follow schema.ts. + """ + The user action in response to the elicitation. + - "accept": User submitted the form/confirmed the action + - "decline": User explicitly decline the action + - "cancel": User dismissed without making an explicit choice + """ + # schema.json renders this `number` as integer; widen per schema.ts so floats validate. content: dict[str, list[str] | str | int | float | bool] | None = None - """Submitted form data; only present when action is "accept" and mode was "form".""" + """ + The submitted form data, only present when action is "accept" and mode was "form". + Contains values matching the requested schema. + Omitted for out-of-band mode responses. + """ class ElicitationCompleteNotificationParams(WireModel): elicitation_id: Annotated[str, Field(alias="elicitationId")] + """ + The ID of the elicitation that completed. + """ class ElicitationCompleteNotification(WireModel): - """Server-to-client notification that an out-of-band elicitation completed.""" + """An optional notification from the server to the client, informing it of a completion of a out-of-band + elicitation request. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/elicitation/complete"] @@ -159,50 +267,141 @@ class ElicitationCompleteNotification(WireModel): class Error(WireModel): code: int + """ + The error type that occurred. + """ data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ class GetTaskPayloadRequestParams(WireModel): task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier to retrieve results for. + """ class GetTaskPayloadResult(WireModel): - """Response to tasks/result; structure matches the original request's result type.""" + """The response to a tasks/result request. + The structure matches the result type of the original request. + For example, a tools/call task would return the CallToolResult structure. + """ meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ class GetTaskRequestParams(WireModel): task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier to query. + """ class Icon(WireModel): """An optionally-sized icon that can be displayed in a user interface.""" mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + Optional MIME type override if the source MIME type is missing or generic. + For example: `"image/png"`, `"image/jpeg"`, or `"image/svg+xml"`. + """ sizes: list[str] | None = None - """Each entry is WxH (e.g. "48x48") or "any" for scalable formats.""" + """ + Optional array of strings that specify sizes at which the icon can be used. + Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG. + + If not provided, the client should assume that the icon can be used at any size. + """ src: str - """HTTP/HTTPS URL or `data:` URI.""" + """ + A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a + `data:` URI with Base64-encoded image data. + + Consumers SHOULD takes steps to ensure URLs serving icons are from the + same domain as the client/server or a trusted domain. + + Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain + executable JavaScript. + """ theme: Literal["dark", "light"] | None = None + """ + Optional specifier for the theme this icon is designed for. `light` indicates + the icon is designed to be used with a light background, and `dark` indicates + the icon is designed to be used with a dark background. + + If not provided, the client should assume the icon can be used with any theme. + """ class Icons(WireModel): - """Base interface adding an `icons` property.""" + """Base interface to add `icons` property.""" icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ class Implementation(WireModel): """Describes the MCP implementation.""" description: str | None = None + """ + An optional human-readable description of what this implementation does. + + This can be used by clients or servers to provide context about their purpose + and capabilities. For example, a server might describe the types of resources + or tools it provides, while a client might describe its intended use case. + """ icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ version: str website_url: Annotated[str | None, Field(alias="websiteUrl")] = None + """ + An optional URL of the website for this implementation. + """ class JSONRPCNotification(WireModel): @@ -214,13 +413,18 @@ class JSONRPCNotification(WireModel): class LegacyTitledEnumSchema(WireModel): - """Use TitledSingleSelectEnumSchema instead.""" + """Use TitledSingleSelectEnumSchema instead. + This interface will be removed in a future version. + """ default: str | None = None description: str | None = None enum: list[str] enum_names: Annotated[list[str] | None, Field(alias="enumNames")] = None - """Display names for enum values (legacy, non-standard JSON Schema).""" + """ + (Legacy) Display names for enum values. + Non-standard according to JSON schema 2020-12. + """ title: str | None = None type: Literal["string"] @@ -229,27 +433,90 @@ class LegacyTitledEnumSchema(WireModel): class LoggingMessageNotificationParams(WireModel): + """Parameters for a `notifications/message` notification.""" + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ data: Any + """ + The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. + """ level: LoggingLevel + """ + The severity of this log message. + """ logger: str | None = None + """ + An optional name of the logger issuing this message. + """ class ModelHint(WireModel): - """Hints to use for model selection.""" + """Hints to use for model selection. + + Keys not declared here are currently left unspecified by the spec and are up + to the client to interpret. + """ name: str | None = None - """Substring of a model name; the client may map it to another provider's equivalent.""" + """ + A hint for a model name. + + The client SHOULD treat this as a substring of a model name; for example: + - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022` + - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. + - `claude` should match any Claude model + + The client MAY also map the string to a different provider's model name or a different model family, as long as it + fills a similar niche; for example: + - `gemini-1.5-flash` could match `claude-3-haiku-20240307` + """ class ModelPreferences(WireModel): - """The server's advisory preferences for model selection during sampling.""" + """The server's preferences for model selection, requested of the client during sampling. + + Because LLMs can vary along multiple dimensions, choosing the "best" model is + rarely straightforward. Different models excel in different areas—some are + faster but less capable, others are more capable but more expensive, and so + on. This interface allows servers to express their priorities across multiple + dimensions to help clients make an appropriate selection for their use case. + + These preferences are always advisory. The client MAY ignore them. It is also + up to the client to decide how to interpret these preferences and how to + balance them against other considerations. + """ cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize cost when selecting a model. A value of 0 means cost + is not important, while a value of 1 means cost is the most important + factor. + """ hints: list[ModelHint] | None = None - """Evaluated in order; first match wins.""" + """ + Optional hints to use for model selection. + + If multiple hints are specified, the client MUST evaluate them in order + (such that the first match is taken). + + The client SHOULD prioritize these hints over the numeric priorities, but + MAY still use the priorities to select from ambiguous matches. + """ intelligence_priority: Annotated[float | None, Field(alias="intelligencePriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize intelligence and capabilities when selecting a + model. A value of 0 means intelligence is not important, while a value of 1 + means intelligence is the most important factor. + """ speed_priority: Annotated[float | None, Field(alias="speedPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize sampling speed (latency) when selecting a model. A + value of 0 means speed is not important, while a value of 1 means speed is + the most important factor. + """ class Notification(WireModel): @@ -259,14 +526,18 @@ class Notification(WireModel): class NotificationParams(WireModel): meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ class NumberSchema(WireModel): - # Deviation: schema.json renders these as "integer" but schema.ts types - # them `number` (JSON Schema bounds are numbers). Follow schema.ts. + # schema.json renders this `number` as integer; widen per schema.ts so floats validate. default: int | float | None = None description: str | None = None + # schema.json renders this `number` as integer; widen per schema.ts so floats validate. maximum: int | float | None = None + # schema.json renders this `number` as integer; widen per schema.ts so floats validate. minimum: int | float | None = None title: str | None = None type: Literal["integer", "number"] @@ -274,8 +545,14 @@ class NumberSchema(WireModel): class PaginatedResult(WireModel): meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None - """Opaque pagination token; if present, more results may be available.""" + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ ProgressToken: TypeAlias = str | int @@ -285,13 +562,33 @@ class PromptArgument(WireModel): """Describes an argument that a prompt can accept.""" description: str | None = None + """ + A human-readable description of the argument. + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ required: bool | None = None + """ + Whether this argument must be provided. + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ class PromptListChangedNotification(WireModel): - """Server-to-client notification that the prompt list has changed.""" + """An optional notification from the server to the client, informing it that the list of prompts it offers has + changed. This may be issued by servers without any previous subscription from the client. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/prompts/list_changed"] @@ -302,26 +599,55 @@ class PromptReference(WireModel): """Identifies a prompt.""" name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ type: Literal["ref/prompt"] class Meta(OpenWireModel): - """Request `_meta` object.""" + """See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.""" progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None - """If set, the caller wants `notifications/progress` for this request, tagged with this token.""" + """ + If specified, the caller is requesting out-of-band progress notifications for this request (as represented by + notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent + notifications. The receiver is not obligated to provide these notifications. + """ class ReadResourceRequestParams(WireModel): + """Parameters for a `resources/read` request.""" + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ uri: str + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ class RelatedTaskMetadata(WireModel): - """Associates a message with a task via `_meta["io.modelcontextprotocol/related-task"]`.""" + """Metadata for associating messages with a task. + Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`. + """ task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier this message is associated with. + """ class Request(WireModel): @@ -333,19 +659,35 @@ class Request(WireModel): class RequestParams(WireModel): + """Common params for any request.""" + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ class ResourceContents(WireModel): """The contents of a specific resource or sub-resource.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ uri: str + """ + The URI of this resource. + """ class ResourceListChangedNotification(WireModel): - """Server-to-client notification that the resource list has changed.""" + """An optional notification from the server to the client, informing it that the list of resources it can read + from has changed. This may be issued by servers without any previous subscription from the client. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/resources/list_changed"] @@ -356,7 +698,13 @@ class ResourceRequestParams(WireModel): """Common parameters when working with resources.""" meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ uri: str + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ class ResourceTemplateReference(WireModel): @@ -364,33 +712,61 @@ class ResourceTemplateReference(WireModel): type: Literal["ref/resource"] uri: str - """The URI or URI template of the resource.""" + """ + The URI or URI template of the resource. + """ class ResourceUpdatedNotificationParams(WireModel): + """Parameters for a `notifications/resources/updated` notification.""" + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ uri: str - """May be a sub-resource of the one the client subscribed to.""" + """ + The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually + subscribed to. + """ class Result(WireModel): meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ Role: TypeAlias = Literal["assistant", "user"] class Root(WireModel): - """A root directory or file that the server can operate on.""" + """Represents a root directory or file that the server can operate on.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ name: str | None = None + """ + An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. + """ uri: str - """Must start with file:// for now.""" + """ + The URI identifying the root. This *must* start with file:// for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + """ class RootsListChangedNotification(WireModel): - """Client-to-server notification that the roots list has changed.""" + """A notification from the client to the server, informing it that the list of roots has changed. + This notification should be sent whenever the client adds, removes, or modifies any root. + The server should then request an updated list of roots using the ListRootsRequest. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/roots/list_changed"] @@ -401,57 +777,115 @@ class Prompts(WireModel): """Present if the server offers any prompt templates.""" list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the prompt list. + """ class Resources(WireModel): """Present if the server offers any resources to read.""" list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the resource list. + """ subscribe: bool | None = None + """ + Whether this server supports subscribing to resource updates. + """ class Tools(WireModel): """Task support for tool-related requests.""" call: dict[str, Any] | None = None + """ + Whether the server supports task-augmented tools/call requests. + """ class Requests1(WireModel): """Specifies which request types can be augmented with tasks.""" tools: Tools | None = None + """ + Task support for tool-related requests. + """ class Tasks1(WireModel): """Present if the server supports task-augmented requests.""" cancel: dict[str, Any] | None = None + """ + Whether this server supports tasks/cancel. + """ list: dict[str, Any] | None = None + """ + Whether this server supports tasks/list. + """ requests: Requests1 | None = None + """ + Specifies which request types can be augmented with tasks. + """ class Tools1(WireModel): """Present if the server offers any tools to call.""" list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the tool list. + """ class ServerCapabilities(WireModel): - """Capabilities a server may support. Not a closed set.""" + """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a + closed set: any server can define its own, additional capabilities. + """ completions: dict[str, Any] | None = None + """ + Present if the server supports argument autocompletion suggestions. + """ experimental: dict[str, dict[str, Any]] | None = None + """ + Experimental, non-standard capabilities that the server supports. + """ logging: dict[str, Any] | None = None + """ + Present if the server supports sending log messages to the client. + """ prompts: Prompts | None = None + """ + Present if the server offers any prompt templates. + """ resources: Resources | None = None + """ + Present if the server offers any resources to read. + """ tasks: Tasks1 | None = None + """ + Present if the server supports task-augmented requests. + """ tools: Tools1 | None = None + """ + Present if the server offers any tools to call. + """ class SetLevelRequestParams(WireModel): + """Parameters for a `logging/setLevel` request.""" + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ level: LoggingLevel - """Minimum severity to send to the client as notifications/message.""" + """ + The level of logging that the client wants to receive from the server. The server should send all logs at this level + and higher (i.e., more severe) to the client as notifications/message. + """ class StringSchema(WireModel): @@ -465,15 +899,27 @@ class StringSchema(WireModel): class SubscribeRequestParams(WireModel): + """Parameters for a `resources/subscribe` request.""" + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ uri: str + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ class TaskMetadata(WireModel): - """Metadata for augmenting a request with task execution (the `task` param field).""" + """Metadata for augmenting a request with task execution. + Include this in the `task` field of the request parameters. + """ ttl: int | None = None - """Requested retention from creation, in milliseconds.""" + """ + Requested duration in milliseconds to retain task from creation. + """ TaskStatus: TypeAlias = Literal["cancelled", "completed", "failed", "input_required", "working"] @@ -481,53 +927,110 @@ class TaskMetadata(WireModel): class TextResourceContents(WireModel): meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ text: str + """ + The text of the item. This must only be set if the item can actually be represented as text (not binary data). + """ uri: str + """ + The URI of this resource. + """ class AnyOfItem(WireModel): const: str + """ + The constant enum value. + """ title: str + """ + Display title for this option. + """ class Items(WireModel): """Schema for array items with enum options and display labels.""" any_of: Annotated[list[AnyOfItem], Field(alias="anyOf")] + """ + Array of enum options with values and display labels. + """ class TitledMultiSelectEnumSchema(WireModel): - """Multiple-selection enum with display titles for each option.""" + """Schema for multiple-selection enumeration with display titles for each option.""" default: list[str] | None = None + """ + Optional default value. + """ description: str | None = None + """ + Optional description for the enum field. + """ items: Items + """ + Schema for array items with enum options and display labels. + """ max_items: Annotated[int | None, Field(alias="maxItems")] = None + """ + Maximum number of items to select. + """ min_items: Annotated[int | None, Field(alias="minItems")] = None + """ + Minimum number of items to select. + """ title: str | None = None + """ + Optional title for the enum field. + """ type: Literal["array"] class OneOfItem(WireModel): const: str + """ + The enum value. + """ title: str + """ + Display label for this option. + """ class TitledSingleSelectEnumSchema(WireModel): - """Single-selection enum with display titles for each option.""" + """Schema for single-selection enumeration with display titles for each option.""" default: str | None = None + """ + Optional default value. + """ description: str | None = None + """ + Optional description for the enum field. + """ one_of: Annotated[list[OneOfItem], Field(alias="oneOf")] + """ + Array of enum options with values and display labels. + """ title: str | None = None + """ + Optional title for the enum field. + """ type: Literal["string"] class InputSchema(WireModel): """A JSON Schema object defining the expected parameters for the tool.""" - # Kept open: arbitrary JSON Schema keywords ride extra fields. + # Stays open: schema keywords beyond the declared properties ride extra fields. model_config = ConfigDict( extra="allow", ) @@ -538,9 +1041,14 @@ class InputSchema(WireModel): class OutputSchema(WireModel): - """A JSON Schema object defining the structure of `CallToolResult.structuredContent`.""" + """An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. - # Kept open: arbitrary JSON Schema keywords ride extra fields. + Defaults to JSON Schema 2020-12 when no explicit $schema is provided. + Currently restricted to type: "object" at the root level. + """ + + # Stays open: schema keywords beyond the declared properties ride extra fields. model_config = ConfigDict( extra="allow", ) @@ -551,34 +1059,88 @@ class OutputSchema(WireModel): class ToolAnnotations(WireModel): - """Untrusted hints describing a tool's behavior to clients.""" + """Additional properties describing a Tool to clients. + + NOTE: all properties in ToolAnnotations are **hints**. + They are not guaranteed to provide a faithful description of + tool behavior (including descriptive properties like `title`). + + Clients should never make tool use decisions based on ToolAnnotations + received from untrusted servers. + """ destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None - """Only meaningful when `readOnlyHint` is false. Default: true.""" + """ + If true, the tool may perform destructive updates to its environment. + If false, the tool performs only additive updates. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: true + """ idempotent_hint: Annotated[bool | None, Field(alias="idempotentHint")] = None - """Only meaningful when `readOnlyHint` is false. Default: false.""" + """ + If true, calling the tool repeatedly with the same arguments + will have no additional effect on its environment. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: false + """ open_world_hint: Annotated[bool | None, Field(alias="openWorldHint")] = None - """Default: true.""" + """ + If true, this tool may interact with an "open world" of external + entities. If false, the tool's domain of interaction is closed. + For example, the world of a web search tool is open, whereas that + of a memory tool is not. + + Default: true + """ read_only_hint: Annotated[bool | None, Field(alias="readOnlyHint")] = None - """Default: false.""" + """ + If true, the tool does not modify its environment. + + Default: false + """ title: str | None = None + """ + A human-readable title for the tool. + """ class ToolChoice(WireModel): """Controls tool selection behavior for sampling requests.""" mode: Literal["auto", "none", "required"] | None = None + """ + Controls the tool use ability of the model: + - "auto": Model decides whether to use tools (default) + - "required": Model MUST use at least one tool before completing + - "none": Model MUST NOT use any tools + """ class ToolExecution(WireModel): """Execution-related properties for a tool.""" task_support: Annotated[Literal["forbidden", "optional", "required"] | None, Field(alias="taskSupport")] = None - """Whether this tool supports task-augmented execution. Default: "forbidden".""" + """ + Indicates whether this tool supports task-augmented execution. + This allows clients to handle long-running operations through polling + the task system. + + - "forbidden": Tool does not support task-augmented execution (default when absent) + - "optional": Tool may support task-augmented execution + - "required": Tool requires task-augmented execution + + Default: "forbidden" + """ class ToolListChangedNotification(WireModel): - """Server-to-client notification that the tool list has changed.""" + """An optional notification from the server to the client, informing it that the list of tools it offers has + changed. This may be issued by servers without any previous subscription from the client. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/tools/list_changed"] @@ -589,74 +1151,180 @@ class ToolUseContent(WireModel): """A request from the assistant to call a tool.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool use. Clients SHOULD preserve this field when + including tool uses in subsequent sampling requests to enable caching optimizations. + + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ id: str - """Unique identifier matched against `ToolResultContent.tool_use_id`.""" + """ + A unique identifier for this tool use. + + This ID is used to match tool results to their corresponding tool uses. + """ input: dict[str, Any] + """ + The arguments to pass to the tool, conforming to the tool's input schema. + """ name: str + """ + The name of the tool to call. + """ type: Literal["tool_use"] class UnsubscribeRequestParams(WireModel): + """Parameters for a `resources/unsubscribe` request.""" + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ uri: str + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ class Items1(WireModel): """Schema for the array items.""" enum: list[str] + """ + Array of enum values to choose from. + """ type: Literal["string"] class UntitledMultiSelectEnumSchema(WireModel): - """Multiple-selection enum without per-option display titles.""" + """Schema for multiple-selection enumeration without display titles for options.""" default: list[str] | None = None + """ + Optional default value. + """ description: str | None = None + """ + Optional description for the enum field. + """ items: Items1 + """ + Schema for the array items. + """ max_items: Annotated[int | None, Field(alias="maxItems")] = None + """ + Maximum number of items to select. + """ min_items: Annotated[int | None, Field(alias="minItems")] = None + """ + Minimum number of items to select. + """ title: str | None = None + """ + Optional title for the enum field. + """ type: Literal["array"] class UntitledSingleSelectEnumSchema(WireModel): - """Single-selection enum without per-option display titles.""" + """Schema for single-selection enumeration without display titles for options.""" default: str | None = None + """ + Optional default value. + """ description: str | None = None + """ + Optional description for the enum field. + """ enum: list[str] + """ + Array of enum values to choose from. + """ title: str | None = None + """ + Optional title for the enum field. + """ type: Literal["string"] class Annotations(WireModel): - """Optional annotations for the client.""" + """Optional annotations for the client. The client can use annotations to inform how objects are used or + displayed + """ audience: list[Role] | None = None + """ + Describes who the intended audience of this object or data is. + + It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). + """ last_modified: Annotated[str | None, Field(alias="lastModified")] = None - """ISO 8601 timestamp.""" + """ + The moment the resource was last modified, as an ISO 8601 formatted string. + + Should be an ISO 8601 formatted string (e.g., "2025-01-12T15:00:58Z"). + + Examples: last activity timestamp in an open file, timestamp when the resource + was attached, etc. + """ priority: Annotated[float | None, Field(ge=0.0, le=1.0)] = None - """1 means effectively required, 0 means entirely optional.""" + """ + Describes how important this data is for operating the server. + + A value of 1 means "most important," and indicates that the data is + effectively required, while 0 means "least important," and indicates that + the data is entirely optional. + """ class AudioContent(WireModel): """Audio provided to or from an LLM.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ annotations: Annotations | None = None + """ + Optional annotations for the client. + """ data: str - """Base64-encoded audio data.""" + """ + The base64-encoded audio data. + """ mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the audio. Different providers may support different audio types. + """ type: Literal["audio"] class CallToolRequestParams(WireModel): + """Parameters for a `tools/call` request.""" + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ arguments: dict[str, Any] | None = None + """ + Arguments to use for the tool call. + """ name: str + """ + The name of the tool. + """ task: TaskMetadata | None = None - """If set, run as a task and return `CreateTaskResult` immediately.""" + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ class CancelTaskRequest(WireModel): @@ -669,37 +1337,94 @@ class CancelTaskRequest(WireModel): class CancelledNotificationParams(WireModel): + """Parameters for a `notifications/cancelled` notification.""" + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ reason: str | None = None + """ + An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. + """ request_id: Annotated[RequestId | None, Field(alias="requestId")] = None - """Required for non-task requests; MUST NOT be used for tasks (use `tasks/cancel`).""" + """ + The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + This MUST be provided for cancelling non-task requests. + This MUST NOT be used for cancelling tasks (use the `tasks/cancel` request instead). + """ class CompleteRequestParams(WireModel): + """Parameters for a `completion/complete` request.""" + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ argument: Argument + """ + The argument's information + """ context: Context | None = None + """ + Additional, optional context for completions + """ ref: PromptReference | ResourceTemplateReference class ElicitRequestURLParams(WireModel): - """Parameters for a URL-mode elicitation request.""" + """The parameters for a request to elicit information from the user via a URL in the client.""" meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ elicitation_id: Annotated[str, Field(alias="elicitationId")] - """Server-unique opaque ID.""" + """ + The ID of the elicitation, which must be unique within the context of the server. + The client MUST treat this ID as an opaque value. + """ message: str + """ + The message to present to the user explaining why the interaction is needed. + """ mode: Literal["url"] + """ + The elicitation mode. + """ task: TaskMetadata | None = None - """If set, run as a task and return `CreateTaskResult` immediately.""" + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ url: str + """ + The URL that the user should navigate to. + """ class EmbeddedResource(WireModel): - """The contents of a resource, embedded into a prompt or tool call result.""" + """The contents of a resource, embedded into a prompt or tool call result. + + It is up to the client how best to render embedded resources for the benefit + of the LLM and/or the user. + """ meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ annotations: Annotations | None = None + """ + Optional annotations for the client. + """ resource: TextResourceContents | BlobResourceContents type: Literal["resource"] @@ -717,9 +1442,20 @@ class EmbeddedResource(WireModel): class GetPromptRequestParams(WireModel): + """Parameters for a `prompts/get` request.""" + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ arguments: dict[str, str] | None = None + """ + Arguments to use for templating the prompt. + """ name: str + """ + The name of the prompt or prompt template. + """ class GetTaskPayloadRequest(WireModel): @@ -744,35 +1480,65 @@ class ImageContent(WireModel): """An image provided to or from an LLM.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ annotations: Annotations | None = None + """ + Optional annotations for the client. + """ data: str - """Base64-encoded image data.""" + """ + The base64-encoded image data. + """ mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the image. Different providers may support different image types. + """ type: Literal["image"] class InitializeRequestParams(WireModel): + """Parameters for an `initialize` request.""" + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ capabilities: ClientCapabilities client_info: Annotated[Implementation, Field(alias="clientInfo")] protocol_version: Annotated[str, Field(alias="protocolVersion")] - """The latest protocol version the client supports.""" + """ + The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older + versions as well. + """ class InitializeResult(WireModel): - """The server's response to an initialize request.""" + """After receiving an initialize request from the client, the server sends this response.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ capabilities: ServerCapabilities instructions: str | None = None - """Instructions describing how to use the server and its features.""" + """ + Instructions describing how to use the server and its features. + + This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought + of like a "hint" to the model. For example, this information MAY be added to the system prompt. + """ protocol_version: Annotated[str, Field(alias="protocolVersion")] - """The protocol version the server wants to use; the client MUST disconnect if unsupported.""" + """ + The version of the Model Context Protocol that the server wants to use. This may not match the version that the + client requested. If the client cannot support this version, it MUST disconnect. + """ server_info: Annotated[Implementation, Field(alias="serverInfo")] class InitializedNotification(WireModel): - """Sent from the client to the server after initialization has finished.""" + """This notification is sent from the client to the server after initialization has finished.""" jsonrpc: Literal["2.0"] method: Literal["notifications/initialized"] @@ -805,7 +1571,14 @@ class JSONRPCResultResponse(WireModel): class ListRootsRequest(WireModel): - """Sent from the server to request a list of root URIs from the client.""" + """Sent from the server to request a list of root URIs from the client. Roots allow + servers to ask for specific directories or files to operate on. A common example + for roots is providing a set of repositories or directories a server should operate + on. + + This request is typically used when the server needs to understand the file system + structure or access specific locations that the client has permission to read from. + """ id: RequestId jsonrpc: Literal["2.0"] @@ -814,14 +1587,22 @@ class ListRootsRequest(WireModel): class ListRootsResult(WireModel): - """The client's response to a roots/list request.""" + """The client's response to a roots/list request from the server. + This result contains an array of Root objects, each representing a root directory + or file that the server can operate on. + """ meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ roots: list[Root] class LoggingMessageNotification(WireModel): - """A log message passed from server to client.""" + """JSONRPCNotification of a log message passed from server to client. If no logging/setLevel request has been + sent from the client, the server MAY decide which messages to send automatically. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/message"] @@ -832,13 +1613,23 @@ class LoggingMessageNotification(WireModel): class PaginatedRequestParams(WireModel): + """Common parameters for paginated requests.""" + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ cursor: str | None = None - """Opaque pagination token; results start after this position.""" + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ class PingRequest(WireModel): - """A ping, issued by either side, to check that the other party is still alive.""" + """A ping, issued by either the server or the client, to check that the other party is still alive. The receiver + must promptly respond, or else may be disconnected. + """ id: RequestId jsonrpc: Literal["2.0"] @@ -859,28 +1650,76 @@ class PingRequest(WireModel): class ProgressNotificationParams(WireModel): + """Parameters for a `notifications/progress` notification.""" + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ message: str | None = None + """ + An optional message describing the current progress. + """ progress: float - """Monotonically increasing, even if `total` is unknown.""" + """ + The progress thus far. This should increase every time progress is made, even if the total is unknown. + """ progress_token: Annotated[ProgressToken, Field(alias="progressToken")] - """The token from the originating request's `_meta.progressToken`.""" + """ + The progress token which was given in the initial request, used to associate this notification with the request that + is proceeding. + """ total: float | None = None + """ + Total number of items to process (or total progress required), if known. + """ class Prompt(WireModel): """A prompt or prompt template that the server offers.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ arguments: list[PromptArgument] | None = None + """ + A list of arguments to use for templating the prompt. + """ description: str | None = None + """ + An optional description of what this prompt provides + """ icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ class ReadResourceRequest(WireModel): - """Sent from the client to read a specific resource URI.""" + """Sent from the client to the server, to read a specific resource URI.""" id: RequestId jsonrpc: Literal["2.0"] @@ -889,9 +1728,12 @@ class ReadResourceRequest(WireModel): class ReadResourceResult(WireModel): - """The server's response to a resources/read request.""" + """The server's response to a resources/read request from the client.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ contents: list[TextResourceContents | BlobResourceContents] @@ -899,50 +1741,185 @@ class Resource(WireModel): """A known resource that the server is capable of reading.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ annotations: Annotations | None = None + """ + Optional annotations for the client. + """ description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ size: int | None = None - """Raw content size in bytes (before base64 encoding), if known.""" + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ uri: str + """ + The URI of this resource. + """ class ResourceLink(WireModel): - """A resource link included in a prompt or tool call result.""" + """A resource that the server is capable of reading, included in a prompt or tool call result. + + Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. + """ meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ annotations: Annotations | None = None + """ + Optional annotations for the client. + """ description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ size: int | None = None - """Raw content size in bytes (before base64 encoding), if known.""" + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ type: Literal["resource_link"] uri: str + """ + The URI of this resource. + """ class ResourceTemplate(WireModel): """A template description for resources available on the server.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ annotations: Annotations | None = None + """ + Optional annotations for the client. + """ description: str | None = None + """ + A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ mime_type: Annotated[str | None, Field(alias="mimeType")] = None - """Only set if all resources matching this template share the same type.""" + """ + The MIME type for all resources that match this template. This should only be included if all resources matching + this template have the same type. + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ uri_template: Annotated[str, Field(alias="uriTemplate")] - """RFC 6570 URI template.""" + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ class ResourceUpdatedNotification(WireModel): - """Server-to-client notification that a subscribed resource has changed.""" + """A notification from the server to the client, informing it that a resource has changed and may need to be read + again. This should only be sent if the client previously sent a resources/subscribe request. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/resources/updated"] @@ -950,7 +1927,7 @@ class ResourceUpdatedNotification(WireModel): class SetLevelRequest(WireModel): - """A request from the client to enable or adjust logging.""" + """A request from the client to the server, to enable or adjust logging.""" id: RequestId jsonrpc: Literal["2.0"] @@ -962,7 +1939,9 @@ class SetLevelRequest(WireModel): class SubscribeRequest(WireModel): - """Sent from the client to request resources/updated notifications for a resource.""" + """Sent from the client to request resources/updated notifications from the server whenever a particular resource + changes. + """ id: RequestId jsonrpc: Literal["2.0"] @@ -974,47 +1953,113 @@ class Task(WireModel): """Data associated with a task.""" created_at: Annotated[str, Field(alias="createdAt")] - """ISO 8601 timestamp.""" + """ + ISO 8601 timestamp when the task was created. + """ last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] - """ISO 8601 timestamp.""" + """ + ISO 8601 timestamp when the task was last updated. + """ poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None - """Suggested polling interval in milliseconds.""" + """ + Suggested polling interval in milliseconds. + """ status: TaskStatus + """ + Current task state. + """ status_message: Annotated[str | None, Field(alias="statusMessage")] = None + """ + Optional human-readable message describing the current task state. + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) + """ task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier. + """ ttl: int | None - """Actual retention from creation in milliseconds; null means unlimited.""" + """ + Actual retention duration from creation in milliseconds, null for unlimited. + """ class TaskAugmentedRequestParams(WireModel): """Common params for any task-augmented request.""" meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ task: TaskMetadata | None = None - """If set, run as a task and return `CreateTaskResult` immediately.""" + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ class TaskStatusNotificationParams(WireModel): + """Parameters for a `notifications/tasks/status` notification.""" + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ created_at: Annotated[str, Field(alias="createdAt")] - """ISO 8601 timestamp.""" + """ + ISO 8601 timestamp when the task was created. + """ last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] - """ISO 8601 timestamp.""" + """ + ISO 8601 timestamp when the task was last updated. + """ poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None - """Suggested polling interval in milliseconds.""" + """ + Suggested polling interval in milliseconds. + """ status: TaskStatus + """ + Current task state. + """ status_message: Annotated[str | None, Field(alias="statusMessage")] = None + """ + Optional human-readable message describing the current task state. + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) + """ task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier. + """ ttl: int | None - """Actual retention from creation in milliseconds; null means unlimited.""" + """ + Actual retention duration from creation in milliseconds, null for unlimited. + """ class TextContent(WireModel): """Text provided to or from an LLM.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ annotations: Annotations | None = None + """ + Optional annotations for the client. + """ text: str + """ + The text content of the message. + """ type: Literal["text"] @@ -1022,30 +2067,94 @@ class Tool(WireModel): """Definition for a tool the client can call.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ annotations: ToolAnnotations | None = None + """ + Optional additional tool information. + + Display name precedence order is: title, annotations.title, then name. + """ description: str | None = None + """ + A human-readable description of the tool. + + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a + "hint" to the model. + """ execution: ToolExecution | None = None + """ + Execution-related properties for this tool. + """ icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + """ + A JSON Schema object defining the expected parameters for the tool. + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ output_schema: Annotated[OutputSchema | None, Field(alias="outputSchema")] = None + """ + An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. + + Defaults to JSON Schema 2020-12 when no explicit $schema is provided. + Currently restricted to type: "object" at the root level. + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ class Data(WireModel): - """Error data carrying pending URL elicitations.""" + """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ elicitations: list[ElicitRequestURLParams] class Error1(WireModel): code: Literal[-32042] + """ + The error type that occurred. + """ data: Data + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ class URLElicitationRequiredError(WireModel): - """Error response indicating the server requires a URL-mode elicitation.""" + """An error response that indicates that the server requires the client to provide additional information via an + elicitation request. + """ error: Error1 id: RequestId | None = None @@ -1053,7 +2162,9 @@ class URLElicitationRequiredError(WireModel): class UnsubscribeRequest(WireModel): - """Sent from the client to cancel resources/updated notifications for a resource.""" + """Sent from the client to request cancellation of resources/updated notifications from the server. This should + follow a previous resources/subscribe request. + """ id: RequestId jsonrpc: Literal["2.0"] @@ -1074,21 +2185,55 @@ class CancelTaskResult(WireModel): """The response to a tasks/cancel request.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ created_at: Annotated[str, Field(alias="createdAt")] - """ISO 8601 timestamp.""" + """ + ISO 8601 timestamp when the task was created. + """ last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] - """ISO 8601 timestamp.""" + """ + ISO 8601 timestamp when the task was last updated. + """ poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None - """Suggested polling interval in milliseconds.""" + """ + Suggested polling interval in milliseconds. + """ status: TaskStatus + """ + Current task state. + """ status_message: Annotated[str | None, Field(alias="statusMessage")] = None + """ + Optional human-readable message describing the current task state. + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) + """ task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier. + """ ttl: int | None - """Actual retention from creation in milliseconds; null means unlimited.""" + """ + Actual retention duration from creation in milliseconds, null for unlimited. + """ class CancelledNotification(WireModel): - """Sent by either side to cancel an in-flight request (not for tasks; use `tasks/cancel`).""" + """This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + + The request SHOULD still be in-flight, but due to communication latency, it is always possible that this + notification MAY arrive after the request has already finished. + + This notification indicates that the result will be unused, so any associated processing SHOULD cease. + + A client MUST NOT attempt to cancel its `initialize` request. + + For task cancellation, use the `tasks/cancel` request instead of this notification. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/cancelled"] @@ -1096,7 +2241,7 @@ class CancelledNotification(WireModel): class CompleteRequest(WireModel): - """A request from the client to ask for completion options.""" + """A request from the client to the server, to ask for completion options.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1111,11 +2256,16 @@ class CreateTaskResult(WireModel): """A response to a task-augmented request.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ task: Task class RequestedSchema(WireModel): - """A restricted JSON Schema subset: top-level properties only, no nesting.""" + """A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ schema_: Annotated[str | None, Field(alias="$schema")] = None properties: dict[str, PrimitiveSchemaDefinition] @@ -1124,14 +2274,34 @@ class RequestedSchema(WireModel): class ElicitRequestFormParams(WireModel): - """Parameters for a form-mode elicitation request.""" + """The parameters for a request to elicit non-sensitive information from the user via a form in the client.""" meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ message: str + """ + The message to present to the user describing what information is being requested. + """ mode: Literal["form"] = "form" + """ + The elicitation mode. + """ requested_schema: Annotated[RequestedSchema, Field(alias="requestedSchema")] + """ + A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ task: TaskMetadata | None = None - """If set, run as a task and return `CreateTaskResult` immediately.""" + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ ElicitRequestParams: TypeAlias = ElicitRequestURLParams | ElicitRequestFormParams @@ -1150,21 +2320,45 @@ class GetTaskResult(WireModel): """The response to a tasks/get request.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ created_at: Annotated[str, Field(alias="createdAt")] - """ISO 8601 timestamp.""" + """ + ISO 8601 timestamp when the task was created. + """ last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] - """ISO 8601 timestamp.""" + """ + ISO 8601 timestamp when the task was last updated. + """ poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None - """Suggested polling interval in milliseconds.""" + """ + Suggested polling interval in milliseconds. + """ status: TaskStatus + """ + Current task state. + """ status_message: Annotated[str | None, Field(alias="statusMessage")] = None + """ + Optional human-readable message describing the current task state. + This can provide context for any status, including: + - Reasons for "cancelled" status + - Summaries for "completed" status + - Diagnostic information for "failed" status (e.g., error details, what went wrong) + """ task_id: Annotated[str, Field(alias="taskId")] + """ + The task identifier. + """ ttl: int | None - """Actual retention from creation in milliseconds; null means unlimited.""" + """ + Actual retention duration from creation in milliseconds, null for unlimited. + """ class InitializeRequest(WireModel): - """Sent from the client to the server when it first connects.""" + """This request is sent from the client to the server when it first connects, asking it to begin initialization.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1179,7 +2373,7 @@ class InitializeRequest(WireModel): class ListPromptsRequest(WireModel): - """Sent from the client to request a list of prompts and prompt templates.""" + """Sent from the client to request a list of prompts and prompt templates the server has.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1188,15 +2382,22 @@ class ListPromptsRequest(WireModel): class ListPromptsResult(WireModel): - """The server's response to a prompts/list request.""" + """The server's response to a prompts/list request from the client.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ prompts: list[Prompt] class ListResourceTemplatesRequest(WireModel): - """Sent from the client to request a list of resource templates.""" + """Sent from the client to request a list of resource templates the server has.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1205,15 +2406,22 @@ class ListResourceTemplatesRequest(WireModel): class ListResourceTemplatesResult(WireModel): - """The server's response to a resources/templates/list request.""" + """The server's response to a resources/templates/list request from the client.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] class ListResourcesRequest(WireModel): - """Sent from the client to request a list of resources.""" + """Sent from the client to request a list of resources the server has.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1222,10 +2430,17 @@ class ListResourcesRequest(WireModel): class ListResourcesResult(WireModel): - """The server's response to a resources/list request.""" + """The server's response to a resources/list request from the client.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ resources: list[Resource] @@ -1242,12 +2457,19 @@ class ListTasksResult(WireModel): """The response to a tasks/list request.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ tasks: list[Task] class ListToolsRequest(WireModel): - """Sent from the client to request a list of tools.""" + """Sent from the client to request a list of tools the server has.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1256,10 +2478,17 @@ class ListToolsRequest(WireModel): class ListToolsResult(WireModel): - """The server's response to a tools/list request.""" + """The server's response to a tools/list request from the client.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ tools: list[Tool] @@ -1271,7 +2500,7 @@ class PaginatedRequest(WireModel): class ProgressNotification(WireModel): - """An out-of-band progress update for a long-running request.""" + """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" jsonrpc: Literal["2.0"] method: Literal["notifications/progress"] @@ -1279,14 +2508,20 @@ class ProgressNotification(WireModel): class PromptMessage(WireModel): - """A message returned as part of a prompt; like `SamplingMessage` but allows embedded resources.""" + """Describes a message returned as part of a prompt. + + This is similar to `SamplingMessage`, but also supports the embedding of + resources from the MCP server. + """ content: ContentBlock role: Role class TaskStatusNotification(WireModel): - """An optional notification that a task's status has changed.""" + """An optional notification from the receiver to the requestor, informing them that a task's status has changed. + Receivers are not required to send these notifications. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/tasks/status"] @@ -1297,13 +2532,38 @@ class ToolResultContent(WireModel): """The result of a tool use, provided by the user back to the assistant.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool result. Clients SHOULD preserve this field when + including tool results in subsequent sampling requests to enable caching optimizations. + + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ content: list[ContentBlock] + """ + The unstructured result content of the tool use. + + This has the same format as CallToolResult.content and can include text, images, + audio, resource links, and embedded resources. + """ is_error: Annotated[bool | None, Field(alias="isError")] = None - """Default: false.""" - # 2025-11-25 schema: object-only; 2026-07-28 widens to unknown + """ + Whether the tool use resulted in an error. + + If true, the content typically describes the error that occurred. + Default: false + """ structured_content: Annotated[dict[str, Any] | None, Field(alias="structuredContent")] = None + """ + An optional structured result object. + + If the tool defined an outputSchema, this SHOULD conform to that schema. + """ tool_use_id: Annotated[str, Field(alias="toolUseId")] - """Must match the `id` of an earlier `ToolUseContent`.""" + """ + The ID of the tool use this result corresponds to. + + This MUST match the ID from a previous ToolUseContent. + """ type: Literal["tool_result"] @@ -1311,10 +2571,32 @@ class CallToolResult(WireModel): """The server's response to a tool call.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ content: list[ContentBlock] + """ + A list of content objects that represent the unstructured result of the tool call. + """ is_error: Annotated[bool | None, Field(alias="isError")] = None - """Tool errors should set this true (not raise a protocol error). Default: false.""" + """ + Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. + """ structured_content: Annotated[dict[str, Any] | None, Field(alias="structuredContent")] = None + """ + An optional JSON object that represents the structured result of the tool call. + """ ClientNotification: TypeAlias = ( @@ -1348,7 +2630,7 @@ class CallToolResult(WireModel): class ElicitRequest(WireModel): - """A request from the server to elicit additional information from the user.""" + """A request from the server to elicit additional information from the user via the client.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1357,10 +2639,16 @@ class ElicitRequest(WireModel): class GetPromptResult(WireModel): - """The server's response to a prompts/get request.""" + """The server's response to a prompts/get request from the client.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ description: str | None = None + """ + An optional description for the prompt. + """ messages: list[PromptMessage] @@ -1399,9 +2687,15 @@ class GetPromptResult(WireModel): class CreateMessageResult(WireModel): - """The client's response to a sampling/createMessage request.""" + """The client's response to a sampling/createMessage request from the server. + The client should inform the user before returning the sampled message, to allow them + to inspect the response (human in the loop) and decide whether to allow the server to see it. + """ meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ content: ( TextContent | ImageContent @@ -1411,15 +2705,31 @@ class CreateMessageResult(WireModel): | list[SamplingMessageContentBlock] ) model: str + """ + The name of the model that generated the message. + """ role: Role stop_reason: Annotated[str | None, Field(alias="stopReason")] = None - """Standard values: "endTurn", "stopSequence", "maxTokens", "toolUse"; open string.""" + """ + The reason why sampling stopped, if known. + + Standard values: + - "endTurn": Natural end of the assistant's turn + - "stopSequence": A stop sequence was encountered + - "maxTokens": Maximum token limit was reached + - "toolUse": The model wants to use one or more tools + + This field is an open string to allow for provider-specific stop reasons. + """ class SamplingMessage(WireModel): - """A message issued to or received from an LLM API.""" + """Describes a message issued to or received from an LLM API.""" meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ content: ( TextContent | ImageContent @@ -1444,28 +2754,72 @@ class SamplingMessage(WireModel): class CreateMessageRequestParams(WireModel): + """Parameters for a `sampling/createMessage` request.""" + meta: Annotated[Meta | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ include_context: Annotated[ Literal["allServers", "none", "thisServer"] | None, Field(alias="includeContext"), ] = None - """Default "none"; "thisServer"/"allServers" are soft-deprecated.""" + """ + A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. + The client MAY ignore this request. + + Default is "none". Values "thisServer" and "allServers" are soft-deprecated. Servers SHOULD only use these values if + the client + declares ClientCapabilities.sampling.context. These values may be removed in future spec releases. + """ max_tokens: Annotated[int, Field(alias="maxTokens")] + """ + The requested maximum number of tokens to sample (to prevent runaway completions). + + The client MAY choose to sample fewer tokens than the requested maximum. + """ messages: list[SamplingMessage] metadata: dict[str, Any] | None = None - """Provider-specific passthrough.""" + """ + Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + """ model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + """ + The server's preferences for which model to select. The client MAY ignore these preferences. + """ stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + """ + An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. + """ task: TaskMetadata | None = None - """If set, run as a task and return `CreateTaskResult` immediately.""" + """ + If specified, the caller is requesting task-augmented execution for this request. + The request will return a CreateTaskResult immediately, and the actual result can be + retrieved later via tasks/result. + + Task augmentation is subject to capability negotiation - receivers MUST declare support + for task augmentation of specific request types in their capabilities. + """ temperature: float | None = None tool_choice: Annotated[ToolChoice | None, Field(alias="toolChoice")] = None + """ + Controls how the model uses tools. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + Default is `{ mode: "auto" }`. + """ tools: list[Tool] | None = None + """ + Tools that the model may use during generation. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + """ class CreateMessageRequest(WireModel): - """A request from the server to sample an LLM via the client.""" + """A request from the server to sample an LLM via the client. The client has full discretion over which model to + select. The client should also inform the user before beginning sampling, to allow them to inspect the request + (human in the loop) and decide whether to approve it. + """ id: RequestId jsonrpc: Literal["2.0"] diff --git a/src/mcp/types/v2026_07_28/__init__.py b/src/mcp/types/v2026_07_28/__init__.py index 8a970b4f23..eb7d9dee9a 100644 --- a/src/mcp/types/v2026_07_28/__init__.py +++ b/src/mcp/types/v2026_07_28/__init__.py @@ -1,10 +1,10 @@ """Internal wire-shape models for protocol 2026-07-28. Not part of the public API. -Schema-exact validators that the wire-method maps in `mcp.types.methods` point -inbound 2026-07-28 validation at. Generated from schema/draft/schema.json -@ 6d441518de8a9d5adbab0b10a76a667a63f90665 and hand-maintained against that -revision. Models use `extra="ignore"` (unknown keys accepted and dropped) unless -commented otherwise; see `mcp.types._wire_base`. +Generated by `scripts/gen_surface_types.py` from `schema/2026-07-28.json` +@ 6d441518de8a9d5adbab0b10a76a667a63f90665. Do not edit; regenerate. Schema-exact validators that the +wire-method maps in `mcp.types.methods` point inbound 2026-07-28 validation at. +Models use `extra="ignore"` (unknown keys accepted and dropped) unless commented +otherwise; see `mcp.types._wire_base`. """ from __future__ import annotations @@ -16,8 +16,7 @@ from mcp.types._wire_base import OpenWireModel, WireModel -# Deviates from schema.json (renders only string|integer|boolean); follows -# schema.ts, which defines all six JSON types, so floats and null validate. +# schema.json renders the primitive arm without float/null; widen per schema.ts. JSONValue = TypeAliasType("JSONValue", "JSONObject | list[JSONValue] | str | int | float | bool | None") @@ -25,12 +24,22 @@ class BaseMetadata(WireModel): - """Base interface for metadata with name (identifier) and title (display name).""" + """Base interface for metadata with name (identifier) and title (display name) properties.""" name: str - """Programmatic identifier; also the display fallback when `title` is absent.""" + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ title: str | None = None - """Human-readable display name.""" + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ class BooleanSchema(WireModel): @@ -41,61 +50,99 @@ class BooleanSchema(WireModel): class Argument(WireModel): - """The argument being completed.""" + """The argument's information""" name: str + """ + The name of the argument + """ value: str - """Value to use for completion matching.""" + """ + The value of the argument to use for completion matching. + """ class Context(WireModel): - """Additional context for completions.""" + """Additional, optional context for completions""" arguments: dict[str, str] | None = None - """Already-resolved variables in a URI template or prompt.""" + """ + Previously-resolved variables in a URI template or prompt. + """ class Completion(WireModel): has_more: Annotated[bool | None, Field(alias="hasMore")] = None - """Whether more options exist beyond this response, even if `total` is unknown.""" + """ + Indicates whether there are additional completion options beyond those provided in the current response, even if the + exact total is unknown. + """ total: int | None = None - """Total options available; can exceed `len(values)`.""" + """ + The total number of completion options available. This can exceed the number of values actually sent in the + response. + """ values: Annotated[list[str], Field(max_length=100)] - """Completion values; at most 100 items.""" + """ + An array of completion values. Must not exceed 100 items. + """ Cursor: TypeAlias = str class ElicitRequestURLParams(WireModel): - """Parameters for a URL-mode `elicitation/create` request.""" + """The parameters for a request to elicit information from the user via a URL in the client.""" elicitation_id: Annotated[str, Field(alias="elicitationId")] - """Server-unique opaque ID for this elicitation.""" + """ + The ID of the elicitation, which must be unique within the context of the server. + The client MUST treat this ID as an opaque value. + """ message: str + """ + The message to present to the user explaining why the interaction is needed. + """ mode: Literal["url"] + """ + The elicitation mode. + """ url: str - """URL the user should navigate to.""" + """ + The URL that the user should navigate to. + """ class ElicitResult(WireModel): - """Client's result for an `elicitation/create` request.""" + """The result returned by the client for an ElicitRequestelicitation/create request.""" action: Literal["accept", "cancel", "decline"] - """`accept` = submitted, `decline` = explicit no, `cancel` = dismissed.""" - # Deviates from schema.json (renders number arm as integer); follows - # schema.ts (string|number|boolean|string[]) so float answers validate. + """ + The user action in response to the elicitation. + - `"accept"`: User submitted the form/confirmed the action + - `"decline"`: User explicitly declined the action + - `"cancel"`: User dismissed without making an explicit choice + """ + # schema.json renders this `number` as integer; widen per schema.ts so floats validate. content: dict[str, list[str] | str | int | float | bool] | None = None - """Submitted form data; only present when `action == "accept"` and mode was `"form"`.""" + """ + The submitted form data, only present when action is `"accept"` and mode was `"form"`. + Contains values matching the requested schema. + Omitted for out-of-band mode responses. + """ class ElicitationCompleteNotificationParams(WireModel): elicitation_id: Annotated[str, Field(alias="elicitationId")] - """ID of the elicitation that completed.""" + """ + The ID of the elicitation that completed. + """ class ElicitationCompleteNotification(WireModel): - """Server-to-client: an out-of-band elicitation has completed.""" + """An optional notification from the server to the client, informing it of a completion of a out-of-band + elicitation request. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/elicitation/complete"] @@ -104,65 +151,191 @@ class ElicitationCompleteNotification(WireModel): class Error(WireModel): code: int + """ + The error type that occurred. + """ data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ class Icon(WireModel): - """An optionally-sized icon for display in a UI.""" + """An optionally-sized icon that can be displayed in a user interface.""" mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + Optional MIME type override if the source MIME type is missing or generic. + For example: `"image/png"`, `"image/jpeg"`, or `"image/svg+xml"`. + """ sizes: list[str] | None = None - """Sizes the icon supports, each as `"WxH"` or `"any"`; absent means any size.""" + """ + Optional array of strings that specify sizes at which the icon can be used. + Each string should be in WxH format (e.g., `"48x48"`, `"96x96"`) or `"any"` for scalable formats like SVG. + + If not provided, the client should assume that the icon can be used at any size. + """ src: str - """HTTP(S) or `data:` URI; consumers should vet origin and sandbox SVG.""" + """ + A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a + `data:` URI with Base64-encoded image data. + + Consumers SHOULD take steps to ensure URLs serving icons are from the + same domain as the client/server or a trusted domain. + + Consumers SHOULD take appropriate precautions when consuming SVGs as they can contain + executable JavaScript. + """ theme: Literal["dark", "light"] | None = None - """Background theme this icon is designed for; absent means any theme.""" + """ + Optional specifier for the theme this icon is designed for. `"light"` indicates + the icon is designed to be used with a light background, and `"dark"` indicates + the icon is designed to be used with a dark background. + + If not provided, the client should assume the icon can be used with any theme. + """ class Icons(WireModel): - """Mixin adding the `icons` property.""" + """Base interface to add `icons` property.""" icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ class Implementation(WireModel): - """Describes an MCP implementation.""" + """Describes the MCP implementation.""" description: str | None = None + """ + An optional human-readable description of what this implementation does. + + This can be used by clients or servers to provide context about their purpose + and capabilities. For example, a server might describe the types of resources + or tools it provides, while a client might describe its intended use case. + """ icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ version: str + """ + The version of this implementation. + """ website_url: Annotated[str | None, Field(alias="websiteUrl")] = None + """ + An optional URL of the website for this implementation. + """ class InternalError(WireModel): - """JSON-RPC: internal error on the receiver.""" + """A JSON-RPC error indicating that an internal error occurred on the receiver. This error is returned when the + receiver encounters an unexpected condition that prevents it from fulfilling the request. + """ code: Literal[-32603] + """ + The error type that occurred. + """ data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ class InvalidParamsError(WireModel): - """JSON-RPC: method parameters are invalid or malformed.""" + """A JSON-RPC error indicating that the method parameters are invalid or malformed. + + In MCP, this error is returned in various contexts when request parameters fail validation: + + - **Tools**: Unknown tool name or invalid tool arguments + - **Prompts**: Unknown prompt name or missing required arguments + - **Pagination**: Invalid or expired cursor values + - **Logging**: Invalid log level + - **Elicitation**: Server requests an elicitation mode not declared in client capabilities + - **Sampling**: Missing tool result or tool results mixed with other content + """ code: Literal[-32602] + """ + The error type that occurred. + """ data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ class InvalidRequestError(WireModel): - """JSON-RPC: request object does not conform to JSON-RPC 2.0.""" + """A JSON-RPC error indicating that the request is not a valid request object. This error is returned when the + message structure does not conform to the JSON-RPC 2.0 specification requirements for a request (e.g., missing + required fields like `jsonrpc` or `method`, or using invalid types for these fields). + """ code: Literal[-32600] + """ + The error type that occurred. + """ data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ class JSONRPCNotification(WireModel): - """A notification, which does not expect a response.""" + """A notification which does not expect a response.""" jsonrpc: Literal["2.0"] method: str @@ -170,13 +343,18 @@ class JSONRPCNotification(WireModel): class LegacyTitledEnumSchema(WireModel): - """Deprecated; use `TitledSingleSelectEnumSchema`.""" + """Use TitledSingleSelectEnumSchema instead. + This interface will be removed in a future version. + """ default: str | None = None description: str | None = None enum: list[str] enum_names: Annotated[list[str] | None, Field(alias="enumNames")] = None - """Display names for enum values (non-standard for JSON Schema 2020-12).""" + """ + (Legacy) Display names for enum values. + Non-standard according to JSON schema 2020-12. + """ title: str | None = None type: Literal["string"] @@ -185,32 +363,120 @@ class LegacyTitledEnumSchema(WireModel): class MetaObject(OpenWireModel): - """Contents of a `_meta` field; see the schema for key naming and reservation rules.""" + """Represents the contents of a `_meta` field, which clients and servers use to attach additional metadata to their + interactions. + + Certain 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. + + Valid keys have two segments: + + **Prefix:** + - Optional — if specified, MUST be a series of _labels_ separated by dots (`.`), followed by a slash (`/`). + - Labels MUST start with a letter and end with a letter or digit. Interior characters may be letters, digits, or + hyphens (`-`). + - Implementations SHOULD use reverse DNS notation (e.g., `com.example/` rather than `example.com/`). + - Any prefix where the second label is `modelcontextprotocol` or `mcp` is **reserved** for MCP use. For example: + `io.modelcontextprotocol/`, `dev.mcp/`, `org.modelcontextprotocol.api/`, and `com.mcp.tools/` are all reserved. + However, `com.example.mcp/` is NOT reserved, as the second label is `example`. + + **Name:** + - Unless empty, MUST start and end with an alphanumeric character (`[a-z0-9A-Z]`). + - Interior characters may be alphanumeric, hyphens (`-`), underscores (`_`), or dots (`.`). + """ class MethodNotFoundError(WireModel): - """JSON-RPC: requested method does not exist or is not available.""" + """A JSON-RPC error indicating that the requested method does not exist or is not available. + + In MCP, a server returns this error when a client invokes a method the server does not implement — either a + genuinely unknown method, or one gated behind a server capability the server did not advertise (e.g., calling + `prompts/list` when the `prompts` capability was not advertised). + + A request that requires a client capability the client did not declare is signalled instead by + MissingRequiredClientCapabilityError (`-32003`). + """ code: Literal[-32601] + """ + The error type that occurred. + """ data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ class ModelHint(WireModel): - """Hints for model selection; undeclared keys are client-defined.""" + """Hints to use for model selection. + + Keys not declared here are currently left unspecified by the spec and are up + to the client to interpret. + """ name: str | None = None - """Model-name substring hint; the client may also map it to an equivalent from another provider.""" + """ + A hint for a model name. + + The client SHOULD treat this as a substring of a model name; for example: + - `claude-3-5-sonnet` should match `claude-3-5-sonnet-20241022` + - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. + - `claude` should match any Claude model + + The client MAY also map the string to a different provider's model name or a different model family, as long as it + fills a similar niche; for example: + - `gemini-1.5-flash` could match `claude-3-haiku-20240307` + """ class ModelPreferences(WireModel): - """Server's advisory preferences for model selection during sampling.""" + """The server's preferences for model selection, requested of the client during sampling. + + Because LLMs can vary along multiple dimensions, choosing the "best" model is + rarely straightforward. Different models excel in different areas—some are + faster but less capable, others are more capable but more expensive, and so + on. This interface allows servers to express their priorities across multiple + dimensions to help clients make an appropriate selection for their use case. + + These preferences are always advisory. The client MAY ignore them. It is also + up to the client to decide how to interpret these preferences and how to + balance them against other considerations. + """ cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize cost when selecting a model. A value of 0 means cost + is not important, while a value of 1 means cost is the most important + factor. + """ hints: list[ModelHint] | None = None - """Evaluated in order (first match wins); should outweigh the numeric priorities.""" + """ + Optional hints to use for model selection. + + If multiple hints are specified, the client MUST evaluate them in order + (such that the first match is taken). + + The client SHOULD prioritize these hints over the numeric priorities, but + MAY still use the priorities to select from ambiguous matches. + """ intelligence_priority: Annotated[float | None, Field(alias="intelligencePriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize intelligence and capabilities when selecting a + model. A value of 0 means intelligence is not important, while a value of 1 + means intelligence is the most important factor. + """ speed_priority: Annotated[float | None, Field(alias="speedPriority", ge=0.0, le=1.0)] = None + """ + How much to prioritize sampling speed (latency) when selecting a model. A + value of 0 means speed is not important, while a value of 1 means speed is + the most important factor. + """ class Notification(WireModel): @@ -219,6 +485,8 @@ class Notification(WireModel): class NotificationParams(WireModel): + """Common params for any notification.""" + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None @@ -234,17 +502,40 @@ class NumberSchema(WireModel): class PaginatedResult(WireModel): meta: Annotated[MetaObject | None, Field(alias="_meta")] = None next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None - """Opaque pagination position; if present, more results may be available.""" + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ class ParseError(WireModel): - """JSON-RPC: invalid JSON received.""" + """A JSON-RPC error indicating that invalid JSON was received by the server. This error is returned when the + server cannot parse the JSON text of a message. + """ code: Literal[-32700] + """ + The error type that occurred. + """ data: Any | None = None + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ ProgressToken: TypeAlias = str | int @@ -254,13 +545,33 @@ class PromptArgument(WireModel): """Describes an argument that a prompt can accept.""" description: str | None = None + """ + A human-readable description of the argument. + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ required: bool | None = None + """ + Whether this argument must be provided. + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ class PromptListChangedNotification(WireModel): - """Server-to-client: the prompt list has changed.""" + """An optional notification from the server to the client, informing it that the list of prompts it offers has + changed. This may be issued by servers without any previous subscription from the client. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/prompts/list_changed"] @@ -271,7 +582,19 @@ class PromptReference(WireModel): """Identifies a prompt.""" name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ type: Literal["ref/prompt"] @@ -288,11 +611,19 @@ class ResourceContents(WireModel): meta: Annotated[MetaObject | None, Field(alias="_meta")] = None mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ uri: str + """ + The URI of this resource. + """ class ResourceListChangedNotification(WireModel): - """Server-to-client: the resource list has changed.""" + """An optional notification from the server to the client, informing it that the list of resources it can read + from has changed. This may be issued by servers without any previous subscription from the client. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/resources/list_changed"] @@ -304,13 +635,20 @@ class ResourceTemplateReference(WireModel): type: Literal["ref/resource"] uri: str - """URI or URI template of the resource.""" + """ + The URI or URI template of the resource. + """ class ResourceUpdatedNotificationParams(WireModel): + """Parameters for a `notifications/resources/updated` notification.""" + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None uri: str - """URI of the updated resource; may be a sub-resource of the subscription URI.""" + """ + The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually + subscribed to. + """ class Result(WireModel): @@ -318,7 +656,15 @@ class Result(WireModel): meta: Annotated[MetaObject | None, Field(alias="_meta")] = None result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ ResultType: TypeAlias = str @@ -328,35 +674,52 @@ class Result(WireModel): class Root(WireModel): - """A root directory or file the server can operate on.""" + """Represents a root directory or file that the server can operate on.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None name: str | None = None + """ + An optional name for the root. This can be used to provide a human-readable + identifier for the root, which may be useful for display purposes or for + referencing the root in other parts of the application. + """ uri: str - """Must start with `file://` for now.""" + """ + The URI identifying the root. This *must* start with `file://` for now. + This restriction may be relaxed in future versions of the protocol to allow + other URI schemes. + """ class Prompts(WireModel): """Present if the server offers any prompt templates.""" list_changed: Annotated[bool | None, Field(alias="listChanged")] = None - """Whether the server supports prompt-list-changed notifications.""" + """ + Whether this server supports notifications for changes to the prompt list. + """ class Resources(WireModel): """Present if the server offers any resources to read.""" list_changed: Annotated[bool | None, Field(alias="listChanged")] = None - """Whether the server supports resource-list-changed notifications.""" + """ + Whether this server supports notifications for changes to the resource list. + """ subscribe: bool | None = None - """Whether the server supports subscribing to resource updates.""" + """ + Whether this server supports subscribing to resource updates. + """ class Tools(WireModel): """Present if the server offers any tools to call.""" list_changed: Annotated[bool | None, Field(alias="listChanged")] = None - """Whether the server supports tool-list-changed notifications.""" + """ + Whether this server supports notifications for changes to the tool list. + """ class StringSchema(WireModel): @@ -370,77 +733,161 @@ class StringSchema(WireModel): class SubscriptionFilter(WireModel): - """Notification types a client opts in to on `subscriptions/listen`; each is opt-in.""" + """The set of notification types a client may opt in to on a + SubscriptionsListenRequestsubscriptions/listen request. + + Each notification type is **opt-in**; the server **MUST NOT** send + notification types the client has not explicitly requested here. + """ # Stays open: filter contents are extensible on the wire. model_config = ConfigDict( extra="allow", ) prompts_list_changed: Annotated[bool | None, Field(alias="promptsListChanged")] = None - """Receive `notifications/prompts/list_changed`.""" + """ + If true, receive PromptListChangedNotificationnotifications/prompts/list_changed. + """ resource_subscriptions: Annotated[list[str] | None, Field(alias="resourceSubscriptions")] = None - """Receive `notifications/resources/updated` for these resource URIs.""" + """ + Subscribe to ResourceUpdatedNotificationnotifications/resources/updated for these resource URIs. + Replaces the former `resources/subscribe` RPC. + """ resources_list_changed: Annotated[bool | None, Field(alias="resourcesListChanged")] = None - """Receive `notifications/resources/list_changed`.""" + """ + If true, receive ResourceListChangedNotificationnotifications/resources/list_changed. + """ tools_list_changed: Annotated[bool | None, Field(alias="toolsListChanged")] = None - """Receive `notifications/tools/list_changed`.""" + """ + If true, receive ToolListChangedNotificationnotifications/tools/list_changed. + """ class SubscriptionsAcknowledgedNotificationParams(WireModel): + """Parameters for a SubscriptionsAcknowledgedNotificationnotifications/subscriptions/acknowledged notification.""" + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None notifications: SubscriptionFilter - """Subset of requested notification types the server agreed to honor.""" + """ + The subset of requested notification types the server agreed to honor. + Only includes notification types the server actually supports; if the + client requested an unsupported type (e.g., `promptsListChanged` when + the server has no prompts), it is omitted from this set. + """ class TextResourceContents(WireModel): meta: Annotated[MetaObject | None, Field(alias="_meta")] = None mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ text: str + """ + The text of the item. This must only be set if the item can actually be represented as text (not binary data). + """ uri: str + """ + The URI of this resource. + """ class AnyOfItem(WireModel): const: str + """ + The constant enum value. + """ title: str + """ + Display title for this option. + """ class Items(WireModel): - """Array-item schema with enum options and display labels.""" + """Schema for array items with enum options and display labels.""" any_of: Annotated[list[AnyOfItem], Field(alias="anyOf")] + """ + Array of enum options with values and display labels. + """ class TitledMultiSelectEnumSchema(WireModel): - """Multi-select enum schema with per-option display titles.""" + """Schema for multiple-selection enumeration with display titles for each option.""" default: list[str] | None = None + """ + Optional default value. + """ description: str | None = None + """ + Optional description for the enum field. + """ items: Items + """ + Schema for array items with enum options and display labels. + """ max_items: Annotated[int | None, Field(alias="maxItems")] = None + """ + Maximum number of items to select. + """ min_items: Annotated[int | None, Field(alias="minItems")] = None + """ + Minimum number of items to select. + """ title: str | None = None + """ + Optional title for the enum field. + """ type: Literal["array"] class OneOfItem(WireModel): const: str + """ + The enum value. + """ title: str + """ + Display label for this option. + """ class TitledSingleSelectEnumSchema(WireModel): - """Single-select enum schema with per-option display titles.""" + """Schema for single-selection enumeration with display titles for each option.""" default: str | None = None + """ + Optional default value. + """ description: str | None = None + """ + Optional description for the enum field. + """ one_of: Annotated[list[OneOfItem], Field(alias="oneOf")] + """ + Array of enum options with values and display labels. + """ title: str | None = None + """ + Optional title for the enum field. + """ type: Literal["string"] class InputSchema(WireModel): - """JSON Schema for tool parameters; root must be `type: "object"`, defaulting to 2020-12.""" + """A JSON Schema object defining the expected parameters for the tool. + + Tool arguments are always JSON objects, so `type: "object"` is required at the root. + Beyond that, any JSON Schema 2020-12 keyword may appear alongside `type` — including + composition keywords (`oneOf`, `anyOf`, `allOf`, `not`), conditional keywords + (`if`/`then`/`else`), reference keywords (`$ref`, `$defs`, `$anchor`), and any other + standard validation or annotation keywords. + + Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. + """ - # Stays open: arbitrary JSON Schema keywords ride extra fields. + # Stays open: schema keywords beyond the declared properties ride extra fields. model_config = ConfigDict( extra="allow", ) @@ -449,9 +896,13 @@ class InputSchema(WireModel): class OutputSchema(WireModel): - """JSON Schema for a tool's `structuredContent` output; defaults to 2020-12.""" + """An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. This can be any valid JSON Schema 2020-12. - # Stays open: arbitrary JSON Schema keywords ride extra fields. + Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. + """ + + # Stays open: schema keywords beyond the declared properties ride extra fields. model_config = ConfigDict( extra="allow", ) @@ -459,28 +910,71 @@ class OutputSchema(WireModel): class ToolAnnotations(WireModel): - """Tool hints; not guaranteed faithful, never trust from untrusted servers.""" + """Additional properties describing a Tool to clients. + + NOTE: all properties in `ToolAnnotations` are **hints**. + They are not guaranteed to provide a faithful description of + tool behavior (including descriptive properties like `title`). + + Clients should never make tool use decisions based on `ToolAnnotations` + received from untrusted servers. + """ destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None - """May perform destructive updates (only meaningful when not read-only); default true.""" + """ + If true, the tool may perform destructive updates to its environment. + If false, the tool performs only additive updates. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: true + """ idempotent_hint: Annotated[bool | None, Field(alias="idempotentHint")] = None - """Repeat calls with same args have no additional effect; default false.""" + """ + If true, calling the tool repeatedly with the same arguments + will have no additional effect on its environment. + + (This property is meaningful only when `readOnlyHint == false`) + + Default: false + """ open_world_hint: Annotated[bool | None, Field(alias="openWorldHint")] = None - """Interacts with an open world of external entities; default true.""" + """ + If true, this tool may interact with an "open world" of external + entities. If false, the tool's domain of interaction is closed. + For example, the world of a web search tool is open, whereas that + of a memory tool is not. + + Default: true + """ read_only_hint: Annotated[bool | None, Field(alias="readOnlyHint")] = None - """Does not modify its environment; default false.""" + """ + If true, the tool does not modify its environment. + + Default: false + """ title: str | None = None + """ + A human-readable title for the tool. + """ class ToolChoice(WireModel): - """Controls tool-selection behavior for sampling requests.""" + """Controls tool selection behavior for sampling requests.""" mode: Literal["auto", "none", "required"] | None = None - """`auto` (default) = model decides, `required` = must use a tool, `none` = must not.""" + """ + Controls the tool use ability of the model: + - `"auto"`: Model decides whether to use tools (default) + - `"required"`: Model MUST use at least one tool before completing + - `"none"`: Model MUST NOT use any tools + """ class ToolListChangedNotification(WireModel): - """Server-to-client: the tool list has changed.""" + """An optional notification from the server to the client, informing it that the list of tools it offers has + changed. This may be issued by servers without any previous subscription from the client. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/tools/list_changed"] @@ -491,29 +985,65 @@ class ToolUseContent(WireModel): """A request from the assistant to call a tool.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool use. Clients SHOULD preserve this field when + including tool uses in subsequent sampling requests to enable caching optimizations. + """ id: str - """Unique ID matching this tool use to its result.""" + """ + A unique identifier for this tool use. + + This ID is used to match tool results to their corresponding tool uses. + """ input: dict[str, Any] - """Arguments conforming to the tool's input schema.""" + """ + The arguments to pass to the tool, conforming to the tool's input schema. + """ name: str + """ + The name of the tool to call. + """ type: Literal["tool_use"] class Data1(WireModel): + """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + requested: str - """Protocol version the client requested.""" + """ + The protocol version that was requested by the client. + """ supported: list[str] - """Protocol versions the server supports; the client should retry with one of these.""" + """ + Protocol versions the server supports. The client should choose a + mutually supported version from this list and retry. + """ class Error2(WireModel): code: Literal[-32004] + """ + The error type that occurred. + """ data: Data1 + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ class UnsupportedProtocolVersionError(WireModel): - """The requested protocol version is unknown or unsupported (HTTP: `400 Bad Request`).""" + """Returned when the request's protocol version is unknown to the server or + unsupported (e.g., a known experimental or draft version the server has + chosen not to implement). For HTTP, the response status code MUST be + `400 Bad Request`. + """ error: Error2 id: RequestId | None = None @@ -521,43 +1051,95 @@ class UnsupportedProtocolVersionError(WireModel): class Items1(WireModel): - """Array-item schema.""" + """Schema for the array items.""" enum: list[str] + """ + Array of enum values to choose from. + """ type: Literal["string"] class UntitledMultiSelectEnumSchema(WireModel): - """Multi-select enum schema without per-option display titles.""" + """Schema for multiple-selection enumeration without display titles for options.""" default: list[str] | None = None + """ + Optional default value. + """ description: str | None = None + """ + Optional description for the enum field. + """ items: Items1 + """ + Schema for the array items. + """ max_items: Annotated[int | None, Field(alias="maxItems")] = None + """ + Maximum number of items to select. + """ min_items: Annotated[int | None, Field(alias="minItems")] = None + """ + Minimum number of items to select. + """ title: str | None = None + """ + Optional title for the enum field. + """ type: Literal["array"] class UntitledSingleSelectEnumSchema(WireModel): - """Single-select enum schema without per-option display titles.""" + """Schema for single-selection enumeration without display titles for options.""" default: str | None = None + """ + Optional default value. + """ description: str | None = None + """ + Optional description for the enum field. + """ enum: list[str] + """ + Array of enum values to choose from. + """ title: str | None = None + """ + Optional title for the enum field. + """ type: Literal["string"] class Annotations(WireModel): - """Client-facing annotations informing how objects are used or displayed.""" + """Optional annotations for the client. The client can use annotations to inform how objects are used or + displayed + """ audience: list[Role] | None = None - """Intended audience(s) of this object or data.""" + """ + Describes who the intended audience of this object or data is. + + It can include multiple entries to indicate content useful for multiple audiences (e.g., `["user", "assistant"]`). + """ last_modified: Annotated[str | None, Field(alias="lastModified")] = None - """ISO 8601 timestamp of last modification.""" + """ + The moment the resource was last modified, as an ISO 8601 formatted string. + + Should be an ISO 8601 formatted string (e.g., "2025-01-12T15:00:58Z"). + + Examples: last activity timestamp in an open file, timestamp when the resource + was attached, etc. + """ priority: Annotated[float | None, Field(ge=0.0, le=1.0)] = None - """Importance for operating the server: 1 = effectively required, 0 = entirely optional.""" + """ + Describes how important this data is for operating the server. + + A value of 1 means "most important," and indicates that the data is + effectively required, while 0 means "least important," and indicates that + the data is entirely optional. + """ class AudioContent(WireModel): @@ -565,53 +1147,112 @@ class AudioContent(WireModel): meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None + """ + Optional annotations for the client. + """ data: str - """Base64-encoded audio data.""" + """ + The base64-encoded audio data. + """ mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the audio. Different providers may support different audio types. + """ type: Literal["audio"] class BlobResourceContents(WireModel): meta: Annotated[MetaObject | None, Field(alias="_meta")] = None blob: str - """Base64-encoded binary data.""" + """ + A base64-encoded string representing the binary data of the item. + """ mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ uri: str + """ + The URI of this resource. + """ class CacheableResult(WireModel): - """A result carrying a TTL hint for client-side caching.""" + """A result that supports a time-to-live (TTL) hint for client-side caching.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] - """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] - """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ class CancelledNotificationParams(WireModel): + """Parameters for a `notifications/cancelled` notification.""" + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None reason: str | None = None + """ + An optional string describing the reason for the cancellation. This MAY be logged or presented to the user. + """ request_id: Annotated[RequestId | None, Field(alias="requestId")] = None - """ID of a request previously issued in the same direction.""" + """ + The ID of the request to cancel. + + This MUST correspond to the ID of a request previously issued in the same direction. + """ ClientResult: TypeAlias = Result class CompleteResult(WireModel): - """Server's result for a `completion/complete` request.""" + """The result returned by the server for a CompleteRequestcompletion/complete request.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None completion: Completion result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ class CompleteResultResponse(WireModel): - """Successful response to a `completion/complete` request.""" + """A successful response from the server for a CompleteRequestcompletion/complete request.""" id: RequestId jsonrpc: Literal["2.0"] @@ -619,10 +1260,17 @@ class CompleteResultResponse(WireModel): class EmbeddedResource(WireModel): - """Resource contents embedded in a prompt or tool-call result.""" + """The contents of a resource, embedded into a prompt or tool call result. + + It is up to the client how best to render embedded resources for the benefit + of the LLM and/or the user. + """ meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None + """ + Optional annotations for the client. + """ resource: TextResourceContents | BlobResourceContents type: Literal["resource"] @@ -644,14 +1292,22 @@ class ImageContent(WireModel): meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None + """ + Optional annotations for the client. + """ data: str - """Base64-encoded image data.""" + """ + The base64-encoded image data. + """ mime_type: Annotated[str, Field(alias="mimeType")] + """ + The MIME type of the image. Different providers may support different image types. + """ type: Literal["image"] class JSONRPCErrorResponse(WireModel): - """An error response to a request.""" + """A response to a request that indicates an error occurred.""" error: Error id: RequestId | None = None @@ -676,17 +1332,30 @@ class JSONRPCResultResponse(WireModel): class ListRootsResult(WireModel): - """Client's result for a `roots/list` request.""" + """The result returned by the client for a ListRootsRequestroots/list request. + This result contains an array of Root objects, each representing a root directory + or file that the server can operate on. + """ roots: list[Root] class LoggingMessageNotificationParams(WireModel): + """Parameters for a `notifications/message` notification.""" + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None data: Any - """Any JSON-serializable log payload.""" + """ + The data to be logged, such as a string message or an object. Any JSON serializable type is allowed here. + """ level: LoggingLevel + """ + The severity of this log message. + """ logger: str | None = None + """ + An optional name of the logger issuing this message. + """ MultiSelectEnumSchema: TypeAlias = UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema @@ -705,68 +1374,224 @@ class LoggingMessageNotificationParams(WireModel): class ProgressNotificationParams(WireModel): + """Parameters for a ProgressNotificationnotifications/progress notification.""" + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None message: str | None = None + """ + An optional message describing the current progress. + """ progress: float - """Monotonically increasing progress value; `total` may be unknown.""" + """ + The progress thus far. This should increase every time progress is made, even if the total is unknown. + """ progress_token: Annotated[ProgressToken, Field(alias="progressToken")] - """Token from the originating request, associating this notification with it.""" + """ + The progress token which was given in the initial request, used to associate this notification with the request that + is proceeding. + """ total: float | None = None + """ + Total number of items to process (or total progress required), if known. + """ class Prompt(WireModel): - """A prompt or prompt template the server offers.""" + """A prompt or prompt template that the server offers.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None arguments: list[PromptArgument] | None = None + """ + A list of arguments to use for templating the prompt. + """ description: str | None = None + """ + An optional description of what this prompt provides + """ icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ class ReadResourceResult(WireModel): - """Server's result for a `resources/read` request.""" + """The result returned by the server for a ReadResourceRequestresources/read request.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] - """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ contents: list[TextResourceContents | BlobResourceContents] result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] - """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ class Resource(WireModel): - """A known resource the server is capable of reading.""" + """A known resource that the server is capable of reading.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None + """ + Optional annotations for the client. + """ description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ size: int | None = None - """Raw content size in bytes (before base64), if known.""" + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ uri: str + """ + The URI of this resource. + """ class ResourceLink(WireModel): - """A resource reference in a prompt or tool-call result; not guaranteed to appear in `resources/list`.""" + """A resource that the server is capable of reading, included in a prompt or tool call result. + + Note: resource links returned by tools are not guaranteed to appear in the results of + ListResourcesRequestresources/list requests. + """ meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None + """ + Optional annotations for the client. + """ description: str | None = None + """ + A description of what this resource represents. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ mime_type: Annotated[str | None, Field(alias="mimeType")] = None + """ + The MIME type of this resource, if known. + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ size: int | None = None - """Raw content size in bytes (before base64), if known.""" + """ + The size of the raw resource content, in bytes (i.e., before base64 encoding or any tokenization), if known. + + This can be used by Hosts to display file sizes and estimate context window usage. + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ type: Literal["resource_link"] uri: str + """ + The URI of this resource. + """ class ResourceTemplate(WireModel): @@ -774,18 +1599,58 @@ class ResourceTemplate(WireModel): meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None + """ + Optional annotations for the client. + """ description: str | None = None + """ + A description of what this template is for. + + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a + "hint" to the model. + """ icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ mime_type: Annotated[str | None, Field(alias="mimeType")] = None - """MIME type for all matching resources; include only if uniform across matches.""" + """ + The MIME type for all resources that match this template. This should only be included if all resources matching + this template have the same type. + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ uri_template: Annotated[str, Field(alias="uriTemplate")] - """RFC 6570 URI template.""" + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ class ResourceUpdatedNotification(WireModel): - """Server-to-client: a resource the client subscribed to via `subscriptions/listen` has changed.""" + """A notification from the server to the client, informing it that a resource has changed and may need to be read + again. This is only sent for resources the client opted in to via the `resourceSubscriptions` field of a + SubscriptionsListenRequestsubscriptions/listen request. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/resources/updated"] @@ -796,7 +1661,11 @@ class ResourceUpdatedNotification(WireModel): class SubscriptionsAcknowledgedNotification(WireModel): - """First message on a `subscriptions/listen` stream, reporting which notifications the server honors.""" + """Sent by the server as the first message on a + SubscriptionsListenRequestsubscriptions/listen stream to acknowledge + that the subscription has been established and to report which notification + types it agreed to honor. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/subscriptions/acknowledged"] @@ -808,7 +1677,13 @@ class TextContent(WireModel): meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None + """ + Optional annotations for the client. + """ text: str + """ + The text content of the message. + """ type: Literal["text"] @@ -817,17 +1692,73 @@ class Tool(WireModel): meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: ToolAnnotations | None = None - """Display-name precedence: `title`, `annotations.title`, then `name`.""" + """ + Optional additional tool information. + + Display name precedence order is: `title`, `annotations.title`, then `name`. + """ description: str | None = None + """ + A human-readable description of the tool. + + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a + "hint" to the model. + """ icons: list[Icon] | None = None + """ + Optional set of sized icons that the client can display in a user interface. + + Clients that support rendering icons MUST support at least the following MIME types: + - `image/png` - PNG images (safe, universal compatibility) + - `image/jpeg` (and `image/jpg`) - JPEG images (safe, universal compatibility) + + Clients that support rendering icons SHOULD also support: + - `image/svg+xml` - SVG images (scalable but requires security precautions) + - `image/webp` - WebP images (modern, efficient format) + """ input_schema: Annotated[InputSchema, Field(alias="inputSchema")] + """ + A JSON Schema object defining the expected parameters for the tool. + + Tool arguments are always JSON objects, so `type: "object"` is required at the root. + Beyond that, any JSON Schema 2020-12 keyword may appear alongside `type` — including + composition keywords (`oneOf`, `anyOf`, `allOf`, `not`), conditional keywords + (`if`/`then`/`else`), reference keywords (`$ref`, `$defs`, `$anchor`), and any other + standard validation or annotation keywords. + + Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. + """ name: str + """ + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't + present). + """ output_schema: Annotated[OutputSchema | None, Field(alias="outputSchema")] = None + """ + An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a CallToolResult. This can be any valid JSON Schema 2020-12. + + Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. + """ title: str | None = None + """ + Intended for UI and end-user contexts — optimized to be human-readable and easily understood, + even by those unfamiliar with domain-specific terminology. + + If not provided, the name should be used for display (except for Tool, + where `annotations.title` should be given precedence over using `name`, + if present). + """ class CancelledNotification(WireModel): - """Either side cancelling a previously-issued request; the result will be unused.""" + """This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + + The request SHOULD still be in-flight, but due to communication latency, it is always possible that this + notification MAY arrive after the request has already finished. + + This notification indicates that the result will be unused, so any associated processing SHOULD cease. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/cancelled"] @@ -838,7 +1769,9 @@ class CancelledNotification(WireModel): class RequestedSchema(WireModel): - """Restricted JSON Schema subset: top-level properties only, no nesting.""" + """A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ schema_: Annotated[str | None, Field(alias="$schema")] = None properties: dict[str, PrimitiveSchemaDefinition] @@ -847,11 +1780,21 @@ class RequestedSchema(WireModel): class ElicitRequestFormParams(WireModel): - """Parameters for a form-mode `elicitation/create` request.""" + """The parameters for a request to elicit non-sensitive information from the user via a form in the client.""" message: str + """ + The message to present to the user describing what information is being requested. + """ mode: Literal["form"] = "form" + """ + The elicitation mode. + """ requested_schema: Annotated[RequestedSchema, Field(alias="requestedSchema")] + """ + A restricted subset of JSON Schema. + Only top-level properties are allowed, without nesting. + """ ElicitRequestParams: TypeAlias = ElicitRequestFormParams | ElicitRequestURLParams @@ -864,22 +1807,51 @@ class ElicitRequestFormParams(WireModel): class ListPromptsResult(WireModel): - """Server's result for a `prompts/list` request.""" + """The result returned by the server for a ListPromptsRequestprompts/list request.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] - """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None - """Opaque pagination position; if present, more results may be available.""" + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ prompts: list[Prompt] result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] - """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ class ListPromptsResultResponse(WireModel): - """Successful response to a `prompts/list` request.""" + """A successful response from the server for a ListPromptsRequestprompts/list request.""" id: RequestId jsonrpc: Literal["2.0"] @@ -887,22 +1859,51 @@ class ListPromptsResultResponse(WireModel): class ListResourceTemplatesResult(WireModel): - """Server's result for a `resources/templates/list` request.""" + """The result returned by the server for a ListResourceTemplatesRequestresources/templates/list request.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] - """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None - """Opaque pagination position; if present, more results may be available.""" + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ resource_templates: Annotated[list[ResourceTemplate], Field(alias="resourceTemplates")] result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] - """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ class ListResourceTemplatesResultResponse(WireModel): - """Successful response to a `resources/templates/list` request.""" + """A successful response from the server for a ListResourceTemplatesRequestresources/templates/list request.""" id: RequestId jsonrpc: Literal["2.0"] @@ -910,22 +1911,51 @@ class ListResourceTemplatesResultResponse(WireModel): class ListResourcesResult(WireModel): - """Server's result for a `resources/list` request.""" + """The result returned by the server for a ListResourcesRequestresources/list request.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] - """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None - """Opaque pagination position; if present, more results may be available.""" + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ resources: list[Resource] result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] - """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ class ListResourcesResultResponse(WireModel): - """Successful response to a `resources/list` request.""" + """A successful response from the server for a ListResourcesRequestresources/list request.""" id: RequestId jsonrpc: Literal["2.0"] @@ -933,22 +1963,51 @@ class ListResourcesResultResponse(WireModel): class ListToolsResult(WireModel): - """Server's result for a `tools/list` request.""" + """The result returned by the server for a ListToolsRequesttools/list request.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] - """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None - """Opaque pagination position; if present, more results may be available.""" + """ + An opaque token representing the pagination position after the last returned result. + If present, there may be more results available. + """ result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ tools: list[Tool] ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] - """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ class ListToolsResultResponse(WireModel): - """Successful response to a `tools/list` request.""" + """A successful response from the server for a ListToolsRequesttools/list request.""" id: RequestId jsonrpc: Literal["2.0"] @@ -956,7 +2015,9 @@ class ListToolsResultResponse(WireModel): class LoggingMessageNotification(WireModel): - """Server-to-client log message; opted in via `io.modelcontextprotocol/logLevel` in request `_meta`.""" + """JSONRPCNotification of a log message passed from server to client. The client opts in by setting + `"io.modelcontextprotocol/logLevel"` in a request's `_meta`. + """ jsonrpc: Literal["2.0"] method: Literal["notifications/message"] @@ -964,7 +2025,7 @@ class LoggingMessageNotification(WireModel): class ProgressNotification(WireModel): - """Out-of-band progress update for a long-running request.""" + """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" jsonrpc: Literal["2.0"] method: Literal["notifications/progress"] @@ -972,7 +2033,11 @@ class ProgressNotification(WireModel): class PromptMessage(WireModel): - """A message returned as part of a prompt; like `SamplingMessage` but supports embedded resources.""" + """Describes a message returned as part of a prompt. + + This is similar to SamplingMessage, but also supports the embedding of + resources from the MCP server. + """ content: ContentBlock role: Role @@ -995,56 +2060,121 @@ class ToolResultContent(WireModel): """The result of a tool use, provided by the user back to the assistant.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool result. Clients SHOULD preserve this field when + including tool results in subsequent sampling requests to enable caching optimizations. + """ content: list[ContentBlock] - """Unstructured result; same shape as `CallToolResult.content`.""" + """ + The unstructured result content of the tool use. + + This has the same format as CallToolResult.content and can include text, images, + audio, resource links, and embedded resources. + """ is_error: Annotated[bool | None, Field(alias="isError")] = None - """Default false.""" + """ + Whether the tool use resulted in an error. + + If true, the content typically describes the error that occurred. + Default: false + """ structured_content: Annotated[Any | None, Field(alias="structuredContent")] = None - """Any JSON value; should conform to the tool's `outputSchema` if defined.""" + """ + An optional structured result value. + + This can be any JSON value (object, array, string, number, boolean, or null). + If the tool defined an Tool.outputSchema, this SHOULD conform to that schema. + """ tool_use_id: Annotated[str, Field(alias="toolUseId")] - """ID of the corresponding `ToolUseContent`.""" + """ + The ID of the tool use this result corresponds to. + + This MUST match the ID from a previous ToolUseContent. + """ type: Literal["tool_result"] class CallToolResult(WireModel): - """Server's result for a `tools/call` request.""" + """The result returned by the server for a CallToolRequesttools/call request.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None content: list[ContentBlock] - """Unstructured result of the tool call.""" + """ + A list of content objects that represent the unstructured result of the tool call. + """ is_error: Annotated[bool | None, Field(alias="isError")] = None - """Default false; tool-level errors go here, not as protocol-level errors, so the LLM can see them.""" + """ + Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `isError` set to true, _not_ as an MCP protocol-level error + response. Otherwise, the LLM would not be able to see that an error occurred + and self-correct. + + However, any errors in _finding_ the tool, an error indicating that the + server does not support tool calls, or any other exceptional conditions, + should be reported as an MCP error response. + """ result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ structured_content: Annotated[Any | None, Field(alias="structuredContent")] = None - """Any JSON value conforming to the tool's `outputSchema` if defined.""" + """ + An optional JSON value that represents the structured result of the tool call. + + This can be any JSON value (object, array, string, number, boolean, or null) + that conforms to the tool's outputSchema if one is defined. + """ ClientNotification: TypeAlias = CancelledNotification | ProgressNotification class ElicitRequest(WireModel): - """Server request to elicit additional information from the user via the client.""" + """A request from the server to elicit additional information from the user via the client.""" method: Literal["elicitation/create"] params: ElicitRequestParams class GetPromptResult(WireModel): - """Server's result for a `prompts/get` request.""" + """The result returned by the server for a GetPromptRequestprompts/get request.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None description: str | None = None + """ + An optional description for the prompt. + """ messages: list[PromptMessage] result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent class CreateMessageResult(WireModel): - """Client's result for a `sampling/createMessage` request.""" + """The result returned by the client for a CreateMessageRequestsampling/createMessage request. + The client should inform the user before returning the sampled message, to allow them + to inspect the response (human in the loop) and decide whether to allow the server to see it. + """ meta: Annotated[MetaObject | None, Field(alias="_meta")] = None content: ( @@ -1056,21 +2186,35 @@ class CreateMessageResult(WireModel): | list[SamplingMessageContentBlock] ) model: str - """Name of the model that generated the message.""" + """ + The name of the model that generated the message. + """ role: Role stop_reason: Annotated[str | None, Field(alias="stopReason")] = None - """Open string; standard values are `"endTurn"`, `"stopSequence"`, `"maxTokens"`, `"toolUse"`.""" + """ + The reason why sampling stopped, if known. + + Standard values: + - `"endTurn"`: Natural end of the assistant's turn + - `"stopSequence"`: A stop sequence was encountered + - `"maxTokens"`: Maximum token limit was reached + - `"toolUse"`: The model wants to use one or more tools + + This field is an open string to allow for provider-specific stop reasons. + """ InputResponse: TypeAlias = CreateMessageResult | ListRootsResult | ElicitResult InputResponses: TypeAlias = dict[str, InputResponse] -"""Client responses to server-initiated requests, keyed by the matching `InputRequests` key.""" +"""A map of client responses to server-initiated requests. +Keys correspond to the keys in the InputRequests map; +values are the client's result for each request.""" class SamplingMessage(WireModel): - """A message issued to or received from an LLM API.""" + """Describes a message issued to or received from an LLM API.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None content: ( @@ -1085,7 +2229,7 @@ class SamplingMessage(WireModel): class CallToolRequest(WireModel): - """Client request to invoke a tool provided by the server.""" + """Used by the client to invoke a tool provided by the server.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1094,15 +2238,23 @@ class CallToolRequest(WireModel): class CallToolRequestParams(WireModel): + """Parameters for a `tools/call` request.""" + meta: Annotated[RequestMetaObject, Field(alias="_meta")] arguments: dict[str, Any] | None = None + """ + Arguments to use for the tool call. + """ input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None name: str + """ + The name of the tool. + """ request_state: Annotated[str | None, Field(alias="requestState")] = None class CallToolResultResponse(WireModel): - """Successful response to a `tools/call` request.""" + """A successful response from the server for a CallToolRequesttools/call request.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1120,24 +2272,47 @@ class Sampling(WireModel): """Present if the client supports sampling from an LLM.""" context: JSONObject | None = None - """Declares support for context inclusion via `includeContext`.""" + """ + Whether the client supports context inclusion via `includeContext` parameter. + If not declared, servers SHOULD only use `includeContext: "none"` (or omit it). + """ tools: JSONObject | None = None - """Declares support for `tools` and `toolChoice`.""" + """ + Whether the client supports tool use via `tools` and `toolChoice` parameters. + """ class ClientCapabilities(WireModel): - """Capabilities a client may support; not a closed set.""" + """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a + closed set: any client can define its own, additional capabilities. + """ elicitation: Elicitation | None = None + """ + Present if the client supports elicitation from the server. + """ experimental: dict[str, JSONObject] | None = None + """ + Experimental, non-standard capabilities that the client supports. + """ extensions: dict[str, JSONObject] | None = None - """Supported MCP extensions, keyed by extension identifier.""" + """ + Optional MCP extensions that the client supports. Keys are extension identifiers + (e.g., "io.modelcontextprotocol/oauth-client-credentials"), and values are + per-extension settings objects. An empty object indicates support with no settings. + """ roots: dict[str, Any] | None = None + """ + Present if the client supports listing roots. + """ sampling: Sampling | None = None + """ + Present if the client supports sampling from an LLM. + """ class CompleteRequest(WireModel): - """Client request for completion options.""" + """A request from the client to the server, to ask for completion options.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1146,42 +2321,85 @@ class CompleteRequest(WireModel): class CompleteRequestParams(WireModel): + """Parameters for a `completion/complete` request.""" + meta: Annotated[RequestMetaObject, Field(alias="_meta")] argument: Argument + """ + The argument's information + """ context: Context | None = None + """ + Additional, optional context for completions + """ ref: PromptReference | ResourceTemplateReference class CreateMessageRequest(WireModel): - """Server request for the client to sample an LLM (with human-in-the-loop approval).""" + """A request from the server to sample an LLM via the client. The client has full discretion over which model to + select. The client should also inform the user before beginning sampling, to allow them to inspect the request + (human in the loop) and decide whether to approve it. + """ method: Literal["sampling/createMessage"] params: CreateMessageRequestParams class CreateMessageRequestParams(WireModel): + """Parameters for a `sampling/createMessage` request.""" + include_context: Annotated[ Literal["allServers", "none", "thisServer"] | None, Field(alias="includeContext"), ] = None - """Default `"none"`; `"thisServer"`/`"allServers"` are deprecated (SEP-2596).""" + """ + A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. + The client MAY ignore this request. + + Default is `"none"`. The values `"thisServer"` and `"allServers"` are deprecated (SEP-2596): servers SHOULD + omit this field or use `"none"`, and SHOULD only use the deprecated values if the client declares + ClientCapabilities.sampling.context. + """ max_tokens: Annotated[int, Field(alias="maxTokens")] - """Requested cap; the client may sample fewer.""" + """ + The requested maximum number of tokens to sample (to prevent runaway completions). + + The client MAY choose to sample fewer tokens than the requested maximum. + """ messages: list[SamplingMessage] metadata: JSONObject | None = None - """Provider-specific passthrough metadata.""" + """ + Optional metadata to pass through to the LLM provider. The format of this metadata is provider-specific. + """ model_preferences: Annotated[ModelPreferences | None, Field(alias="modelPreferences")] = None + """ + The server's preferences for which model to select. The client MAY ignore these preferences. + """ stop_sequences: Annotated[list[str] | None, Field(alias="stopSequences")] = None system_prompt: Annotated[str | None, Field(alias="systemPrompt")] = None + """ + An optional system prompt the server wants to use for sampling. The client MAY modify or omit this prompt. + """ temperature: float | None = None tool_choice: Annotated[ToolChoice | None, Field(alias="toolChoice")] = None - """Error if set without `ClientCapabilities.sampling.tools`; default `{mode: "auto"}`.""" + """ + Controls how the model uses tools. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + Default is `{ mode: "auto" }`. + """ tools: list[Tool] | None = None - """Error if set without `ClientCapabilities.sampling.tools`.""" + """ + Tools that the model may use during generation. + The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + """ class DiscoverRequest(WireModel): - """Client request for the server's supported versions, capabilities, and metadata; servers must implement.""" + """A request from the client asking the server to advertise its supported + protocol versions, capabilities, and other metadata. Servers **MUST** + implement `server/discover`. Clients **MAY** call it but are not required + to — version negotiation can also happen inline via per-request `_meta`. + """ id: RequestId jsonrpc: Literal["2.0"] @@ -1190,25 +2408,67 @@ class DiscoverRequest(WireModel): class DiscoverResult(WireModel): - """Server's result for a `server/discover` request.""" + """The result returned by the server for a DiscoverRequestserver/discover request.""" meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] - """`"public"` = shareable across users/intermediaries; `"private"` = requesting user only.""" + """ + Indicates the intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + - `"public"`: Any client or intermediary (e.g., shared gateway, proxy) + MAY cache the response and serve it to any user. + - `"private"`: Only the requesting user's client MAY cache the response. + Shared caches (e.g., multi-tenant gateways) MUST NOT serve a cached + copy to a different user. + """ capabilities: ServerCapabilities + """ + The capabilities of the server. + """ instructions: str | None = None - """Natural-language guidance for the LLM; should not duplicate tool descriptions.""" + """ + Natural-language guidance describing the server and its features. + + This can be used by clients to improve an LLM's understanding of + available tools (e.g., by including it in a system prompt). It should + focus on information that helps the model use the server effectively + and should not duplicate information already in tool descriptions. + """ result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ server_info: Annotated[Implementation, Field(alias="serverInfo")] + """ + Information about the server software implementation. + """ supported_versions: Annotated[list[str], Field(alias="supportedVersions")] - """Protocol versions this server supports.""" + """ + MCP Protocol Versions this server supports. The client should choose a + version from this list for use in subsequent requests. + """ ttl_ms: Annotated[int, Field(alias="ttlMs", ge=0)] - """Cache freshness hint in ms (HTTP max-age semantics; 0 = immediately stale).""" + """ + A hint from the server indicating how long (in milliseconds) the + client MAY cache this response before re-fetching. Semantics are + analogous to HTTP Cache-Control max-age. + + - If 0, The response SHOULD be considered immediately stale, + The client MAY re-fetch every time the result is needed. + - If positive, the client SHOULD consider the result fresh for this many + milliseconds after receiving the response. + """ class DiscoverResultResponse(WireModel): - """Successful response to a `server/discover` request.""" + """A successful response from the server for a DiscoverRequestserver/discover request.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1216,7 +2476,7 @@ class DiscoverResultResponse(WireModel): class GetPromptRequest(WireModel): - """Client request for a prompt provided by the server.""" + """Used by the client to get a prompt provided by the server.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1225,15 +2485,23 @@ class GetPromptRequest(WireModel): class GetPromptRequestParams(WireModel): + """Parameters for a `prompts/get` request.""" + meta: Annotated[RequestMetaObject, Field(alias="_meta")] arguments: dict[str, str] | None = None + """ + Arguments to use for templating the prompt. + """ input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None name: str + """ + The name of the prompt or prompt template. + """ request_state: Annotated[str | None, Field(alias="requestState")] = None class GetPromptResultResponse(WireModel): - """Successful response to a `prompts/get` request.""" + """A successful response from the server for a GetPromptRequestprompts/get request.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1241,13 +2509,25 @@ class GetPromptResultResponse(WireModel): class InputRequiredResult(WireModel): - """Server signals that more input is needed; at least one of `inputRequests` or `requestState` must be present.""" + """An InputRequiredResult sent by the server to indicate that additional input is needed + before the request can be completed. + + At least one of `inputRequests` or `requestState` MUST be present. + """ meta: Annotated[MetaObject | None, Field(alias="_meta")] = None input_requests: Annotated[InputRequests | None, Field(alias="inputRequests")] = None request_state: Annotated[str | None, Field(alias="requestState")] = None result_type: Annotated[str, Field(alias="resultType")] - """Result-type discriminator; treat absence (pre-2026-07-28 peer) as `"complete"`.""" + """ + Indicates the type of the result, which allows the client to determine + how to parse the result object. + + Servers implementing this protocol version MUST include this field. + For backward compatibility, when a client receives a result from a + server implementing an earlier protocol version (which does not include + `resultType`), the client MUST treat the absent field as `"complete"`. + """ class InputResponseRequestParams(WireModel): @@ -1257,7 +2537,7 @@ class InputResponseRequestParams(WireModel): class ListPromptsRequest(WireModel): - """Client request for the server's prompts and prompt templates.""" + """Sent from the client to request a list of prompts and prompt templates the server has.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1266,7 +2546,7 @@ class ListPromptsRequest(WireModel): class ListResourceTemplatesRequest(WireModel): - """Client request for the server's resource templates.""" + """Sent from the client to request a list of resource templates the server has.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1275,7 +2555,7 @@ class ListResourceTemplatesRequest(WireModel): class ListResourcesRequest(WireModel): - """Client request for the server's resources.""" + """Sent from the client to request a list of resources the server has.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1284,14 +2564,21 @@ class ListResourcesRequest(WireModel): class ListRootsRequest(WireModel): - """Server request for the client's root URIs.""" + """Sent from the server to request a list of root URIs from the client. Roots allow + servers to ask for specific directories or files to operate on. A common example + for roots is providing a set of repositories or directories a server should operate + on. + + This request is typically used when the server needs to understand the file system + structure or access specific locations that the client has permission to read from. + """ method: Literal["roots/list"] params: RequestParams | None = None class ListToolsRequest(WireModel): - """Client request for the server's tools.""" + """Sent from the client to request a list of tools the server has.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1300,18 +2587,37 @@ class ListToolsRequest(WireModel): class Data(WireModel): + """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ + required_capabilities: Annotated[ClientCapabilities, Field(alias="requiredCapabilities")] - """Capabilities the server requires from the client to process the request.""" + """ + The capabilities the server requires from the client to process this request. + """ class Error1(WireModel): code: Literal[-32003] + """ + The error type that occurred. + """ data: Data + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error + information, nested errors etc.). + """ message: str + """ + A short description of the error. The message SHOULD be limited to a concise single sentence. + """ class MissingRequiredClientCapabilityError(WireModel): - """The request requires a client capability not declared in `clientCapabilities` (HTTP: `400 Bad Request`).""" + """Returned when processing a request requires a capability the client did not + declare in `clientCapabilities`. For HTTP, the response status code MUST be + `400 Bad Request`. + """ error: Error1 id: RequestId | None = None @@ -1326,13 +2632,18 @@ class PaginatedRequest(WireModel): class PaginatedRequestParams(WireModel): + """Common params for paginated requests.""" + meta: Annotated[RequestMetaObject, Field(alias="_meta")] cursor: str | None = None - """Opaque pagination position; results start after this cursor.""" + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ class ReadResourceRequest(WireModel): - """Client request to read a specific resource URI.""" + """Sent from the client to the server, to read a specific resource URI.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1341,15 +2652,19 @@ class ReadResourceRequest(WireModel): class ReadResourceRequestParams(WireModel): + """Parameters for a `resources/read` request.""" + meta: Annotated[RequestMetaObject, Field(alias="_meta")] input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None request_state: Annotated[str | None, Field(alias="requestState")] = None uri: str - """Any URI scheme; interpretation is server-defined.""" + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ class ReadResourceResultResponse(WireModel): - """Successful response to a `resources/read` request.""" + """A successful response from the server for a ReadResourceRequestresources/read request.""" id: RequestId jsonrpc: Literal["2.0"] @@ -1357,49 +2672,110 @@ class ReadResourceResultResponse(WireModel): class RequestMetaObject(OpenWireModel): - """Extends `MetaObject` with request-specific reserved keys; same key naming rules apply.""" + """Extends MetaObject with additional request-specific fields. All key naming rules from `MetaObject` apply.""" io_modelcontextprotocol_client_capabilities: Annotated[ ClientCapabilities, Field(alias="io.modelcontextprotocol/clientCapabilities") ] - """Per-request client capabilities; servers must not infer from prior requests.""" + """ + The client's capabilities for this specific request. Required. + + Capabilities are declared per-request rather than once at initialization; + an empty object means the client supports no optional capabilities. + Servers MUST NOT infer capabilities from prior requests. + """ io_modelcontextprotocol_client_info: Annotated[Implementation, Field(alias="io.modelcontextprotocol/clientInfo")] - """Identifies the client software making the request.""" + """ + Identifies the client software making the request. Required. + + The Implementation schema requires `name` and `version`; other + fields are optional. + """ io_modelcontextprotocol_log_level: Annotated[ LoggingLevel | None, Field(alias="io.modelcontextprotocol/logLevel") ] = None - """Log level for this request; absent means no `notifications/message` may be sent.""" + """ + The desired log level for this request. Optional. + + If absent, the server MUST NOT send any LoggingMessageNotificationnotifications/message + notifications for this request. The client opts in to log messages by + explicitly setting a level. Replaces the former `logging/setLevel` RPC. + """ io_modelcontextprotocol_protocol_version: Annotated[str, Field(alias="io.modelcontextprotocol/protocolVersion")] - """Protocol version for this request; over HTTP, must match the `MCP-Protocol-Version` header.""" + """ + The MCP Protocol Version being used for this request. Required. + + For the HTTP transport, this value MUST match the `MCP-Protocol-Version` + header; otherwise the server MUST return a `400 Bad Request`. If the + server does not support the requested version, it MUST return an + UnsupportedProtocolVersionError. + """ progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None - """Opaque token opting in to `notifications/progress` for this request.""" + """ + If specified, the caller is requesting out-of-band progress notifications for this request (as represented by + ProgressNotificationnotifications/progress). The value of this parameter is an opaque token that will be attached to + any subsequent notifications. The receiver is not obligated to provide these notifications. + """ class RequestParams(WireModel): + """Common params for any request.""" + meta: Annotated[RequestMetaObject, Field(alias="_meta")] class ResourceRequestParams(WireModel): + """Common params for resource-related requests.""" + meta: Annotated[RequestMetaObject, Field(alias="_meta")] uri: str - """Any URI scheme; interpretation is server-defined.""" + """ + The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. + """ class ServerCapabilities(WireModel): - """Capabilities a server may support; not a closed set.""" + """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a + closed set: any server can define its own, additional capabilities. + """ completions: JSONObject | None = None + """ + Present if the server supports argument autocompletion suggestions. + """ experimental: dict[str, JSONObject] | None = None + """ + Experimental, non-standard capabilities that the server supports. + """ extensions: dict[str, JSONObject] | None = None - """Supported MCP extensions, keyed by extension identifier.""" + """ + Optional MCP extensions that the server supports. Keys are extension identifiers + (e.g., "io.modelcontextprotocol/tasks"), and values are per-extension settings + objects. An empty object indicates support with no settings. + """ logging: JSONObject | None = None + """ + Present if the server supports sending log messages to the client. + """ prompts: Prompts | None = None + """ + Present if the server offers any prompt templates. + """ resources: Resources | None = None + """ + Present if the server offers any resources to read. + """ tools: Tools | None = None + """ + Present if the server offers any tools to call. + """ class SubscriptionsListenRequest(WireModel): - """Client request to open a long-lived channel for receiving notifications outside any specific request.""" + """Sent from the client to open a long-lived channel for receiving notifications + outside the context of a specific request. Replaces the previous HTTP GET + endpoint and ensures consistent behavior between HTTP and STDIO. + """ id: RequestId jsonrpc: Literal["2.0"] @@ -1408,19 +2784,15 @@ class SubscriptionsListenRequest(WireModel): class SubscriptionsListenRequestParams(WireModel): + """Parameters for a SubscriptionsListenRequestsubscriptions/listen request.""" + meta: Annotated[RequestMetaObject, Field(alias="_meta")] notifications: SubscriptionFilter - """Notification types the client opts in to on this stream.""" - - -AnyCallToolResult: TypeAlias = CallToolResult | InputRequiredResult -"""Named alias for `CallToolResultResponse.result` so the wire-method maps can reference it as a value.""" - -AnyGetPromptResult: TypeAlias = GetPromptResult | InputRequiredResult -"""Everything a `prompts/get` response's `result` may be at this version.""" - -AnyReadResourceResult: TypeAlias = ReadResourceResult | InputRequiredResult -"""Everything a `resources/read` response's `result` may be at this version.""" + """ + The notifications the client opts in to on this stream. The server + **MUST NOT** send notification types the client has not explicitly + requested. + """ ServerResult: TypeAlias = ( @@ -1456,12 +2828,23 @@ class SubscriptionsListenRequestParams(WireModel): InputRequests: TypeAlias = dict[str, InputRequest] -"""Server-initiated requests the client must fulfill, keyed by server-assigned identifier.""" +"""A map of server-initiated requests that the client must fulfill. +Keys are server-assigned identifiers; values are the request objects.""" JSONArray: TypeAlias = list["JSONValue"] +AnyCallToolResult: TypeAlias = CallToolResult | InputRequiredResult +"""Named alias for `CallToolResultResponse.result` so the wire-method maps can reference it as a value.""" + +AnyGetPromptResult: TypeAlias = GetPromptResult | InputRequiredResult +"""Everything a `prompts/get` response's `result` may be at this version.""" + +AnyReadResourceResult: TypeAlias = ReadResourceResult | InputRequiredResult +"""Everything a `resources/read` response's `result` may be at this version.""" + + CallToolRequest.model_rebuild() CallToolRequestParams.model_rebuild() CallToolResultResponse.model_rebuild() diff --git a/uv.lock b/uv.lock index b9755c382b..7970d1cc2d 100644 --- a/uv.lock +++ b/uv.lock @@ -62,6 +62,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/6f/12/e5e0282d673bb9746bacfb6e2dba8719989d3660cdb2ea79aee9a9651afb/anyio-4.10.0-py3-none-any.whl", hash = "sha256:60e474ac86736bbfd6f210f7a61218939c318f43f9972497381f1c5e930ed3d1", size = 107213, upload-time = "2025-08-04T08:54:24.882Z" }, ] +[[package]] +name = "argcomplete" +version = "3.6.3" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/38/61/0b9ae6399dd4a58d8c1b1dc5a27d6f2808023d0b5dd3104bb99f45a33ff6/argcomplete-3.6.3.tar.gz", hash = "sha256:62e8ed4fd6a45864acc8235409461b72c9a28ee785a2011cc5eb78318786c89c", size = 73754, upload-time = "2025-10-20T03:33:34.741Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/74/f5/9373290775639cb67a2fce7f629a1c240dce9f12fe927bc32b2736e16dfc/argcomplete-3.6.3-py3-none-any.whl", hash = "sha256:f5007b3a600ccac5d25bbce33089211dfd49eab4a7718da3f10e3082525a92ce", size = 43846, upload-time = "2025-10-20T03:33:33.021Z" }, +] + [[package]] name = "asttokens" version = "3.0.0" @@ -528,6 +537,26 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/21/0e/8459ca4413e1a21a06c97d134bfaf18adfd27cea068813dc0faae06cbf00/cssselect2-0.9.0-py3-none-any.whl", hash = "sha256:6a99e5f91f9a016a304dd929b0966ca464bcfda15177b6fb4a118fc0fb5d9563", size = 15453, upload-time = "2026-02-12T17:16:38.317Z" }, ] +[[package]] +name = "datamodel-code-generator" +version = "0.57.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "argcomplete" }, + { name = "black" }, + { name = "genson" }, + { name = "inflect" }, + { name = "isort" }, + { name = "jinja2" }, + { name = "pydantic" }, + { name = "pyyaml" }, + { name = "tomli", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/5d/44/87d5980f813a1e323c5d726b3ac5fec8c915ce8a77fcdceaf9c00457dbae/datamodel_code_generator-0.57.0.tar.gz", hash = "sha256:0eda778ea06eaa476e542a5f1fe1d14cc3bbf686edb33a0ad6151c7d19089906", size = 932941, upload-time = "2026-05-07T16:21:55.819Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/c5/c1/4fb9a44bb4a305b860c5a5b1866dcccfac3b76f5f170a9e68fc7733e16d2/datamodel_code_generator-0.57.0-py3-none-any.whl", hash = "sha256:d26bf5defe5154493d0aa5a822b7725332b9e9dd2abccc2f8856052286aa83b5", size = 259343, upload-time = "2026-05-07T16:21:53.823Z" }, +] + [[package]] name = "defusedxml" version = "0.7.1" @@ -576,6 +605,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/c1/ea/53f2148663b321f21b5a606bd5f191517cf40b7072c0497d3c92c4a13b1e/executing-2.2.1-py2.py3-none-any.whl", hash = "sha256:760643d3452b4d777d295bb167ccc74c64a81df23fb5e08eff250c425a4b2017", size = 28317, upload-time = "2025-09-01T09:48:08.5Z" }, ] +[[package]] +name = "genson" +version = "1.3.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/c5/cf/2303c8ad276dcf5ee2ad6cf69c4338fd86ef0f471a5207b069adf7a393cf/genson-1.3.0.tar.gz", hash = "sha256:e02db9ac2e3fd29e65b5286f7135762e2cd8a986537c075b06fc5f1517308e37", size = 34919, upload-time = "2024-05-15T22:08:49.123Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/f8/5c/e226de133afd8bb267ec27eead9ae3d784b95b39a287ed404caab39a5f50/genson-1.3.0-py3-none-any.whl", hash = "sha256:468feccd00274cc7e4c09e84b08704270ba8d95232aa280f65b986139cec67f7", size = 21470, upload-time = "2024-05-15T22:08:47.056Z" }, +] + [[package]] name = "ghp-import" version = "2.1.0" @@ -679,6 +717,19 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/fa/5e/f8e9a1d23b9c20a551a8a02ea3637b4642e22c2626e3a13a9a29cdea99eb/importlib_metadata-8.7.1-py3-none-any.whl", hash = "sha256:5a1f80bf1daa489495071efbb095d75a634cf28a8bc299581244063b53176151", size = 27865, upload-time = "2025-12-21T10:00:18.329Z" }, ] +[[package]] +name = "inflect" +version = "7.5.0" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "more-itertools" }, + { name = "typeguard" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/78/c6/943357d44a21fd995723d07ccaddd78023eace03c1846049a2645d4324a3/inflect-7.5.0.tar.gz", hash = "sha256:faf19801c3742ed5a05a8ce388e0d8fe1a07f8d095c82201eb904f5d27ad571f", size = 73751, upload-time = "2024-12-28T17:11:18.897Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/8a/eb/427ed2b20a38a4ee29f24dbe4ae2dafab198674fe9a85e3d6adf9e5f5f41/inflect-7.5.0-py3-none-any.whl", hash = "sha256:2aea70e5e70c35d8350b8097396ec155ffd68def678c7ff97f51aa69c1d92344", size = 35197, upload-time = "2024-12-28T17:11:15.931Z" }, +] + [[package]] name = "iniconfig" version = "2.1.0" @@ -704,6 +755,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/e6/04/190b336a006d4e1275c2dde1bf953336e818d18b779f24947579bb4ba48d/inline_snapshot-0.28.0-py3-none-any.whl", hash = "sha256:9988f82ee5e719445bbc437d0dc01e0a3c4c94f0ba910f8ad8b573cf15aa8348", size = 69026, upload-time = "2025-08-24T21:48:02.342Z" }, ] +[[package]] +name = "isort" +version = "8.0.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ef/7c/ec4ab396d31b3b395e2e999c8f46dec78c5e29209fac49d1f4dace04041d/isort-8.0.1.tar.gz", hash = "sha256:171ac4ff559cdc060bcfff550bc8404a486fee0caab245679c2abe7cb253c78d", size = 769592, upload-time = "2026-02-28T10:08:20.685Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/3e/95/c7c34aa53c16353c56d0b802fba48d5f5caa2cdee7958acbcb795c830416/isort-8.0.1-py3-none-any.whl", hash = "sha256:28b89bc70f751b559aeca209e6120393d43fbe2490de0559662be7a9787e3d75", size = 89733, upload-time = "2026-02-28T10:08:19.466Z" }, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -872,6 +932,9 @@ rich = [ ] [package.dev-dependencies] +codegen = [ + { name = "datamodel-code-generator" }, +] dev = [ { name = "coverage", extra = ["toml"] }, { name = "dirty-equals" }, @@ -925,6 +988,7 @@ requires-dist = [ provides-extras = ["cli", "rich"] [package.metadata.requires-dev] +codegen = [{ name = "datamodel-code-generator", specifier = "==0.57.0" }] dev = [ { name = "coverage", extras = ["toml"], specifier = ">=7.10.7,<=7.13" }, { name = "dirty-equals", specifier = ">=0.9.0" }, @@ -1559,6 +1623,15 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/81/06/c5f8deba7d2cbdfa7967a716ae801aa9ca5f734b8f54fd473ef77a088dbe/mkdocstrings_python-2.0.1-py3-none-any.whl", hash = "sha256:66ecff45c5f8b71bf174e11d49afc845c2dfc7fc0ab17a86b6b337e0f24d8d90", size = 105055, upload-time = "2025-12-03T14:26:10.184Z" }, ] +[[package]] +name = "more-itertools" +version = "11.1.0" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/de/1d/f4da6f02cdffe04d6362210b807146a26044c88d839208aec273bb0d9184/more_itertools-11.1.0.tar.gz", hash = "sha256:48e8f4d9e7e5878571ecf6f2b4e57634f93cd474cc8cfbd2376f2d11b396e30d", size = 145772, upload-time = "2026-05-22T14:14:29.909Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/e8/3d/1087453384dbde46a8c7f9356eead2c58be8a7bf156bca40243377c85715/more_itertools-11.1.0-py3-none-any.whl", hash = "sha256:4b65538ae22f6fed0ce4874efd317463a7489796a0939fa66824dd542125a192", size = 72226, upload-time = "2026-05-22T14:14:28.824Z" }, +] + [[package]] name = "mypy-extensions" version = "1.1.0" @@ -2655,6 +2728,18 @@ wheels = [ { url = "https://files.pythonhosted.org/packages/31/5b/94237a3485620dbff9741df02ff6d8acaa5fdec67d81ab3f62e4d8511bf7/trio-0.31.0-py3-none-any.whl", hash = "sha256:b5d14cd6293d79298b49c3485ffd9c07e3ce03a6da8c7dfbe0cb3dd7dc9a4774", size = 512679, upload-time = "2025-09-09T15:17:13.821Z" }, ] +[[package]] +name = "typeguard" +version = "4.5.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "typing-extensions" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/67/1c/dfba5c4633cafc4c701f237d2ba63b416805047fd6d96aab4cfc40969f98/typeguard-4.5.2.tar.gz", hash = "sha256:5a16dcac23502039299c97c8941651bc33d7ea8cc4b2f7d6bbb1b528f6eea423", size = 80240, upload-time = "2026-05-14T12:59:40.857Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/5b/29/74eeb4d3f3ae61ca096b018ad486b3b3c74b17bec09ab4edab721cbefec3/typeguard-4.5.2-py3-none-any.whl", hash = "sha256:fcf9de18bd945cdb4c7b996e12b4c51ce83f92f191314a6d7cf1739586ec98cf", size = 36748, upload-time = "2026-05-14T12:59:39.473Z" }, +] + [[package]] name = "typer" version = "0.17.4" From 30ca8b9d3240403cf389893e986a96849f17038d Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Mon, 15 Jun 2026 17:21:54 +0000 Subject: [PATCH 3/7] Simplify surface-type generator to raw codegen output with minimal fixes --- pyproject.toml | 2 + scripts/gen_surface_types.py | 1018 ++------------- src/mcp/types/methods.py | 25 +- src/mcp/types/v2025_11_25/__init__.py | 1659 +++++++++++++++++-------- src/mcp/types/v2026_07_28/__init__.py | 1476 ++++++++++++++++------ tests/types/test_methods.py | 16 +- tests/types/test_parity.py | 9 +- 7 files changed, 2407 insertions(+), 1798 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2ea5cf1a08..9ce06a4dc5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -178,6 +178,8 @@ max-complexity = 24 # Default is 10 [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] +# Generated by scripts/gen_surface_types.py: raw datamodel-codegen output. +"src/mcp/types/v*/__init__.py" = ["D212", "E501", "I001", "TID251", "UP007", "UP037"] "tests/server/mcpserver/test_func_metadata.py" = ["E501"] "tests/shared/test_progress_notifications.py" = ["PLW0603"] diff --git a/scripts/gen_surface_types.py b/scripts/gen_surface_types.py index 5125933e4c..d8da3ad4e6 100644 --- a/scripts/gen_surface_types.py +++ b/scripts/gen_surface_types.py @@ -1,971 +1,185 @@ """Regenerate the per-version wire-shape surface packages from vendored schemas. -The two surfaces under ``src/mcp/types/v/__init__.py`` are pure -generator output: this script is the only writer. For each entry of -``schema/PINNED.json`` it verifies the vendored ``schema/.json`` -against the recorded sha256, runs ``datamodel-code-generator`` (installed via -the ``codegen`` dependency group), applies the post-processing passes -documented on each pass function below, runs ruff, imports the candidate to -audit definition names / field requiredness / ``_meta`` openness, and writes -the result. - -Usage (from the repo root):: - - uv run --group codegen python scripts/gen_surface_types.py [--check] - -``--check`` regenerates to a temp location and diffs against the committed -files; non-zero exit (with a unified diff) on any drift. +Runs ``datamodel-code-generator`` over each ``schema/PINNED.json`` entry and +writes the result to ``src/mcp/types/v/__init__.py`` with only the +fixes the raw output needs: a small JSON pre-patch for the known +``number``-as-``integer`` schema.json defect, a header, and per-version +epilogue aliases. Run with ``uv run --group codegen python scripts/gen_surface_types.py [--check]``. """ from __future__ import annotations import argparse -import ast import difflib import hashlib -import importlib.util import json import re import shutil import subprocess import sys import tempfile -import textwrap -import typing -from dataclasses import dataclass from pathlib import Path -from types import ModuleType from typing import Any -from pydantic import BaseModel - REPO_ROOT = Path(__file__).resolve().parent.parent SCHEMA_DIR = REPO_ROOT / "schema" -PINNED_PATH = SCHEMA_DIR / "PINNED.json" -SRC_TYPES_DIR = REPO_ROOT / "src" / "mcp" / "types" - -_SRC_BASE_CLASS = "mcp.types._wire_base.WireModel" - -# {@link Target} or {@link Target | label}: emit the label when present. -_JSDOC_LINK_RE = re.compile(r"\{@link\s+(?:[^}|]+?\s*\|\s*)?([^}|]+?)\s*\}") -_GENERATOR_BANNER_RE = re.compile(r"\A# generated by datamodel-codegen:\n(# filename: [^\n]*\n)?\n*") -_FIELD_ALIAS_RE = re.compile(r"alias=['\"]([^'\"]+)['\"]") - -# Wire names whose referenced classes stay extra="allow": schema-open -# pass-through interiors whose unknown keys must survive revalidation. -_OPEN_INTERIOR_FIELD_ALIASES = frozenset({"inputSchema", "outputSchema"}) -_OPEN_INTERIOR_COMMENT = " # Stays open: schema keywords beyond the declared properties ride extra fields.\n" -_SUBSCRIPTION_FILTER_COMMENT = " # Stays open: filter contents are extensible on the wire.\n" - -# Names a relocated cyclic-alias block may reference besides its own members: -# anything else means relocation could break runtime evaluation order. -_RELOCATABLE_NAMES = frozenset( - {"Annotated", "Any", "Literal", "None", "Optional", "Union"} - | {"bool", "bytes", "dict", "float", "frozenset", "int", "list", "set", "str", "tuple"} -) - -# Per-version module docstrings (the generator emits these verbatim). -SRC_HEADERS: dict[str, str] = { - "2025-11-25": '''\ -"""Internal wire-shape models for protocol 2025-11-25. Not part of the public API. - -Generated by `scripts/gen_surface_types.py` from `schema/2025-11-25.json` -@ {spec_commit}. Do not edit; regenerate. Serves inbound validation for every -protocol version through 2025-11-25 (each earlier schema is a strict subset of -this one). Models default to `extra="ignore"`; the few kept open are commented -in place. See `mcp.types._wire_base` and `mcp.types.methods`. -""" -''', - "2026-07-28": '''\ -"""Internal wire-shape models for protocol 2026-07-28. Not part of the public API. - -Generated by `scripts/gen_surface_types.py` from `schema/2026-07-28.json` -@ {spec_commit}. Do not edit; regenerate. Schema-exact validators that the -wire-method maps in `mcp.types.methods` point inbound 2026-07-28 validation at. -Models use `extra="ignore"` (unknown keys accepted and dropped) unless commented -otherwise; see `mcp.types._wire_base`. -""" -''', -} +TYPES_DIR = REPO_ROOT / "src" / "mcp" / "types" # schema.ts -> schema.json renders TypeScript ``number`` as JSON Schema -# ``integer`` at these sites, so the generator emits ``int``-only annotations -# that reject the floats schema.ts permits. Each entry rewrites one field's -# annotation back to what schema.ts declares. -_NUMBER_PATCH_NOTE = " # schema.json renders this `number` as integer; widen per schema.ts so floats validate.\n" -FIELD_TYPE_PATCHES: dict[str, list[tuple[str, str, str]]] = { +# ``integer`` at these sites; patch the JSON before codegen so floats validate. +SCHEMA_PATCHES: dict[str, list[tuple[str, Any, Any]]] = { "2025-11-25": [ - ("ElicitResult", "content", "dict[str, list[str] | str | int | float | bool] | None"), - ("NumberSchema", "default", "int | float | None"), - ("NumberSchema", "maximum", "int | float | None"), - ("NumberSchema", "minimum", "int | float | None"), + ("$defs/NumberSchema/properties/default/type", "integer", "number"), + ("$defs/NumberSchema/properties/maximum/type", "integer", "number"), + ("$defs/NumberSchema/properties/minimum/type", "integer", "number"), + ( + "$defs/ElicitResult/properties/content/additionalProperties/anyOf/1/type", + ["string", "integer", "boolean"], + ["string", "number", "boolean"], + ), ], "2026-07-28": [ - ("ElicitResult", "content", "dict[str, list[str] | str | int | float | bool] | None"), + ( + "$defs/ElicitResult/properties/content/additionalProperties/anyOf/1/type", + ["string", "integer", "boolean"], + ["string", "number", "boolean"], + ), + ("$defs/JSONValue/anyOf/2/type", ["string", "integer", "boolean"], ["string", "number", "boolean"]), ], } -# Same defect for the recursive JSONValue alias (2026 only): schema.json's -# primitive arm is string|integer|boolean where schema.ts has all six JSON -# types. Rewrites the TypeAliasType value string. -ALIAS_TYPE_PATCHES: dict[str, dict[str, str]] = { - "2026-07-28": {"JSONValue": "JSONObject | list[JSONValue] | str | int | float | bool | None"}, -} - -# Hand-written union aliases the wire-method maps reference by value; the -# schema has no named definition for "everything tools/call may return", so the -# generator emits the union inline on the response model and these name it. +# Hand-written union aliases the wire-method maps reference by value; the schema +# has no named definition for "everything tools/call may return", so name it here. EPILOGUES: dict[str, str] = { - "2026-07-28": '''\ -AnyCallToolResult: TypeAlias = CallToolResult | InputRequiredResult -"""Named alias for `CallToolResultResponse.result` so the wire-method maps can reference it as a value.""" - -AnyGetPromptResult: TypeAlias = GetPromptResult | InputRequiredResult -"""Everything a `prompts/get` response's `result` may be at this version.""" - -AnyReadResourceResult: TypeAlias = ReadResourceResult | InputRequiredResult -"""Everything a `resources/read` response's `result` may be at this version.""" -''', + "2026-07-28": ( + "AnyCallToolResult = CallToolResult | InputRequiredResult\n" + "AnyGetPromptResult = GetPromptResult | InputRequiredResult\n" + "AnyReadResourceResult = ReadResourceResult | InputRequiredResult\n" + ), } - -class PipelineError(Exception): - """A stage of the pipeline failed; the message says which and why.""" - - -@dataclass -class Entry: - """One PINNED.json entry.""" - - protocol_version: str - source_path_in_spec_repo: str - spec_commit: str - sha256: str - - @property - def module(self) -> str: - return "v" + self.protocol_version.replace("-", "_") - - @property - def schema_file(self) -> Path: - return SCHEMA_DIR / f"{self.protocol_version}.json" - - @property - def target(self) -> Path: - return SRC_TYPES_DIR / self.module / "__init__.py" +HEADER = ( + '"""Internal wire-shape models for protocol {version}. Generated; do not edit.\n' + "\n" + "Regenerate with ``scripts/gen_surface_types.py`` from ``schema/{version}.json``\n" + '(sha256 ``{sha}``)."""\n' + "# pyright: reportIncompatibleVariableOverride=false, reportGeneralTypeIssues=false\n" +) -def load_pinned() -> list[Entry]: - """Read schema/PINNED.json and verify each vendored file's sha256.""" - with PINNED_PATH.open() as f: - raw: list[dict[str, str]] = json.load(f) - entries = [Entry(**item) for item in raw] +def load_pinned() -> list[dict[str, str]]: + """Read ``schema/PINNED.json`` and verify each vendored file's sha256.""" + entries: list[dict[str, str]] = json.loads((SCHEMA_DIR / "PINNED.json").read_text()) for entry in entries: - if not entry.schema_file.is_file(): - raise PipelineError(f"{entry.protocol_version}: vendored schema {entry.schema_file} is missing") - actual = hashlib.sha256(entry.schema_file.read_bytes()).hexdigest() - if actual != entry.sha256: - raise PipelineError( - f"{entry.protocol_version}: sha256 mismatch for {entry.schema_file.name} " - f"(PINNED.json={entry.sha256}, on disk={actual})" - ) + path = SCHEMA_DIR / f"{entry['protocol_version']}.json" + actual = hashlib.sha256(path.read_bytes()).hexdigest() + if actual != entry["sha256"]: + raise SystemExit(f"sha256 mismatch for {path.name}: PINNED={entry['sha256']} disk={actual}") return entries -def run_generator(schema_path: Path, output_path: Path) -> None: +def patch_schema(schema: dict[str, Any], patches: list[tuple[str, Any, Any]]) -> None: + """Apply ``(path, old, new)`` JSON-pointer-ish patches in place, asserting the old value.""" + for path, old, new in patches: + *parts, leaf = path.split("/") + node: Any = schema + for part in parts: + node = node[int(part) if part.isdigit() else part] + if node[leaf] != old: + raise SystemExit(f"schema patch {path}: expected {old!r}, found {node[leaf]!r}") + node[leaf] = new + + +def run_codegen(schema_path: Path, output_path: Path) -> None: """Run datamodel-code-generator (from the ``codegen`` dependency group).""" exe = shutil.which("datamodel-codegen") cmd = [exe] if exe else ["uv", "run", "--group", "codegen", "datamodel-codegen"] + # fmt: off result = subprocess.run( [ *cmd, - "--input", - str(schema_path), - "--input-file-type", - "jsonschema", - "--output", - str(output_path), - "--output-model-type", - "pydantic_v2.BaseModel", - "--target-python-version", - "3.10", - "--base-class", - _SRC_BASE_CLASS, - "--snake-case-field", - "--remove-special-field-name-prefix", - "--use-annotated", - "--use-field-description", - "--use-schema-description", - "--enum-field-as-literal", - "all", - "--use-union-operator", - "--use-double-quotes", - "--extra-fields", - "allow", + "--input", str(schema_path), + "--input-file-type", "jsonschema", + "--output", str(output_path), + "--output-model-type", "pydantic_v2.BaseModel", + "--target-python-version", "3.10", + "--base-class", "mcp.types._wire_base.WireModel", + "--snake-case-field", "--remove-special-field-name-prefix", + "--use-annotated", "--use-field-description", "--use-schema-description", + "--enum-field-as-literal", "all", + "--use-union-operator", "--use-double-quotes", + "--extra-fields", "allow", "--disable-timestamp", ], - capture_output=True, - text=True, - ) - if result.returncode != 0 or not output_path.is_file(): - raise PipelineError(f"datamodel-codegen failed:\n{result.stderr.strip()}") - - -def _end_lineno(node: ast.stmt) -> int: - """End line of a statement (ast fills end_lineno for parsed code).""" - end = node.end_lineno - if end is None: - raise PipelineError(f"ast node at line {node.lineno} has no end_lineno") - return end - - -def _collect_rootmodel_aliases(tree: ast.Module) -> dict[str, tuple[ast.ClassDef, str]]: - """Map class name -> (node, unparsed RootModel type argument) for RootModel subclasses.""" - aliases: dict[str, tuple[ast.ClassDef, str]] = {} - for node in tree.body: - if not isinstance(node, ast.ClassDef) or len(node.bases) != 1: - continue - base = node.bases[0] - if isinstance(base, ast.Subscript) and isinstance(base.value, ast.Name) and base.value.id == "RootModel": - aliases[node.name] = (node, ast.unparse(base.slice)) - return aliases - - -def _find_recursive_aliases(aliases: dict[str, tuple[ast.ClassDef, str]]) -> set[str]: - """Names of alias classes that participate in a reference cycle.""" - - def refs(type_expr: str) -> set[str]: - bare = type_expr.replace("'", " ").replace('"', " ") - return {name for name in aliases if re.search(rf"\b{name}\b", bare)} - - cyclic: set[str] = set() - for name, (_, type_expr) in aliases.items(): - seen: set[str] = set() - stack = list(refs(type_expr)) - while stack: - current = stack.pop() - if current == name: - cyclic.add(name) - break - if current in seen or current not in aliases: - continue - seen.add(current) - stack.extend(refs(aliases[current][1])) - return cyclic - - -def _alias_statement(name: str, node: ast.ClassDef, alias_type: str, *, string_form: bool) -> str: - """One converted alias definition (docstring preserved as a trailing string).""" - if string_form: - value = alias_type.replace("'", "").replace('"', "") - text = f'{name} = TypeAliasType("{name}", "{value}")\n' - else: - text = f'{name} = TypeAliasType("{name}", {alias_type})\n' - docstring = ast.get_docstring(node) - if docstring and '"""' not in docstring: - text += '"""' + docstring + '"""\n' - return text - - -def _emit_cyclic_block( - ordered: list[str], aliases: dict[str, tuple[ast.ClassDef, str]], alias_patches: dict[str, str] -) -> str: - """Emit a group of mutually recursive aliases in the one form pyright accepts. - - Self-referencing members go first as a whole-string value (lazily parsed), - the rest follow in expression form so quoted references point backwards; - the caller relocates the whole block to just after the imports. Any - ``alias_patches`` entry overrides the generator's value (always emitted - string-form, with a leading comment). - """ - - def has_self_ref(name: str) -> bool: - bare = aliases[name][1].replace("'", " ").replace('"', " ") - return re.search(rf"\b{name}\b", bare) is not None - - string_members = [n for n in ordered if has_self_ref(n)] - expr_members = [n for n in ordered if not has_self_ref(n)] - parts: list[str] = [] - for name in [*string_members, *expr_members]: - node, generated = aliases[name] - if name in alias_patches: - parts.append( - "# schema.json renders the primitive arm without float/null; widen per schema.ts.\n" - + _alias_statement(name, node, alias_patches[name], string_form=True) - ) - else: - parts.append(_alias_statement(name, node, generated, string_form=name in string_members)) - return "\n\n".join(parts) - - -def _group_is_self_contained(cyclic: set[str], aliases: dict[str, tuple[ast.ClassDef, str]]) -> bool: - """True if the cyclic group references only its own members and builtins.""" - allowed = cyclic | _RELOCATABLE_NAMES - for name in cyclic: - bare = aliases[name][1].replace("'", " ").replace('"', " ") - identifiers = set(re.findall(r"[A-Za-z_][A-Za-z0-9_]*", bare)) - if not identifiers <= allowed: - return False - return True - - -def postprocess(source: str, alias_patches: dict[str, str]) -> tuple[str, list[str]]: - """Convert RootModel wrappers to aliases and clean up generator artifacts. - - Returns the rewritten source and the sorted recursive-alias names (for the - follow-up assertion that the relocated block survived). - """ - source = _GENERATOR_BANNER_RE.sub("", source) - tree = ast.parse(source) - lines = source.splitlines(keepends=True) - - aliases = _collect_rootmodel_aliases(tree) - cyclic = _find_recursive_aliases(aliases) - - converted = 0 - cyclic_in_order = [name for name in aliases if name in cyclic] - relocate = bool(cyclic) and _group_is_self_contained(cyclic, aliases) - replacements: list[tuple[int, int, str]] = [] # 1-based inclusive line ranges - for name, (node, alias_type) in aliases.items(): - if name == "Model" and alias_type == "Any": - # The generator's synthetic artifact for the schema's empty root. - replacements.append((node.lineno, _end_lineno(node), "")) - continue - if name in cyclic: - # The whole group is emitted as one block (see _emit_cyclic_block): - # relocated to just after the imports when self-contained, else at - # the first member's position. - if relocate or name != cyclic_in_order[0]: - new = "" - else: - new = _emit_cyclic_block(cyclic_in_order, aliases, alias_patches) - else: - new = f"{name}: TypeAlias = {alias_type}\n" - docstring = ast.get_docstring(node) - if docstring and '"""' not in docstring: - new += '"""' + docstring + '"""\n' - replacements.append((node.lineno, _end_lineno(node), new)) - converted += 1 - if relocate: - last_import_end = max(_end_lineno(node) for node in tree.body if isinstance(node, ast.Import | ast.ImportFrom)) - block = _emit_cyclic_block(cyclic_in_order, aliases, alias_patches) - # Pure insertion AFTER the last import line (start = end + 1), never a - # replacement of it: the import line itself may carry its own - # replacement (dropping RootModel), and two edits to one line range - # silently clobber each other. - replacements.append((last_import_end + 1, last_import_end, "\n" + block)) - - # Import fixes, on the same parsed tree: drop RootModel from the pydantic - # import, add TypeAlias / TypeAliasType where conversions need them. - need_type_alias = converted > len(cyclic) - need_type_alias_type = bool(cyclic) - typing_handled = False - for node in tree.body: - if not isinstance(node, ast.ImportFrom): - continue - if node.module == "pydantic": - names = [alias for alias in node.names if alias.name != "RootModel"] - text = ast.unparse(ast.ImportFrom(module="pydantic", names=names, level=0)) + "\n" if names else "" - replacements.append((node.lineno, _end_lineno(node), text)) - elif node.module == "typing": - typing_handled = True - names = list(node.names) - if need_type_alias and not any(alias.name == "TypeAlias" for alias in names): - names.append(ast.alias(name="TypeAlias")) - text = ast.unparse(ast.ImportFrom(module="typing", names=names, level=0)) + "\n" - if need_type_alias_type: - text += "from typing_extensions import TypeAliasType\n" - replacements.append((node.lineno, _end_lineno(node), text)) - if not typing_handled and (need_type_alias or need_type_alias_type): - extra = "from typing import TypeAlias\n" if need_type_alias else "" - if need_type_alias_type: - extra += "from typing_extensions import TypeAliasType\n" - for node in tree.body: - if isinstance(node, ast.ImportFrom) and node.module == "__future__": - replacements.append((node.lineno, _end_lineno(node), lines[node.lineno - 1] + extra)) - break - - for start, end, new in sorted(replacements, reverse=True): - lines[start - 1 : end] = [new] - source = "".join(lines) - - # model_rebuild() is only valid on classes; drop calls for converted names. - for name in [*aliases, "Model"]: - source = re.sub(rf"^{name}\.model_rebuild\(\)\n", "", source, flags=re.MULTILINE) - - source = _JSDOC_LINK_RE.sub(r"\1", source) - return source, sorted(cyclic) - - -def assert_alias_blocks_present(source: str, recursive: list[str]) -> None: - """Assert every recursive alias survived post-processing into the output.""" - for name in recursive: - if not re.search(rf'^{name} = TypeAliasType\("{name}"', source, flags=re.MULTILINE): - raise PipelineError(f"recursive alias {name!r} is missing from the post-processed output") - - -def downgrade_value_transforms(source: str) -> str: - """Replace value-transforming pydantic types with plain ``str``. - - URL normalization and base64 re-encoding change bytes on a validate -> - re-dump round trip; the wire models must reproduce input bytes exactly. - """ - source = re.sub(r"\b(AnyUrl|FileUrl|Base64Str)\b", "str", source) - match = re.search(r"^from pydantic import (.+)$", source, flags=re.MULTILINE) - if match: - names = [n.strip() for n in match.group(1).split(",")] - kept = [n for n in names if n != "str"] - source = source.replace(match.group(0), f"from pydantic import {', '.join(kept)}") - return source - - -def widen_structured_content(source: str) -> str: - """Widen typed ``structuredContent`` fields to ``Any`` (2026-07-28 only). - - The 2026-07-28 schema types the field as any JSON value; the 2025-11-25 - surface keeps the schema's ``dict[str, Any]`` so this pass is gated. - """ - tree = ast.parse(source) - lines = source.splitlines(keepends=True) - statements = [ - stmt - for node in tree.body - if isinstance(node, ast.ClassDef) - for stmt in node.body - if isinstance(stmt, ast.AnnAssign) - and isinstance(stmt.target, ast.Name) - and stmt.target.id == "structured_content" - ] - for stmt in sorted(statements, key=lambda s: s.lineno, reverse=True): - start, end = stmt.lineno, _end_lineno(stmt) - segment = "".join(lines[start - 1 : end]) - lines[start - 1 : end] = [segment.replace("dict[str, Any]", "Any")] - return "".join(lines) - - -def _class_field_aliases(node: ast.ClassDef) -> list[tuple[ast.AnnAssign, str, str]]: - """(statement, field name, wire alias) for every annotated field of a class.""" - out: list[tuple[ast.AnnAssign, str, str]] = [] - for stmt in node.body: - if not isinstance(stmt, ast.AnnAssign) or not isinstance(stmt.target, ast.Name): - continue - annotation_text = ast.unparse(stmt.annotation) - alias_match = _FIELD_ALIAS_RE.search(annotation_text) - alias = alias_match.group(1) if alias_match else stmt.target.id - out.append((stmt, stmt.target.id, alias)) - return out - - -def apply_field_type_patches(source: str, patches: list[tuple[str, str, str]]) -> str: - """Rewrite the annotations listed in ``FIELD_TYPE_PATCHES`` for one version. - - Each patch names a (class, field) and the replacement annotation; the - generator's annotation is found via AST and the new one is spliced into the - same line range with a one-line provenance comment above it. - """ - if not patches: - return source - tree = ast.parse(source) - lines = source.splitlines(keepends=True) - classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} - replacements: list[tuple[int, int, str]] = [] - for cls_name, field_name, new_annotation in patches: - node = classes.get(cls_name) - if node is None: - raise PipelineError(f"field-type patch targets missing class {cls_name}") - stmt = next( - (s for s, name, _ in _class_field_aliases(node) if name == field_name), - None, - ) - if stmt is None: - raise PipelineError(f"field-type patch targets missing field {cls_name}.{field_name}") - default = f" = {ast.unparse(stmt.value)}" if stmt.value is not None else "" - new = f"{_NUMBER_PATCH_NOTE} {field_name}: {new_annotation}{default}\n" - replacements.append((stmt.lineno, _end_lineno(stmt), new)) - for start, end, new in sorted(replacements, reverse=True): - lines[start - 1 : end] = [new] - return "".join(lines) - - -def flatten_inheritance(source: str) -> str: - """Flatten classes that subclass other generated classes. - - The generator emits subclassing where one definition narrows another; - pyright's dataclass-transform rules reject the narrowed overrides, and the - committed packages carry no suppressions. Flattening re-declares inherited - fields directly: base fields in base order, overrides in place, own fields - appended. - """ - tree = ast.parse(source) - lines = source.splitlines(keepends=True) - classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} - - def field_segments(node: ast.ClassDef) -> list[tuple[str, str]]: - segments: list[tuple[str, str]] = [] - body = list(node.body) - for index, stmt in enumerate(body): - if not isinstance(stmt, ast.AnnAssign) or not isinstance(stmt.target, ast.Name): - continue - end = _end_lineno(stmt) - if index + 1 < len(body): - nxt = body[index + 1] - if isinstance(nxt, ast.Expr) and isinstance(nxt.value, ast.Constant): - if isinstance(nxt.value.value, str): - end = _end_lineno(nxt) - segments.append((stmt.target.id, "".join(lines[stmt.lineno - 1 : end]))) - return segments - - resolved: dict[str, list[tuple[str, str]]] = {} - - def resolve(name: str) -> list[tuple[str, str]]: - if name in resolved: - return resolved[name] - node = classes[name] - base_names = [b.id for b in node.bases if isinstance(b, ast.Name) and b.id in classes] - merged: list[tuple[str, str]] = [] - for base in base_names: - for field_name, text in resolve(base): - if field_name not in {n for n, _ in merged}: - merged.append((field_name, text)) - for field_name, text in field_segments(node): - existing = [i for i, (n, _) in enumerate(merged) if n == field_name] - if existing: - merged[existing[0]] = (field_name, text) - else: - merged.append((field_name, text)) - resolved[name] = merged - return merged - - replacements: list[tuple[int, int, str]] = [] - for name, node in classes.items(): - generated_bases = [b.id for b in node.bases if isinstance(b, ast.Name) and b.id in classes] - if not generated_bases: - continue - parts: list[str] = [f"class {name}(WireModel):\n"] - if ast.get_docstring(node) is not None: - stmt = node.body[0] - parts.append("".join(lines[stmt.lineno - 1 : _end_lineno(stmt)])) - parts.extend( - "".join(lines[stmt.lineno - 1 : _end_lineno(stmt)]) - for stmt in node.body - if isinstance(stmt, ast.Assign) - and any(isinstance(t, ast.Name) and t.id == "model_config" for t in stmt.targets) - ) - parts.extend(text for _, text in resolve(name)) - replacements.append((node.lineno, _end_lineno(node), "".join(parts))) - - for start, end, new in sorted(replacements, reverse=True): - lines[start - 1 : end] = [new] - return "".join(lines) - - -def rebase_meta_carriers(source: str) -> tuple[str, list[str]]: - """Rebase classes referenced from ``_meta`` fields onto ``OpenWireModel``.""" - tree = ast.parse(source) - classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} - carriers: set[str] = set() - for node in classes.values(): - for stmt, _, alias in _class_field_aliases(node): - if alias != "_meta": - continue - annotation_text = ast.unparse(stmt.annotation) - carriers.update(name for name in classes if re.search(rf"\b{name}\b", annotation_text)) - for name in sorted(carriers): - source = re.sub( - rf"^class {name}\(WireModel\):$", - f"class {name}(OpenWireModel):", - source, - flags=re.MULTILINE, - ) - return source, sorted(carriers) - - -def apply_extra_policy(source: str, carriers: list[str]) -> str: - """Force ``extra="ignore"`` everywhere except the enumerated open classes.""" - tree = ast.parse(source) - lines = source.splitlines(keepends=True) - classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} - - keep_allow: set[str] = set() - for node in classes.values(): - for stmt, _, alias in _class_field_aliases(node): - if alias in _OPEN_INTERIOR_FIELD_ALIASES: - annotation_text = ast.unparse(stmt.annotation) - keep_allow.update(name for name in classes if re.search(rf"\b{name}\b", annotation_text)) - if "SubscriptionFilter" in classes: - keep_allow.add("SubscriptionFilter") - - replacements: list[tuple[int, int, str]] = [] - for name, node in classes.items(): - config_stmt = next( - ( - stmt - for stmt in node.body - if isinstance(stmt, ast.Assign) - and any(isinstance(t, ast.Name) and t.id == "model_config" for t in stmt.targets) - ), - None, - ) - if config_stmt is None: - continue - value = config_stmt.value - is_plain_allow = ( - isinstance(value, ast.Call) - and isinstance(value.func, ast.Name) - and value.func.id == "ConfigDict" - and len(value.keywords) == 1 - and value.keywords[0].arg == "extra" - and isinstance(value.keywords[0].value, ast.Constant) - and value.keywords[0].value.value == "allow" - ) - if not is_plain_allow: - raise PipelineError(f"unexpected model_config on {name}: {ast.unparse(config_stmt)}") - if name in keep_allow and name not in carriers: - comment = _SUBSCRIPTION_FILTER_COMMENT if name == "SubscriptionFilter" else _OPEN_INTERIOR_COMMENT - replacements.append((config_stmt.lineno, config_stmt.lineno - 1, comment)) - continue - remaining = [stmt for stmt in node.body if stmt is not config_stmt] - new = "" if remaining else " pass\n" - replacements.append((config_stmt.lineno, _end_lineno(config_stmt), new)) - - for start, end, new in sorted(replacements, reverse=True): - lines[start - 1 : end] = [new] - return "".join(lines) - - -def deterministic_synthetic_names(source: str) -> str: - """Rename counter-based synthetic classes after their single owner.""" - tree = ast.parse(source) - classes: dict[str, ast.ClassDef] = {n.name: n for n in tree.body if isinstance(n, ast.ClassDef)} - synthetic = {name for name in classes if re.fullmatch(r"(?:Params|Meta)\d*", name)} - references: dict[str, list[tuple[str, str]]] = {name: [] for name in synthetic} - for node in classes.values(): - for stmt, field_name, _ in _class_field_aliases(node): - annotation_text = ast.unparse(stmt.annotation) - for name in synthetic: - if re.search(rf"\b{name}\b", annotation_text): - references[name].append((node.name, field_name)) - - renames: dict[str, str] = {} - taken = set(classes) - for name in sorted(synthetic): - refs = references[name] - if len(refs) != 1: - continue - owner, field_name = refs[0] - suffix = {"params": "Params", "meta": "Meta"}.get(field_name) - if suffix is None: - continue - target = f"{owner}{suffix}" - if target in taken: - continue - renames[name] = target - taken.add(target) - - if renames: - pattern = re.compile(r"\b(" + "|".join(sorted(renames, key=len, reverse=True)) + r")\b") - source = pattern.sub(lambda m: renames[m.group(1)], source) - return source - - -def _join_class_docstring_summaries(source: str) -> str: - """Move each class docstring summary onto the opening quote line. - - The D212 fix, applied first so the long-line wrapping below stays in - charge of the resulting line length. - """ - tree = ast.parse(source) - lines = source.splitlines(keepends=True) - for node in tree.body: - if not isinstance(node, ast.ClassDef) or ast.get_docstring(node) is None: - continue - stmt = node.body[0] - first = lines[stmt.lineno - 1] - if first.strip() != '"""' or _end_lineno(stmt) == stmt.lineno: - continue - summary = lines[stmt.lineno].strip() - single_content = _end_lineno(stmt) == stmt.lineno + 2 - indent = first[: len(first) - len(first.lstrip())] - if single_content: - if len(indent) + len(summary) + 6 <= 120: - lines[stmt.lineno - 1] = f'{indent}"""{summary}"""\n' - lines[stmt.lineno] = "" - lines[stmt.lineno + 1] = "" - else: - wrapped = textwrap.wrap( - summary, - width=117, - initial_indent=indent + '"""', - subsequent_indent=indent, - break_long_words=False, - break_on_hyphens=False, - ) - lines[stmt.lineno - 1] = "".join(part + "\n" for part in wrapped) - lines[stmt.lineno] = "" - continue - lines[stmt.lineno - 1] = first.rstrip("\n") + summary + "\n" - lines[stmt.lineno] = "" - return "".join(lines) - - -def wrap_docstrings(source: str) -> str: - """Wrap docstring lines to the repo line length (no per-file E501 exemption).""" - source = _join_class_docstring_summaries(source) - out: list[str] = [] - in_docstring = False - for line in source.splitlines(keepends=True): - quotes = line.count('"""') - long = len(line.rstrip("\n")) > 120 - indent = line[: len(line) - len(line.lstrip())] - if not in_docstring: - if quotes == 1: - in_docstring = True - if long: - out.extend( - part + "\n" - for part in textwrap.wrap( - line.strip().removeprefix('"""'), - width=120, - initial_indent=indent + '"""', - subsequent_indent=indent, - break_long_words=False, - break_on_hyphens=False, - ) - ) - continue - out.append(line) - continue - if quotes: - in_docstring = False - out.append(line) - continue - if not long: - out.append(line) - continue - out.extend( - part + "\n" - for part in textwrap.wrap( - line.strip(), - width=120, - initial_indent=indent, - subsequent_indent=indent, - break_long_words=False, - break_on_hyphens=False, - ) - ) - return "".join(out) - - -def assert_only_wire_bases(source: str) -> None: - """Assert every class in the emitted package sits on one of the two bases.""" - tree = ast.parse(source) - for node in tree.body: - if not isinstance(node, ast.ClassDef): - continue - base_names = [ast.unparse(b) for b in node.bases] - if base_names not in (["WireModel"], ["OpenWireModel"]): - raise PipelineError(f"class {node.name} has unexpected bases {base_names} after the src passes") - - -def run_ruff(path: Path) -> None: - """ruff check --fix + format with the repo config; abort on any residual finding.""" - common = ["uv", "run", "--frozen", "ruff"] - fix = subprocess.run( - [*common, "check", "--no-cache", "--fix", "--exit-zero", str(path)], - cwd=REPO_ROOT, - capture_output=True, - text=True, - ) - if fix.returncode != 0: - raise PipelineError(f"ruff check --fix failed:\n{fix.stderr.strip()}") - fmt = subprocess.run([*common, "format", "--no-cache", str(path)], cwd=REPO_ROOT, capture_output=True, text=True) - if fmt.returncode != 0: - raise PipelineError(f"ruff format failed:\n{fmt.stderr.strip()}") - report = subprocess.run( - [*common, "check", "--no-cache", "--output-format", "json", "--exit-zero", str(path)], - cwd=REPO_ROOT, - capture_output=True, - text=True, + capture_output=True, text=True, ) - if report.returncode != 0: - raise PipelineError(f"ruff check (report) failed:\n{report.stderr.strip()}") - findings: list[dict[str, Any]] = json.loads(report.stdout) - if findings: - details = "\n".join( - f" {f.get('code')}: {f.get('message')} (line {f.get('location', {}).get('row')})" for f in findings - ) - raise PipelineError(f"ruff residual findings in {path.name}:\n{details}") + # fmt: on + if result.returncode != 0: + raise SystemExit(f"datamodel-codegen failed:\n{result.stderr}") -def schema_defs(schema: dict[str, Any]) -> dict[str, Any]: - """The definitions table of a schema ($defs in 2020-12, definitions in draft-07).""" - defs = schema.get("$defs") or schema.get("definitions") - if not defs: - raise PipelineError("schema has no $defs/definitions table") - return defs - - -def import_candidate(path: Path, module: str) -> ModuleType: - """Import a staged candidate module under a private name.""" - name = f"_candidate_{module}" - spec = importlib.util.spec_from_file_location(name, path) - if spec is None or spec.loader is None: - raise PipelineError(f"cannot load candidate module from {path}") - mod = importlib.util.module_from_spec(spec) - sys.modules[name] = mod - spec.loader.exec_module(mod) - return mod - - -def audit_candidate(mod: ModuleType, schema: dict[str, Any], entry: Entry) -> None: - """Def-name + alias + requiredness audit; raises on any failure.""" - problems: list[str] = [] - for def_name, def_schema in schema_defs(schema).items(): - obj = getattr(mod, def_name, None) - if obj is None: - problems.append(f"def {def_name!r} does not resolve to a module attribute") - continue - if not (isinstance(obj, type) and issubclass(obj, BaseModel)): - continue - properties: dict[str, Any] = def_schema.get("properties", {}) - required: list[str] = def_schema.get("required", []) - fields_by_wire_name = { - (info.serialization_alias or info.alias or field_name): info - for field_name, info in obj.model_fields.items() - } - for prop_name in properties: - info = fields_by_wire_name.get(prop_name) - if info is None: - problems.append(f"{def_name}: no field with wire name {prop_name!r}") - continue - if info.is_required() != (prop_name in required): - problems.append( - f"{def_name}.{prop_name}: requiredness mismatch " - f"(model={info.is_required()}, schema={prop_name in required})" - ) - if problems: - raise PipelineError(f"{entry.protocol_version}: audit failed:\n" + "\n".join(f" - {p}" for p in problems)) - - -def _annotation_models(annotation: Any) -> list[type[BaseModel]]: - """Model classes appearing anywhere in an annotation.""" - if isinstance(annotation, type) and issubclass(annotation, BaseModel): - return [annotation] - found: list[type[BaseModel]] = [] - for arg in typing.get_args(annotation): - found.extend(_annotation_models(arg)) - return found - - -def audit_meta_carriers(mod: ModuleType, entry: Entry) -> None: - """Assert every ``_meta`` field is open: a dict, or an extra='allow' model.""" - problems: list[str] = [] - for name in dir(mod): - obj = getattr(mod, name) - if not (isinstance(obj, type) and issubclass(obj, BaseModel)): - continue - for field_name, info in obj.model_fields.items(): - if (info.serialization_alias or info.alias or field_name) != "_meta": - continue - problems.extend( - f"{name}._meta references {referenced.__name__}, which is not extra='allow'" - for referenced in _annotation_models(info.annotation) - if referenced.model_config.get("extra") != "allow" - ) - if problems: - raise PipelineError( - f"{entry.protocol_version}: _meta carrier audit failed:\n" + "\n".join(f" - {p}" for p in problems) - ) - - -def build_candidate(entry: Entry) -> str: - """Full pipeline for one entry; returns the formatted, audited source text.""" - schema: dict[str, Any] = json.loads(entry.schema_file.read_text()) +def build(entry: dict[str, str]) -> str: + """Generate, post-process, and format one version's surface module text.""" + version = entry["protocol_version"] + schema = json.loads((SCHEMA_DIR / f"{version}.json").read_text()) + patch_schema(schema, SCHEMA_PATCHES.get(version, [])) with tempfile.TemporaryDirectory() as tmp: - raw_file = Path(tmp) / "raw.py" - run_generator(entry.schema_file, raw_file) - source, recursive = postprocess(raw_file.read_text(), ALIAS_TYPE_PATCHES.get(entry.protocol_version, {})) - assert_alias_blocks_present(source, recursive) - - source = downgrade_value_transforms(source) - if entry.protocol_version == "2026-07-28": - source = widen_structured_content(source) - source = flatten_inheritance(source) - source, carriers = rebase_meta_carriers(source) - if carriers: - source = source.replace( - "from mcp.types._wire_base import WireModel", - "from mcp.types._wire_base import OpenWireModel, WireModel", - ) - source = apply_extra_policy(source, carriers) - source = deterministic_synthetic_names(source) - source = apply_field_type_patches(source, FIELD_TYPE_PATCHES.get(entry.protocol_version, [])) - source = wrap_docstrings(source) - - epilogue = EPILOGUES.get(entry.protocol_version, "") - if epilogue: - # Before model_rebuild(), not after: pyright's evaluation order for the - # recursive TypeAliasType block at the top is sensitive to where the - # last cross-module-referenced symbol sits. + patched = Path(tmp) / "schema.json" + patched.write_text(json.dumps(schema)) + raw = Path(tmp) / "raw.py" + run_codegen(patched, raw) + source = raw.read_text() + + source = re.sub(r"\A# generated by datamodel-codegen:\n#[^\n]*\n", "", source) + source = re.sub(r"^class Model\(RootModel\[Any\]\):\n {4}root: Any\n+", "", source, count=1, flags=re.MULTILINE) + if epilogue := EPILOGUES.get(version, ""): + # Insert before the trailing model_rebuild() block: pyright's evaluation + # order for the recursive RootModel block is sensitive to placement. match = re.search(r"^\w+\.model_rebuild\(\)$", source, flags=re.MULTILINE) cut = match.start() if match else len(source) - source = source[:cut] + epilogue + "\n\n" + source[cut:] - header = SRC_HEADERS[entry.protocol_version].format(spec_commit=entry.spec_commit) + source = f"{source[:cut]}{epilogue}\n\n{source[cut:]}" + source = HEADER.format(version=version, sha=entry["sha256"]) + source - staging = SRC_TYPES_DIR / f"_staging_{entry.module}.py" + staging = TYPES_DIR / f"_staging_{version}.py" try: - staging.write_text(header + source) - run_ruff(staging) - candidate = staging.read_text() - assert_only_wire_bases(candidate) - mod = import_candidate(staging, entry.module) - audit_candidate(mod, schema, entry) - audit_meta_carriers(mod, entry) + staging.write_text(source) + subprocess.run( + ["uv", "run", "--frozen", "ruff", "format", "--no-cache", str(staging)], + cwd=REPO_ROOT, capture_output=True, check=True, + ) # fmt: skip + return staging.read_text() finally: staging.unlink(missing_ok=True) - return candidate def main(argv: list[str] | None = None) -> int: - """CLI entry point.""" - parser = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + """CLI entry point: write each surface package, or diff under ``--check``.""" + parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("--check", action="store_true", help="diff regenerated output against committed files") args = parser.parse_args(argv) - if importlib.util.find_spec("mcp.types._wire_base") is None: - raise PipelineError("src/mcp/types/_wire_base.py (WireModel/OpenWireModel) is required") - - entries = load_pinned() - drifted: list[str] = [] - for entry in entries: - candidate = build_candidate(entry) - if args.check: - committed = entry.target.read_text() if entry.target.is_file() else "" - if committed == candidate: - print(f"{entry.protocol_version}: ok ({entry.target.relative_to(REPO_ROOT)})") - else: - drifted.append(entry.protocol_version) - diff = difflib.unified_diff( + drift = False + for entry in load_pinned(): + target = TYPES_DIR / ("v" + entry["protocol_version"].replace("-", "_")) / "__init__.py" + candidate = build(entry) + if not args.check: + target.parent.mkdir(parents=True, exist_ok=True) + target.write_text(candidate) + print(f"{entry['protocol_version']}: wrote {target.relative_to(REPO_ROOT)} ({len(candidate)} bytes)") + continue + committed = target.read_text() if target.is_file() else "" + if committed != candidate: + drift = True + sys.stderr.writelines( + difflib.unified_diff( committed.splitlines(keepends=True), candidate.splitlines(keepends=True), - fromfile=str(entry.target.relative_to(REPO_ROOT)), + fromfile=str(target.relative_to(REPO_ROOT)), tofile="", ) - sys.stderr.writelines(diff) - else: - entry.target.parent.mkdir(parents=True, exist_ok=True) - entry.target.write_text(candidate) - print(f"{entry.protocol_version}: wrote {entry.target.relative_to(REPO_ROOT)} ({len(candidate)} bytes)") - - if drifted: - print( - f"DRIFT: {drifted} differ from committed output; " - "run `uv run --group codegen python scripts/gen_surface_types.py`", - file=sys.stderr, - ) - return 1 - return 0 + ) + return 1 if drift else 0 if __name__ == "__main__": diff --git a/src/mcp/types/methods.py b/src/mcp/types/methods.py index f3431b84ef..4ec4a51071 100644 --- a/src/mcp/types/methods.py +++ b/src/mcp/types/methods.py @@ -17,7 +17,6 @@ import mcp.types.v2025_11_25 as v2025 import mcp.types.v2026_07_28 as v2026 from mcp.shared.version import KNOWN_PROTOCOL_VERSIONS -from mcp.types._wire_base import WireModel __all__ = [ "CLIENT_NOTIFICATIONS", @@ -40,7 +39,7 @@ # --- Surface maps: client-to-server --- -CLIENT_REQUESTS: Final[Mapping[tuple[str, str], type[WireModel]]] = MappingProxyType( +CLIENT_REQUESTS: Final[Mapping[tuple[str, str], type[BaseModel]]] = MappingProxyType( { # 2024-11-05 ("completion/complete", "2024-11-05"): v2025.CompleteRequest, @@ -112,7 +111,7 @@ } ) -CLIENT_NOTIFICATIONS: Final[Mapping[tuple[str, str], type[WireModel]]] = MappingProxyType( +CLIENT_NOTIFICATIONS: Final[Mapping[tuple[str, str], type[BaseModel]]] = MappingProxyType( { # 2024-11-05 ("notifications/cancelled", "2024-11-05"): v2025.CancelledNotification, @@ -143,7 +142,7 @@ # --- Surface maps: server-to-client --- -SERVER_REQUESTS: Final[Mapping[tuple[str, str], type[WireModel]]] = MappingProxyType( +SERVER_REQUESTS: Final[Mapping[tuple[str, str], type[BaseModel]]] = MappingProxyType( { # 2024-11-05 ("ping", "2024-11-05"): v2025.PingRequest, @@ -167,7 +166,7 @@ } ) -SERVER_NOTIFICATIONS: Final[Mapping[tuple[str, str], type[WireModel]]] = MappingProxyType( +SERVER_NOTIFICATIONS: Final[Mapping[tuple[str, str], type[BaseModel]]] = MappingProxyType( { # 2024-11-05 ("notifications/cancelled", "2024-11-05"): v2025.CancelledNotification, @@ -218,7 +217,7 @@ # --- Surface maps: results --- -SERVER_RESULTS: Final[Mapping[tuple[str, str], type[WireModel] | UnionType]] = MappingProxyType( +SERVER_RESULTS: Final[Mapping[tuple[str, str], type[BaseModel] | UnionType]] = MappingProxyType( { # 2024-11-05 ("completion/complete", "2024-11-05"): v2025.CompleteResult, @@ -291,7 +290,7 @@ ) """Results servers send, keyed by the originating client request's (method, version).""" -CLIENT_RESULTS: Final[Mapping[tuple[str, str], type[WireModel] | UnionType]] = MappingProxyType( +CLIENT_RESULTS: Final[Mapping[tuple[str, str], type[BaseModel] | UnionType]] = MappingProxyType( { # 2024-11-05 ("ping", "2024-11-05"): v2025.EmptyResult, @@ -433,7 +432,7 @@ def parse_client_request( version: str, params: Mapping[str, Any] | None, *, - surface: Mapping[tuple[str, str], type[WireModel]] = CLIENT_REQUESTS, + surface: Mapping[tuple[str, str], type[BaseModel]] = CLIENT_REQUESTS, monolith: Mapping[str, type[types.Request[Any, Any]]] = MONOLITH_REQUESTS, ) -> types.Request[Any, Any]: """Validate a client request against `surface`, then parse and return its `monolith` model. @@ -462,7 +461,7 @@ def parse_server_request( version: str, params: Mapping[str, Any] | None, *, - surface: Mapping[tuple[str, str], type[WireModel]] = SERVER_REQUESTS, + surface: Mapping[tuple[str, str], type[BaseModel]] = SERVER_REQUESTS, monolith: Mapping[str, type[types.Request[Any, Any]]] = MONOLITH_REQUESTS, ) -> types.Request[Any, Any]: """Validate a server request against `surface`, then parse and return its `monolith` model. @@ -491,7 +490,7 @@ def parse_client_notification( version: str, params: Mapping[str, Any] | None, *, - surface: Mapping[tuple[str, str], type[WireModel]] = CLIENT_NOTIFICATIONS, + surface: Mapping[tuple[str, str], type[BaseModel]] = CLIENT_NOTIFICATIONS, monolith: Mapping[str, type[types.Notification[Any, Any]]] = MONOLITH_NOTIFICATIONS, ) -> types.Notification[Any, Any]: """Validate a client notification against `surface`, then parse and return its `monolith` model. @@ -520,7 +519,7 @@ def parse_server_notification( version: str, params: Mapping[str, Any] | None, *, - surface: Mapping[tuple[str, str], type[WireModel]] = SERVER_NOTIFICATIONS, + surface: Mapping[tuple[str, str], type[BaseModel]] = SERVER_NOTIFICATIONS, monolith: Mapping[str, type[types.Notification[Any, Any]]] = MONOLITH_NOTIFICATIONS, ) -> types.Notification[Any, Any]: """Validate a server notification against `surface`, then parse and return its `monolith` model. @@ -549,7 +548,7 @@ def parse_server_result( version: str, data: Mapping[str, Any], *, - surface: Mapping[tuple[str, str], type[WireModel] | UnionType] = SERVER_RESULTS, + surface: Mapping[tuple[str, str], type[BaseModel] | UnionType] = SERVER_RESULTS, monolith: Mapping[str, type[types.Result] | UnionType] = MONOLITH_RESULTS, ) -> types.Result: """Validate a server result against `surface`, then parse and return its `monolith` model. @@ -578,7 +577,7 @@ def parse_client_result( version: str, data: Mapping[str, Any], *, - surface: Mapping[tuple[str, str], type[WireModel] | UnionType] = CLIENT_RESULTS, + surface: Mapping[tuple[str, str], type[BaseModel] | UnionType] = CLIENT_RESULTS, monolith: Mapping[str, type[types.Result] | UnionType] = MONOLITH_RESULTS, ) -> types.Result: """Validate a client result against `surface`, then parse and return its `monolith` model. diff --git a/src/mcp/types/v2025_11_25/__init__.py b/src/mcp/types/v2025_11_25/__init__.py index aeddcba6a6..ffc8853811 100644 --- a/src/mcp/types/v2025_11_25/__init__.py +++ b/src/mcp/types/v2025_11_25/__init__.py @@ -1,28 +1,28 @@ -"""Internal wire-shape models for protocol 2025-11-25. Not part of the public API. +"""Internal wire-shape models for protocol 2025-11-25. Generated; do not edit. -Generated by `scripts/gen_surface_types.py` from `schema/2025-11-25.json` -@ 6d441518de8a9d5adbab0b10a76a667a63f90665. Do not edit; regenerate. Serves inbound validation for every -protocol version through 2025-11-25 (each earlier schema is a strict subset of -this one). Models default to `extra="ignore"`; the few kept open are commented -in place. See `mcp.types._wire_base` and `mcp.types.methods`. -""" +Regenerate with ``scripts/gen_surface_types.py`` from ``schema/2025-11-25.json`` +(sha256 ``4e01628360a2149892eab8f298ceee626d24a58862184eb8ec85d95b8f353e31``).""" +# pyright: reportIncompatibleVariableOverride=false, reportGeneralTypeIssues=false from __future__ import annotations -from typing import Annotated, Any, Literal, TypeAlias +from typing import Annotated, Any, Literal -from pydantic import ConfigDict, Field - -from mcp.types._wire_base import OpenWireModel, WireModel +from mcp.types._wire_base import WireModel +from pydantic import AnyUrl, Base64Str, ConfigDict, Field, RootModel class BaseMetadata(WireModel): - """Base interface for metadata with name (identifier) and title (display name) properties.""" + """ + Base interface for metadata with name (identifier) and title (display name) properties. + """ + model_config = ConfigDict( + extra="allow", + ) name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ title: str | None = None """ @@ -36,11 +36,14 @@ class BaseMetadata(WireModel): class BlobResourceContents(WireModel): + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ - blob: str + blob: Base64Str """ A base64-encoded string representing the binary data of the item. """ @@ -48,20 +51,26 @@ class BlobResourceContents(WireModel): """ The MIME type of this resource, if known. """ - uri: str + uri: AnyUrl """ The URI of this resource. """ class BooleanSchema(WireModel): + model_config = ConfigDict( + extra="allow", + ) default: bool | None = None description: str | None = None title: str | None = None type: Literal["boolean"] -class CancelTaskRequestParams(WireModel): +class Params(WireModel): + model_config = ConfigDict( + extra="allow", + ) task_id: Annotated[str, Field(alias="taskId")] """ The task identifier to cancel. @@ -69,15 +78,25 @@ class CancelTaskRequestParams(WireModel): class Elicitation(WireModel): - """Present if the client supports elicitation from the server.""" + """ + Present if the client supports elicitation from the server. + """ + model_config = ConfigDict( + extra="allow", + ) form: dict[str, Any] | None = None url: dict[str, Any] | None = None class Roots(WireModel): - """Present if the client supports listing roots.""" + """ + Present if the client supports listing roots. + """ + model_config = ConfigDict( + extra="allow", + ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ Whether the client supports notifications for changes to the roots list. @@ -85,8 +104,13 @@ class Roots(WireModel): class Sampling(WireModel): - """Present if the client supports sampling from an LLM.""" + """ + Present if the client supports sampling from an LLM. + """ + model_config = ConfigDict( + extra="allow", + ) context: dict[str, Any] | None = None """ Whether the client supports context inclusion via includeContext parameter. @@ -99,8 +123,13 @@ class Sampling(WireModel): class Elicitation1(WireModel): - """Task support for elicitation-related requests.""" + """ + Task support for elicitation-related requests. + """ + model_config = ConfigDict( + extra="allow", + ) create: dict[str, Any] | None = None """ Whether the client supports task-augmented elicitation/create requests. @@ -108,8 +137,13 @@ class Elicitation1(WireModel): class Sampling1(WireModel): - """Task support for sampling-related requests.""" + """ + Task support for sampling-related requests. + """ + model_config = ConfigDict( + extra="allow", + ) create_message: Annotated[dict[str, Any] | None, Field(alias="createMessage")] = None """ Whether the client supports task-augmented sampling/createMessage requests. @@ -117,8 +151,13 @@ class Sampling1(WireModel): class Requests(WireModel): - """Specifies which request types can be augmented with tasks.""" + """ + Specifies which request types can be augmented with tasks. + """ + model_config = ConfigDict( + extra="allow", + ) elicitation: Elicitation1 | None = None """ Task support for elicitation-related requests. @@ -130,8 +169,13 @@ class Requests(WireModel): class Tasks(WireModel): - """Present if the client supports task-augmented requests.""" + """ + Present if the client supports task-augmented requests. + """ + model_config = ConfigDict( + extra="allow", + ) cancel: dict[str, Any] | None = None """ Whether this client supports tasks/cancel. @@ -147,10 +191,13 @@ class Tasks(WireModel): class ClientCapabilities(WireModel): - """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a - closed set: any client can define its own, additional capabilities. + """ + Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities. """ + model_config = ConfigDict( + extra="allow", + ) elicitation: Elicitation | None = None """ Present if the client supports elicitation from the server. @@ -174,8 +221,13 @@ class ClientCapabilities(WireModel): class Argument(WireModel): - """The argument's information""" + """ + The argument's information + """ + model_config = ConfigDict( + extra="allow", + ) name: str """ The name of the argument @@ -187,8 +239,13 @@ class Argument(WireModel): class Context(WireModel): - """Additional, optional context for completions""" + """ + Additional, optional context for completions + """ + model_config = ConfigDict( + extra="allow", + ) arguments: dict[str, str] | None = None """ Previously-resolved variables in a URI template or prompt. @@ -196,15 +253,16 @@ class Context(WireModel): class Completion(WireModel): + model_config = ConfigDict( + extra="allow", + ) has_more: Annotated[bool | None, Field(alias="hasMore")] = None """ - Indicates whether there are additional completion options beyond those provided in the current response, even if the - exact total is unknown. + Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. """ total: int | None = None """ - The total number of completion options available. This can exceed the number of values actually sent in the - response. + The total number of completion options available. This can exceed the number of values actually sent in the response. """ values: list[str] """ @@ -213,8 +271,13 @@ class Completion(WireModel): class CompleteResult(WireModel): - """The server's response to a completion/complete request""" + """ + The server's response to a completion/complete request + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -222,12 +285,21 @@ class CompleteResult(WireModel): completion: Completion -Cursor: TypeAlias = str +class Cursor(RootModel[str]): + root: str + """ + An opaque token used to represent a cursor for pagination. + """ class ElicitResult(WireModel): - """The client's response to an elicitation request.""" + """ + The client's response to an elicitation request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -239,8 +311,7 @@ class ElicitResult(WireModel): - "decline": User explicitly decline the action - "cancel": User dismissed without making an explicit choice """ - # schema.json renders this `number` as integer; widen per schema.ts so floats validate. - content: dict[str, list[str] | str | int | float | bool] | None = None + content: dict[str, list[str] | str | float | bool] | None = None """ The submitted form data, only present when action is "accept" and mode was "form". Contains values matching the requested schema. @@ -248,7 +319,10 @@ class ElicitResult(WireModel): """ -class ElicitationCompleteNotificationParams(WireModel): +class Params1(WireModel): + model_config = ConfigDict( + extra="allow", + ) elicitation_id: Annotated[str, Field(alias="elicitationId")] """ The ID of the elicitation that completed. @@ -256,24 +330,29 @@ class ElicitationCompleteNotificationParams(WireModel): class ElicitationCompleteNotification(WireModel): - """An optional notification from the server to the client, informing it of a completion of a out-of-band - elicitation request. + """ + An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/elicitation/complete"] - params: ElicitationCompleteNotificationParams + params: Params1 class Error(WireModel): + model_config = ConfigDict( + extra="allow", + ) code: int """ The error type that occurred. """ data: Any | None = None """ - Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ message: str """ @@ -281,7 +360,10 @@ class Error(WireModel): """ -class GetTaskPayloadRequestParams(WireModel): +class Params2(WireModel): + model_config = ConfigDict( + extra="allow", + ) task_id: Annotated[str, Field(alias="taskId")] """ The task identifier to retrieve results for. @@ -289,18 +371,25 @@ class GetTaskPayloadRequestParams(WireModel): class GetTaskPayloadResult(WireModel): - """The response to a tasks/result request. + """ + The response to a tasks/result request. The structure matches the result type of the original request. For example, a tools/call task would return the CallToolResult structure. """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ -class GetTaskRequestParams(WireModel): +class Params3(WireModel): + model_config = ConfigDict( + extra="allow", + ) task_id: Annotated[str, Field(alias="taskId")] """ The task identifier to query. @@ -308,8 +397,13 @@ class GetTaskRequestParams(WireModel): class Icon(WireModel): - """An optionally-sized icon that can be displayed in a user interface.""" + """ + An optionally-sized icon that can be displayed in a user interface. + """ + model_config = ConfigDict( + extra="allow", + ) mime_type: Annotated[str | None, Field(alias="mimeType")] = None """ Optional MIME type override if the source MIME type is missing or generic. @@ -322,7 +416,7 @@ class Icon(WireModel): If not provided, the client should assume that the icon can be used at any size. """ - src: str + src: AnyUrl """ A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a `data:` URI with Base64-encoded image data. @@ -344,8 +438,13 @@ class Icon(WireModel): class Icons(WireModel): - """Base interface to add `icons` property.""" + """ + Base interface to add `icons` property. + """ + model_config = ConfigDict( + extra="allow", + ) icons: list[Icon] | None = None """ Optional set of sized icons that the client can display in a user interface. @@ -361,8 +460,13 @@ class Icons(WireModel): class Implementation(WireModel): - """Describes the MCP implementation.""" + """ + Describes the MCP implementation. + """ + model_config = ConfigDict( + extra="allow", + ) description: str | None = None """ An optional human-readable description of what this implementation does. @@ -385,8 +489,7 @@ class Implementation(WireModel): """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ title: str | None = None """ @@ -398,25 +501,34 @@ class Implementation(WireModel): if present). """ version: str - website_url: Annotated[str | None, Field(alias="websiteUrl")] = None + website_url: Annotated[AnyUrl | None, Field(alias="websiteUrl")] = None """ An optional URL of the website for this implementation. """ class JSONRPCNotification(WireModel): - """A notification which does not expect a response.""" + """ + A notification which does not expect a response. + """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: str params: dict[str, Any] | None = None class LegacyTitledEnumSchema(WireModel): - """Use TitledSingleSelectEnumSchema instead. + """ + Use TitledSingleSelectEnumSchema instead. This interface will be removed in a future version. """ + model_config = ConfigDict( + extra="allow", + ) default: str | None = None description: str | None = None enum: list[str] @@ -429,12 +541,37 @@ class LegacyTitledEnumSchema(WireModel): type: Literal["string"] -LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] +class LoggingLevel( + RootModel[ + Literal[ + "alert", + "critical", + "debug", + "emergency", + "error", + "info", + "notice", + "warning", + ] + ] +): + root: Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + """ + The severity of a log message. + + These map to syslog message severities, as specified in RFC-5424: + https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1 + """ class LoggingMessageNotificationParams(WireModel): - """Parameters for a `notifications/message` notification.""" + """ + Parameters for a `notifications/message` notification. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -454,12 +591,16 @@ class LoggingMessageNotificationParams(WireModel): class ModelHint(WireModel): - """Hints to use for model selection. + """ + Hints to use for model selection. Keys not declared here are currently left unspecified by the spec and are up to the client to interpret. """ + model_config = ConfigDict( + extra="allow", + ) name: str | None = None """ A hint for a model name. @@ -469,14 +610,14 @@ class ModelHint(WireModel): - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. - `claude` should match any Claude model - The client MAY also map the string to a different provider's model name or a different model family, as long as it - fills a similar niche; for example: + The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example: - `gemini-1.5-flash` could match `claude-3-haiku-20240307` """ class ModelPreferences(WireModel): - """The server's preferences for model selection, requested of the client during sampling. + """ + The server's preferences for model selection, requested of the client during sampling. Because LLMs can vary along multiple dimensions, choosing the "best" model is rarely straightforward. Different models excel in different areas—some are @@ -489,6 +630,9 @@ class ModelPreferences(WireModel): balance them against other considerations. """ + model_config = ConfigDict( + extra="allow", + ) cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None """ How much to prioritize cost when selecting a model. A value of 0 means cost @@ -520,11 +664,17 @@ class ModelPreferences(WireModel): class Notification(WireModel): + model_config = ConfigDict( + extra="allow", + ) method: str params: dict[str, Any] | None = None class NotificationParams(WireModel): + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -532,18 +682,21 @@ class NotificationParams(WireModel): class NumberSchema(WireModel): - # schema.json renders this `number` as integer; widen per schema.ts so floats validate. - default: int | float | None = None + model_config = ConfigDict( + extra="allow", + ) + default: float | None = None description: str | None = None - # schema.json renders this `number` as integer; widen per schema.ts so floats validate. - maximum: int | float | None = None - # schema.json renders this `number` as integer; widen per schema.ts so floats validate. - minimum: int | float | None = None + maximum: float | None = None + minimum: float | None = None title: str | None = None type: Literal["integer", "number"] class PaginatedResult(WireModel): + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -555,20 +708,28 @@ class PaginatedResult(WireModel): """ -ProgressToken: TypeAlias = str | int +class ProgressToken(RootModel[str | int]): + root: str | int + """ + A progress token, used to associate progress notifications with the original request. + """ class PromptArgument(WireModel): - """Describes an argument that a prompt can accept.""" + """ + Describes an argument that a prompt can accept. + """ + model_config = ConfigDict( + extra="allow", + ) description: str | None = None """ A human-readable description of the argument. """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ required: bool | None = None """ @@ -586,22 +747,29 @@ class PromptArgument(WireModel): class PromptListChangedNotification(WireModel): - """An optional notification from the server to the client, informing it that the list of prompts it offers has - changed. This may be issued by servers without any previous subscription from the client. + """ + An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/prompts/list_changed"] params: NotificationParams | None = None class PromptReference(WireModel): - """Identifies a prompt.""" + """ + Identifies a prompt. + """ + model_config = ConfigDict( + extra="allow", + ) name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ title: str | None = None """ @@ -615,35 +783,47 @@ class PromptReference(WireModel): type: Literal["ref/prompt"] -class Meta(OpenWireModel): - """See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage.""" +class Meta(WireModel): + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + model_config = ConfigDict( + extra="allow", + ) progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None """ - If specified, the caller is requesting out-of-band progress notifications for this request (as represented by - notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent - notifications. The receiver is not obligated to provide these notifications. + If specified, the caller is requesting out-of-band progress notifications for this request (as represented by notifications/progress). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. """ class ReadResourceRequestParams(WireModel): - """Parameters for a `resources/read` request.""" + """ + Parameters for a `resources/read` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ - uri: str + uri: AnyUrl """ The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. """ class RelatedTaskMetadata(WireModel): - """Metadata for associating messages with a task. + """ + Metadata for associating messages with a task. Include this in the `_meta` field under the key `io.modelcontextprotocol/related-task`. """ + model_config = ConfigDict( + extra="allow", + ) task_id: Annotated[str, Field(alias="taskId")] """ The task identifier this message is associated with. @@ -651,16 +831,28 @@ class RelatedTaskMetadata(WireModel): class Request(WireModel): + model_config = ConfigDict( + extra="allow", + ) method: str params: dict[str, Any] | None = None -RequestId: TypeAlias = str | int +class RequestId(RootModel[str | int]): + root: str | int + """ + A uniquely identifying ID for a request in JSON-RPC. + """ class RequestParams(WireModel): - """Common params for any request.""" + """ + Common params for any request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -668,8 +860,13 @@ class RequestParams(WireModel): class ResourceContents(WireModel): - """The contents of a specific resource or sub-resource.""" + """ + The contents of a specific resource or sub-resource. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -678,38 +875,51 @@ class ResourceContents(WireModel): """ The MIME type of this resource, if known. """ - uri: str + uri: AnyUrl """ The URI of this resource. """ class ResourceListChangedNotification(WireModel): - """An optional notification from the server to the client, informing it that the list of resources it can read - from has changed. This may be issued by servers without any previous subscription from the client. + """ + An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/resources/list_changed"] params: NotificationParams | None = None class ResourceRequestParams(WireModel): - """Common parameters when working with resources.""" + """ + Common parameters when working with resources. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ - uri: str + uri: AnyUrl """ The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. """ class ResourceTemplateReference(WireModel): - """A reference to a resource or resource template definition.""" + """ + A reference to a resource or resource template definition. + """ + model_config = ConfigDict( + extra="allow", + ) type: Literal["ref/resource"] uri: str """ @@ -718,32 +928,48 @@ class ResourceTemplateReference(WireModel): class ResourceUpdatedNotificationParams(WireModel): - """Parameters for a `notifications/resources/updated` notification.""" + """ + Parameters for a `notifications/resources/updated` notification. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ - uri: str + uri: AnyUrl """ - The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually - subscribed to. + The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. """ class Result(WireModel): + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ -Role: TypeAlias = Literal["assistant", "user"] +class Role(RootModel[Literal["assistant", "user"]]): + root: Literal["assistant", "user"] + """ + The sender or recipient of messages and data in a conversation. + """ class Root(WireModel): - """Represents a root directory or file that the server can operate on.""" + """ + Represents a root directory or file that the server can operate on. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -754,7 +980,7 @@ class Root(WireModel): identifier for the root, which may be useful for display purposes or for referencing the root in other parts of the application. """ - uri: str + uri: AnyUrl """ The URI identifying the root. This *must* start with file:// for now. This restriction may be relaxed in future versions of the protocol to allow @@ -763,19 +989,28 @@ class Root(WireModel): class RootsListChangedNotification(WireModel): - """A notification from the client to the server, informing it that the list of roots has changed. + """ + A notification from the client to the server, informing it that the list of roots has changed. This notification should be sent whenever the client adds, removes, or modifies any root. The server should then request an updated list of roots using the ListRootsRequest. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/roots/list_changed"] params: NotificationParams | None = None class Prompts(WireModel): - """Present if the server offers any prompt templates.""" + """ + Present if the server offers any prompt templates. + """ + model_config = ConfigDict( + extra="allow", + ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ Whether this server supports notifications for changes to the prompt list. @@ -783,21 +1018,31 @@ class Prompts(WireModel): class Resources(WireModel): - """Present if the server offers any resources to read.""" - - list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ - Whether this server supports notifications for changes to the resource list. + Present if the server offers any resources to read. """ - subscribe: bool | None = None + + model_config = ConfigDict( + extra="allow", + ) + list_changed: Annotated[bool | None, Field(alias="listChanged")] = None + """ + Whether this server supports notifications for changes to the resource list. + """ + subscribe: bool | None = None """ Whether this server supports subscribing to resource updates. """ class Tools(WireModel): - """Task support for tool-related requests.""" + """ + Task support for tool-related requests. + """ + model_config = ConfigDict( + extra="allow", + ) call: dict[str, Any] | None = None """ Whether the server supports task-augmented tools/call requests. @@ -805,8 +1050,13 @@ class Tools(WireModel): class Requests1(WireModel): - """Specifies which request types can be augmented with tasks.""" + """ + Specifies which request types can be augmented with tasks. + """ + model_config = ConfigDict( + extra="allow", + ) tools: Tools | None = None """ Task support for tool-related requests. @@ -814,8 +1064,13 @@ class Requests1(WireModel): class Tasks1(WireModel): - """Present if the server supports task-augmented requests.""" + """ + Present if the server supports task-augmented requests. + """ + model_config = ConfigDict( + extra="allow", + ) cancel: dict[str, Any] | None = None """ Whether this server supports tasks/cancel. @@ -831,8 +1086,13 @@ class Tasks1(WireModel): class Tools1(WireModel): - """Present if the server offers any tools to call.""" + """ + Present if the server offers any tools to call. + """ + model_config = ConfigDict( + extra="allow", + ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ Whether this server supports notifications for changes to the tool list. @@ -840,10 +1100,13 @@ class Tools1(WireModel): class ServerCapabilities(WireModel): - """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a - closed set: any server can define its own, additional capabilities. + """ + Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities. """ + model_config = ConfigDict( + extra="allow", + ) completions: dict[str, Any] | None = None """ Present if the server supports argument autocompletion suggestions. @@ -875,20 +1138,27 @@ class ServerCapabilities(WireModel): class SetLevelRequestParams(WireModel): - """Parameters for a `logging/setLevel` request.""" + """ + Parameters for a `logging/setLevel` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ level: LoggingLevel """ - The level of logging that the client wants to receive from the server. The server should send all logs at this level - and higher (i.e., more severe) to the client as notifications/message. + The level of logging that the client wants to receive from the server. The server should send all logs at this level and higher (i.e., more severe) to the client as notifications/message. """ class StringSchema(WireModel): + model_config = ConfigDict( + extra="allow", + ) default: str | None = None description: str | None = None format: Literal["date", "date-time", "email", "uri"] | None = None @@ -899,33 +1169,49 @@ class StringSchema(WireModel): class SubscribeRequestParams(WireModel): - """Parameters for a `resources/subscribe` request.""" + """ + Parameters for a `resources/subscribe` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ - uri: str + uri: AnyUrl """ The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. """ class TaskMetadata(WireModel): - """Metadata for augmenting a request with task execution. + """ + Metadata for augmenting a request with task execution. Include this in the `task` field of the request parameters. """ + model_config = ConfigDict( + extra="allow", + ) ttl: int | None = None """ Requested duration in milliseconds to retain task from creation. """ -TaskStatus: TypeAlias = Literal["cancelled", "completed", "failed", "input_required", "working"] +class TaskStatus(RootModel[Literal["cancelled", "completed", "failed", "input_required", "working"]]): + root: Literal["cancelled", "completed", "failed", "input_required", "working"] + """ + The status of a task. + """ class TextResourceContents(WireModel): + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -938,13 +1224,16 @@ class TextResourceContents(WireModel): """ The text of the item. This must only be set if the item can actually be represented as text (not binary data). """ - uri: str + uri: AnyUrl """ The URI of this resource. """ class AnyOfItem(WireModel): + model_config = ConfigDict( + extra="allow", + ) const: str """ The constant enum value. @@ -956,8 +1245,13 @@ class AnyOfItem(WireModel): class Items(WireModel): - """Schema for array items with enum options and display labels.""" + """ + Schema for array items with enum options and display labels. + """ + model_config = ConfigDict( + extra="allow", + ) any_of: Annotated[list[AnyOfItem], Field(alias="anyOf")] """ Array of enum options with values and display labels. @@ -965,8 +1259,13 @@ class Items(WireModel): class TitledMultiSelectEnumSchema(WireModel): - """Schema for multiple-selection enumeration with display titles for each option.""" + """ + Schema for multiple-selection enumeration with display titles for each option. + """ + model_config = ConfigDict( + extra="allow", + ) default: list[str] | None = None """ Optional default value. @@ -995,6 +1294,9 @@ class TitledMultiSelectEnumSchema(WireModel): class OneOfItem(WireModel): + model_config = ConfigDict( + extra="allow", + ) const: str """ The enum value. @@ -1006,8 +1308,13 @@ class OneOfItem(WireModel): class TitledSingleSelectEnumSchema(WireModel): - """Schema for single-selection enumeration with display titles for each option.""" + """ + Schema for single-selection enumeration with display titles for each option. + """ + model_config = ConfigDict( + extra="allow", + ) default: str | None = None """ Optional default value. @@ -1028,9 +1335,10 @@ class TitledSingleSelectEnumSchema(WireModel): class InputSchema(WireModel): - """A JSON Schema object defining the expected parameters for the tool.""" + """ + A JSON Schema object defining the expected parameters for the tool. + """ - # Stays open: schema keywords beyond the declared properties ride extra fields. model_config = ConfigDict( extra="allow", ) @@ -1041,14 +1349,14 @@ class InputSchema(WireModel): class OutputSchema(WireModel): - """An optional JSON Schema object defining the structure of the tool's output returned in + """ + An optional JSON Schema object defining the structure of the tool's output returned in the structuredContent field of a CallToolResult. Defaults to JSON Schema 2020-12 when no explicit $schema is provided. Currently restricted to type: "object" at the root level. """ - # Stays open: schema keywords beyond the declared properties ride extra fields. model_config = ConfigDict( extra="allow", ) @@ -1059,7 +1367,8 @@ class OutputSchema(WireModel): class ToolAnnotations(WireModel): - """Additional properties describing a Tool to clients. + """ + Additional properties describing a Tool to clients. NOTE: all properties in ToolAnnotations are **hints**. They are not guaranteed to provide a faithful description of @@ -1069,6 +1378,9 @@ class ToolAnnotations(WireModel): received from untrusted servers. """ + model_config = ConfigDict( + extra="allow", + ) destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None """ If true, the tool may perform destructive updates to its environment. @@ -1109,8 +1421,13 @@ class ToolAnnotations(WireModel): class ToolChoice(WireModel): - """Controls tool selection behavior for sampling requests.""" + """ + Controls tool selection behavior for sampling requests. + """ + model_config = ConfigDict( + extra="allow", + ) mode: Literal["auto", "none", "required"] | None = None """ Controls the tool use ability of the model: @@ -1121,8 +1438,13 @@ class ToolChoice(WireModel): class ToolExecution(WireModel): - """Execution-related properties for a tool.""" + """ + Execution-related properties for a tool. + """ + model_config = ConfigDict( + extra="allow", + ) task_support: Annotated[Literal["forbidden", "optional", "required"] | None, Field(alias="taskSupport")] = None """ Indicates whether this tool supports task-augmented execution. @@ -1138,18 +1460,26 @@ class ToolExecution(WireModel): class ToolListChangedNotification(WireModel): - """An optional notification from the server to the client, informing it that the list of tools it offers has - changed. This may be issued by servers without any previous subscription from the client. + """ + An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/tools/list_changed"] params: NotificationParams | None = None class ToolUseContent(WireModel): - """A request from the assistant to call a tool.""" + """ + A request from the assistant to call a tool. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ Optional metadata about the tool use. Clients SHOULD preserve this field when @@ -1175,21 +1505,31 @@ class ToolUseContent(WireModel): class UnsubscribeRequestParams(WireModel): - """Parameters for a `resources/unsubscribe` request.""" + """ + Parameters for a `resources/unsubscribe` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ - uri: str + uri: AnyUrl """ The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. """ class Items1(WireModel): - """Schema for the array items.""" + """ + Schema for the array items. + """ + model_config = ConfigDict( + extra="allow", + ) enum: list[str] """ Array of enum values to choose from. @@ -1198,8 +1538,13 @@ class Items1(WireModel): class UntitledMultiSelectEnumSchema(WireModel): - """Schema for multiple-selection enumeration without display titles for options.""" + """ + Schema for multiple-selection enumeration without display titles for options. + """ + model_config = ConfigDict( + extra="allow", + ) default: list[str] | None = None """ Optional default value. @@ -1228,8 +1573,13 @@ class UntitledMultiSelectEnumSchema(WireModel): class UntitledSingleSelectEnumSchema(WireModel): - """Schema for single-selection enumeration without display titles for options.""" + """ + Schema for single-selection enumeration without display titles for options. + """ + model_config = ConfigDict( + extra="allow", + ) default: str | None = None """ Optional default value. @@ -1250,10 +1600,13 @@ class UntitledSingleSelectEnumSchema(WireModel): class Annotations(WireModel): - """Optional annotations for the client. The client can use annotations to inform how objects are used or - displayed + """ + Optional annotations for the client. The client can use annotations to inform how objects are used or displayed """ + model_config = ConfigDict( + extra="allow", + ) audience: list[Role] | None = None """ Describes who the intended audience of this object or data is. @@ -1280,8 +1633,13 @@ class Annotations(WireModel): class AudioContent(WireModel): - """Audio provided to or from an LLM.""" + """ + Audio provided to or from an LLM. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1290,7 +1648,7 @@ class AudioContent(WireModel): """ Optional annotations for the client. """ - data: str + data: Base64Str """ The base64-encoded audio data. """ @@ -1302,8 +1660,13 @@ class AudioContent(WireModel): class CallToolRequestParams(WireModel): - """Parameters for a `tools/call` request.""" + """ + Parameters for a `tools/call` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1328,17 +1691,27 @@ class CallToolRequestParams(WireModel): class CancelTaskRequest(WireModel): - """A request to cancel a task.""" + """ + A request to cancel a task. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["tasks/cancel"] - params: CancelTaskRequestParams + params: Params class CancelledNotificationParams(WireModel): - """Parameters for a `notifications/cancelled` notification.""" + """ + Parameters for a `notifications/cancelled` notification. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1358,8 +1731,13 @@ class CancelledNotificationParams(WireModel): class CompleteRequestParams(WireModel): - """Parameters for a `completion/complete` request.""" + """ + Parameters for a `completion/complete` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1376,8 +1754,13 @@ class CompleteRequestParams(WireModel): class ElicitRequestURLParams(WireModel): - """The parameters for a request to elicit information from the user via a URL in the client.""" + """ + The parameters for a request to elicit information from the user via a URL in the client. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1404,19 +1787,23 @@ class ElicitRequestURLParams(WireModel): Task augmentation is subject to capability negotiation - receivers MUST declare support for task augmentation of specific request types in their capabilities. """ - url: str + url: AnyUrl """ The URL that the user should navigate to. """ class EmbeddedResource(WireModel): - """The contents of a resource, embedded into a prompt or tool call result. + """ + The contents of a resource, embedded into a prompt or tool call result. It is up to the client how best to render embedded resources for the benefit of the LLM and/or the user. """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1429,21 +1816,36 @@ class EmbeddedResource(WireModel): type: Literal["resource"] -EmptyResult: TypeAlias = Result - - -EnumSchema: TypeAlias = ( - UntitledSingleSelectEnumSchema - | TitledSingleSelectEnumSchema - | UntitledMultiSelectEnumSchema - | TitledMultiSelectEnumSchema - | LegacyTitledEnumSchema -) +class EmptyResult(RootModel[Result]): + root: Result + + +class EnumSchema( + RootModel[ + UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema + ] +): + root: ( + UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema + ) class GetPromptRequestParams(WireModel): - """Parameters for a `prompts/get` request.""" + """ + Parameters for a `prompts/get` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1459,26 +1861,41 @@ class GetPromptRequestParams(WireModel): class GetTaskPayloadRequest(WireModel): - """A request to retrieve the result of a completed task.""" + """ + A request to retrieve the result of a completed task. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["tasks/result"] - params: GetTaskPayloadRequestParams + params: Params2 class GetTaskRequest(WireModel): - """A request to retrieve the state of a task.""" + """ + A request to retrieve the state of a task. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["tasks/get"] - params: GetTaskRequestParams + params: Params3 class ImageContent(WireModel): - """An image provided to or from an LLM.""" + """ + An image provided to or from an LLM. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1487,7 +1904,7 @@ class ImageContent(WireModel): """ Optional annotations for the client. """ - data: str + data: Base64Str """ The base64-encoded image data. """ @@ -1499,8 +1916,13 @@ class ImageContent(WireModel): class InitializeRequestParams(WireModel): - """Parameters for an `initialize` request.""" + """ + Parameters for an `initialize` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1509,14 +1931,18 @@ class InitializeRequestParams(WireModel): client_info: Annotated[Implementation, Field(alias="clientInfo")] protocol_version: Annotated[str, Field(alias="protocolVersion")] """ - The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older - versions as well. + The latest version of the Model Context Protocol that the client supports. The client MAY decide to support older versions as well. """ class InitializeResult(WireModel): - """After receiving an initialize request from the client, the server sends this response.""" + """ + After receiving an initialize request from the client, the server sends this response. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1526,36 +1952,49 @@ class InitializeResult(WireModel): """ Instructions describing how to use the server and its features. - This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought - of like a "hint" to the model. For example, this information MAY be added to the system prompt. + This can be used by clients to improve the LLM's understanding of available tools, resources, etc. It can be thought of like a "hint" to the model. For example, this information MAY be added to the system prompt. """ protocol_version: Annotated[str, Field(alias="protocolVersion")] """ - The version of the Model Context Protocol that the server wants to use. This may not match the version that the - client requested. If the client cannot support this version, it MUST disconnect. + The version of the Model Context Protocol that the server wants to use. This may not match the version that the client requested. If the client cannot support this version, it MUST disconnect. """ server_info: Annotated[Implementation, Field(alias="serverInfo")] class InitializedNotification(WireModel): - """This notification is sent from the client to the server after initialization has finished.""" + """ + This notification is sent from the client to the server after initialization has finished. + """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/initialized"] params: NotificationParams | None = None class JSONRPCErrorResponse(WireModel): - """A response to a request that indicates an error occurred.""" + """ + A response to a request that indicates an error occurred. + """ + model_config = ConfigDict( + extra="allow", + ) error: Error id: RequestId | None = None jsonrpc: Literal["2.0"] class JSONRPCRequest(WireModel): - """A request that expects a response.""" + """ + A request that expects a response. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: str @@ -1563,15 +2002,21 @@ class JSONRPCRequest(WireModel): class JSONRPCResultResponse(WireModel): - """A successful (non-error) response to a request.""" + """ + A successful (non-error) response to a request. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] result: Result class ListRootsRequest(WireModel): - """Sent from the server to request a list of root URIs from the client. Roots allow + """ + Sent from the server to request a list of root URIs from the client. Roots allow servers to ask for specific directories or files to operate on. A common example for roots is providing a set of repositories or directories a server should operate on. @@ -1580,6 +2025,9 @@ class ListRootsRequest(WireModel): structure or access specific locations that the client has permission to read from. """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["roots/list"] @@ -1587,11 +2035,15 @@ class ListRootsRequest(WireModel): class ListRootsResult(WireModel): - """The client's response to a roots/list request from the server. + """ + The client's response to a roots/list request from the server. This result contains an array of Root objects, each representing a root directory or file that the server can operate on. """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1600,21 +2052,30 @@ class ListRootsResult(WireModel): class LoggingMessageNotification(WireModel): - """JSONRPCNotification of a log message passed from server to client. If no logging/setLevel request has been - sent from the client, the server MAY decide which messages to send automatically. + """ + JSONRPCNotification of a log message passed from server to client. If no logging/setLevel request has been sent from the client, the server MAY decide which messages to send automatically. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/message"] params: LoggingMessageNotificationParams -MultiSelectEnumSchema: TypeAlias = UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema +class MultiSelectEnumSchema(RootModel[UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema]): + root: UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema class PaginatedRequestParams(WireModel): - """Common parameters for paginated requests.""" + """ + Common parameters for paginated requests. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1627,31 +2088,55 @@ class PaginatedRequestParams(WireModel): class PingRequest(WireModel): - """A ping, issued by either the server or the client, to check that the other party is still alive. The receiver - must promptly respond, or else may be disconnected. + """ + A ping, issued by either the server or the client, to check that the other party is still alive. The receiver must promptly respond, or else may be disconnected. """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["ping"] params: RequestParams | None = None -PrimitiveSchemaDefinition: TypeAlias = ( - StringSchema - | NumberSchema - | BooleanSchema - | UntitledSingleSelectEnumSchema - | TitledSingleSelectEnumSchema - | UntitledMultiSelectEnumSchema - | TitledMultiSelectEnumSchema - | LegacyTitledEnumSchema -) +class PrimitiveSchemaDefinition( + RootModel[ + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema + ] +): + root: ( + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema + ) + """ + Restricted schema definitions that only allow primitive types + without nested objects or arrays. + """ class ProgressNotificationParams(WireModel): - """Parameters for a `notifications/progress` notification.""" + """ + Parameters for a `notifications/progress` notification. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1666,8 +2151,7 @@ class ProgressNotificationParams(WireModel): """ progress_token: Annotated[ProgressToken, Field(alias="progressToken")] """ - The progress token which was given in the initial request, used to associate this notification with the request that - is proceeding. + The progress token which was given in the initial request, used to associate this notification with the request that is proceeding. """ total: float | None = None """ @@ -1676,11 +2160,16 @@ class ProgressNotificationParams(WireModel): class Prompt(WireModel): - """A prompt or prompt template that the server offers.""" - - meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ - See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + A prompt or prompt template that the server offers. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ arguments: list[PromptArgument] | None = None """ @@ -1704,8 +2193,7 @@ class Prompt(WireModel): """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ title: str | None = None """ @@ -1719,8 +2207,13 @@ class Prompt(WireModel): class ReadResourceRequest(WireModel): - """Sent from the client to the server, to read a specific resource URI.""" + """ + Sent from the client to the server, to read a specific resource URI. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["resources/read"] @@ -1728,8 +2221,13 @@ class ReadResourceRequest(WireModel): class ReadResourceResult(WireModel): - """The server's response to a resources/read request from the client.""" + """ + The server's response to a resources/read request from the client. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1738,8 +2236,13 @@ class ReadResourceResult(WireModel): class Resource(WireModel): - """A known resource that the server is capable of reading.""" + """ + A known resource that the server is capable of reading. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1752,8 +2255,7 @@ class Resource(WireModel): """ A description of what this resource represents. - This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a - "hint" to the model. + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. """ icons: list[Icon] | None = None """ @@ -1773,8 +2275,7 @@ class Resource(WireModel): """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ size: int | None = None """ @@ -1791,18 +2292,22 @@ class Resource(WireModel): where `annotations.title` should be given precedence over using `name`, if present). """ - uri: str + uri: AnyUrl """ The URI of this resource. """ class ResourceLink(WireModel): - """A resource that the server is capable of reading, included in a prompt or tool call result. + """ + A resource that the server is capable of reading, included in a prompt or tool call result. Note: resource links returned by tools are not guaranteed to appear in the results of `resources/list` requests. """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1815,8 +2320,7 @@ class ResourceLink(WireModel): """ A description of what this resource represents. - This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a - "hint" to the model. + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. """ icons: list[Icon] | None = None """ @@ -1836,8 +2340,7 @@ class ResourceLink(WireModel): """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ size: int | None = None """ @@ -1855,15 +2358,20 @@ class ResourceLink(WireModel): if present). """ type: Literal["resource_link"] - uri: str + uri: AnyUrl """ The URI of this resource. """ class ResourceTemplate(WireModel): - """A template description for resources available on the server.""" + """ + A template description for resources available on the server. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -1876,8 +2384,7 @@ class ResourceTemplate(WireModel): """ A description of what this template is for. - This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a - "hint" to the model. + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. """ icons: list[Icon] | None = None """ @@ -1893,13 +2400,11 @@ class ResourceTemplate(WireModel): """ mime_type: Annotated[str | None, Field(alias="mimeType")] = None """ - The MIME type for all resources that match this template. This should only be included if all resources matching - this template have the same type. + The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ title: str | None = None """ @@ -1917,32 +2422,44 @@ class ResourceTemplate(WireModel): class ResourceUpdatedNotification(WireModel): - """A notification from the server to the client, informing it that a resource has changed and may need to be read - again. This should only be sent if the client previously sent a resources/subscribe request. + """ + A notification from the server to the client, informing it that a resource has changed and may need to be read again. This should only be sent if the client previously sent a resources/subscribe request. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/resources/updated"] params: ResourceUpdatedNotificationParams class SetLevelRequest(WireModel): - """A request from the client to the server, to enable or adjust logging.""" + """ + A request from the client to the server, to enable or adjust logging. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["logging/setLevel"] params: SetLevelRequestParams -SingleSelectEnumSchema: TypeAlias = UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema +class SingleSelectEnumSchema(RootModel[UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema]): + root: UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema class SubscribeRequest(WireModel): - """Sent from the client to request resources/updated notifications from the server whenever a particular resource - changes. + """ + Sent from the client to request resources/updated notifications from the server whenever a particular resource changes. """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["resources/subscribe"] @@ -1950,8 +2467,13 @@ class SubscribeRequest(WireModel): class Task(WireModel): - """Data associated with a task.""" + """ + Data associated with a task. + """ + model_config = ConfigDict( + extra="allow", + ) created_at: Annotated[str, Field(alias="createdAt")] """ ISO 8601 timestamp when the task was created. @@ -1987,8 +2509,13 @@ class Task(WireModel): class TaskAugmentedRequestParams(WireModel): - """Common params for any task-augmented request.""" + """ + Common params for any task-augmented request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2004,50 +2531,24 @@ class TaskAugmentedRequestParams(WireModel): """ -class TaskStatusNotificationParams(WireModel): - """Parameters for a `notifications/tasks/status` notification.""" - - meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None - """ - See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. +class TaskStatusNotificationParams(NotificationParams, Task): """ - created_at: Annotated[str, Field(alias="createdAt")] - """ - ISO 8601 timestamp when the task was created. - """ - last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] - """ - ISO 8601 timestamp when the task was last updated. - """ - poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None - """ - Suggested polling interval in milliseconds. - """ - status: TaskStatus - """ - Current task state. - """ - status_message: Annotated[str | None, Field(alias="statusMessage")] = None - """ - Optional human-readable message describing the current task state. - This can provide context for any status, including: - - Reasons for "cancelled" status - - Summaries for "completed" status - - Diagnostic information for "failed" status (e.g., error details, what went wrong) - """ - task_id: Annotated[str, Field(alias="taskId")] - """ - The task identifier. - """ - ttl: int | None - """ - Actual retention duration from creation in milliseconds, null for unlimited. + Parameters for a `notifications/tasks/status` notification. """ + model_config = ConfigDict( + extra="allow", + ) + class TextContent(WireModel): - """Text provided to or from an LLM.""" + """ + Text provided to or from an LLM. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2064,8 +2565,13 @@ class TextContent(WireModel): class Tool(WireModel): - """Definition for a tool the client can call.""" + """ + Definition for a tool the client can call. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2080,8 +2586,7 @@ class Tool(WireModel): """ A human-readable description of the tool. - This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a - "hint" to the model. + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a "hint" to the model. """ execution: ToolExecution | None = None """ @@ -2105,8 +2610,7 @@ class Tool(WireModel): """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ output_schema: Annotated[OutputSchema | None, Field(alias="outputSchema")] = None """ @@ -2128,44 +2632,51 @@ class Tool(WireModel): class Data(WireModel): - """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ + model_config = ConfigDict( + extra="allow", + ) elicitations: list[ElicitRequestURLParams] -class Error1(WireModel): +class Error1(Error): + model_config = ConfigDict( + extra="allow", + ) code: Literal[-32042] """ The error type that occurred. """ data: Data """ - Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). - """ - message: str - """ - A short description of the error. The message SHOULD be limited to a concise single sentence. + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ class URLElicitationRequiredError(WireModel): - """An error response that indicates that the server requires the client to provide additional information via an - elicitation request. + """ + An error response that indicates that the server requires the client to provide additional information via an elicitation request. """ + model_config = ConfigDict( + extra="allow", + ) error: Error1 id: RequestId | None = None jsonrpc: Literal["2.0"] class UnsubscribeRequest(WireModel): - """Sent from the client to request cancellation of resources/updated notifications from the server. This should - follow a previous resources/subscribe request. + """ + Sent from the client to request cancellation of resources/updated notifications from the server. This should follow a previous resources/subscribe request. """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["resources/unsubscribe"] @@ -2173,60 +2684,34 @@ class UnsubscribeRequest(WireModel): class CallToolRequest(WireModel): - """Used by the client to invoke a tool provided by the server.""" + """ + Used by the client to invoke a tool provided by the server. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["tools/call"] params: CallToolRequestParams -class CancelTaskResult(WireModel): - """The response to a tasks/cancel request.""" - - meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None - """ - See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. - """ - created_at: Annotated[str, Field(alias="createdAt")] +class CancelTaskResult(Result, Task): """ - ISO 8601 timestamp when the task was created. - """ - last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] - """ - ISO 8601 timestamp when the task was last updated. - """ - poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None - """ - Suggested polling interval in milliseconds. - """ - status: TaskStatus - """ - Current task state. - """ - status_message: Annotated[str | None, Field(alias="statusMessage")] = None - """ - Optional human-readable message describing the current task state. - This can provide context for any status, including: - - Reasons for "cancelled" status - - Summaries for "completed" status - - Diagnostic information for "failed" status (e.g., error details, what went wrong) - """ - task_id: Annotated[str, Field(alias="taskId")] - """ - The task identifier. - """ - ttl: int | None - """ - Actual retention duration from creation in milliseconds, null for unlimited. + The response to a tasks/cancel request. """ + model_config = ConfigDict( + extra="allow", + ) + class CancelledNotification(WireModel): - """This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + """ + This notification can be sent by either side to indicate that it is cancelling a previously-issued request. - The request SHOULD still be in-flight, but due to communication latency, it is always possible that this - notification MAY arrive after the request has already finished. + The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. This notification indicates that the result will be unused, so any associated processing SHOULD cease. @@ -2235,26 +2720,40 @@ class CancelledNotification(WireModel): For task cancellation, use the `tasks/cancel` request instead of this notification. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/cancelled"] params: CancelledNotificationParams class CompleteRequest(WireModel): - """A request from the client to the server, to ask for completion options.""" + """ + A request from the client to the server, to ask for completion options. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["completion/complete"] params: CompleteRequestParams -ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource +class ContentBlock(RootModel[TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource]): + root: TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource class CreateTaskResult(WireModel): - """A response to a task-augmented request.""" + """ + A response to a task-augmented request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2263,10 +2762,14 @@ class CreateTaskResult(WireModel): class RequestedSchema(WireModel): - """A restricted subset of JSON Schema. + """ + A restricted subset of JSON Schema. Only top-level properties are allowed, without nesting. """ + model_config = ConfigDict( + extra="allow", + ) schema_: Annotated[str | None, Field(alias="$schema")] = None properties: dict[str, PrimitiveSchemaDefinition] required: list[str] | None = None @@ -2274,8 +2777,13 @@ class RequestedSchema(WireModel): class ElicitRequestFormParams(WireModel): - """The parameters for a request to elicit non-sensitive information from the user via a form in the client.""" + """ + The parameters for a request to elicit non-sensitive information from the user via a form in the client. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2304,77 +2812,73 @@ class ElicitRequestFormParams(WireModel): """ -ElicitRequestParams: TypeAlias = ElicitRequestURLParams | ElicitRequestFormParams +class ElicitRequestParams(RootModel[ElicitRequestURLParams | ElicitRequestFormParams]): + root: ElicitRequestURLParams | ElicitRequestFormParams + """ + The parameters for a request to elicit additional information from the user via the client. + """ class GetPromptRequest(WireModel): - """Used by the client to get a prompt provided by the server.""" + """ + Used by the client to get a prompt provided by the server. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["prompts/get"] params: GetPromptRequestParams -class GetTaskResult(WireModel): - """The response to a tasks/get request.""" - - meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None - """ - See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. +class GetTaskResult(Result, Task): """ - created_at: Annotated[str, Field(alias="createdAt")] - """ - ISO 8601 timestamp when the task was created. - """ - last_updated_at: Annotated[str, Field(alias="lastUpdatedAt")] - """ - ISO 8601 timestamp when the task was last updated. - """ - poll_interval: Annotated[int | None, Field(alias="pollInterval")] = None - """ - Suggested polling interval in milliseconds. - """ - status: TaskStatus - """ - Current task state. - """ - status_message: Annotated[str | None, Field(alias="statusMessage")] = None - """ - Optional human-readable message describing the current task state. - This can provide context for any status, including: - - Reasons for "cancelled" status - - Summaries for "completed" status - - Diagnostic information for "failed" status (e.g., error details, what went wrong) - """ - task_id: Annotated[str, Field(alias="taskId")] - """ - The task identifier. - """ - ttl: int | None - """ - Actual retention duration from creation in milliseconds, null for unlimited. + The response to a tasks/get request. """ + model_config = ConfigDict( + extra="allow", + ) + class InitializeRequest(WireModel): - """This request is sent from the client to the server when it first connects, asking it to begin initialization.""" + """ + This request is sent from the client to the server when it first connects, asking it to begin initialization. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["initialize"] params: InitializeRequestParams -JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse +class JSONRPCMessage(RootModel[JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse]): + root: JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse + """ + Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent. + """ -JSONRPCResponse: TypeAlias = JSONRPCResultResponse | JSONRPCErrorResponse +class JSONRPCResponse(RootModel[JSONRPCResultResponse | JSONRPCErrorResponse]): + root: JSONRPCResultResponse | JSONRPCErrorResponse + """ + A response to a request, containing either the result or error. + """ class ListPromptsRequest(WireModel): - """Sent from the client to request a list of prompts and prompt templates the server has.""" + """ + Sent from the client to request a list of prompts and prompt templates the server has. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["prompts/list"] @@ -2382,8 +2886,13 @@ class ListPromptsRequest(WireModel): class ListPromptsResult(WireModel): - """The server's response to a prompts/list request from the client.""" + """ + The server's response to a prompts/list request from the client. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2397,8 +2906,13 @@ class ListPromptsResult(WireModel): class ListResourceTemplatesRequest(WireModel): - """Sent from the client to request a list of resource templates the server has.""" + """ + Sent from the client to request a list of resource templates the server has. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["resources/templates/list"] @@ -2406,8 +2920,13 @@ class ListResourceTemplatesRequest(WireModel): class ListResourceTemplatesResult(WireModel): - """The server's response to a resources/templates/list request from the client.""" + """ + The server's response to a resources/templates/list request from the client. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2421,8 +2940,13 @@ class ListResourceTemplatesResult(WireModel): class ListResourcesRequest(WireModel): - """Sent from the client to request a list of resources the server has.""" + """ + Sent from the client to request a list of resources the server has. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["resources/list"] @@ -2430,8 +2954,13 @@ class ListResourcesRequest(WireModel): class ListResourcesResult(WireModel): - """The server's response to a resources/list request from the client.""" + """ + The server's response to a resources/list request from the client. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2445,8 +2974,13 @@ class ListResourcesResult(WireModel): class ListTasksRequest(WireModel): - """A request to retrieve a list of tasks.""" + """ + A request to retrieve a list of tasks. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["tasks/list"] @@ -2454,8 +2988,13 @@ class ListTasksRequest(WireModel): class ListTasksResult(WireModel): - """The response to a tasks/list request.""" + """ + The response to a tasks/list request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2469,8 +3008,13 @@ class ListTasksResult(WireModel): class ListToolsRequest(WireModel): - """Sent from the client to request a list of tools the server has.""" + """ + Sent from the client to request a list of tools the server has. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["tools/list"] @@ -2478,8 +3022,13 @@ class ListToolsRequest(WireModel): class ListToolsResult(WireModel): - """The server's response to a tools/list request from the client.""" + """ + The server's response to a tools/list request from the client. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2493,6 +3042,9 @@ class ListToolsResult(WireModel): class PaginatedRequest(WireModel): + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: str @@ -2500,37 +3052,54 @@ class PaginatedRequest(WireModel): class ProgressNotification(WireModel): - """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" + """ + An out-of-band notification used to inform the receiver of a progress update for a long-running request. + """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/progress"] params: ProgressNotificationParams class PromptMessage(WireModel): - """Describes a message returned as part of a prompt. + """ + Describes a message returned as part of a prompt. This is similar to `SamplingMessage`, but also supports the embedding of resources from the MCP server. """ + model_config = ConfigDict( + extra="allow", + ) content: ContentBlock role: Role class TaskStatusNotification(WireModel): - """An optional notification from the receiver to the requestor, informing them that a task's status has changed. - Receivers are not required to send these notifications. + """ + An optional notification from the receiver to the requestor, informing them that a task's status has changed. Receivers are not required to send these notifications. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/tasks/status"] params: TaskStatusNotificationParams class ToolResultContent(WireModel): - """The result of a tool use, provided by the user back to the assistant.""" + """ + The result of a tool use, provided by the user back to the assistant. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ Optional metadata about the tool result. Clients SHOULD preserve this field when @@ -2568,8 +3137,13 @@ class ToolResultContent(WireModel): class CallToolResult(WireModel): - """The server's response to a tool call.""" + """ + The server's response to a tool call. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2599,39 +3173,74 @@ class CallToolResult(WireModel): """ -ClientNotification: TypeAlias = ( - CancelledNotification - | InitializedNotification - | ProgressNotification - | TaskStatusNotification - | RootsListChangedNotification -) - - -ClientRequest: TypeAlias = ( - InitializeRequest - | PingRequest - | ListResourcesRequest - | ListResourceTemplatesRequest - | ReadResourceRequest - | SubscribeRequest - | UnsubscribeRequest - | ListPromptsRequest - | GetPromptRequest - | ListToolsRequest - | CallToolRequest - | GetTaskRequest - | GetTaskPayloadRequest - | CancelTaskRequest - | ListTasksRequest - | SetLevelRequest - | CompleteRequest -) +class ClientNotification( + RootModel[ + CancelledNotification + | InitializedNotification + | ProgressNotification + | TaskStatusNotification + | RootsListChangedNotification + ] +): + root: ( + CancelledNotification + | InitializedNotification + | ProgressNotification + | TaskStatusNotification + | RootsListChangedNotification + ) + + +class ClientRequest( + RootModel[ + InitializeRequest + | PingRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | GetTaskRequest + | GetTaskPayloadRequest + | CancelTaskRequest + | ListTasksRequest + | SetLevelRequest + | CompleteRequest + ] +): + root: ( + InitializeRequest + | PingRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscribeRequest + | UnsubscribeRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | GetTaskRequest + | GetTaskPayloadRequest + | CancelTaskRequest + | ListTasksRequest + | SetLevelRequest + | CompleteRequest + ) class ElicitRequest(WireModel): - """A request from the server to elicit additional information from the user via the client.""" + """ + A request from the server to elicit additional information from the user via the client. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["elicitation/create"] @@ -2639,8 +3248,13 @@ class ElicitRequest(WireModel): class GetPromptResult(WireModel): - """The server's response to a prompts/get request from the client.""" + """ + The server's response to a prompts/get request from the client. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2652,46 +3266,84 @@ class GetPromptResult(WireModel): messages: list[PromptMessage] -SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent - - -ServerNotification: TypeAlias = ( - CancelledNotification - | ProgressNotification - | ResourceListChangedNotification - | ResourceUpdatedNotification - | PromptListChangedNotification - | ToolListChangedNotification - | TaskStatusNotification - | LoggingMessageNotification - | ElicitationCompleteNotification -) - - -ServerResult: TypeAlias = ( - Result - | InitializeResult - | ListResourcesResult - | ListResourceTemplatesResult - | ReadResourceResult - | ListPromptsResult - | GetPromptResult - | ListToolsResult - | CallToolResult - | GetTaskResult - | GetTaskPayloadResult - | CancelTaskResult - | ListTasksResult - | CompleteResult -) +class SamplingMessageContentBlock( + RootModel[TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent] +): + root: TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent + + +class ServerNotification( + RootModel[ + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | TaskStatusNotification + | LoggingMessageNotification + | ElicitationCompleteNotification + ] +): + root: ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | TaskStatusNotification + | LoggingMessageNotification + | ElicitationCompleteNotification + ) + + +class ServerResult( + RootModel[ + Result + | InitializeResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | GetTaskResult + | GetTaskPayloadResult + | CancelTaskResult + | ListTasksResult + | CompleteResult + ] +): + root: ( + Result + | InitializeResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | GetTaskResult + | GetTaskPayloadResult + | CancelTaskResult + | ListTasksResult + | CompleteResult + ) class CreateMessageResult(WireModel): - """The client's response to a sampling/createMessage request from the server. + """ + The client's response to a sampling/createMessage request from the server. The client should inform the user before returning the sampled message, to allow them to inspect the response (human in the loop) and decide whether to allow the server to see it. """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2724,8 +3376,13 @@ class CreateMessageResult(WireModel): class SamplingMessage(WireModel): - """Describes a message issued to or received from an LLM API.""" + """ + Describes a message issued to or received from an LLM API. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2741,21 +3398,38 @@ class SamplingMessage(WireModel): role: Role -ClientResult: TypeAlias = ( - Result - | GetTaskResult - | GetTaskPayloadResult - | CancelTaskResult - | ListTasksResult - | CreateMessageResult - | ListRootsResult - | ElicitResult -) +class ClientResult( + RootModel[ + Result + | GetTaskResult + | GetTaskPayloadResult + | CancelTaskResult + | ListTasksResult + | CreateMessageResult + | ListRootsResult + | ElicitResult + ] +): + root: ( + Result + | GetTaskResult + | GetTaskPayloadResult + | CancelTaskResult + | ListTasksResult + | CreateMessageResult + | ListRootsResult + | ElicitResult + ) class CreateMessageRequestParams(WireModel): - """Parameters for a `sampling/createMessage` request.""" + """ + Parameters for a `sampling/createMessage` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. @@ -2768,8 +3442,7 @@ class CreateMessageRequestParams(WireModel): A request to include context from one or more MCP servers (including the caller), to be attached to the prompt. The client MAY ignore this request. - Default is "none". Values "thisServer" and "allServers" are soft-deprecated. Servers SHOULD only use these values if - the client + Default is "none". Values "thisServer" and "allServers" are soft-deprecated. Servers SHOULD only use these values if the client declares ClientCapabilities.sampling.context. These values may be removed in future spec releases. """ max_tokens: Annotated[int, Field(alias="maxTokens")] @@ -2816,24 +3489,38 @@ class CreateMessageRequestParams(WireModel): class CreateMessageRequest(WireModel): - """A request from the server to sample an LLM via the client. The client has full discretion over which model to - select. The client should also inform the user before beginning sampling, to allow them to inspect the request - (human in the loop) and decide whether to approve it. + """ + A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it. """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["sampling/createMessage"] params: CreateMessageRequestParams -ServerRequest: TypeAlias = ( - PingRequest - | GetTaskRequest - | GetTaskPayloadRequest - | CancelTaskRequest - | ListTasksRequest - | CreateMessageRequest - | ListRootsRequest - | ElicitRequest -) +class ServerRequest( + RootModel[ + PingRequest + | GetTaskRequest + | GetTaskPayloadRequest + | CancelTaskRequest + | ListTasksRequest + | CreateMessageRequest + | ListRootsRequest + | ElicitRequest + ] +): + root: ( + PingRequest + | GetTaskRequest + | GetTaskPayloadRequest + | CancelTaskRequest + | ListTasksRequest + | CreateMessageRequest + | ListRootsRequest + | ElicitRequest + ) diff --git a/src/mcp/types/v2026_07_28/__init__.py b/src/mcp/types/v2026_07_28/__init__.py index eb7d9dee9a..a31dcf1cc4 100644 --- a/src/mcp/types/v2026_07_28/__init__.py +++ b/src/mcp/types/v2026_07_28/__init__.py @@ -1,48 +1,44 @@ -"""Internal wire-shape models for protocol 2026-07-28. Not part of the public API. +"""Internal wire-shape models for protocol 2026-07-28. Generated; do not edit. -Generated by `scripts/gen_surface_types.py` from `schema/2026-07-28.json` -@ 6d441518de8a9d5adbab0b10a76a667a63f90665. Do not edit; regenerate. Schema-exact validators that the -wire-method maps in `mcp.types.methods` point inbound 2026-07-28 validation at. -Models use `extra="ignore"` (unknown keys accepted and dropped) unless commented -otherwise; see `mcp.types._wire_base`. -""" +Regenerate with ``scripts/gen_surface_types.py`` from ``schema/2026-07-28.json`` +(sha256 ``bce2e7c9622bb0b449475ba6d8d80228c71190a09250e75dabd502b280ecf3cb``).""" +# pyright: reportIncompatibleVariableOverride=false, reportGeneralTypeIssues=false from __future__ import annotations -from typing import Annotated, Any, Literal, TypeAlias +from typing import Annotated, Any, Literal, Union -from pydantic import ConfigDict, Field -from typing_extensions import TypeAliasType - -from mcp.types._wire_base import OpenWireModel, WireModel - -# schema.json renders the primitive arm without float/null; widen per schema.ts. -JSONValue = TypeAliasType("JSONValue", "JSONObject | list[JSONValue] | str | int | float | bool | None") - - -JSONObject = TypeAliasType("JSONObject", dict[str, "JSONValue"]) +from mcp.types._wire_base import WireModel +from pydantic import AnyUrl, Base64Str, ConfigDict, Field, RootModel class BaseMetadata(WireModel): - """Base interface for metadata with name (identifier) and title (display name) properties.""" + """ + Base interface for metadata with name (identifier) and title (display name) properties. + """ + model_config = ConfigDict( + extra="allow", + ) name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ title: str | None = None """ Intended for UI and end-user contexts — optimized to be human-readable and easily understood, even by those unfamiliar with domain-specific terminology. - If not provided, the name should be used for display (except for Tool, + If not provided, the name should be used for display (except for {@link Tool}, where `annotations.title` should be given precedence over using `name`, if present). """ class BooleanSchema(WireModel): + model_config = ConfigDict( + extra="allow", + ) default: bool | None = None description: str | None = None title: str | None = None @@ -50,8 +46,13 @@ class BooleanSchema(WireModel): class Argument(WireModel): - """The argument's information""" + """ + The argument's information + """ + model_config = ConfigDict( + extra="allow", + ) name: str """ The name of the argument @@ -63,8 +64,13 @@ class Argument(WireModel): class Context(WireModel): - """Additional, optional context for completions""" + """ + Additional, optional context for completions + """ + model_config = ConfigDict( + extra="allow", + ) arguments: dict[str, str] | None = None """ Previously-resolved variables in a URI template or prompt. @@ -72,15 +78,16 @@ class Context(WireModel): class Completion(WireModel): + model_config = ConfigDict( + extra="allow", + ) has_more: Annotated[bool | None, Field(alias="hasMore")] = None """ - Indicates whether there are additional completion options beyond those provided in the current response, even if the - exact total is unknown. + Indicates whether there are additional completion options beyond those provided in the current response, even if the exact total is unknown. """ total: int | None = None """ - The total number of completion options available. This can exceed the number of values actually sent in the - response. + The total number of completion options available. This can exceed the number of values actually sent in the response. """ values: Annotated[list[str], Field(max_length=100)] """ @@ -88,12 +95,21 @@ class Completion(WireModel): """ -Cursor: TypeAlias = str +class Cursor(RootModel[str]): + root: str + """ + An opaque token used to represent a cursor for pagination. + """ class ElicitRequestURLParams(WireModel): - """The parameters for a request to elicit information from the user via a URL in the client.""" + """ + The parameters for a request to elicit information from the user via a URL in the client. + """ + model_config = ConfigDict( + extra="allow", + ) elicitation_id: Annotated[str, Field(alias="elicitationId")] """ The ID of the elicitation, which must be unique within the context of the server. @@ -107,15 +123,20 @@ class ElicitRequestURLParams(WireModel): """ The elicitation mode. """ - url: str + url: AnyUrl """ The URL that the user should navigate to. """ class ElicitResult(WireModel): - """The result returned by the client for an ElicitRequestelicitation/create request.""" + """ + The result returned by the client for an {@link ElicitRequestelicitation/create} request. + """ + model_config = ConfigDict( + extra="allow", + ) action: Literal["accept", "cancel", "decline"] """ The user action in response to the elicitation. @@ -123,8 +144,7 @@ class ElicitResult(WireModel): - `"decline"`: User explicitly declined the action - `"cancel"`: User dismissed without making an explicit choice """ - # schema.json renders this `number` as integer; widen per schema.ts so floats validate. - content: dict[str, list[str] | str | int | float | bool] | None = None + content: dict[str, list[str] | str | float | bool] | None = None """ The submitted form data, only present when action is `"accept"` and mode was `"form"`. Contains values matching the requested schema. @@ -132,7 +152,10 @@ class ElicitResult(WireModel): """ -class ElicitationCompleteNotificationParams(WireModel): +class Params(WireModel): + model_config = ConfigDict( + extra="allow", + ) elicitation_id: Annotated[str, Field(alias="elicitationId")] """ The ID of the elicitation that completed. @@ -140,24 +163,29 @@ class ElicitationCompleteNotificationParams(WireModel): class ElicitationCompleteNotification(WireModel): - """An optional notification from the server to the client, informing it of a completion of a out-of-band - elicitation request. + """ + An optional notification from the server to the client, informing it of a completion of a out-of-band elicitation request. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/elicitation/complete"] - params: ElicitationCompleteNotificationParams + params: Params class Error(WireModel): + model_config = ConfigDict( + extra="allow", + ) code: int """ The error type that occurred. """ data: Any | None = None """ - Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ message: str """ @@ -166,8 +194,13 @@ class Error(WireModel): class Icon(WireModel): - """An optionally-sized icon that can be displayed in a user interface.""" + """ + An optionally-sized icon that can be displayed in a user interface. + """ + model_config = ConfigDict( + extra="allow", + ) mime_type: Annotated[str | None, Field(alias="mimeType")] = None """ Optional MIME type override if the source MIME type is missing or generic. @@ -180,7 +213,7 @@ class Icon(WireModel): If not provided, the client should assume that the icon can be used at any size. """ - src: str + src: AnyUrl """ A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a `data:` URI with Base64-encoded image data. @@ -202,8 +235,13 @@ class Icon(WireModel): class Icons(WireModel): - """Base interface to add `icons` property.""" + """ + Base interface to add `icons` property. + """ + model_config = ConfigDict( + extra="allow", + ) icons: list[Icon] | None = None """ Optional set of sized icons that the client can display in a user interface. @@ -219,8 +257,13 @@ class Icons(WireModel): class Implementation(WireModel): - """Describes the MCP implementation.""" + """ + Describes the MCP implementation. + """ + model_config = ConfigDict( + extra="allow", + ) description: str | None = None """ An optional human-readable description of what this implementation does. @@ -243,15 +286,14 @@ class Implementation(WireModel): """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ title: str | None = None """ Intended for UI and end-user contexts — optimized to be human-readable and easily understood, even by those unfamiliar with domain-specific terminology. - If not provided, the name should be used for display (except for Tool, + If not provided, the name should be used for display (except for {@link Tool}, where `annotations.title` should be given precedence over using `name`, if present). """ @@ -259,25 +301,27 @@ class Implementation(WireModel): """ The version of this implementation. """ - website_url: Annotated[str | None, Field(alias="websiteUrl")] = None + website_url: Annotated[AnyUrl | None, Field(alias="websiteUrl")] = None """ An optional URL of the website for this implementation. """ class InternalError(WireModel): - """A JSON-RPC error indicating that an internal error occurred on the receiver. This error is returned when the - receiver encounters an unexpected condition that prevents it from fulfilling the request. + """ + A JSON-RPC error indicating that an internal error occurred on the receiver. This error is returned when the receiver encounters an unexpected condition that prevents it from fulfilling the request. """ + model_config = ConfigDict( + extra="allow", + ) code: Literal[-32603] """ The error type that occurred. """ data: Any | None = None """ - Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ message: str """ @@ -286,7 +330,8 @@ class InternalError(WireModel): class InvalidParamsError(WireModel): - """A JSON-RPC error indicating that the method parameters are invalid or malformed. + """ + A JSON-RPC error indicating that the method parameters are invalid or malformed. In MCP, this error is returned in various contexts when request parameters fail validation: @@ -298,14 +343,16 @@ class InvalidParamsError(WireModel): - **Sampling**: Missing tool result or tool results mixed with other content """ + model_config = ConfigDict( + extra="allow", + ) code: Literal[-32602] """ The error type that occurred. """ data: Any | None = None """ - Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ message: str """ @@ -314,19 +361,20 @@ class InvalidParamsError(WireModel): class InvalidRequestError(WireModel): - """A JSON-RPC error indicating that the request is not a valid request object. This error is returned when the - message structure does not conform to the JSON-RPC 2.0 specification requirements for a request (e.g., missing - required fields like `jsonrpc` or `method`, or using invalid types for these fields). + """ + A JSON-RPC error indicating that the request is not a valid request object. This error is returned when the message structure does not conform to the JSON-RPC 2.0 specification requirements for a request (e.g., missing required fields like `jsonrpc` or `method`, or using invalid types for these fields). """ + model_config = ConfigDict( + extra="allow", + ) code: Literal[-32600] """ The error type that occurred. """ data: Any | None = None """ - Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ message: str """ @@ -335,18 +383,27 @@ class InvalidRequestError(WireModel): class JSONRPCNotification(WireModel): - """A notification which does not expect a response.""" + """ + A notification which does not expect a response. + """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: str params: dict[str, Any] | None = None class LegacyTitledEnumSchema(WireModel): - """Use TitledSingleSelectEnumSchema instead. + """ + Use {@link TitledSingleSelectEnumSchema} instead. This interface will be removed in a future version. """ + model_config = ConfigDict( + extra="allow", + ) default: str | None = None description: str | None = None enum: list[str] @@ -359,53 +416,72 @@ class LegacyTitledEnumSchema(WireModel): type: Literal["string"] -LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] +class LoggingLevel( + RootModel[ + Literal[ + "alert", + "critical", + "debug", + "emergency", + "error", + "info", + "notice", + "warning", + ] + ] +): + root: Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + """ + The severity of a log message. + + These map to syslog message severities, as specified in RFC-5424: + https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1 + """ -class MetaObject(OpenWireModel): - """Represents the contents of a `_meta` field, which clients and servers use to attach additional metadata to their - interactions. +class MetaObject(WireModel): + """ + Represents the contents of a `_meta` field, which clients and servers use to attach additional metadata to their interactions. - Certain 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. + Certain 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. Valid keys have two segments: **Prefix:** - Optional — if specified, MUST be a series of _labels_ separated by dots (`.`), followed by a slash (`/`). - - Labels MUST start with a letter and end with a letter or digit. Interior characters may be letters, digits, or - hyphens (`-`). + - Labels MUST start with a letter and end with a letter or digit. Interior characters may be letters, digits, or hyphens (`-`). - Implementations SHOULD use reverse DNS notation (e.g., `com.example/` rather than `example.com/`). - - Any prefix where the second label is `modelcontextprotocol` or `mcp` is **reserved** for MCP use. For example: - `io.modelcontextprotocol/`, `dev.mcp/`, `org.modelcontextprotocol.api/`, and `com.mcp.tools/` are all reserved. - However, `com.example.mcp/` is NOT reserved, as the second label is `example`. + - Any prefix where the second label is `modelcontextprotocol` or `mcp` is **reserved** for MCP use. For example: `io.modelcontextprotocol/`, `dev.mcp/`, `org.modelcontextprotocol.api/`, and `com.mcp.tools/` are all reserved. However, `com.example.mcp/` is NOT reserved, as the second label is `example`. **Name:** - Unless empty, MUST start and end with an alphanumeric character (`[a-z0-9A-Z]`). - Interior characters may be alphanumeric, hyphens (`-`), underscores (`_`), or dots (`.`). """ + model_config = ConfigDict( + extra="allow", + ) + class MethodNotFoundError(WireModel): - """A JSON-RPC error indicating that the requested method does not exist or is not available. + """ + A JSON-RPC error indicating that the requested method does not exist or is not available. - In MCP, a server returns this error when a client invokes a method the server does not implement — either a - genuinely unknown method, or one gated behind a server capability the server did not advertise (e.g., calling - `prompts/list` when the `prompts` capability was not advertised). + In MCP, a server returns this error when a client invokes a method the server does not implement — either a genuinely unknown method, or one gated behind a server capability the server did not advertise (e.g., calling `prompts/list` when the `prompts` capability was not advertised). - A request that requires a client capability the client did not declare is signalled instead by - MissingRequiredClientCapabilityError (`-32003`). + A request that requires a client capability the client did not declare is signalled instead by {@link MissingRequiredClientCapabilityError} (`-32003`). """ + model_config = ConfigDict( + extra="allow", + ) code: Literal[-32601] """ The error type that occurred. """ data: Any | None = None """ - Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ message: str """ @@ -414,12 +490,16 @@ class MethodNotFoundError(WireModel): class ModelHint(WireModel): - """Hints to use for model selection. + """ + Hints to use for model selection. Keys not declared here are currently left unspecified by the spec and are up to the client to interpret. """ + model_config = ConfigDict( + extra="allow", + ) name: str | None = None """ A hint for a model name. @@ -429,14 +509,14 @@ class ModelHint(WireModel): - `sonnet` should match `claude-3-5-sonnet-20241022`, `claude-3-sonnet-20240229`, etc. - `claude` should match any Claude model - The client MAY also map the string to a different provider's model name or a different model family, as long as it - fills a similar niche; for example: + The client MAY also map the string to a different provider's model name or a different model family, as long as it fills a similar niche; for example: - `gemini-1.5-flash` could match `claude-3-haiku-20240307` """ class ModelPreferences(WireModel): - """The server's preferences for model selection, requested of the client during sampling. + """ + The server's preferences for model selection, requested of the client during sampling. Because LLMs can vary along multiple dimensions, choosing the "best" model is rarely straightforward. Different models excel in different areas—some are @@ -449,6 +529,9 @@ class ModelPreferences(WireModel): balance them against other considerations. """ + model_config = ConfigDict( + extra="allow", + ) cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None """ How much to prioritize cost when selecting a model. A value of 0 means cost @@ -480,17 +563,28 @@ class ModelPreferences(WireModel): class Notification(WireModel): + model_config = ConfigDict( + extra="allow", + ) method: str params: dict[str, Any] | None = None class NotificationParams(WireModel): - """Common params for any notification.""" + """ + Common params for any notification. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None class NumberSchema(WireModel): + model_config = ConfigDict( + extra="allow", + ) default: float | None = None description: str | None = None maximum: float | None = None @@ -500,6 +594,9 @@ class NumberSchema(WireModel): class PaginatedResult(WireModel): + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None """ @@ -519,18 +616,20 @@ class PaginatedResult(WireModel): class ParseError(WireModel): - """A JSON-RPC error indicating that invalid JSON was received by the server. This error is returned when the - server cannot parse the JSON text of a message. + """ + A JSON-RPC error indicating that invalid JSON was received by the server. This error is returned when the server cannot parse the JSON text of a message. """ + model_config = ConfigDict( + extra="allow", + ) code: Literal[-32700] """ The error type that occurred. """ data: Any | None = None """ - Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ message: str """ @@ -538,20 +637,28 @@ class ParseError(WireModel): """ -ProgressToken: TypeAlias = str | int +class ProgressToken(RootModel[str | int]): + root: str | int + """ + A progress token, used to associate progress notifications with the original request. + """ class PromptArgument(WireModel): - """Describes an argument that a prompt can accept.""" + """ + Describes an argument that a prompt can accept. + """ + model_config = ConfigDict( + extra="allow", + ) description: str | None = None """ A human-readable description of the argument. """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ required: bool | None = None """ @@ -562,36 +669,43 @@ class PromptArgument(WireModel): Intended for UI and end-user contexts — optimized to be human-readable and easily understood, even by those unfamiliar with domain-specific terminology. - If not provided, the name should be used for display (except for Tool, + If not provided, the name should be used for display (except for {@link Tool}, where `annotations.title` should be given precedence over using `name`, if present). """ class PromptListChangedNotification(WireModel): - """An optional notification from the server to the client, informing it that the list of prompts it offers has - changed. This may be issued by servers without any previous subscription from the client. + """ + An optional notification from the server to the client, informing it that the list of prompts it offers has changed. This may be issued by servers without any previous subscription from the client. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/prompts/list_changed"] params: NotificationParams | None = None class PromptReference(WireModel): - """Identifies a prompt.""" + """ + Identifies a prompt. + """ + model_config = ConfigDict( + extra="allow", + ) name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ title: str | None = None """ Intended for UI and end-user contexts — optimized to be human-readable and easily understood, even by those unfamiliar with domain-specific terminology. - If not provided, the name should be used for display (except for Tool, + If not provided, the name should be used for display (except for {@link Tool}, where `annotations.title` should be given precedence over using `name`, if present). """ @@ -599,40 +713,60 @@ class PromptReference(WireModel): class Request(WireModel): + model_config = ConfigDict( + extra="allow", + ) method: str params: dict[str, Any] | None = None -RequestId: TypeAlias = str | int +class RequestId(RootModel[str | int]): + root: str | int + """ + A uniquely identifying ID for a request in JSON-RPC. + """ class ResourceContents(WireModel): - """The contents of a specific resource or sub-resource.""" + """ + The contents of a specific resource or sub-resource. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None mime_type: Annotated[str | None, Field(alias="mimeType")] = None """ The MIME type of this resource, if known. """ - uri: str + uri: AnyUrl """ The URI of this resource. """ class ResourceListChangedNotification(WireModel): - """An optional notification from the server to the client, informing it that the list of resources it can read - from has changed. This may be issued by servers without any previous subscription from the client. + """ + An optional notification from the server to the client, informing it that the list of resources it can read from has changed. This may be issued by servers without any previous subscription from the client. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/resources/list_changed"] params: NotificationParams | None = None class ResourceTemplateReference(WireModel): - """A reference to a resource or resource template definition.""" + """ + A reference to a resource or resource template definition. + """ + model_config = ConfigDict( + extra="allow", + ) type: Literal["ref/resource"] uri: str """ @@ -641,19 +775,28 @@ class ResourceTemplateReference(WireModel): class ResourceUpdatedNotificationParams(WireModel): - """Parameters for a `notifications/resources/updated` notification.""" + """ + Parameters for a `notifications/resources/updated` notification. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None - uri: str + uri: AnyUrl """ - The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually - subscribed to. + The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. """ class Result(WireModel): - """Common result fields.""" + """ + Common result fields. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None result_type: Annotated[str, Field(alias="resultType")] """ @@ -667,15 +810,32 @@ class Result(WireModel): """ -ResultType: TypeAlias = str +class ResultType(RootModel[str]): + root: str + """ + Indicates the type of a {@link Result} object, allowing the client to + determine how to parse the response. + + complete - the request completed successfully and the result contains the final content. + input_required - the request requires additional input and the result contains an {@link InputRequiredResult} object with instructions for the client to provide additional input before retrying the original request. + """ -Role: TypeAlias = Literal["assistant", "user"] +class Role(RootModel[Literal["assistant", "user"]]): + root: Literal["assistant", "user"] + """ + The sender or recipient of messages and data in a conversation. + """ class Root(WireModel): - """Represents a root directory or file that the server can operate on.""" + """ + Represents a root directory or file that the server can operate on. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None name: str | None = None """ @@ -683,7 +843,7 @@ class Root(WireModel): identifier for the root, which may be useful for display purposes or for referencing the root in other parts of the application. """ - uri: str + uri: AnyUrl """ The URI identifying the root. This *must* start with `file://` for now. This restriction may be relaxed in future versions of the protocol to allow @@ -692,8 +852,13 @@ class Root(WireModel): class Prompts(WireModel): - """Present if the server offers any prompt templates.""" + """ + Present if the server offers any prompt templates. + """ + model_config = ConfigDict( + extra="allow", + ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ Whether this server supports notifications for changes to the prompt list. @@ -701,8 +866,13 @@ class Prompts(WireModel): class Resources(WireModel): - """Present if the server offers any resources to read.""" + """ + Present if the server offers any resources to read. + """ + model_config = ConfigDict( + extra="allow", + ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ Whether this server supports notifications for changes to the resource list. @@ -714,8 +884,13 @@ class Resources(WireModel): class Tools(WireModel): - """Present if the server offers any tools to call.""" + """ + Present if the server offers any tools to call. + """ + model_config = ConfigDict( + extra="allow", + ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ Whether this server supports notifications for changes to the tool list. @@ -723,6 +898,9 @@ class Tools(WireModel): class StringSchema(WireModel): + model_config = ConfigDict( + extra="allow", + ) default: str | None = None description: str | None = None format: Literal["date", "date-time", "email", "uri"] | None = None @@ -733,39 +911,44 @@ class StringSchema(WireModel): class SubscriptionFilter(WireModel): - """The set of notification types a client may opt in to on a - SubscriptionsListenRequestsubscriptions/listen request. + """ + The set of notification types a client may opt in to on a + {@link SubscriptionsListenRequestsubscriptions/listen} request. Each notification type is **opt-in**; the server **MUST NOT** send notification types the client has not explicitly requested here. """ - # Stays open: filter contents are extensible on the wire. model_config = ConfigDict( extra="allow", ) prompts_list_changed: Annotated[bool | None, Field(alias="promptsListChanged")] = None """ - If true, receive PromptListChangedNotificationnotifications/prompts/list_changed. + If true, receive {@link PromptListChangedNotificationnotifications/prompts/list_changed}. """ resource_subscriptions: Annotated[list[str] | None, Field(alias="resourceSubscriptions")] = None """ - Subscribe to ResourceUpdatedNotificationnotifications/resources/updated for these resource URIs. + Subscribe to {@link ResourceUpdatedNotificationnotifications/resources/updated} for these resource URIs. Replaces the former `resources/subscribe` RPC. """ resources_list_changed: Annotated[bool | None, Field(alias="resourcesListChanged")] = None """ - If true, receive ResourceListChangedNotificationnotifications/resources/list_changed. + If true, receive {@link ResourceListChangedNotificationnotifications/resources/list_changed}. """ tools_list_changed: Annotated[bool | None, Field(alias="toolsListChanged")] = None """ - If true, receive ToolListChangedNotificationnotifications/tools/list_changed. + If true, receive {@link ToolListChangedNotificationnotifications/tools/list_changed}. """ class SubscriptionsAcknowledgedNotificationParams(WireModel): - """Parameters for a SubscriptionsAcknowledgedNotificationnotifications/subscriptions/acknowledged notification.""" + """ + Parameters for a {@link SubscriptionsAcknowledgedNotificationnotifications/subscriptions/acknowledged} notification. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None notifications: SubscriptionFilter """ @@ -777,6 +960,9 @@ class SubscriptionsAcknowledgedNotificationParams(WireModel): class TextResourceContents(WireModel): + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None mime_type: Annotated[str | None, Field(alias="mimeType")] = None """ @@ -786,13 +972,16 @@ class TextResourceContents(WireModel): """ The text of the item. This must only be set if the item can actually be represented as text (not binary data). """ - uri: str + uri: AnyUrl """ The URI of this resource. """ class AnyOfItem(WireModel): + model_config = ConfigDict( + extra="allow", + ) const: str """ The constant enum value. @@ -804,8 +993,13 @@ class AnyOfItem(WireModel): class Items(WireModel): - """Schema for array items with enum options and display labels.""" + """ + Schema for array items with enum options and display labels. + """ + model_config = ConfigDict( + extra="allow", + ) any_of: Annotated[list[AnyOfItem], Field(alias="anyOf")] """ Array of enum options with values and display labels. @@ -813,8 +1007,13 @@ class Items(WireModel): class TitledMultiSelectEnumSchema(WireModel): - """Schema for multiple-selection enumeration with display titles for each option.""" + """ + Schema for multiple-selection enumeration with display titles for each option. + """ + model_config = ConfigDict( + extra="allow", + ) default: list[str] | None = None """ Optional default value. @@ -843,6 +1042,9 @@ class TitledMultiSelectEnumSchema(WireModel): class OneOfItem(WireModel): + model_config = ConfigDict( + extra="allow", + ) const: str """ The enum value. @@ -854,8 +1056,13 @@ class OneOfItem(WireModel): class TitledSingleSelectEnumSchema(WireModel): - """Schema for single-selection enumeration with display titles for each option.""" + """ + Schema for single-selection enumeration with display titles for each option. + """ + model_config = ConfigDict( + extra="allow", + ) default: str | None = None """ Optional default value. @@ -876,7 +1083,8 @@ class TitledSingleSelectEnumSchema(WireModel): class InputSchema(WireModel): - """A JSON Schema object defining the expected parameters for the tool. + """ + A JSON Schema object defining the expected parameters for the tool. Tool arguments are always JSON objects, so `type: "object"` is required at the root. Beyond that, any JSON Schema 2020-12 keyword may appear alongside `type` — including @@ -887,7 +1095,6 @@ class InputSchema(WireModel): Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. """ - # Stays open: schema keywords beyond the declared properties ride extra fields. model_config = ConfigDict( extra="allow", ) @@ -896,13 +1103,13 @@ class InputSchema(WireModel): class OutputSchema(WireModel): - """An optional JSON Schema object defining the structure of the tool's output returned in - the structuredContent field of a CallToolResult. This can be any valid JSON Schema 2020-12. + """ + An optional JSON Schema object defining the structure of the tool's output returned in + the structuredContent field of a {@link CallToolResult}. This can be any valid JSON Schema 2020-12. Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. """ - # Stays open: schema keywords beyond the declared properties ride extra fields. model_config = ConfigDict( extra="allow", ) @@ -910,7 +1117,8 @@ class OutputSchema(WireModel): class ToolAnnotations(WireModel): - """Additional properties describing a Tool to clients. + """ + Additional properties describing a {@link Tool} to clients. NOTE: all properties in `ToolAnnotations` are **hints**. They are not guaranteed to provide a faithful description of @@ -920,6 +1128,9 @@ class ToolAnnotations(WireModel): received from untrusted servers. """ + model_config = ConfigDict( + extra="allow", + ) destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None """ If true, the tool may perform destructive updates to its environment. @@ -960,8 +1171,13 @@ class ToolAnnotations(WireModel): class ToolChoice(WireModel): - """Controls tool selection behavior for sampling requests.""" + """ + Controls tool selection behavior for sampling requests. + """ + model_config = ConfigDict( + extra="allow", + ) mode: Literal["auto", "none", "required"] | None = None """ Controls the tool use ability of the model: @@ -972,22 +1188,30 @@ class ToolChoice(WireModel): class ToolListChangedNotification(WireModel): - """An optional notification from the server to the client, informing it that the list of tools it offers has - changed. This may be issued by servers without any previous subscription from the client. + """ + An optional notification from the server to the client, informing it that the list of tools it offers has changed. This may be issued by servers without any previous subscription from the client. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/tools/list_changed"] params: NotificationParams | None = None class ToolUseContent(WireModel): - """A request from the assistant to call a tool.""" - - meta: Annotated[MetaObject | None, Field(alias="_meta")] = None """ - Optional metadata about the tool use. Clients SHOULD preserve this field when - including tool uses in subsequent sampling requests to enable caching optimizations. + A request from the assistant to call a tool. + """ + + model_config = ConfigDict( + extra="allow", + ) + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + """ + Optional metadata about the tool use. Clients SHOULD preserve this field when + including tool uses in subsequent sampling requests to enable caching optimizations. """ id: str """ @@ -1007,10 +1231,13 @@ class ToolUseContent(WireModel): class Data1(WireModel): - """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ + model_config = ConfigDict( + extra="allow", + ) requested: str """ The protocol version that was requested by the client. @@ -1022,37 +1249,44 @@ class Data1(WireModel): """ -class Error2(WireModel): +class Error2(Error): + model_config = ConfigDict( + extra="allow", + ) code: Literal[-32004] """ The error type that occurred. """ data: Data1 """ - Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). - """ - message: str - """ - A short description of the error. The message SHOULD be limited to a concise single sentence. + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ class UnsupportedProtocolVersionError(WireModel): - """Returned when the request's protocol version is unknown to the server or + """ + Returned when the request's protocol version is unknown to the server or unsupported (e.g., a known experimental or draft version the server has chosen not to implement). For HTTP, the response status code MUST be `400 Bad Request`. """ + model_config = ConfigDict( + extra="allow", + ) error: Error2 id: RequestId | None = None jsonrpc: Literal["2.0"] class Items1(WireModel): - """Schema for the array items.""" + """ + Schema for the array items. + """ + model_config = ConfigDict( + extra="allow", + ) enum: list[str] """ Array of enum values to choose from. @@ -1061,8 +1295,13 @@ class Items1(WireModel): class UntitledMultiSelectEnumSchema(WireModel): - """Schema for multiple-selection enumeration without display titles for options.""" + """ + Schema for multiple-selection enumeration without display titles for options. + """ + model_config = ConfigDict( + extra="allow", + ) default: list[str] | None = None """ Optional default value. @@ -1091,8 +1330,13 @@ class UntitledMultiSelectEnumSchema(WireModel): class UntitledSingleSelectEnumSchema(WireModel): - """Schema for single-selection enumeration without display titles for options.""" + """ + Schema for single-selection enumeration without display titles for options. + """ + model_config = ConfigDict( + extra="allow", + ) default: str | None = None """ Optional default value. @@ -1113,10 +1357,13 @@ class UntitledSingleSelectEnumSchema(WireModel): class Annotations(WireModel): - """Optional annotations for the client. The client can use annotations to inform how objects are used or - displayed + """ + Optional annotations for the client. The client can use annotations to inform how objects are used or displayed """ + model_config = ConfigDict( + extra="allow", + ) audience: list[Role] | None = None """ Describes who the intended audience of this object or data is. @@ -1143,14 +1390,19 @@ class Annotations(WireModel): class AudioContent(WireModel): - """Audio provided to or from an LLM.""" + """ + Audio provided to or from an LLM. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None """ Optional annotations for the client. """ - data: str + data: Base64Str """ The base64-encoded audio data. """ @@ -1162,8 +1414,11 @@ class AudioContent(WireModel): class BlobResourceContents(WireModel): + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None - blob: str + blob: Base64Str """ A base64-encoded string representing the binary data of the item. """ @@ -1171,15 +1426,20 @@ class BlobResourceContents(WireModel): """ The MIME type of this resource, if known. """ - uri: str + uri: AnyUrl """ The URI of this resource. """ class CacheableResult(WireModel): - """A result that supports a time-to-live (TTL) hint for client-side caching.""" + """ + A result that supports a time-to-live (TTL) hint for client-side caching. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] """ @@ -1216,8 +1476,13 @@ class CacheableResult(WireModel): class CancelledNotificationParams(WireModel): - """Parameters for a `notifications/cancelled` notification.""" + """ + Parameters for a `notifications/cancelled` notification. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None reason: str | None = None """ @@ -1231,12 +1496,21 @@ class CancelledNotificationParams(WireModel): """ -ClientResult: TypeAlias = Result +class ClientResult(RootModel[Result]): + root: Result + """ + Common result fields. + """ class CompleteResult(WireModel): - """The result returned by the server for a CompleteRequestcompletion/complete request.""" + """ + The result returned by the server for a {@link CompleteRequestcompletion/complete} request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None completion: Completion result_type: Annotated[str, Field(alias="resultType")] @@ -1252,20 +1526,29 @@ class CompleteResult(WireModel): class CompleteResultResponse(WireModel): - """A successful response from the server for a CompleteRequestcompletion/complete request.""" + """ + A successful response from the server for a {@link CompleteRequestcompletion/complete} request. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] result: CompleteResult class EmbeddedResource(WireModel): - """The contents of a resource, embedded into a prompt or tool call result. + """ + The contents of a resource, embedded into a prompt or tool call result. It is up to the client how best to render embedded resources for the benefit of the LLM and/or the user. """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None """ @@ -1275,27 +1558,45 @@ class EmbeddedResource(WireModel): type: Literal["resource"] -EmptyResult: TypeAlias = Result +class EmptyResult(RootModel[Result]): + root: Result + """ + Common result fields. + """ -EnumSchema: TypeAlias = ( - UntitledSingleSelectEnumSchema - | TitledSingleSelectEnumSchema - | UntitledMultiSelectEnumSchema - | TitledMultiSelectEnumSchema - | LegacyTitledEnumSchema -) +class EnumSchema( + RootModel[ + UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema + ] +): + root: ( + UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema + ) class ImageContent(WireModel): - """An image provided to or from an LLM.""" + """ + An image provided to or from an LLM. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None """ Optional annotations for the client. """ - data: str + data: Base64Str """ The base64-encoded image data. """ @@ -1307,16 +1608,26 @@ class ImageContent(WireModel): class JSONRPCErrorResponse(WireModel): - """A response to a request that indicates an error occurred.""" + """ + A response to a request that indicates an error occurred. + """ + model_config = ConfigDict( + extra="allow", + ) error: Error id: RequestId | None = None jsonrpc: Literal["2.0"] class JSONRPCRequest(WireModel): - """A request that expects a response.""" + """ + A request that expects a response. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: str @@ -1324,25 +1635,39 @@ class JSONRPCRequest(WireModel): class JSONRPCResultResponse(WireModel): - """A successful (non-error) response to a request.""" + """ + A successful (non-error) response to a request. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] result: Result class ListRootsResult(WireModel): - """The result returned by the client for a ListRootsRequestroots/list request. - This result contains an array of Root objects, each representing a root directory + """ + The result returned by the client for a {@link ListRootsRequestroots/list} request. + This result contains an array of {@link Root} objects, each representing a root directory or file that the server can operate on. """ + model_config = ConfigDict( + extra="allow", + ) roots: list[Root] class LoggingMessageNotificationParams(WireModel): - """Parameters for a `notifications/message` notification.""" + """ + Parameters for a `notifications/message` notification. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None data: Any """ @@ -1358,24 +1683,46 @@ class LoggingMessageNotificationParams(WireModel): """ -MultiSelectEnumSchema: TypeAlias = UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema +class MultiSelectEnumSchema(RootModel[UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema]): + root: UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema -PrimitiveSchemaDefinition: TypeAlias = ( - StringSchema - | NumberSchema - | BooleanSchema - | UntitledSingleSelectEnumSchema - | TitledSingleSelectEnumSchema - | UntitledMultiSelectEnumSchema - | TitledMultiSelectEnumSchema - | LegacyTitledEnumSchema -) +class PrimitiveSchemaDefinition( + RootModel[ + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema + ] +): + root: ( + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema + ) + """ + Restricted schema definitions that only allow primitive types + without nested objects or arrays. + """ class ProgressNotificationParams(WireModel): - """Parameters for a ProgressNotificationnotifications/progress notification.""" + """ + Parameters for a {@link ProgressNotificationnotifications/progress} notification. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None message: str | None = None """ @@ -1387,8 +1734,7 @@ class ProgressNotificationParams(WireModel): """ progress_token: Annotated[ProgressToken, Field(alias="progressToken")] """ - The progress token which was given in the initial request, used to associate this notification with the request that - is proceeding. + The progress token which was given in the initial request, used to associate this notification with the request that is proceeding. """ total: float | None = None """ @@ -1397,8 +1743,13 @@ class ProgressNotificationParams(WireModel): class Prompt(WireModel): - """A prompt or prompt template that the server offers.""" + """ + A prompt or prompt template that the server offers. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None arguments: list[PromptArgument] | None = None """ @@ -1422,23 +1773,27 @@ class Prompt(WireModel): """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ title: str | None = None """ Intended for UI and end-user contexts — optimized to be human-readable and easily understood, even by those unfamiliar with domain-specific terminology. - If not provided, the name should be used for display (except for Tool, + If not provided, the name should be used for display (except for {@link Tool}, where `annotations.title` should be given precedence over using `name`, if present). """ class ReadResourceResult(WireModel): - """The result returned by the server for a ReadResourceRequestresources/read request.""" + """ + The result returned by the server for a {@link ReadResourceRequestresources/read} request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] """ @@ -1476,8 +1831,13 @@ class ReadResourceResult(WireModel): class Resource(WireModel): - """A known resource that the server is capable of reading.""" + """ + A known resource that the server is capable of reading. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None """ @@ -1487,8 +1847,7 @@ class Resource(WireModel): """ A description of what this resource represents. - This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a - "hint" to the model. + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. """ icons: list[Icon] | None = None """ @@ -1508,8 +1867,7 @@ class Resource(WireModel): """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ size: int | None = None """ @@ -1522,23 +1880,26 @@ class Resource(WireModel): Intended for UI and end-user contexts — optimized to be human-readable and easily understood, even by those unfamiliar with domain-specific terminology. - If not provided, the name should be used for display (except for Tool, + If not provided, the name should be used for display (except for {@link Tool}, where `annotations.title` should be given precedence over using `name`, if present). """ - uri: str + uri: AnyUrl """ The URI of this resource. """ class ResourceLink(WireModel): - """A resource that the server is capable of reading, included in a prompt or tool call result. + """ + A resource that the server is capable of reading, included in a prompt or tool call result. - Note: resource links returned by tools are not guaranteed to appear in the results of - ListResourcesRequestresources/list requests. + Note: resource links returned by tools are not guaranteed to appear in the results of {@link ListResourcesRequestresources/list} requests. """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None """ @@ -1548,8 +1909,7 @@ class ResourceLink(WireModel): """ A description of what this resource represents. - This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a - "hint" to the model. + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. """ icons: list[Icon] | None = None """ @@ -1569,8 +1929,7 @@ class ResourceLink(WireModel): """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ size: int | None = None """ @@ -1583,20 +1942,25 @@ class ResourceLink(WireModel): Intended for UI and end-user contexts — optimized to be human-readable and easily understood, even by those unfamiliar with domain-specific terminology. - If not provided, the name should be used for display (except for Tool, + If not provided, the name should be used for display (except for {@link Tool}, where `annotations.title` should be given precedence over using `name`, if present). """ type: Literal["resource_link"] - uri: str + uri: AnyUrl """ The URI of this resource. """ class ResourceTemplate(WireModel): - """A template description for resources available on the server.""" + """ + A template description for resources available on the server. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None """ @@ -1606,8 +1970,7 @@ class ResourceTemplate(WireModel): """ A description of what this template is for. - This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a - "hint" to the model. + This can be used by clients to improve the LLM's understanding of available resources. It can be thought of like a "hint" to the model. """ icons: list[Icon] | None = None """ @@ -1623,20 +1986,18 @@ class ResourceTemplate(WireModel): """ mime_type: Annotated[str | None, Field(alias="mimeType")] = None """ - The MIME type for all resources that match this template. This should only be included if all resources matching - this template have the same type. + The MIME type for all resources that match this template. This should only be included if all resources matching this template have the same type. """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ title: str | None = None """ Intended for UI and end-user contexts — optimized to be human-readable and easily understood, even by those unfamiliar with domain-specific terminology. - If not provided, the name should be used for display (except for Tool, + If not provided, the name should be used for display (except for {@link Tool}, where `annotations.title` should be given precedence over using `name`, if present). """ @@ -1647,34 +2008,46 @@ class ResourceTemplate(WireModel): class ResourceUpdatedNotification(WireModel): - """A notification from the server to the client, informing it that a resource has changed and may need to be read - again. This is only sent for resources the client opted in to via the `resourceSubscriptions` field of a - SubscriptionsListenRequestsubscriptions/listen request. + """ + A notification from the server to the client, informing it that a resource has changed and may need to be read again. This is only sent for resources the client opted in to via the `resourceSubscriptions` field of a {@link SubscriptionsListenRequestsubscriptions/listen} request. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/resources/updated"] params: ResourceUpdatedNotificationParams -SingleSelectEnumSchema: TypeAlias = UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema +class SingleSelectEnumSchema(RootModel[UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema]): + root: UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema class SubscriptionsAcknowledgedNotification(WireModel): - """Sent by the server as the first message on a - SubscriptionsListenRequestsubscriptions/listen stream to acknowledge + """ + Sent by the server as the first message on a + {@link SubscriptionsListenRequestsubscriptions/listen} stream to acknowledge that the subscription has been established and to report which notification types it agreed to honor. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/subscriptions/acknowledged"] params: SubscriptionsAcknowledgedNotificationParams class TextContent(WireModel): - """Text provided to or from an LLM.""" + """ + Text provided to or from an LLM. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None """ @@ -1688,8 +2061,13 @@ class TextContent(WireModel): class Tool(WireModel): - """Definition for a tool the client can call.""" + """ + Definition for a tool the client can call. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: ToolAnnotations | None = None """ @@ -1701,8 +2079,7 @@ class Tool(WireModel): """ A human-readable description of the tool. - This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a - "hint" to the model. + This can be used by clients to improve the LLM's understanding of available tools. It can be thought of like a "hint" to the model. """ icons: list[Icon] | None = None """ @@ -1730,13 +2107,12 @@ class Tool(WireModel): """ name: str """ - Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't - present). + Intended for programmatic or logical use, but used as a display name in past specs or fallback (if title isn't present). """ output_schema: Annotated[OutputSchema | None, Field(alias="outputSchema")] = None """ An optional JSON Schema object defining the structure of the tool's output returned in - the structuredContent field of a CallToolResult. This can be any valid JSON Schema 2020-12. + the structuredContent field of a {@link CallToolResult}. This can be any valid JSON Schema 2020-12. Defaults to JSON Schema 2020-12 when no explicit `$schema` is provided. """ @@ -1745,34 +2121,42 @@ class Tool(WireModel): Intended for UI and end-user contexts — optimized to be human-readable and easily understood, even by those unfamiliar with domain-specific terminology. - If not provided, the name should be used for display (except for Tool, + If not provided, the name should be used for display (except for {@link Tool}, where `annotations.title` should be given precedence over using `name`, if present). """ class CancelledNotification(WireModel): - """This notification can be sent by either side to indicate that it is cancelling a previously-issued request. + """ + This notification can be sent by either side to indicate that it is cancelling a previously-issued request. - The request SHOULD still be in-flight, but due to communication latency, it is always possible that this - notification MAY arrive after the request has already finished. + The request SHOULD still be in-flight, but due to communication latency, it is always possible that this notification MAY arrive after the request has already finished. This notification indicates that the result will be unused, so any associated processing SHOULD cease. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/cancelled"] params: CancelledNotificationParams -ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource +class ContentBlock(RootModel[TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource]): + root: TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource class RequestedSchema(WireModel): - """A restricted subset of JSON Schema. + """ + A restricted subset of JSON Schema. Only top-level properties are allowed, without nesting. """ + model_config = ConfigDict( + extra="allow", + ) schema_: Annotated[str | None, Field(alias="$schema")] = None properties: dict[str, PrimitiveSchemaDefinition] required: list[str] | None = None @@ -1780,8 +2164,13 @@ class RequestedSchema(WireModel): class ElicitRequestFormParams(WireModel): - """The parameters for a request to elicit non-sensitive information from the user via a form in the client.""" + """ + The parameters for a request to elicit non-sensitive information from the user via a form in the client. + """ + model_config = ConfigDict( + extra="allow", + ) message: str """ The message to present to the user describing what information is being requested. @@ -1797,18 +2186,35 @@ class ElicitRequestFormParams(WireModel): """ -ElicitRequestParams: TypeAlias = ElicitRequestFormParams | ElicitRequestURLParams +class ElicitRequestParams(RootModel[ElicitRequestFormParams | ElicitRequestURLParams]): + root: ElicitRequestFormParams | ElicitRequestURLParams + """ + The parameters for a request to elicit additional information from the user via the client. + """ -JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse +class JSONRPCMessage(RootModel[JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse]): + root: JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse + """ + Refers to any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent. + """ -JSONRPCResponse: TypeAlias = JSONRPCResultResponse | JSONRPCErrorResponse +class JSONRPCResponse(RootModel[JSONRPCResultResponse | JSONRPCErrorResponse]): + root: JSONRPCResultResponse | JSONRPCErrorResponse + """ + A response to a request, containing either the result or error. + """ class ListPromptsResult(WireModel): - """The result returned by the server for a ListPromptsRequestprompts/list request.""" + """ + The result returned by the server for a {@link ListPromptsRequestprompts/list} request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] """ @@ -1851,16 +2257,26 @@ class ListPromptsResult(WireModel): class ListPromptsResultResponse(WireModel): - """A successful response from the server for a ListPromptsRequestprompts/list request.""" + """ + A successful response from the server for a {@link ListPromptsRequestprompts/list} request. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] result: ListPromptsResult class ListResourceTemplatesResult(WireModel): - """The result returned by the server for a ListResourceTemplatesRequestresources/templates/list request.""" + """ + The result returned by the server for a {@link ListResourceTemplatesRequestresources/templates/list} request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] """ @@ -1903,16 +2319,26 @@ class ListResourceTemplatesResult(WireModel): class ListResourceTemplatesResultResponse(WireModel): - """A successful response from the server for a ListResourceTemplatesRequestresources/templates/list request.""" + """ + A successful response from the server for a {@link ListResourceTemplatesRequestresources/templates/list} request. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] result: ListResourceTemplatesResult class ListResourcesResult(WireModel): - """The result returned by the server for a ListResourcesRequestresources/list request.""" + """ + The result returned by the server for a {@link ListResourcesRequestresources/list} request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] """ @@ -1955,16 +2381,26 @@ class ListResourcesResult(WireModel): class ListResourcesResultResponse(WireModel): - """A successful response from the server for a ListResourcesRequestresources/list request.""" + """ + A successful response from the server for a {@link ListResourcesRequestresources/list} request. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] result: ListResourcesResult class ListToolsResult(WireModel): - """The result returned by the server for a ListToolsRequesttools/list request.""" + """ + The result returned by the server for a {@link ListToolsRequesttools/list} request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] """ @@ -2007,58 +2443,93 @@ class ListToolsResult(WireModel): class ListToolsResultResponse(WireModel): - """A successful response from the server for a ListToolsRequesttools/list request.""" + """ + A successful response from the server for a {@link ListToolsRequesttools/list} request. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] result: ListToolsResult class LoggingMessageNotification(WireModel): - """JSONRPCNotification of a log message passed from server to client. The client opts in by setting - `"io.modelcontextprotocol/logLevel"` in a request's `_meta`. + """ + JSONRPCNotification of a log message passed from server to client. The client opts in by setting `"io.modelcontextprotocol/logLevel"` in a request's `_meta`. """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/message"] params: LoggingMessageNotificationParams class ProgressNotification(WireModel): - """An out-of-band notification used to inform the receiver of a progress update for a long-running request.""" + """ + An out-of-band notification used to inform the receiver of a progress update for a long-running request. + """ + model_config = ConfigDict( + extra="allow", + ) jsonrpc: Literal["2.0"] method: Literal["notifications/progress"] params: ProgressNotificationParams class PromptMessage(WireModel): - """Describes a message returned as part of a prompt. + """ + Describes a message returned as part of a prompt. - This is similar to SamplingMessage, but also supports the embedding of + This is similar to {@link SamplingMessage}, but also supports the embedding of resources from the MCP server. """ + model_config = ConfigDict( + extra="allow", + ) content: ContentBlock role: Role -ServerNotification: TypeAlias = ( - CancelledNotification - | ProgressNotification - | ResourceListChangedNotification - | SubscriptionsAcknowledgedNotification - | ResourceUpdatedNotification - | PromptListChangedNotification - | ToolListChangedNotification - | LoggingMessageNotification - | ElicitationCompleteNotification -) +class ServerNotification( + RootModel[ + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | SubscriptionsAcknowledgedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | LoggingMessageNotification + | ElicitationCompleteNotification + ] +): + root: ( + CancelledNotification + | ProgressNotification + | ResourceListChangedNotification + | SubscriptionsAcknowledgedNotification + | ResourceUpdatedNotification + | PromptListChangedNotification + | ToolListChangedNotification + | LoggingMessageNotification + | ElicitationCompleteNotification + ) class ToolResultContent(WireModel): - """The result of a tool use, provided by the user back to the assistant.""" + """ + The result of a tool use, provided by the user back to the assistant. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None """ Optional metadata about the tool result. Clients SHOULD preserve this field when @@ -2068,7 +2539,7 @@ class ToolResultContent(WireModel): """ The unstructured result content of the tool use. - This has the same format as CallToolResult.content and can include text, images, + This has the same format as {@link CallToolResult.content} and can include text, images, audio, resource links, and embedded resources. """ is_error: Annotated[bool | None, Field(alias="isError")] = None @@ -2083,20 +2554,25 @@ class ToolResultContent(WireModel): An optional structured result value. This can be any JSON value (object, array, string, number, boolean, or null). - If the tool defined an Tool.outputSchema, this SHOULD conform to that schema. + If the tool defined an {@link Tool.outputSchema}, this SHOULD conform to that schema. """ tool_use_id: Annotated[str, Field(alias="toolUseId")] """ The ID of the tool use this result corresponds to. - This MUST match the ID from a previous ToolUseContent. + This MUST match the ID from a previous {@link ToolUseContent}. """ type: Literal["tool_result"] class CallToolResult(WireModel): - """The result returned by the server for a CallToolRequesttools/call request.""" + """ + The result returned by the server for a {@link CallToolRequesttools/call} request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None content: list[ContentBlock] """ @@ -2136,19 +2612,30 @@ class CallToolResult(WireModel): """ -ClientNotification: TypeAlias = CancelledNotification | ProgressNotification +class ClientNotification(RootModel[CancelledNotification | ProgressNotification]): + root: CancelledNotification | ProgressNotification class ElicitRequest(WireModel): - """A request from the server to elicit additional information from the user via the client.""" + """ + A request from the server to elicit additional information from the user via the client. + """ + model_config = ConfigDict( + extra="allow", + ) method: Literal["elicitation/create"] params: ElicitRequestParams class GetPromptResult(WireModel): - """The result returned by the server for a GetPromptRequestprompts/get request.""" + """ + The result returned by the server for a {@link GetPromptRequestprompts/get} request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None description: str | None = None """ @@ -2167,15 +2654,22 @@ class GetPromptResult(WireModel): """ -SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent +class SamplingMessageContentBlock( + RootModel[TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent] +): + root: TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent class CreateMessageResult(WireModel): - """The result returned by the client for a CreateMessageRequestsampling/createMessage request. + """ + The result returned by the client for a {@link CreateMessageRequestsampling/createMessage} request. The client should inform the user before returning the sampled message, to allow them to inspect the response (human in the loop) and decide whether to allow the server to see it. """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None content: ( TextContent @@ -2204,18 +2698,28 @@ class CreateMessageResult(WireModel): """ -InputResponse: TypeAlias = CreateMessageResult | ListRootsResult | ElicitResult +class InputResponse(RootModel[CreateMessageResult | ListRootsResult | ElicitResult]): + root: CreateMessageResult | ListRootsResult | ElicitResult + +class InputResponses(RootModel[dict[str, InputResponse]]): + """ + A map of client responses to server-initiated requests. + Keys correspond to the keys in the {@link InputRequests} map; + values are the client's result for each request. + """ -InputResponses: TypeAlias = dict[str, InputResponse] -"""A map of client responses to server-initiated requests. -Keys correspond to the keys in the InputRequests map; -values are the client's result for each request.""" + root: dict[str, InputResponse] class SamplingMessage(WireModel): - """Describes a message issued to or received from an LLM API.""" + """ + Describes a message issued to or received from an LLM API. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None content: ( TextContent @@ -2229,8 +2733,13 @@ class SamplingMessage(WireModel): class CallToolRequest(WireModel): - """Used by the client to invoke a tool provided by the server.""" + """ + Used by the client to invoke a tool provided by the server. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["tools/call"] @@ -2238,8 +2747,13 @@ class CallToolRequest(WireModel): class CallToolRequestParams(WireModel): - """Parameters for a `tools/call` request.""" + """ + Parameters for a `tools/call` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] arguments: dict[str, Any] | None = None """ @@ -2254,23 +2768,38 @@ class CallToolRequestParams(WireModel): class CallToolResultResponse(WireModel): - """A successful response from the server for a CallToolRequesttools/call request.""" + """ + A successful response from the server for a {@link CallToolRequesttools/call} request. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] result: InputRequiredResult | CallToolResult class Elicitation(WireModel): - """Present if the client supports elicitation from the server.""" + """ + Present if the client supports elicitation from the server. + """ + model_config = ConfigDict( + extra="allow", + ) form: JSONObject | None = None url: JSONObject | None = None class Sampling(WireModel): - """Present if the client supports sampling from an LLM.""" + """ + Present if the client supports sampling from an LLM. + """ + model_config = ConfigDict( + extra="allow", + ) context: JSONObject | None = None """ Whether the client supports context inclusion via `includeContext` parameter. @@ -2283,10 +2812,13 @@ class Sampling(WireModel): class ClientCapabilities(WireModel): - """Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a - closed set: any client can define its own, additional capabilities. + """ + Capabilities a client may support. Known capabilities are defined here, in this schema, but this is not a closed set: any client can define its own, additional capabilities. """ + model_config = ConfigDict( + extra="allow", + ) elicitation: Elicitation | None = None """ Present if the client supports elicitation from the server. @@ -2312,8 +2844,13 @@ class ClientCapabilities(WireModel): class CompleteRequest(WireModel): - """A request from the client to the server, to ask for completion options.""" + """ + A request from the client to the server, to ask for completion options. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["completion/complete"] @@ -2321,8 +2858,13 @@ class CompleteRequest(WireModel): class CompleteRequestParams(WireModel): - """Parameters for a `completion/complete` request.""" + """ + Parameters for a `completion/complete` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] argument: Argument """ @@ -2336,18 +2878,25 @@ class CompleteRequestParams(WireModel): class CreateMessageRequest(WireModel): - """A request from the server to sample an LLM via the client. The client has full discretion over which model to - select. The client should also inform the user before beginning sampling, to allow them to inspect the request - (human in the loop) and decide whether to approve it. + """ + A request from the server to sample an LLM via the client. The client has full discretion over which model to select. The client should also inform the user before beginning sampling, to allow them to inspect the request (human in the loop) and decide whether to approve it. """ + model_config = ConfigDict( + extra="allow", + ) method: Literal["sampling/createMessage"] params: CreateMessageRequestParams class CreateMessageRequestParams(WireModel): - """Parameters for a `sampling/createMessage` request.""" + """ + Parameters for a `sampling/createMessage` request. + """ + model_config = ConfigDict( + extra="allow", + ) include_context: Annotated[ Literal["allServers", "none", "thisServer"] | None, Field(alias="includeContext"), @@ -2358,7 +2907,7 @@ class CreateMessageRequestParams(WireModel): Default is `"none"`. The values `"thisServer"` and `"allServers"` are deprecated (SEP-2596): servers SHOULD omit this field or use `"none"`, and SHOULD only use the deprecated values if the client declares - ClientCapabilities.sampling.context. + {@link ClientCapabilities.sampling.context}. """ max_tokens: Annotated[int, Field(alias="maxTokens")] """ @@ -2384,23 +2933,27 @@ class CreateMessageRequestParams(WireModel): tool_choice: Annotated[ToolChoice | None, Field(alias="toolChoice")] = None """ Controls how the model uses tools. - The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + The client MUST return an error if this field is provided but {@link ClientCapabilities.sampling.tools} is not declared. Default is `{ mode: "auto" }`. """ tools: list[Tool] | None = None """ Tools that the model may use during generation. - The client MUST return an error if this field is provided but ClientCapabilities.sampling.tools is not declared. + The client MUST return an error if this field is provided but {@link ClientCapabilities.sampling.tools} is not declared. """ class DiscoverRequest(WireModel): - """A request from the client asking the server to advertise its supported + """ + A request from the client asking the server to advertise its supported protocol versions, capabilities, and other metadata. Servers **MUST** implement `server/discover`. Clients **MAY** call it but are not required to — version negotiation can also happen inline via per-request `_meta`. """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["server/discover"] @@ -2408,8 +2961,13 @@ class DiscoverRequest(WireModel): class DiscoverResult(WireModel): - """The result returned by the server for a DiscoverRequestserver/discover request.""" + """ + The result returned by the server for a {@link DiscoverRequestserver/discover} request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] """ @@ -2468,16 +3026,26 @@ class DiscoverResult(WireModel): class DiscoverResultResponse(WireModel): - """A successful response from the server for a DiscoverRequestserver/discover request.""" + """ + A successful response from the server for a {@link DiscoverRequestserver/discover} request. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] result: DiscoverResult class GetPromptRequest(WireModel): - """Used by the client to get a prompt provided by the server.""" + """ + Used by the client to get a prompt provided by the server. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["prompts/get"] @@ -2485,8 +3053,13 @@ class GetPromptRequest(WireModel): class GetPromptRequestParams(WireModel): - """Parameters for a `prompts/get` request.""" + """ + Parameters for a `prompts/get` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] arguments: dict[str, str] | None = None """ @@ -2501,20 +3074,29 @@ class GetPromptRequestParams(WireModel): class GetPromptResultResponse(WireModel): - """A successful response from the server for a GetPromptRequestprompts/get request.""" + """ + A successful response from the server for a {@link GetPromptRequestprompts/get} request. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] result: InputRequiredResult | GetPromptResult class InputRequiredResult(WireModel): - """An InputRequiredResult sent by the server to indicate that additional input is needed + """ + An InputRequiredResult sent by the server to indicate that additional input is needed before the request can be completed. At least one of `inputRequests` or `requestState` MUST be present. """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None input_requests: Annotated[InputRequests | None, Field(alias="inputRequests")] = None request_state: Annotated[str | None, Field(alias="requestState")] = None @@ -2531,14 +3113,22 @@ class InputRequiredResult(WireModel): class InputResponseRequestParams(WireModel): + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None request_state: Annotated[str | None, Field(alias="requestState")] = None class ListPromptsRequest(WireModel): - """Sent from the client to request a list of prompts and prompt templates the server has.""" + """ + Sent from the client to request a list of prompts and prompt templates the server has. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["prompts/list"] @@ -2546,8 +3136,13 @@ class ListPromptsRequest(WireModel): class ListResourceTemplatesRequest(WireModel): - """Sent from the client to request a list of resource templates the server has.""" + """ + Sent from the client to request a list of resource templates the server has. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["resources/templates/list"] @@ -2555,8 +3150,13 @@ class ListResourceTemplatesRequest(WireModel): class ListResourcesRequest(WireModel): - """Sent from the client to request a list of resources the server has.""" + """ + Sent from the client to request a list of resources the server has. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["resources/list"] @@ -2564,7 +3164,8 @@ class ListResourcesRequest(WireModel): class ListRootsRequest(WireModel): - """Sent from the server to request a list of root URIs from the client. Roots allow + """ + Sent from the server to request a list of root URIs from the client. Roots allow servers to ask for specific directories or files to operate on. A common example for roots is providing a set of repositories or directories a server should operate on. @@ -2573,13 +3174,21 @@ class ListRootsRequest(WireModel): structure or access specific locations that the client has permission to read from. """ + model_config = ConfigDict( + extra="allow", + ) method: Literal["roots/list"] params: RequestParams | None = None class ListToolsRequest(WireModel): - """Sent from the client to request a list of tools the server has.""" + """ + Sent from the client to request a list of tools the server has. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["tools/list"] @@ -2587,44 +3196,52 @@ class ListToolsRequest(WireModel): class Data(WireModel): - """Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). + """ + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ + model_config = ConfigDict( + extra="allow", + ) required_capabilities: Annotated[ClientCapabilities, Field(alias="requiredCapabilities")] """ The capabilities the server requires from the client to process this request. """ -class Error1(WireModel): +class Error1(Error): + model_config = ConfigDict( + extra="allow", + ) code: Literal[-32003] """ The error type that occurred. """ data: Data """ - Additional information about the error. The value of this member is defined by the sender (e.g. detailed error - information, nested errors etc.). - """ - message: str - """ - A short description of the error. The message SHOULD be limited to a concise single sentence. + Additional information about the error. The value of this member is defined by the sender (e.g. detailed error information, nested errors etc.). """ class MissingRequiredClientCapabilityError(WireModel): - """Returned when processing a request requires a capability the client did not + """ + Returned when processing a request requires a capability the client did not declare in `clientCapabilities`. For HTTP, the response status code MUST be `400 Bad Request`. """ + model_config = ConfigDict( + extra="allow", + ) error: Error1 id: RequestId | None = None jsonrpc: Literal["2.0"] class PaginatedRequest(WireModel): + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: str @@ -2632,8 +3249,13 @@ class PaginatedRequest(WireModel): class PaginatedRequestParams(WireModel): - """Common params for paginated requests.""" + """ + Common params for paginated requests. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] cursor: str | None = None """ @@ -2643,8 +3265,13 @@ class PaginatedRequestParams(WireModel): class ReadResourceRequest(WireModel): - """Sent from the client to the server, to read a specific resource URI.""" + """ + Sent from the client to the server, to read a specific resource URI. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["resources/read"] @@ -2652,28 +3279,43 @@ class ReadResourceRequest(WireModel): class ReadResourceRequestParams(WireModel): - """Parameters for a `resources/read` request.""" + """ + Parameters for a `resources/read` request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None request_state: Annotated[str | None, Field(alias="requestState")] = None - uri: str + uri: AnyUrl """ The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. """ class ReadResourceResultResponse(WireModel): - """A successful response from the server for a ReadResourceRequestresources/read request.""" + """ + A successful response from the server for a {@link ReadResourceRequestresources/read} request. + """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] result: InputRequiredResult | ReadResourceResult -class RequestMetaObject(OpenWireModel): - """Extends MetaObject with additional request-specific fields. All key naming rules from `MetaObject` apply.""" +class RequestMetaObject(WireModel): + """ + Extends {@link MetaObject} with additional request-specific fields. All key naming rules from `MetaObject` apply. + """ + model_config = ConfigDict( + extra="allow", + ) io_modelcontextprotocol_client_capabilities: Annotated[ ClientCapabilities, Field(alias="io.modelcontextprotocol/clientCapabilities") ] @@ -2688,7 +3330,7 @@ class RequestMetaObject(OpenWireModel): """ Identifies the client software making the request. Required. - The Implementation schema requires `name` and `version`; other + The {@link Implementation} schema requires `name` and `version`; other fields are optional. """ io_modelcontextprotocol_log_level: Annotated[ @@ -2697,7 +3339,7 @@ class RequestMetaObject(OpenWireModel): """ The desired log level for this request. Optional. - If absent, the server MUST NOT send any LoggingMessageNotificationnotifications/message + If absent, the server MUST NOT send any {@link LoggingMessageNotificationnotifications/message} notifications for this request. The client opts in to log messages by explicitly setting a level. Replaces the former `logging/setLevel` RPC. """ @@ -2708,37 +3350,48 @@ class RequestMetaObject(OpenWireModel): For the HTTP transport, this value MUST match the `MCP-Protocol-Version` header; otherwise the server MUST return a `400 Bad Request`. If the server does not support the requested version, it MUST return an - UnsupportedProtocolVersionError. + {@link UnsupportedProtocolVersionError}. """ progress_token: Annotated[ProgressToken | None, Field(alias="progressToken")] = None """ - If specified, the caller is requesting out-of-band progress notifications for this request (as represented by - ProgressNotificationnotifications/progress). The value of this parameter is an opaque token that will be attached to - any subsequent notifications. The receiver is not obligated to provide these notifications. + If specified, the caller is requesting out-of-band progress notifications for this request (as represented by {@link ProgressNotificationnotifications/progress}). The value of this parameter is an opaque token that will be attached to any subsequent notifications. The receiver is not obligated to provide these notifications. """ class RequestParams(WireModel): - """Common params for any request.""" + """ + Common params for any request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] class ResourceRequestParams(WireModel): - """Common params for resource-related requests.""" + """ + Common params for resource-related requests. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] - uri: str + uri: AnyUrl """ The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. """ class ServerCapabilities(WireModel): - """Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a - closed set: any server can define its own, additional capabilities. + """ + Capabilities that a server may support. Known capabilities are defined here, in this schema, but this is not a closed set: any server can define its own, additional capabilities. """ + model_config = ConfigDict( + extra="allow", + ) completions: JSONObject | None = None """ Present if the server supports argument autocompletion suggestions. @@ -2772,11 +3425,15 @@ class ServerCapabilities(WireModel): class SubscriptionsListenRequest(WireModel): - """Sent from the client to open a long-lived channel for receiving notifications + """ + Sent from the client to open a long-lived channel for receiving notifications outside the context of a specific request. Replaces the previous HTTP GET endpoint and ensures consistent behavior between HTTP and STDIO. """ + model_config = ConfigDict( + extra="allow", + ) id: RequestId jsonrpc: Literal["2.0"] method: Literal["subscriptions/listen"] @@ -2784,8 +3441,13 @@ class SubscriptionsListenRequest(WireModel): class SubscriptionsListenRequestParams(WireModel): - """Parameters for a SubscriptionsListenRequestsubscriptions/listen request.""" + """ + Parameters for a {@link SubscriptionsListenRequestsubscriptions/listen} request. + """ + model_config = ConfigDict( + extra="allow", + ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] notifications: SubscriptionFilter """ @@ -2795,54 +3457,92 @@ class SubscriptionsListenRequestParams(WireModel): """ -ServerResult: TypeAlias = ( - Result - | InputRequiredResult - | DiscoverResult - | ListResourcesResult - | ListResourceTemplatesResult - | ReadResourceResult - | ListPromptsResult - | GetPromptResult - | ListToolsResult - | CallToolResult - | CompleteResult -) +class ServerResult( + RootModel[ + Result + | InputRequiredResult + | DiscoverResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | CompleteResult + ] +): + root: ( + Result + | InputRequiredResult + | DiscoverResult + | ListResourcesResult + | ListResourceTemplatesResult + | ReadResourceResult + | ListPromptsResult + | GetPromptResult + | ListToolsResult + | CallToolResult + | CompleteResult + ) + + +class InputRequest(RootModel[CreateMessageRequest | ListRootsRequest | ElicitRequest]): + root: CreateMessageRequest | ListRootsRequest | ElicitRequest -InputRequest: TypeAlias = CreateMessageRequest | ListRootsRequest | ElicitRequest +class ClientRequest( + RootModel[ + DiscoverRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscriptionsListenRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | CompleteRequest + ] +): + root: ( + DiscoverRequest + | ListResourcesRequest + | ListResourceTemplatesRequest + | ReadResourceRequest + | SubscriptionsListenRequest + | ListPromptsRequest + | GetPromptRequest + | ListToolsRequest + | CallToolRequest + | CompleteRequest + ) -ClientRequest: TypeAlias = ( - DiscoverRequest - | ListResourcesRequest - | ListResourceTemplatesRequest - | ReadResourceRequest - | SubscriptionsListenRequest - | ListPromptsRequest - | GetPromptRequest - | ListToolsRequest - | CallToolRequest - | CompleteRequest -) +class InputRequests(RootModel[dict[str, InputRequest]]): + """ + A map of server-initiated requests that the client must fulfill. + Keys are server-assigned identifiers; values are the request objects. + """ + + root: dict[str, InputRequest] -InputRequests: TypeAlias = dict[str, InputRequest] -"""A map of server-initiated requests that the client must fulfill. -Keys are server-assigned identifiers; values are the request objects.""" +class JSONArray(RootModel[list["JSONValue"]]): + root: list["JSONValue"] -JSONArray: TypeAlias = list["JSONValue"] +class JSONObject(RootModel[dict[str, "JSONValue"]]): + root: dict[str, "JSONValue"] -AnyCallToolResult: TypeAlias = CallToolResult | InputRequiredResult -"""Named alias for `CallToolResultResponse.result` so the wire-method maps can reference it as a value.""" +class JSONValue(RootModel[Union[JSONObject, list["JSONValue"], str | float | bool]]): + root: Union[JSONObject, list["JSONValue"], str | float | bool] -AnyGetPromptResult: TypeAlias = GetPromptResult | InputRequiredResult -"""Everything a `prompts/get` response's `result` may be at this version.""" -AnyReadResourceResult: TypeAlias = ReadResourceResult | InputRequiredResult -"""Everything a `resources/read` response's `result` may be at this version.""" +AnyCallToolResult = CallToolResult | InputRequiredResult +AnyGetPromptResult = GetPromptResult | InputRequiredResult +AnyReadResourceResult = ReadResourceResult | InputRequiredResult CallToolRequest.model_rebuild() @@ -2873,3 +3573,5 @@ class SubscriptionsListenRequestParams(WireModel): ReadResourceRequestParams.model_rebuild() ServerCapabilities.model_rebuild() SubscriptionsListenRequest.model_rebuild() +JSONArray.model_rebuild() +JSONObject.model_rebuild() diff --git a/tests/types/test_methods.py b/tests/types/test_methods.py index 423bafaf47..980ca5c7ce 100644 --- a/tests/types/test_methods.py +++ b/tests/types/test_methods.py @@ -7,13 +7,13 @@ import pydantic import pytest +from pydantic import BaseModel import mcp.types as types import mcp.types.v2025_11_25 as v2025 import mcp.types.v2026_07_28 as v2026 from mcp.shared.version import KNOWN_PROTOCOL_VERSIONS from mcp.types import methods -from mcp.types._wire_base import WireModel # Transcribed from each schema's ClientRequest/ServerRequest/ClientNotification/ # ServerNotification unions, minus the tasks/* family (extensions register those). @@ -210,7 +210,7 @@ } # Pinned per (method, version): class identity, or exact arm tuple for unions. -EXPECTED_SERVER_RESULTS: dict[tuple[str, str], type[WireModel] | tuple[type[WireModel], ...]] = { +EXPECTED_SERVER_RESULTS: dict[tuple[str, str], type[BaseModel] | tuple[type[BaseModel], ...]] = { ("completion/complete", "2024-11-05"): v2025.CompleteResult, ("initialize", "2024-11-05"): v2025.InitializeResult, ("logging/setLevel", "2024-11-05"): v2025.EmptyResult, @@ -275,7 +275,7 @@ ("tools/list", "2026-07-28"): v2026.ListToolsResult, } -EXPECTED_CLIENT_RESULTS: dict[tuple[str, str], type[WireModel] | tuple[type[WireModel], ...]] = { +EXPECTED_CLIENT_RESULTS: dict[tuple[str, str], type[BaseModel] | tuple[type[BaseModel], ...]] = { ("ping", "2024-11-05"): v2025.EmptyResult, ("roots/list", "2024-11-05"): v2025.ListRootsResult, ("sampling/createMessage", "2024-11-05"): v2025.CreateMessageResult, @@ -314,7 +314,7 @@ } # One minimal valid params mapping per surface request class. -REQUEST_PARAMS_FIXTURES: dict[type[WireModel], dict[str, Any] | None] = { +REQUEST_PARAMS_FIXTURES: dict[type[BaseModel], dict[str, Any] | None] = { v2025.CallToolRequest: {"name": "echo"}, v2025.CompleteRequest: {"ref": {"type": "ref/prompt", "name": "p"}, "argument": {"name": "a", "value": "v"}}, v2025.CreateMessageRequest: { @@ -354,7 +354,7 @@ v2026.SubscriptionsListenRequest: {"_meta": META_TRIPLE, "notifications": {}}, } -NOTIFICATION_PARAMS_FIXTURES: dict[type[WireModel], dict[str, Any] | None] = { +NOTIFICATION_PARAMS_FIXTURES: dict[type[BaseModel], dict[str, Any] | None] = { v2025.CancelledNotification: {"requestId": 1}, v2025.ElicitationCompleteNotification: {"elicitationId": "e1"}, v2025.InitializedNotification: None, @@ -377,7 +377,7 @@ } # One minimal valid result body per response row value (class or union alias). -RESULT_BODY_FIXTURES: dict[type[WireModel] | UnionType, dict[str, Any]] = { +RESULT_BODY_FIXTURES: dict[type[BaseModel] | UnionType, dict[str, Any]] = { v2025.CallToolResult: {"content": []}, v2025.CompleteResult: {"completion": {"values": []}}, v2025.CreateMessageResult: {"role": "assistant", "content": {"type": "text", "text": "hi"}, "model": "m"}, @@ -463,11 +463,11 @@ def test_response_row_values_match_the_pinned_classes_and_unions(): def test_surface_keys_agree_with_their_classes_and_the_monolith_maps(): """Each surface key's method matches its class's method literal, its monolith row, and its version's package.""" - request_maps: list[Mapping[tuple[str, str], type[WireModel]]] = [ + request_maps: list[Mapping[tuple[str, str], type[BaseModel]]] = [ methods.CLIENT_REQUESTS, methods.SERVER_REQUESTS, ] - notification_maps: list[Mapping[tuple[str, str], type[WireModel]]] = [ + notification_maps: list[Mapping[tuple[str, str], type[BaseModel]]] = [ methods.CLIENT_NOTIFICATIONS, methods.SERVER_NOTIFICATIONS, ] diff --git a/tests/types/test_parity.py b/tests/types/test_parity.py index 9212d038dc..5fd4285ebf 100644 --- a/tests/types/test_parity.py +++ b/tests/types/test_parity.py @@ -27,7 +27,10 @@ "v2025_11_25.Elicitation": monolith.ElicitationCapability, "v2025_11_25.Elicitation1": _types.TasksElicitationCapability, "v2025_11_25.ElicitationCompleteNotification": monolith.ElicitCompleteNotification, - "v2025_11_25.ElicitationCompleteNotificationParams": monolith.ElicitCompleteNotificationParams, + "v2025_11_25.Params": monolith.CancelTaskRequestParams, + "v2025_11_25.Params1": monolith.ElicitCompleteNotificationParams, + "v2025_11_25.Params2": monolith.GetTaskPayloadRequestParams, + "v2025_11_25.Params3": monolith.GetTaskRequestParams, "v2025_11_25.Error": monolith.ErrorData, "v2025_11_25.JSONRPCErrorResponse": monolith.JSONRPCError, "v2025_11_25.JSONRPCResultResponse": monolith.JSONRPCResponse, @@ -49,7 +52,7 @@ "v2026_07_28.Data1": monolith.UnsupportedProtocolVersionErrorData, "v2026_07_28.Elicitation": monolith.ElicitationCapability, "v2026_07_28.ElicitationCompleteNotification": monolith.ElicitCompleteNotification, - "v2026_07_28.ElicitationCompleteNotificationParams": monolith.ElicitCompleteNotificationParams, + "v2026_07_28.Params": monolith.ElicitCompleteNotificationParams, "v2026_07_28.Error": monolith.ErrorData, "v2026_07_28.JSONRPCErrorResponse": monolith.JSONRPCError, "v2026_07_28.JSONRPCResultResponse": monolith.JSONRPCResponse, @@ -146,6 +149,8 @@ def _surface_classes(module: ModuleType) -> list[tuple[str, type[BaseModel]]]: continue if obj.__module__ != module.__name__ or obj.__name__ != name: continue # re-export or alias to another model + if getattr(obj, "__pydantic_root_model__", False): + continue # RootModel alias wrapper; the field-subset property does not apply out.append((f"{tail}.{name}", obj)) return out From 0c6211ed5cf9082e3880ab14bd3734ea02e76375 Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Mon, 15 Jun 2026 18:46:43 +0000 Subject: [PATCH 4/7] Drop dead OpenWireModel; add --frozen to codegen invocations; tighten generated-file pyright suppression --- .github/workflows/shared.yml | 2 +- pyproject.toml | 2 +- scripts/gen_surface_types.py | 5 +++-- src/mcp/types/_wire_base.py | 16 +++------------- 4 files changed, 8 insertions(+), 17 deletions(-) diff --git a/.github/workflows/shared.yml b/.github/workflows/shared.yml index efea5a7463..16cce84651 100644 --- a/.github/workflows/shared.yml +++ b/.github/workflows/shared.yml @@ -33,7 +33,7 @@ jobs: - name: Surface types match vendored schema run: | uv sync --group codegen --frozen - uv run --group codegen python scripts/gen_surface_types.py --check + uv run --frozen --group codegen python scripts/gen_surface_types.py --check # TODO(Max): Drop this in v2. Deliberate updates (e.g. the v2 status # banner) go through the 'override-readme-freeze' label. diff --git a/pyproject.toml b/pyproject.toml index 9ce06a4dc5..8f8b8c37e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -178,7 +178,7 @@ max-complexity = 24 # Default is 10 [tool.ruff.lint.per-file-ignores] "__init__.py" = ["F401"] -# Generated by scripts/gen_surface_types.py: raw datamodel-codegen output. +# Generated by scripts/gen_surface_types.py: raw datamodel-codegen output (TID251 lifts the repo-wide RootModel ban for these generated validators). "src/mcp/types/v*/__init__.py" = ["D212", "E501", "I001", "TID251", "UP007", "UP037"] "tests/server/mcpserver/test_func_metadata.py" = ["E501"] "tests/shared/test_progress_notifications.py" = ["PLW0603"] diff --git a/scripts/gen_surface_types.py b/scripts/gen_surface_types.py index d8da3ad4e6..6eedc9f41b 100644 --- a/scripts/gen_surface_types.py +++ b/scripts/gen_surface_types.py @@ -4,7 +4,7 @@ writes the result to ``src/mcp/types/v/__init__.py`` with only the fixes the raw output needs: a small JSON pre-patch for the known ``number``-as-``integer`` schema.json defect, a header, and per-version -epilogue aliases. Run with ``uv run --group codegen python scripts/gen_surface_types.py [--check]``. +epilogue aliases. Run with ``uv run --frozen --group codegen python scripts/gen_surface_types.py [--check]``. """ from __future__ import annotations @@ -27,6 +27,7 @@ # schema.ts -> schema.json renders TypeScript ``number`` as JSON Schema # ``integer`` at these sites; patch the JSON before codegen so floats validate. +# TODO: drop once modelcontextprotocol/modelcontextprotocol fixes the schema.ts -> schema.json number rendering. SCHEMA_PATCHES: dict[str, list[tuple[str, Any, Any]]] = { "2025-11-25": [ ("$defs/NumberSchema/properties/default/type", "integer", "number"), @@ -93,7 +94,7 @@ def patch_schema(schema: dict[str, Any], patches: list[tuple[str, Any, Any]]) -> def run_codegen(schema_path: Path, output_path: Path) -> None: """Run datamodel-code-generator (from the ``codegen`` dependency group).""" exe = shutil.which("datamodel-codegen") - cmd = [exe] if exe else ["uv", "run", "--group", "codegen", "datamodel-codegen"] + cmd = [exe] if exe else ["uv", "run", "--frozen", "--group", "codegen", "datamodel-codegen"] # fmt: off result = subprocess.run( [ diff --git a/src/mcp/types/_wire_base.py b/src/mcp/types/_wire_base.py index d6a2e55f7a..0481d9ccdf 100644 --- a/src/mcp/types/_wire_base.py +++ b/src/mcp/types/_wire_base.py @@ -1,19 +1,9 @@ -"""Shared pydantic bases for the `mcp.types.v*` wire-shape packages. - -No alias generator is configured: every wire name is an explicit -`Field(alias=...)` so each surface file shows exactly what goes on the wire. -""" +"""Shared pydantic base for the generated ``mcp.types.v*`` wire-shape packages.""" from pydantic import BaseModel, ConfigDict class WireModel(BaseModel): - """Base for surface-package models: unknown fields are accepted and dropped.""" - - model_config = ConfigDict(populate_by_name=True, extra="ignore") - - -class OpenWireModel(BaseModel): - """Base for `_meta` carrier models: unknown fields are retained for round-tripping.""" + """Base for generated wire models: enables ``populate_by_name``; subclasses set ``extra="allow"`` themselves.""" - model_config = ConfigDict(populate_by_name=True, extra="allow") + model_config = ConfigDict(populate_by_name=True) From 73f1572886e1055d85bfe323a95d779dee94fedb Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Mon, 15 Jun 2026 20:10:45 +0000 Subject: [PATCH 5/7] Wire per-version validation into ServerRunner - Add validate_client_request/notification, validate_server_result to types.methods (surface-only siblings of the parse_* functions) - ServerRunner validates inbound requests/notifications against the negotiated version's surface schema; custom methods fall through to the registered params_type as before - Handler results are validated against the surface schema after dump; a spec-invalid result is logged and returns INTERNAL_ERROR to the client - Map JSON-Schema format byte/uri/uri-template to plain str in codegen (Base64Str and AnyUrl over-assert on annotation-only formats); regenerate both surface packages - Fix two test fixtures that built Tool(input_schema={}) without the spec-required type field - Document handler-result validation in migration.md --- docs/migration.md | 4 ++ scripts/gen_surface_types.py | 3 ++ src/mcp/server/runner.py | 64 ++++++++++++---------- src/mcp/types/methods.py | 68 +++++++++++++++++++++--- src/mcp/types/v2025_11_25/__init__.py | 36 ++++++------- src/mcp/types/v2026_07_28/__init__.py | 32 +++++------ tests/client/test_list_methods_cursor.py | 4 +- tests/server/test_cancel_handling.py | 2 +- tests/server/test_runner.py | 44 +++++++++++++++ tests/types/test_methods.py | 58 ++++++++++++++++++++ 10 files changed, 242 insertions(+), 73 deletions(-) diff --git a/docs/migration.md b/docs/migration.md index 36f7b29430..34b62c07fe 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -225,6 +225,10 @@ Because `populate_by_name=True` is set, the old camelCase names still work as co Serialized results now include `resultType` by default (and `ttlMs`/`cacheScope` on cacheable results). Peers ignore unknown result fields, so this interoperates across protocol versions, but tests or recorded fixtures that compare exact serialized payloads need the new keys added. +### Server handler results are validated against the protocol schema + +Results returned from server handlers are now validated against the negotiated protocol version's schema before being sent. A result that does not conform raises on the server side and the client receives an `INTERNAL_ERROR` response. The case most existing code will hit is `Tool.inputSchema`: the spec requires it to contain `"type": "object"`, so an empty `{}` is now rejected. + ### `args` parameter removed from `ClientSessionGroup.call_tool()` The deprecated `args` parameter has been removed from `ClientSessionGroup.call_tool()`. Use `arguments` instead. diff --git a/scripts/gen_surface_types.py b/scripts/gen_surface_types.py index 6eedc9f41b..03cb5579c5 100644 --- a/scripts/gen_surface_types.py +++ b/scripts/gen_surface_types.py @@ -110,6 +110,9 @@ def run_codegen(schema_path: Path, output_path: Path) -> None: "--enum-field-as-literal", "all", "--use-union-operator", "--use-double-quotes", "--extra-fields", "allow", + # JSON Schema `format` is annotation-only; codegen's defaults + # (Base64Str, AnyUrl) over-assert and reject valid wire data. + "--type-mappings", "byte=string", "uri=string", "uri-template=string", "--disable-timestamp", ], capture_output=True, text=True, diff --git a/src/mcp/server/runner.py b/src/mcp/server/runner.py index 9b10373229..83c05841ca 100644 --- a/src/mcp/server/runner.py +++ b/src/mcp/server/runner.py @@ -19,7 +19,7 @@ from collections.abc import Mapping from dataclasses import dataclass, field from functools import partial, reduce -from typing import TYPE_CHECKING, Any, Generic, cast, get_args +from typing import TYPE_CHECKING, Any, Generic, cast import anyio.abc from opentelemetry.trace import SpanKind, StatusCode @@ -38,19 +38,18 @@ from mcp.shared.transport_context import TransportContext from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS from mcp.types import ( + INTERNAL_ERROR, INVALID_PARAMS, LATEST_PROTOCOL_VERSION, METHOD_NOT_FOUND, - ClientRequest, ErrorData, Implementation, InitializeRequestParams, InitializeResult, - NotificationParams, RequestParams, RequestParamsMeta, - client_request_adapter, ) +from mcp.types import methods as _methods if TYPE_CHECKING: from mcp.server.lowlevel.server import Server @@ -80,14 +79,6 @@ def _extract_meta(params: Mapping[str, Any] | None) -> RequestParamsMeta | None: return None -_SPEC_CLIENT_METHODS: frozenset[str] = frozenset( - cast(type[BaseModel], arm).model_fields["method"].default for arm in get_args(ClientRequest) -) -"""Method names in the spec `ClientRequest` union, derived from the -discriminator literal on each arm. Used to gate upfront validation so custom -methods registered via `add_request_handler` are not rejected.""" - - def otel_middleware(next_on_request: OnRequest) -> OnRequest: """Dispatch-tier middleware that wraps each request in an OpenTelemetry span. @@ -228,16 +219,19 @@ async def _on_request( params: Mapping[str, Any] | None, ) -> dict[str, Any]: ctx = self._make_context(dctx, _extract_meta(params)) + # Fallback covers the initialize handshake itself and stateless mode + # until the per-request version header is plumbed through. + version = self.connection.protocol_version or LATEST_PROTOCOL_VERSION async def _inner() -> HandlerResult: - # TODO(maxisbey): pinned compat: spec methods are validated against - # the ClientRequest union before lookup, so malformed params are - # INVALID_PARAMS even with no handler registered. - if method in _SPEC_CLIENT_METHODS: - payload: dict[str, Any] = {"method": method} - if params is not None: - payload["params"] = dict(params) - client_request_adapter.validate_python(payload, by_name=False) + # Pinned compat: spec methods are surface-validated before lookup, + # so malformed params are INVALID_PARAMS even with no handler + # registered. Custom methods miss the surface map and fall through + # to `entry.params_type` exactly as before. + try: + _methods.validate_client_request(method, version, params) + except KeyError: + pass # not a spec method at this version; lookup below handles it # TODO(maxisbey): the 2026-07-28 spec drops the handshake; this branch and # the gate become a per-version legacy path then. Initialize runs inline # (read loop parked), so awaiting the peer anywhere on this path deadlocks. @@ -265,6 +259,15 @@ async def _inner() -> HandlerResult: call = self._compose_server_middleware(ctx, method, params, _inner) result = _dump_result(await call()) + try: + _methods.validate_server_result(method, version, result) + except KeyError: + pass # custom method; no result schema + except ValidationError: + # Server bug, not client fault. Detail stays in the server log: + # pydantic messages echo the result body. + logger.exception("handler for %r returned an invalid result", method) + raise MCPError(code=INTERNAL_ERROR, message="Handler returned an invalid result") from None if method == "initialize": # Commit only on chain success, so a middleware veto leaves no state. # Race-free: the read loop is parked until this call returns. @@ -278,18 +281,21 @@ async def _on_notify( params: Mapping[str, Any] | None, ) -> None: ctx = self._make_context(dctx, _extract_meta(params)) + # Same fallback as `_on_request`: covers pre-handshake and stateless. + version = self.connection.protocol_version or LATEST_PROTOCOL_VERSION async def _inner() -> None: + try: + _methods.validate_client_notification(method, version, params) + except KeyError: + pass # not a spec notification at this version + except ValidationError: + logger.warning("dropped %r: malformed params", method) + return if method == "notifications/initialized": - # Validate before committing so a malformed notification leaves - # state untouched; then fall through so a registered handler - # observes an initialized connection. - if params is not None: - try: - NotificationParams.model_validate(params, by_name=False) - except ValidationError: - logger.warning("dropped %r: malformed params", method) - return + # Surface validation above already rejected a malformed body, so + # commit; fall through so a registered handler observes an + # initialized connection. self.connection.initialized.set() elif not self.connection.initialize_accepted: logger.debug("dropped %s: received before initialization", method) diff --git a/src/mcp/types/methods.py b/src/mcp/types/methods.py index 4ec4a51071..9996b1d770 100644 --- a/src/mcp/types/methods.py +++ b/src/mcp/types/methods.py @@ -34,6 +34,9 @@ "parse_server_notification", "parse_server_request", "parse_server_result", + "validate_client_notification", + "validate_client_request", + "validate_server_result", ] @@ -427,6 +430,24 @@ def _monolith_row(monolith: Mapping[str, _MonolithT], method: str) -> _MonolithT raise RuntimeError(f"inconsistent extension maps: surface defines {method!r} but monolith does not") from None +def validate_client_request( + method: str, + version: str, + params: Mapping[str, Any] | None, + *, + surface: Mapping[tuple[str, str], type[BaseModel]] = CLIENT_REQUESTS, +) -> None: + """Validate a client request against `surface` only. + + Raises: + ValueError: `version` is not a known protocol version. + KeyError: `(method, version)` is not in `surface` (the version gate). + pydantic.ValidationError: body fails surface validation. + """ + _check_known_version(version) + surface[(method, version)].model_validate({**_REQUEST_STUB, **_body(method, params)}, by_name=False) + + def parse_client_request( method: str, version: str, @@ -450,9 +471,7 @@ def parse_client_request( pydantic.ValidationError: body fails surface or monolith validation. RuntimeError: surface matched but `method` has no monolith row. """ - _check_known_version(version) - surface_type = surface[(method, version)] - surface_type.model_validate({**_REQUEST_STUB, **_body(method, params)}, by_name=False) + validate_client_request(method, version, params, surface=surface) return _monolith_row(monolith, method).model_validate(_body(method, params), by_name=False) @@ -485,6 +504,24 @@ def parse_server_request( return _monolith_row(monolith, method).model_validate(_body(method, params), by_name=False) +def validate_client_notification( + method: str, + version: str, + params: Mapping[str, Any] | None, + *, + surface: Mapping[tuple[str, str], type[BaseModel]] = CLIENT_NOTIFICATIONS, +) -> None: + """Validate a client notification against `surface` only. + + Raises: + ValueError: `version` is not a known protocol version. + KeyError: `(method, version)` is not in `surface`. + pydantic.ValidationError: body fails surface validation. + """ + _check_known_version(version) + surface[(method, version)].model_validate({**_NOTIFICATION_STUB, **_body(method, params)}, by_name=False) + + def parse_client_notification( method: str, version: str, @@ -508,9 +545,7 @@ def parse_client_notification( pydantic.ValidationError: body fails surface or monolith validation. RuntimeError: surface matched but `method` has no monolith row. """ - _check_known_version(version) - surface_type = surface[(method, version)] - surface_type.model_validate({**_NOTIFICATION_STUB, **_body(method, params)}, by_name=False) + validate_client_notification(method, version, params, surface=surface) return _monolith_row(monolith, method).model_validate(_body(method, params), by_name=False) @@ -543,6 +578,24 @@ def parse_server_notification( return _monolith_row(monolith, method).model_validate(_body(method, params), by_name=False) +def validate_server_result( + method: str, + version: str, + data: Mapping[str, Any], + *, + surface: Mapping[tuple[str, str], type[BaseModel] | UnionType] = SERVER_RESULTS, +) -> None: + """Validate a server result against `surface` only. + + Raises: + ValueError: `version` is not a known protocol version. + KeyError: `(method, version)` is not in `surface`. + pydantic.ValidationError: result fails surface validation. + """ + _check_known_version(version) + _adapter(surface[(method, version)]).validate_python(data, by_name=False) + + def parse_server_result( method: str, version: str, @@ -566,8 +619,7 @@ def parse_server_result( pydantic.ValidationError: result fails surface or monolith validation. RuntimeError: surface matched but `method` has no monolith row. """ - _check_known_version(version) - _adapter(surface[(method, version)]).validate_python(data, by_name=False) + validate_server_result(method, version, data, surface=surface) result: types.Result = _adapter(_monolith_row(monolith, method)).validate_python(data, by_name=False) return result diff --git a/src/mcp/types/v2025_11_25/__init__.py b/src/mcp/types/v2025_11_25/__init__.py index ffc8853811..0fcb000760 100644 --- a/src/mcp/types/v2025_11_25/__init__.py +++ b/src/mcp/types/v2025_11_25/__init__.py @@ -9,7 +9,7 @@ from typing import Annotated, Any, Literal from mcp.types._wire_base import WireModel -from pydantic import AnyUrl, Base64Str, ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field, RootModel class BaseMetadata(WireModel): @@ -43,7 +43,7 @@ class BlobResourceContents(WireModel): """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ - blob: Base64Str + blob: str """ A base64-encoded string representing the binary data of the item. """ @@ -51,7 +51,7 @@ class BlobResourceContents(WireModel): """ The MIME type of this resource, if known. """ - uri: AnyUrl + uri: str """ The URI of this resource. """ @@ -416,7 +416,7 @@ class Icon(WireModel): If not provided, the client should assume that the icon can be used at any size. """ - src: AnyUrl + src: str """ A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a `data:` URI with Base64-encoded image data. @@ -501,7 +501,7 @@ class Implementation(WireModel): if present). """ version: str - website_url: Annotated[AnyUrl | None, Field(alias="websiteUrl")] = None + website_url: Annotated[str | None, Field(alias="websiteUrl")] = None """ An optional URL of the website for this implementation. """ @@ -809,7 +809,7 @@ class ReadResourceRequestParams(WireModel): """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ - uri: AnyUrl + uri: str """ The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. """ @@ -875,7 +875,7 @@ class ResourceContents(WireModel): """ The MIME type of this resource, if known. """ - uri: AnyUrl + uri: str """ The URI of this resource. """ @@ -906,7 +906,7 @@ class ResourceRequestParams(WireModel): """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ - uri: AnyUrl + uri: str """ The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. """ @@ -939,7 +939,7 @@ class ResourceUpdatedNotificationParams(WireModel): """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ - uri: AnyUrl + uri: str """ The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. """ @@ -980,7 +980,7 @@ class Root(WireModel): identifier for the root, which may be useful for display purposes or for referencing the root in other parts of the application. """ - uri: AnyUrl + uri: str """ The URI identifying the root. This *must* start with file:// for now. This restriction may be relaxed in future versions of the protocol to allow @@ -1180,7 +1180,7 @@ class SubscribeRequestParams(WireModel): """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ - uri: AnyUrl + uri: str """ The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. """ @@ -1224,7 +1224,7 @@ class TextResourceContents(WireModel): """ The text of the item. This must only be set if the item can actually be represented as text (not binary data). """ - uri: AnyUrl + uri: str """ The URI of this resource. """ @@ -1516,7 +1516,7 @@ class UnsubscribeRequestParams(WireModel): """ See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. """ - uri: AnyUrl + uri: str """ The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. """ @@ -1648,7 +1648,7 @@ class AudioContent(WireModel): """ Optional annotations for the client. """ - data: Base64Str + data: str """ The base64-encoded audio data. """ @@ -1787,7 +1787,7 @@ class ElicitRequestURLParams(WireModel): Task augmentation is subject to capability negotiation - receivers MUST declare support for task augmentation of specific request types in their capabilities. """ - url: AnyUrl + url: str """ The URL that the user should navigate to. """ @@ -1904,7 +1904,7 @@ class ImageContent(WireModel): """ Optional annotations for the client. """ - data: Base64Str + data: str """ The base64-encoded image data. """ @@ -2292,7 +2292,7 @@ class Resource(WireModel): where `annotations.title` should be given precedence over using `name`, if present). """ - uri: AnyUrl + uri: str """ The URI of this resource. """ @@ -2358,7 +2358,7 @@ class ResourceLink(WireModel): if present). """ type: Literal["resource_link"] - uri: AnyUrl + uri: str """ The URI of this resource. """ diff --git a/src/mcp/types/v2026_07_28/__init__.py b/src/mcp/types/v2026_07_28/__init__.py index a31dcf1cc4..1d37fa8a46 100644 --- a/src/mcp/types/v2026_07_28/__init__.py +++ b/src/mcp/types/v2026_07_28/__init__.py @@ -9,7 +9,7 @@ from typing import Annotated, Any, Literal, Union from mcp.types._wire_base import WireModel -from pydantic import AnyUrl, Base64Str, ConfigDict, Field, RootModel +from pydantic import ConfigDict, Field, RootModel class BaseMetadata(WireModel): @@ -123,7 +123,7 @@ class ElicitRequestURLParams(WireModel): """ The elicitation mode. """ - url: AnyUrl + url: str """ The URL that the user should navigate to. """ @@ -213,7 +213,7 @@ class Icon(WireModel): If not provided, the client should assume that the icon can be used at any size. """ - src: AnyUrl + src: str """ A standard URI pointing to an icon resource. May be an HTTP/HTTPS URL or a `data:` URI with Base64-encoded image data. @@ -301,7 +301,7 @@ class Implementation(WireModel): """ The version of this implementation. """ - website_url: Annotated[AnyUrl | None, Field(alias="websiteUrl")] = None + website_url: Annotated[str | None, Field(alias="websiteUrl")] = None """ An optional URL of the website for this implementation. """ @@ -740,7 +740,7 @@ class ResourceContents(WireModel): """ The MIME type of this resource, if known. """ - uri: AnyUrl + uri: str """ The URI of this resource. """ @@ -783,7 +783,7 @@ class ResourceUpdatedNotificationParams(WireModel): extra="allow", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None - uri: AnyUrl + uri: str """ The URI of the resource that has been updated. This might be a sub-resource of the one that the client actually subscribed to. """ @@ -843,7 +843,7 @@ class Root(WireModel): identifier for the root, which may be useful for display purposes or for referencing the root in other parts of the application. """ - uri: AnyUrl + uri: str """ The URI identifying the root. This *must* start with `file://` for now. This restriction may be relaxed in future versions of the protocol to allow @@ -972,7 +972,7 @@ class TextResourceContents(WireModel): """ The text of the item. This must only be set if the item can actually be represented as text (not binary data). """ - uri: AnyUrl + uri: str """ The URI of this resource. """ @@ -1402,7 +1402,7 @@ class AudioContent(WireModel): """ Optional annotations for the client. """ - data: Base64Str + data: str """ The base64-encoded audio data. """ @@ -1418,7 +1418,7 @@ class BlobResourceContents(WireModel): extra="allow", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None - blob: Base64Str + blob: str """ A base64-encoded string representing the binary data of the item. """ @@ -1426,7 +1426,7 @@ class BlobResourceContents(WireModel): """ The MIME type of this resource, if known. """ - uri: AnyUrl + uri: str """ The URI of this resource. """ @@ -1596,7 +1596,7 @@ class ImageContent(WireModel): """ Optional annotations for the client. """ - data: Base64Str + data: str """ The base64-encoded image data. """ @@ -1884,7 +1884,7 @@ class Resource(WireModel): where `annotations.title` should be given precedence over using `name`, if present). """ - uri: AnyUrl + uri: str """ The URI of this resource. """ @@ -1947,7 +1947,7 @@ class ResourceLink(WireModel): if present). """ type: Literal["resource_link"] - uri: AnyUrl + uri: str """ The URI of this resource. """ @@ -3289,7 +3289,7 @@ class ReadResourceRequestParams(WireModel): meta: Annotated[RequestMetaObject, Field(alias="_meta")] input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None request_state: Annotated[str | None, Field(alias="requestState")] = None - uri: AnyUrl + uri: str """ The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. """ @@ -3378,7 +3378,7 @@ class ResourceRequestParams(WireModel): extra="allow", ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] - uri: AnyUrl + uri: str """ The URI of the resource. The URI can use any protocol; it is up to the server how to interpret it. """ diff --git a/tests/client/test_list_methods_cursor.py b/tests/client/test_list_methods_cursor.py index f70fb9277d..e7e63304fc 100644 --- a/tests/client/test_list_methods_cursor.py +++ b/tests/client/test_list_methods_cursor.py @@ -111,7 +111,9 @@ async def handle_list_tools( ) -> ListToolsResult: # Echo back what cursor we received in the tool description cursor = params.cursor if params else None - return ListToolsResult(tools=[types.Tool(name="test_tool", description=f"cursor={cursor}", input_schema={})]) + return ListToolsResult( + tools=[types.Tool(name="test_tool", description=f"cursor={cursor}", input_schema={"type": "object"})] + ) server = Server("test-lowlevel", on_list_tools=handle_list_tools) diff --git a/tests/server/test_cancel_handling.py b/tests/server/test_cancel_handling.py index cff5a37c15..10531fad93 100644 --- a/tests/server/test_cancel_handling.py +++ b/tests/server/test_cancel_handling.py @@ -41,7 +41,7 @@ async def handle_list_tools(ctx: ServerRequestContext, params: PaginatedRequestP Tool( name="test_tool", description="Tool for testing", - input_schema={}, + input_schema={"type": "object"}, ) ] ) diff --git a/tests/server/test_runner.py b/tests/server/test_runner.py index 74724136f6..bc2ba39de0 100644 --- a/tests/server/test_runner.py +++ b/tests/server/test_runner.py @@ -769,6 +769,50 @@ async def greet(ctx: Ctx, params: GreetParams) -> dict[str, Any]: assert received[0].name == "world" +@pytest.mark.anyio +async def test_runner_spec_method_with_invalid_params_is_invalid_params_at_the_negotiated_version(server: SrvT): + async with connected_runner(server) as (client, runner): + assert runner.connection.protocol_version == LATEST_PROTOCOL_VERSION + with pytest.raises(MCPError) as exc: + await client.send_raw_request("tools/call", {"name": 42}) + assert exc.value.error.code == INVALID_PARAMS + + +@pytest.mark.anyio +async def test_runner_handler_returning_malformed_dict_for_spec_method_is_internal_error(server: SrvT): + async def bad_result(ctx: Ctx, params: PaginatedRequestParams | None) -> dict[str, Any]: + return {"tools": 42} + + server.add_request_handler("tools/list", PaginatedRequestParams, bad_result) + async with connected_runner(server) as (client, _): + with pytest.raises(MCPError) as exc: + await client.send_raw_request("tools/list", None) + assert exc.value.error.code == INTERNAL_ERROR + assert exc.value.error.message == "Handler returned an invalid result" + # Result body must not reach the client; detail belongs in the server log. + assert exc.value.error.data is None + + +@pytest.mark.anyio +async def test_runner_handler_returning_typed_monolith_result_passes_outbound_validation(server: SrvT): + async with connected_runner(server) as (client, _): + result = await client.send_raw_request("tools/list", None) + assert result["tools"][0]["name"] == "t" + + +@pytest.mark.anyio +async def test_runner_custom_method_result_is_not_surface_validated(server: SrvT): + """No `SERVER_RESULTS` row for a custom method, so its result reaches the client as-is.""" + + async def custom(ctx: Ctx, params: RequestParams) -> dict[str, Any]: + return {"anything": "goes"} + + server.add_request_handler("custom/greet", RequestParams, custom) + async with connected_runner(server) as (client, _): + result = await client.send_raw_request("custom/greet", None) + assert result == {"anything": "goes"} + + @pytest.mark.anyio async def test_runner_initialize_result_reflects_init_options(): async def list_tools(ctx: Ctx, params: PaginatedRequestParams | None) -> ListToolsResult: diff --git a/tests/types/test_methods.py b/tests/types/test_methods.py index 980ca5c7ce..3033a2cf59 100644 --- a/tests/types/test_methods.py +++ b/tests/types/test_methods.py @@ -728,6 +728,64 @@ def test_sampling_union_keeps_the_complete_arm_first_because_order_is_load_beari assert type(parsed) is types.CreateMessageResultWithTools +def test_validate_functions_accept_reject_and_gate_like_their_parse_siblings(): + methods.validate_client_request("tools/call", "2025-11-25", {"name": "echo"}) + methods.validate_client_notification("notifications/cancelled", "2025-11-25", {"requestId": 1}) + methods.validate_server_result("tools/list", "2025-11-25", {"tools": []}) + with pytest.raises(KeyError): + methods.validate_client_request("custom/greet", "2025-11-25", None) + with pytest.raises(KeyError): + methods.validate_client_notification("custom/ping", "2025-11-25", None) + with pytest.raises(KeyError): + methods.validate_server_result("custom/greet", "2025-11-25", {}) + with pytest.raises(pydantic.ValidationError): + methods.validate_client_request("tools/call", "2025-11-25", None) + with pytest.raises(pydantic.ValidationError): + methods.validate_client_notification("notifications/progress", "2025-11-25", {"progressToken": []}) + with pytest.raises(pydantic.ValidationError): + methods.validate_server_result("tools/list", "2025-11-25", {"tools": 42}) + with pytest.raises(ValueError): + methods.validate_client_request("ping", "2099-01-01", None) + + +# One minimal monolith result instance per request method, dumped via the same +# `_dump_result` path the runner uses. Values are chosen so the dump satisfies +# every `SERVER_RESULTS` row that method appears under. +MONOLITH_RESULT_FIXTURES: dict[str, types.Result] = { + "completion/complete": types.CompleteResult(completion=types.Completion(values=[])), + "initialize": types.InitializeResult( + protocol_version="2025-11-25", + capabilities=types.ServerCapabilities(), + server_info=types.Implementation(name="server", version="1.0"), + ), + "logging/setLevel": types.EmptyResult(), + "ping": types.EmptyResult(), + "prompts/get": types.GetPromptResult(messages=[]), + "prompts/list": types.ListPromptsResult(prompts=[]), + "resources/list": types.ListResourcesResult(resources=[]), + "resources/read": types.ReadResourceResult(contents=[]), + "resources/subscribe": types.EmptyResult(), + "resources/templates/list": types.ListResourceTemplatesResult(resource_templates=[]), + "resources/unsubscribe": types.EmptyResult(), + "server/discover": types.DiscoverResult( + supported_versions=["2026-07-28"], + capabilities=types.ServerCapabilities(), + server_info=types.Implementation(name="server", version="1.0"), + ), + "subscriptions/listen": types.EmptyResult(result_type="complete"), + "tools/call": types.CallToolResult(content=[]), + "tools/list": types.ListToolsResult(tools=[]), +} + + +@pytest.mark.parametrize(("method", "version"), sorted(methods.SERVER_RESULTS)) +def test_dumped_monolith_results_round_trip_through_validate_server_result(method: str, version: str): + """The outbound surface check must accept every correctly-typed handler return.""" + instance = MONOLITH_RESULT_FIXTURES[method] + dumped = instance.model_dump(by_alias=True, mode="json", exclude_none=True) + methods.validate_server_result(method, version, dumped) + + def test_importing_the_module_builds_no_adapters_and_identical_rows_share_one(): # Execute a fresh copy so the cache assertion is order-independent. spec = importlib.util.find_spec("mcp.types.methods") From b952bdf7d9e2144f23c7fb464a3ab90ea35a66c7 Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Mon, 15 Jun 2026 22:07:13 +0000 Subject: [PATCH 6/7] Serialize spec results through the per-version surface schema Generated surface types switch to extra="ignore" (with a per-version allow-list for spec-declared open types: Result, _meta, GetTaskPayloadResult, Tool input/output schemas, URLElicitationRequiredError data). The runner now serializes spec-method results by dumping the monolith model and re-dumping through the negotiated version's surface row, so 2026-only fields (resultType, ttlMs, cacheScope) never reach the wire on a pre-2026 session. - Monolith ttl_ms/cache_scope default to None; the SDK does not stamp a caching policy. EmptyResult.result_type stays None (deployed peer servers strict-validate ping responses). - Spec methods absent at the negotiated version reject with METHOD_NOT_FOUND even if a handler is registered; custom methods fall through unchanged. - Version fallback for pre-handshake/stateless is the literal 2025-11-25, not LATEST_PROTOCOL_VERSION. - Add serialize_server_result to types.methods; codegen drift-guard asserts the open-class substitution count. - Docstrings now say method-gating per version, shape per schema era. --- docs/migration.md | 6 +- scripts/gen_surface_types.py | 36 ++- src/mcp/server/runner.py | 61 ++-- src/mcp/types/_types.py | 16 +- src/mcp/types/_wire_base.py | 2 +- src/mcp/types/methods.py | 76 +++-- src/mcp/types/v2025_11_25/__init__.py | 294 +++++++++--------- src/mcp/types/v2026_07_28/__init__.py | 274 ++++++++-------- .../transports/test_hosting_resume.py | 1 - tests/server/test_runner.py | 68 ++++ tests/server/test_session.py | 2 +- tests/test_types.py | 14 +- tests/types/test_methods.py | 83 ++++- tests/types/test_wire_frames.py | 8 +- 14 files changed, 573 insertions(+), 368 deletions(-) diff --git a/docs/migration.md b/docs/migration.md index 34b62c07fe..f7a5cc98f0 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -221,10 +221,6 @@ Common renames: Because `populate_by_name=True` is set, the old camelCase names still work as constructor kwargs (e.g., `Tool(inputSchema={...})` is accepted), but attribute access must use snake_case (`tool.input_schema`). -### Results now serialize `resultType` and cache-directive defaults - -Serialized results now include `resultType` by default (and `ttlMs`/`cacheScope` on cacheable results). Peers ignore unknown result fields, so this interoperates across protocol versions, but tests or recorded fixtures that compare exact serialized payloads need the new keys added. - ### Server handler results are validated against the protocol schema Results returned from server handlers are now validated against the negotiated protocol version's schema before being sent. A result that does not conform raises on the server side and the client receives an `INTERNAL_ERROR` response. The case most existing code will hit is `Tool.inputSchema`: the spec requires it to contain `"type": "object"`, so an empty `{}` is now rejected. @@ -1237,7 +1233,7 @@ If you relied on extra fields round-tripping through MCP types, move that data i ### 2025-11-25 and 2026-07-28 protocol fields modeled -`mcp.types` models the 2025-11-25 and 2026-07-28 protocol fields (e.g. `resultType`, `ttlMs`/`cacheScope` on cacheable results, `inputResponses`/`requestState` on retried requests), so inbound payloads carrying these keys parse into typed fields and round-trip. Most are optional with `None` defaults; the result-directive fields carry serialized defaults - see [Results now serialize `resultType` and cache-directive defaults](#results-now-serialize-resulttype-and-cache-directive-defaults). +`mcp.types` models the 2025-11-25 and 2026-07-28 protocol fields (e.g. `resultType`, `ttlMs`/`cacheScope` on cacheable results, `inputResponses`/`requestState` on retried requests), so inbound payloads carrying these keys parse into typed fields and round-trip. `ttlMs`/`cacheScope` default to `None`; `resultType` defaults to `"complete"` on concrete results (`None` on `EmptyResult`); the server strips all of them from the wire at pre-2026 versions. ### `streamable_http_app()` available on lowlevel Server diff --git a/scripts/gen_surface_types.py b/scripts/gen_surface_types.py index 03cb5579c5..8b21c2931b 100644 --- a/scripts/gen_surface_types.py +++ b/scripts/gen_surface_types.py @@ -49,6 +49,16 @@ ], } +# Classes the spec defines as open key-value bags: `_meta` content, the +# JSON-Schema-document fields on `Tool`, and the schemas with explicit +# `additionalProperties: {}`. These keep `extra="allow"` so the sieve preserves +# arbitrary keys; every other class ignores extras. Per-version because codegen +# reuses class names across versions for unrelated schemas (e.g. `Data`). +OPEN_CLASSES: dict[str, frozenset[str]] = { + "2025-11-25": frozenset({"Meta", "InputSchema", "OutputSchema", "Result", "GetTaskPayloadResult", "Data"}), + "2026-07-28": frozenset({"MetaObject", "RequestMetaObject", "InputSchema", "OutputSchema", "Result"}), +} + # Hand-written union aliases the wire-method maps reference by value; the schema # has no named definition for "everything tools/call may return", so name it here. EPILOGUES: dict[str, str] = { @@ -109,7 +119,7 @@ def run_codegen(schema_path: Path, output_path: Path) -> None: "--use-annotated", "--use-field-description", "--use-schema-description", "--enum-field-as-literal", "all", "--use-union-operator", "--use-double-quotes", - "--extra-fields", "allow", + "--extra-fields", "ignore", # JSON Schema `format` is annotation-only; codegen's defaults # (Base64Str, AnyUrl) over-assert and reject valid wire data. "--type-mappings", "byte=string", "uri=string", "uri-template=string", @@ -122,6 +132,29 @@ def run_codegen(schema_path: Path, output_path: Path) -> None: raise SystemExit(f"datamodel-codegen failed:\n{result.stderr}") +def allow_open_class_extras(source: str, open_classes: frozenset[str]) -> str: + """Restore ``extra="allow"`` on ``open_classes`` only. + + Every other class uses ``extra="ignore"`` so the surface acts as a sieve; + ``open_classes`` are the places the spec defines as open key-value bags. + """ + + def patch(match: re.Match[str]) -> str: + if match.group(1) not in open_classes: + return match.group(0) + return match.group(0).replace('extra="ignore"', 'extra="allow"') + + source = re.sub( + r'^class (\w+)\(WireModel\):\n(?: {4}.*\n|\n)*? {4}model_config = ConfigDict\(\n {8}extra="ignore",\n {4}\)\n', + patch, + source, + flags=re.MULTILINE, + ) + # Drift guard: substitution count must match the allow-list. + assert source.count('extra="allow"') == len(open_classes), (source.count('extra="allow"'), open_classes) + return source + + def build(entry: dict[str, str]) -> str: """Generate, post-process, and format one version's surface module text.""" version = entry["protocol_version"] @@ -137,6 +170,7 @@ def build(entry: dict[str, str]) -> str: source = re.sub(r"\A# generated by datamodel-codegen:\n#[^\n]*\n", "", source) source = re.sub(r"^class Model\(RootModel\[Any\]\):\n {4}root: Any\n+", "", source, count=1, flags=re.MULTILINE) + source = allow_open_class_extras(source, OPEN_CLASSES[version]) if epilogue := EPILOGUES.get(version, ""): # Insert before the trailing model_rebuild() block: pyright's evaluation # order for the recursive RootModel block is sensitive to placement. diff --git a/src/mcp/server/runner.py b/src/mcp/server/runner.py index 83c05841ca..c5a7323222 100644 --- a/src/mcp/server/runner.py +++ b/src/mcp/server/runner.py @@ -219,19 +219,22 @@ async def _on_request( params: Mapping[str, Any] | None, ) -> dict[str, Any]: ctx = self._make_context(dctx, _extract_meta(params)) - # Fallback covers the initialize handshake itself and stateless mode - # until the per-request version header is plumbed through. - version = self.connection.protocol_version or LATEST_PROTOCOL_VERSION + # Literal, not LATEST_PROTOCOL_VERSION: the fallback covers the initialize + # handshake (which only exists at <=2025) and stateless until the header + # is plumbed; its meaning is fixed regardless of LATEST bumps. + version = self.connection.protocol_version or "2025-11-25" + is_spec_method = method in _methods.MONOLITH_REQUESTS async def _inner() -> HandlerResult: # Pinned compat: spec methods are surface-validated before lookup, # so malformed params are INVALID_PARAMS even with no handler - # registered. Custom methods miss the surface map and fall through + # registered. Custom methods miss the monolith map and fall through # to `entry.params_type` exactly as before. - try: - _methods.validate_client_request(method, version, params) - except KeyError: - pass # not a spec method at this version; lookup below handles it + if is_spec_method: + try: + _methods.validate_client_request(method, version, params) + except KeyError: + raise MCPError(code=METHOD_NOT_FOUND, message="Method not found", data=method) from None # TODO(maxisbey): the 2026-07-28 spec drops the handshake; this branch and # the gate become a per-version legacy path then. Initialize runs inline # (read loop parked), so awaiting the peer anywhere on this path deadlocks. @@ -259,15 +262,21 @@ async def _inner() -> HandlerResult: call = self._compose_server_middleware(ctx, method, params, _inner) result = _dump_result(await call()) - try: - _methods.validate_server_result(method, version, result) - except KeyError: - pass # custom method; no result schema - except ValidationError: - # Server bug, not client fault. Detail stays in the server log: - # pydantic messages echo the result body. - logger.exception("handler for %r returned an invalid result", method) - raise MCPError(code=INTERNAL_ERROR, message="Handler returned an invalid result") from None + # TODO: reject resultType values outside {"complete", "input_required"} unless the + # corresponding extension is in this request's _meta clientCapabilities.extensions; the + # explicit MUST-reject is client-side (basic/index.mdx ResultType), this enforces it proactively. + if is_spec_method: + try: + result = _methods.serialize_server_result(method, version, result) + except KeyError: + # Middleware short-circuited a wrong-version spec method without + # calling `call_next`; it owns the result shape. + pass + except ValidationError: + # Server bug, not client fault. Detail stays in the server log: + # pydantic messages echo the result body. + logger.exception("handler for %r returned an invalid result", method) + raise MCPError(code=INTERNAL_ERROR, message="Handler returned an invalid result") from None if method == "initialize": # Commit only on chain success, so a middleware veto leaves no state. # Race-free: the read loop is parked until this call returns. @@ -282,16 +291,18 @@ async def _on_notify( ) -> None: ctx = self._make_context(dctx, _extract_meta(params)) # Same fallback as `_on_request`: covers pre-handshake and stateless. - version = self.connection.protocol_version or LATEST_PROTOCOL_VERSION + version = self.connection.protocol_version or "2025-11-25" async def _inner() -> None: - try: - _methods.validate_client_notification(method, version, params) - except KeyError: - pass # not a spec notification at this version - except ValidationError: - logger.warning("dropped %r: malformed params", method) - return + if method in _methods.MONOLITH_NOTIFICATIONS: + try: + _methods.validate_client_notification(method, version, params) + except KeyError: + logger.debug("dropped %r: not defined at %s", method, version) + return + except ValidationError: + logger.warning("dropped %r: malformed params", method) + return if method == "notifications/initialized": # Surface validation above already rejected a malformed body, so # commit; fall through so a registered handler observes an diff --git a/src/mcp/types/_types.py b/src/mcp/types/_types.py index 598448f4b2..3542006ac3 100644 --- a/src/mcp/types/_types.py +++ b/src/mcp/types/_types.py @@ -185,16 +185,15 @@ class PaginatedResult(Result): class CacheableResult(Result): """Base class for results that carry client-side caching directives (2026-07-28). - Both fields are required on the 2026-07-28 wire and always serialized by - this SDK; older peers ignore the extra keys. The defaults are SDK choices - (the schema declares none). + Both fields are required on the 2026-07-28 wire; the SDK declares no + default, so a handler answering at 2026-07-28 must set them explicitly. """ - ttl_ms: Annotated[int, Field(ge=0)] = 0 + ttl_ms: Annotated[int, Field(ge=0)] | None = None """How long (ms) the client MAY cache this response, analogous to HTTP `Cache-Control: max-age`. 0 means immediately stale.""" - cache_scope: Literal["public", "private"] = "private" + cache_scope: Literal["public", "private"] | None = None """Analogous to HTTP `Cache-Control: public` vs `private`: "public" allows shared caches to serve the response to any user; "private" forbids that.""" @@ -203,9 +202,10 @@ class EmptyResult(Result): """A result that indicates success but carries no data. `result_type` defaults to None so this dumps as `{}`: deployed TypeScript - and Rust SDK clients validate empty results strictly and reject extra keys. - The 2026-07-28 schema requires `resultType`, so code answering an empty - result on a 2026-07-28+ session must pass `result_type="complete"`. + and Rust SDK peers (clients and servers) validate empty results strictly + and reject extra keys. The 2026-07-28 schema requires `resultType`, so code + answering an empty result on a 2026-07-28+ session must pass + `result_type="complete"`. """ result_type: ResultType | None = None diff --git a/src/mcp/types/_wire_base.py b/src/mcp/types/_wire_base.py index 0481d9ccdf..f10a9da2d9 100644 --- a/src/mcp/types/_wire_base.py +++ b/src/mcp/types/_wire_base.py @@ -4,6 +4,6 @@ class WireModel(BaseModel): - """Base for generated wire models: enables ``populate_by_name``; subclasses set ``extra="allow"`` themselves.""" + """Base for generated wire models: enables ``populate_by_name``; subclasses set ``extra`` themselves.""" model_config = ConfigDict(populate_by_name=True) diff --git a/src/mcp/types/methods.py b/src/mcp/types/methods.py index 9996b1d770..0cd4628451 100644 --- a/src/mcp/types/methods.py +++ b/src/mcp/types/methods.py @@ -1,8 +1,12 @@ -"""Per-version method maps and parse functions for inbound MCP traffic. +"""Per-version method maps and parse/serialize functions for MCP traffic. -Surface maps key `(method, version)` to schema-exact types (key absence is the -version gate); monolith maps key `method` to the version-free `mcp.types` models -user code receives. Session-layer wiring is a follow-up.""" +This module is supported public API; the `mcp.types.v*` packages it draws on +are internal validators and not for direct import. + +Surface maps key `(method, version)` to per-version wire types (key absence is +the version gate; shape validation is per schema era, i.e. 2025-11-25 for every +pre-2026 version and 2026-07-28 for 2026). Monolith maps key `method` to the +version-free `mcp.types` models user code receives.""" from __future__ import annotations @@ -34,6 +38,7 @@ "parse_server_notification", "parse_server_request", "parse_server_result", + "serialize_server_result", "validate_client_notification", "validate_client_request", "validate_server_result", @@ -459,9 +464,9 @@ def parse_client_request( """Validate a client request against `surface`, then parse and return its `monolith` model. Args: - surface: `(method, version)` to schema-exact type map; the version-gate - lookup and shape check run against this. Pass an extended map to - admit custom methods. + surface: `(method, version)` to wire-type map; the version-gate lookup + and (per-schema-era) shape check run against this. Pass an extended + map to admit custom methods. monolith: `method` to version-free model map; the returned instance is parsed from this row. Must cover every method `surface` admits. @@ -486,9 +491,9 @@ def parse_server_request( """Validate a server request against `surface`, then parse and return its `monolith` model. Args: - surface: `(method, version)` to schema-exact type map; the version-gate - lookup and shape check run against this. Pass an extended map to - admit custom methods. + surface: `(method, version)` to wire-type map; the version-gate lookup + and (per-schema-era) shape check run against this. Pass an extended + map to admit custom methods. monolith: `method` to version-free model map; the returned instance is parsed from this row. Must cover every method `surface` admits. @@ -533,9 +538,9 @@ def parse_client_notification( """Validate a client notification against `surface`, then parse and return its `monolith` model. Args: - surface: `(method, version)` to schema-exact type map; the version-gate - lookup and shape check run against this. Pass an extended map to - admit custom methods. + surface: `(method, version)` to wire-type map; the version-gate lookup + and (per-schema-era) shape check run against this. Pass an extended + map to admit custom methods. monolith: `method` to version-free model map; the returned instance is parsed from this row. Must cover every method `surface` admits. @@ -560,9 +565,9 @@ def parse_server_notification( """Validate a server notification against `surface`, then parse and return its `monolith` model. Args: - surface: `(method, version)` to schema-exact type map; the version-gate - lookup and shape check run against this. Pass an extended map to - admit custom methods. + surface: `(method, version)` to wire-type map; the version-gate lookup + and (per-schema-era) shape check run against this. Pass an extended + map to admit custom methods. monolith: `method` to version-free model map; the returned instance is parsed from this row. Must cover every method `surface` admits. @@ -578,6 +583,30 @@ def parse_server_notification( return _monolith_row(monolith, method).model_validate(_body(method, params), by_name=False) +def serialize_server_result( + method: str, + version: str, + data: Mapping[str, Any], + *, + surface: Mapping[tuple[str, str], type[BaseModel] | UnionType] = SERVER_RESULTS, +) -> dict[str, Any]: + """Validate `data` against `surface` and return its surface-shaped dump. + + The surface model carries `extra="ignore"`, so fields not in `version`'s + schema are dropped from the returned dict. + + Raises: + ValueError: `version` is not a known protocol version. + KeyError: `(method, version)` is not in `surface`. + pydantic.ValidationError: result fails surface validation. + """ + _check_known_version(version) + adapter = _adapter(surface[(method, version)]) + return adapter.dump_python( + adapter.validate_python(data, by_name=False), by_alias=True, mode="json", exclude_none=True + ) + + def validate_server_result( method: str, version: str, @@ -592,8 +621,7 @@ def validate_server_result( KeyError: `(method, version)` is not in `surface`. pydantic.ValidationError: result fails surface validation. """ - _check_known_version(version) - _adapter(surface[(method, version)]).validate_python(data, by_name=False) + serialize_server_result(method, version, data, surface=surface) def parse_server_result( @@ -607,9 +635,9 @@ def parse_server_result( """Validate a server result against `surface`, then parse and return its `monolith` model. Args: - surface: `(method, version)` to schema-exact type map; the version-gate - lookup and shape check run against this. Pass an extended map to - admit custom methods. + surface: `(method, version)` to wire-type map; the version-gate lookup + and (per-schema-era) shape check run against this. Pass an extended + map to admit custom methods. monolith: `method` to version-free model map; the returned instance is parsed from this row. Must cover every method `surface` admits. @@ -635,9 +663,9 @@ def parse_client_result( """Validate a client result against `surface`, then parse and return its `monolith` model. Args: - surface: `(method, version)` to schema-exact type map; the version-gate - lookup and shape check run against this. Pass an extended map to - admit custom methods. + surface: `(method, version)` to wire-type map; the version-gate lookup + and (per-schema-era) shape check run against this. Pass an extended + map to admit custom methods. monolith: `method` to version-free model map; the returned instance is parsed from this row. Must cover every method `surface` admits. diff --git a/src/mcp/types/v2025_11_25/__init__.py b/src/mcp/types/v2025_11_25/__init__.py index 0fcb000760..5bac2e1a29 100644 --- a/src/mcp/types/v2025_11_25/__init__.py +++ b/src/mcp/types/v2025_11_25/__init__.py @@ -18,7 +18,7 @@ class BaseMetadata(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) name: str """ @@ -37,7 +37,7 @@ class BaseMetadata(WireModel): class BlobResourceContents(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -59,7 +59,7 @@ class BlobResourceContents(WireModel): class BooleanSchema(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: bool | None = None description: str | None = None @@ -69,7 +69,7 @@ class BooleanSchema(WireModel): class Params(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) task_id: Annotated[str, Field(alias="taskId")] """ @@ -83,7 +83,7 @@ class Elicitation(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) form: dict[str, Any] | None = None url: dict[str, Any] | None = None @@ -95,7 +95,7 @@ class Roots(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ @@ -109,7 +109,7 @@ class Sampling(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) context: dict[str, Any] | None = None """ @@ -128,7 +128,7 @@ class Elicitation1(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) create: dict[str, Any] | None = None """ @@ -142,7 +142,7 @@ class Sampling1(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) create_message: Annotated[dict[str, Any] | None, Field(alias="createMessage")] = None """ @@ -156,7 +156,7 @@ class Requests(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) elicitation: Elicitation1 | None = None """ @@ -174,7 +174,7 @@ class Tasks(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) cancel: dict[str, Any] | None = None """ @@ -196,7 +196,7 @@ class ClientCapabilities(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) elicitation: Elicitation | None = None """ @@ -226,7 +226,7 @@ class Argument(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) name: str """ @@ -244,7 +244,7 @@ class Context(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) arguments: dict[str, str] | None = None """ @@ -254,7 +254,7 @@ class Context(WireModel): class Completion(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) has_more: Annotated[bool | None, Field(alias="hasMore")] = None """ @@ -276,7 +276,7 @@ class CompleteResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -298,7 +298,7 @@ class ElicitResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -321,7 +321,7 @@ class ElicitResult(WireModel): class Params1(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) elicitation_id: Annotated[str, Field(alias="elicitationId")] """ @@ -335,7 +335,7 @@ class ElicitationCompleteNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/elicitation/complete"] @@ -344,7 +344,7 @@ class ElicitationCompleteNotification(WireModel): class Error(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) code: int """ @@ -362,7 +362,7 @@ class Error(WireModel): class Params2(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) task_id: Annotated[str, Field(alias="taskId")] """ @@ -388,7 +388,7 @@ class GetTaskPayloadResult(WireModel): class Params3(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) task_id: Annotated[str, Field(alias="taskId")] """ @@ -402,7 +402,7 @@ class Icon(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) mime_type: Annotated[str | None, Field(alias="mimeType")] = None """ @@ -443,7 +443,7 @@ class Icons(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) icons: list[Icon] | None = None """ @@ -465,7 +465,7 @@ class Implementation(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) description: str | None = None """ @@ -513,7 +513,7 @@ class JSONRPCNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: str @@ -527,7 +527,7 @@ class LegacyTitledEnumSchema(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: str | None = None description: str | None = None @@ -570,7 +570,7 @@ class LoggingMessageNotificationParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -599,7 +599,7 @@ class ModelHint(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) name: str | None = None """ @@ -631,7 +631,7 @@ class ModelPreferences(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None """ @@ -665,7 +665,7 @@ class ModelPreferences(WireModel): class Notification(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) method: str params: dict[str, Any] | None = None @@ -673,7 +673,7 @@ class Notification(WireModel): class NotificationParams(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -683,7 +683,7 @@ class NotificationParams(WireModel): class NumberSchema(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: float | None = None description: str | None = None @@ -695,7 +695,7 @@ class NumberSchema(WireModel): class PaginatedResult(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -721,7 +721,7 @@ class PromptArgument(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) description: str | None = None """ @@ -752,7 +752,7 @@ class PromptListChangedNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/prompts/list_changed"] @@ -765,7 +765,7 @@ class PromptReference(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) name: str """ @@ -803,7 +803,7 @@ class ReadResourceRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -822,7 +822,7 @@ class RelatedTaskMetadata(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) task_id: Annotated[str, Field(alias="taskId")] """ @@ -832,7 +832,7 @@ class RelatedTaskMetadata(WireModel): class Request(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) method: str params: dict[str, Any] | None = None @@ -851,7 +851,7 @@ class RequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -865,7 +865,7 @@ class ResourceContents(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -887,7 +887,7 @@ class ResourceListChangedNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/resources/list_changed"] @@ -900,7 +900,7 @@ class ResourceRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -918,7 +918,7 @@ class ResourceTemplateReference(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) type: Literal["ref/resource"] uri: str @@ -933,7 +933,7 @@ class ResourceUpdatedNotificationParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -968,7 +968,7 @@ class Root(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -996,7 +996,7 @@ class RootsListChangedNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/roots/list_changed"] @@ -1009,7 +1009,7 @@ class Prompts(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ @@ -1023,7 +1023,7 @@ class Resources(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ @@ -1041,7 +1041,7 @@ class Tools(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) call: dict[str, Any] | None = None """ @@ -1055,7 +1055,7 @@ class Requests1(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) tools: Tools | None = None """ @@ -1069,7 +1069,7 @@ class Tasks1(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) cancel: dict[str, Any] | None = None """ @@ -1091,7 +1091,7 @@ class Tools1(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ @@ -1105,7 +1105,7 @@ class ServerCapabilities(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) completions: dict[str, Any] | None = None """ @@ -1143,7 +1143,7 @@ class SetLevelRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -1157,7 +1157,7 @@ class SetLevelRequestParams(WireModel): class StringSchema(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: str | None = None description: str | None = None @@ -1174,7 +1174,7 @@ class SubscribeRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -1193,7 +1193,7 @@ class TaskMetadata(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) ttl: int | None = None """ @@ -1210,7 +1210,7 @@ class TaskStatus(RootModel[Literal["cancelled", "completed", "failed", "input_re class TextResourceContents(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -1232,7 +1232,7 @@ class TextResourceContents(WireModel): class AnyOfItem(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) const: str """ @@ -1250,7 +1250,7 @@ class Items(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) any_of: Annotated[list[AnyOfItem], Field(alias="anyOf")] """ @@ -1264,7 +1264,7 @@ class TitledMultiSelectEnumSchema(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: list[str] | None = None """ @@ -1295,7 +1295,7 @@ class TitledMultiSelectEnumSchema(WireModel): class OneOfItem(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) const: str """ @@ -1313,7 +1313,7 @@ class TitledSingleSelectEnumSchema(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: str | None = None """ @@ -1379,7 +1379,7 @@ class ToolAnnotations(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None """ @@ -1426,7 +1426,7 @@ class ToolChoice(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) mode: Literal["auto", "none", "required"] | None = None """ @@ -1443,7 +1443,7 @@ class ToolExecution(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) task_support: Annotated[Literal["forbidden", "optional", "required"] | None, Field(alias="taskSupport")] = None """ @@ -1465,7 +1465,7 @@ class ToolListChangedNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/tools/list_changed"] @@ -1478,7 +1478,7 @@ class ToolUseContent(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -1510,7 +1510,7 @@ class UnsubscribeRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -1528,7 +1528,7 @@ class Items1(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) enum: list[str] """ @@ -1543,7 +1543,7 @@ class UntitledMultiSelectEnumSchema(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: list[str] | None = None """ @@ -1578,7 +1578,7 @@ class UntitledSingleSelectEnumSchema(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: str | None = None """ @@ -1605,7 +1605,7 @@ class Annotations(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) audience: list[Role] | None = None """ @@ -1638,7 +1638,7 @@ class AudioContent(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -1665,7 +1665,7 @@ class CallToolRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -1696,7 +1696,7 @@ class CancelTaskRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -1710,7 +1710,7 @@ class CancelledNotificationParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -1736,7 +1736,7 @@ class CompleteRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -1759,7 +1759,7 @@ class ElicitRequestURLParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -1802,7 +1802,7 @@ class EmbeddedResource(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -1844,7 +1844,7 @@ class GetPromptRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -1866,7 +1866,7 @@ class GetTaskPayloadRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -1880,7 +1880,7 @@ class GetTaskRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -1894,7 +1894,7 @@ class ImageContent(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -1921,7 +1921,7 @@ class InitializeRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -1941,7 +1941,7 @@ class InitializeResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -1967,7 +1967,7 @@ class InitializedNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/initialized"] @@ -1980,7 +1980,7 @@ class JSONRPCErrorResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) error: Error id: RequestId | None = None @@ -1993,7 +1993,7 @@ class JSONRPCRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2007,7 +2007,7 @@ class JSONRPCResultResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2026,7 +2026,7 @@ class ListRootsRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2042,7 +2042,7 @@ class ListRootsResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2057,7 +2057,7 @@ class LoggingMessageNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/message"] @@ -2074,7 +2074,7 @@ class PaginatedRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -2093,7 +2093,7 @@ class PingRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2135,7 +2135,7 @@ class ProgressNotificationParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2165,7 +2165,7 @@ class Prompt(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2212,7 +2212,7 @@ class ReadResourceRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2226,7 +2226,7 @@ class ReadResourceResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2241,7 +2241,7 @@ class Resource(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2306,7 +2306,7 @@ class ResourceLink(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2370,7 +2370,7 @@ class ResourceTemplate(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2427,7 +2427,7 @@ class ResourceUpdatedNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/resources/updated"] @@ -2440,7 +2440,7 @@ class SetLevelRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2458,7 +2458,7 @@ class SubscribeRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2472,7 +2472,7 @@ class Task(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) created_at: Annotated[str, Field(alias="createdAt")] """ @@ -2514,7 +2514,7 @@ class TaskAugmentedRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -2537,7 +2537,7 @@ class TaskStatusNotificationParams(NotificationParams, Task): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) @@ -2547,7 +2547,7 @@ class TextContent(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2570,7 +2570,7 @@ class Tool(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2644,7 +2644,7 @@ class Data(WireModel): class Error1(Error): model_config = ConfigDict( - extra="allow", + extra="ignore", ) code: Literal[-32042] """ @@ -2662,7 +2662,7 @@ class URLElicitationRequiredError(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) error: Error1 id: RequestId | None = None @@ -2675,7 +2675,7 @@ class UnsubscribeRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2689,7 +2689,7 @@ class CallToolRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2703,7 +2703,7 @@ class CancelTaskResult(Result, Task): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) @@ -2721,7 +2721,7 @@ class CancelledNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/cancelled"] @@ -2734,7 +2734,7 @@ class CompleteRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2752,7 +2752,7 @@ class CreateTaskResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2768,7 +2768,7 @@ class RequestedSchema(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) schema_: Annotated[str | None, Field(alias="$schema")] = None properties: dict[str, PrimitiveSchemaDefinition] @@ -2782,7 +2782,7 @@ class ElicitRequestFormParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -2825,7 +2825,7 @@ class GetPromptRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2839,7 +2839,7 @@ class GetTaskResult(Result, Task): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) @@ -2849,7 +2849,7 @@ class InitializeRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2877,7 +2877,7 @@ class ListPromptsRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2891,7 +2891,7 @@ class ListPromptsResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2911,7 +2911,7 @@ class ListResourceTemplatesRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2925,7 +2925,7 @@ class ListResourceTemplatesResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2945,7 +2945,7 @@ class ListResourcesRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2959,7 +2959,7 @@ class ListResourcesResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -2979,7 +2979,7 @@ class ListTasksRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2993,7 +2993,7 @@ class ListTasksResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -3013,7 +3013,7 @@ class ListToolsRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3027,7 +3027,7 @@ class ListToolsResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -3043,7 +3043,7 @@ class ListToolsResult(WireModel): class PaginatedRequest(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3057,7 +3057,7 @@ class ProgressNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/progress"] @@ -3073,7 +3073,7 @@ class PromptMessage(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) content: ContentBlock role: Role @@ -3085,7 +3085,7 @@ class TaskStatusNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/tasks/status"] @@ -3098,7 +3098,7 @@ class ToolResultContent(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -3142,7 +3142,7 @@ class CallToolResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -3239,7 +3239,7 @@ class ElicitRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3253,7 +3253,7 @@ class GetPromptResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -3342,7 +3342,7 @@ class CreateMessageResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -3381,7 +3381,7 @@ class SamplingMessage(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[dict[str, Any] | None, Field(alias="_meta")] = None """ @@ -3428,7 +3428,7 @@ class CreateMessageRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[Meta | None, Field(alias="_meta")] = None """ @@ -3494,7 +3494,7 @@ class CreateMessageRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] diff --git a/src/mcp/types/v2026_07_28/__init__.py b/src/mcp/types/v2026_07_28/__init__.py index 1d37fa8a46..b82025eb21 100644 --- a/src/mcp/types/v2026_07_28/__init__.py +++ b/src/mcp/types/v2026_07_28/__init__.py @@ -18,7 +18,7 @@ class BaseMetadata(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) name: str """ @@ -37,7 +37,7 @@ class BaseMetadata(WireModel): class BooleanSchema(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: bool | None = None description: str | None = None @@ -51,7 +51,7 @@ class Argument(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) name: str """ @@ -69,7 +69,7 @@ class Context(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) arguments: dict[str, str] | None = None """ @@ -79,7 +79,7 @@ class Context(WireModel): class Completion(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) has_more: Annotated[bool | None, Field(alias="hasMore")] = None """ @@ -108,7 +108,7 @@ class ElicitRequestURLParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) elicitation_id: Annotated[str, Field(alias="elicitationId")] """ @@ -135,7 +135,7 @@ class ElicitResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) action: Literal["accept", "cancel", "decline"] """ @@ -154,7 +154,7 @@ class ElicitResult(WireModel): class Params(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) elicitation_id: Annotated[str, Field(alias="elicitationId")] """ @@ -168,7 +168,7 @@ class ElicitationCompleteNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/elicitation/complete"] @@ -177,7 +177,7 @@ class ElicitationCompleteNotification(WireModel): class Error(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) code: int """ @@ -199,7 +199,7 @@ class Icon(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) mime_type: Annotated[str | None, Field(alias="mimeType")] = None """ @@ -240,7 +240,7 @@ class Icons(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) icons: list[Icon] | None = None """ @@ -262,7 +262,7 @@ class Implementation(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) description: str | None = None """ @@ -313,7 +313,7 @@ class InternalError(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) code: Literal[-32603] """ @@ -344,7 +344,7 @@ class InvalidParamsError(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) code: Literal[-32602] """ @@ -366,7 +366,7 @@ class InvalidRequestError(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) code: Literal[-32600] """ @@ -388,7 +388,7 @@ class JSONRPCNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: str @@ -402,7 +402,7 @@ class LegacyTitledEnumSchema(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: str | None = None description: str | None = None @@ -473,7 +473,7 @@ class MethodNotFoundError(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) code: Literal[-32601] """ @@ -498,7 +498,7 @@ class ModelHint(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) name: str | None = None """ @@ -530,7 +530,7 @@ class ModelPreferences(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) cost_priority: Annotated[float | None, Field(alias="costPriority", ge=0.0, le=1.0)] = None """ @@ -564,7 +564,7 @@ class ModelPreferences(WireModel): class Notification(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) method: str params: dict[str, Any] | None = None @@ -576,14 +576,14 @@ class NotificationParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None class NumberSchema(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: float | None = None description: str | None = None @@ -595,7 +595,7 @@ class NumberSchema(WireModel): class PaginatedResult(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None next_cursor: Annotated[str | None, Field(alias="nextCursor")] = None @@ -621,7 +621,7 @@ class ParseError(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) code: Literal[-32700] """ @@ -650,7 +650,7 @@ class PromptArgument(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) description: str | None = None """ @@ -681,7 +681,7 @@ class PromptListChangedNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/prompts/list_changed"] @@ -694,7 +694,7 @@ class PromptReference(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) name: str """ @@ -714,7 +714,7 @@ class PromptReference(WireModel): class Request(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) method: str params: dict[str, Any] | None = None @@ -733,7 +733,7 @@ class ResourceContents(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None mime_type: Annotated[str | None, Field(alias="mimeType")] = None @@ -752,7 +752,7 @@ class ResourceListChangedNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/resources/list_changed"] @@ -765,7 +765,7 @@ class ResourceTemplateReference(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) type: Literal["ref/resource"] uri: str @@ -780,7 +780,7 @@ class ResourceUpdatedNotificationParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None uri: str @@ -834,7 +834,7 @@ class Root(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None name: str | None = None @@ -857,7 +857,7 @@ class Prompts(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ @@ -871,7 +871,7 @@ class Resources(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ @@ -889,7 +889,7 @@ class Tools(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) list_changed: Annotated[bool | None, Field(alias="listChanged")] = None """ @@ -899,7 +899,7 @@ class Tools(WireModel): class StringSchema(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: str | None = None description: str | None = None @@ -920,7 +920,7 @@ class SubscriptionFilter(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) prompts_list_changed: Annotated[bool | None, Field(alias="promptsListChanged")] = None """ @@ -947,7 +947,7 @@ class SubscriptionsAcknowledgedNotificationParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None notifications: SubscriptionFilter @@ -961,7 +961,7 @@ class SubscriptionsAcknowledgedNotificationParams(WireModel): class TextResourceContents(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None mime_type: Annotated[str | None, Field(alias="mimeType")] = None @@ -980,7 +980,7 @@ class TextResourceContents(WireModel): class AnyOfItem(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) const: str """ @@ -998,7 +998,7 @@ class Items(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) any_of: Annotated[list[AnyOfItem], Field(alias="anyOf")] """ @@ -1012,7 +1012,7 @@ class TitledMultiSelectEnumSchema(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: list[str] | None = None """ @@ -1043,7 +1043,7 @@ class TitledMultiSelectEnumSchema(WireModel): class OneOfItem(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) const: str """ @@ -1061,7 +1061,7 @@ class TitledSingleSelectEnumSchema(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: str | None = None """ @@ -1129,7 +1129,7 @@ class ToolAnnotations(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) destructive_hint: Annotated[bool | None, Field(alias="destructiveHint")] = None """ @@ -1176,7 +1176,7 @@ class ToolChoice(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) mode: Literal["auto", "none", "required"] | None = None """ @@ -1193,7 +1193,7 @@ class ToolListChangedNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/tools/list_changed"] @@ -1206,7 +1206,7 @@ class ToolUseContent(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None """ @@ -1236,7 +1236,7 @@ class Data1(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) requested: str """ @@ -1251,7 +1251,7 @@ class Data1(WireModel): class Error2(Error): model_config = ConfigDict( - extra="allow", + extra="ignore", ) code: Literal[-32004] """ @@ -1272,7 +1272,7 @@ class UnsupportedProtocolVersionError(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) error: Error2 id: RequestId | None = None @@ -1285,7 +1285,7 @@ class Items1(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) enum: list[str] """ @@ -1300,7 +1300,7 @@ class UntitledMultiSelectEnumSchema(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: list[str] | None = None """ @@ -1335,7 +1335,7 @@ class UntitledSingleSelectEnumSchema(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) default: str | None = None """ @@ -1362,7 +1362,7 @@ class Annotations(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) audience: list[Role] | None = None """ @@ -1395,7 +1395,7 @@ class AudioContent(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None @@ -1415,7 +1415,7 @@ class AudioContent(WireModel): class BlobResourceContents(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None blob: str @@ -1438,7 +1438,7 @@ class CacheableResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] @@ -1481,7 +1481,7 @@ class CancelledNotificationParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None reason: str | None = None @@ -1509,7 +1509,7 @@ class CompleteResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None completion: Completion @@ -1531,7 +1531,7 @@ class CompleteResultResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -1547,7 +1547,7 @@ class EmbeddedResource(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None @@ -1589,7 +1589,7 @@ class ImageContent(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None @@ -1613,7 +1613,7 @@ class JSONRPCErrorResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) error: Error id: RequestId | None = None @@ -1626,7 +1626,7 @@ class JSONRPCRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -1640,7 +1640,7 @@ class JSONRPCResultResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -1655,7 +1655,7 @@ class ListRootsResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) roots: list[Root] @@ -1666,7 +1666,7 @@ class LoggingMessageNotificationParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None data: Any @@ -1721,7 +1721,7 @@ class ProgressNotificationParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None message: str | None = None @@ -1748,7 +1748,7 @@ class Prompt(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None arguments: list[PromptArgument] | None = None @@ -1792,7 +1792,7 @@ class ReadResourceResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] @@ -1836,7 +1836,7 @@ class Resource(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None @@ -1898,7 +1898,7 @@ class ResourceLink(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None @@ -1959,7 +1959,7 @@ class ResourceTemplate(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None @@ -2013,7 +2013,7 @@ class ResourceUpdatedNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/resources/updated"] @@ -2033,7 +2033,7 @@ class SubscriptionsAcknowledgedNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/subscriptions/acknowledged"] @@ -2046,7 +2046,7 @@ class TextContent(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: Annotations | None = None @@ -2066,7 +2066,7 @@ class Tool(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None annotations: ToolAnnotations | None = None @@ -2137,7 +2137,7 @@ class CancelledNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/cancelled"] @@ -2155,7 +2155,7 @@ class RequestedSchema(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) schema_: Annotated[str | None, Field(alias="$schema")] = None properties: dict[str, PrimitiveSchemaDefinition] @@ -2169,7 +2169,7 @@ class ElicitRequestFormParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) message: str """ @@ -2213,7 +2213,7 @@ class ListPromptsResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] @@ -2262,7 +2262,7 @@ class ListPromptsResultResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2275,7 +2275,7 @@ class ListResourceTemplatesResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] @@ -2324,7 +2324,7 @@ class ListResourceTemplatesResultResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2337,7 +2337,7 @@ class ListResourcesResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] @@ -2386,7 +2386,7 @@ class ListResourcesResultResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2399,7 +2399,7 @@ class ListToolsResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] @@ -2448,7 +2448,7 @@ class ListToolsResultResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2461,7 +2461,7 @@ class LoggingMessageNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/message"] @@ -2474,7 +2474,7 @@ class ProgressNotification(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) jsonrpc: Literal["2.0"] method: Literal["notifications/progress"] @@ -2490,7 +2490,7 @@ class PromptMessage(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) content: ContentBlock role: Role @@ -2528,7 +2528,7 @@ class ToolResultContent(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None """ @@ -2571,7 +2571,7 @@ class CallToolResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None content: list[ContentBlock] @@ -2622,7 +2622,7 @@ class ElicitRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) method: Literal["elicitation/create"] params: ElicitRequestParams @@ -2634,7 +2634,7 @@ class GetPromptResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None description: str | None = None @@ -2668,7 +2668,7 @@ class CreateMessageResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None content: ( @@ -2718,7 +2718,7 @@ class SamplingMessage(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None content: ( @@ -2738,7 +2738,7 @@ class CallToolRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2752,7 +2752,7 @@ class CallToolRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] arguments: dict[str, Any] | None = None @@ -2773,7 +2773,7 @@ class CallToolResultResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2786,7 +2786,7 @@ class Elicitation(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) form: JSONObject | None = None url: JSONObject | None = None @@ -2798,7 +2798,7 @@ class Sampling(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) context: JSONObject | None = None """ @@ -2817,7 +2817,7 @@ class ClientCapabilities(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) elicitation: Elicitation | None = None """ @@ -2849,7 +2849,7 @@ class CompleteRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2863,7 +2863,7 @@ class CompleteRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] argument: Argument @@ -2883,7 +2883,7 @@ class CreateMessageRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) method: Literal["sampling/createMessage"] params: CreateMessageRequestParams @@ -2895,7 +2895,7 @@ class CreateMessageRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) include_context: Annotated[ Literal["allServers", "none", "thisServer"] | None, @@ -2952,7 +2952,7 @@ class DiscoverRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -2966,7 +2966,7 @@ class DiscoverResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] @@ -3031,7 +3031,7 @@ class DiscoverResultResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3044,7 +3044,7 @@ class GetPromptRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3058,7 +3058,7 @@ class GetPromptRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] arguments: dict[str, str] | None = None @@ -3079,7 +3079,7 @@ class GetPromptResultResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3095,7 +3095,7 @@ class InputRequiredResult(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[MetaObject | None, Field(alias="_meta")] = None input_requests: Annotated[InputRequests | None, Field(alias="inputRequests")] = None @@ -3114,7 +3114,7 @@ class InputRequiredResult(WireModel): class InputResponseRequestParams(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None @@ -3127,7 +3127,7 @@ class ListPromptsRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3141,7 +3141,7 @@ class ListResourceTemplatesRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3155,7 +3155,7 @@ class ListResourcesRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3175,7 +3175,7 @@ class ListRootsRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) method: Literal["roots/list"] params: RequestParams | None = None @@ -3187,7 +3187,7 @@ class ListToolsRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3201,7 +3201,7 @@ class Data(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) required_capabilities: Annotated[ClientCapabilities, Field(alias="requiredCapabilities")] """ @@ -3211,7 +3211,7 @@ class Data(WireModel): class Error1(Error): model_config = ConfigDict( - extra="allow", + extra="ignore", ) code: Literal[-32003] """ @@ -3231,7 +3231,7 @@ class MissingRequiredClientCapabilityError(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) error: Error1 id: RequestId | None = None @@ -3240,7 +3240,7 @@ class MissingRequiredClientCapabilityError(WireModel): class PaginatedRequest(WireModel): model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3254,7 +3254,7 @@ class PaginatedRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] cursor: str | None = None @@ -3270,7 +3270,7 @@ class ReadResourceRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3284,7 +3284,7 @@ class ReadResourceRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] input_responses: Annotated[InputResponses | None, Field(alias="inputResponses")] = None @@ -3301,7 +3301,7 @@ class ReadResourceResultResponse(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3364,7 +3364,7 @@ class RequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] @@ -3375,7 +3375,7 @@ class ResourceRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] uri: str @@ -3390,7 +3390,7 @@ class ServerCapabilities(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) completions: JSONObject | None = None """ @@ -3432,7 +3432,7 @@ class SubscriptionsListenRequest(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) id: RequestId jsonrpc: Literal["2.0"] @@ -3446,7 +3446,7 @@ class SubscriptionsListenRequestParams(WireModel): """ model_config = ConfigDict( - extra="allow", + extra="ignore", ) meta: Annotated[RequestMetaObject, Field(alias="_meta")] notifications: SubscriptionFilter diff --git a/tests/interaction/transports/test_hosting_resume.py b/tests/interaction/transports/test_hosting_resume.py index b835c78024..c7945d56c3 100644 --- a/tests/interaction/transports/test_hosting_resume.py +++ b/tests/interaction/transports/test_hosting_resume.py @@ -108,7 +108,6 @@ async def test_a_post_sse_stream_begins_with_a_priming_event_and_stamps_every_ev "content": [{"type": "text", "text": "counted to 2"}], "structuredContent": {"result": "counted to 2"}, "isError": False, - "resultType": "complete", }, ) ) diff --git a/tests/server/test_runner.py b/tests/server/test_runner.py index bc2ba39de0..240d7c7622 100644 --- a/tests/server/test_runner.py +++ b/tests/server/test_runner.py @@ -305,6 +305,24 @@ async def handler(ctx: Ctx, params: ProgressNotificationParams) -> None: assert "dropped 'notifications/roots/list_changed': malformed params" in caplog.text +@pytest.mark.anyio +async def test_runner_on_notify_drops_a_spec_notification_with_no_client_surface_row( + server: SrvT, caplog: pytest.LogCaptureFixture +): + """`notifications/message` is in `MONOLITH_NOTIFICATIONS` but never a client + notification, so the version gate drops it before handler lookup.""" + + async def handler(ctx: Ctx, params: NotificationParams) -> None: + raise NotImplementedError # the version gate drops the notification first + + server.add_notification_handler("notifications/message", NotificationParams, handler) + with caplog.at_level("DEBUG", logger="mcp.server.runner"): + async with connected_runner(server) as (client, _): + await client.notify("notifications/message", {"level": "info", "data": "x"}) + await client.send_raw_request("tools/list", None) + assert "dropped 'notifications/message': not defined at 2025-11-25" in caplog.text + + @pytest.mark.anyio async def test_runner_on_notify_initialized_sets_flag_and_connection_event(server: SrvT): async with connected_runner(server, initialized=False) as (client, runner): @@ -800,6 +818,56 @@ async def test_runner_handler_returning_typed_monolith_result_passes_outbound_va assert result["tools"][0]["name"] == "t" +@pytest.mark.anyio +async def test_runner_outbound_sieve_drops_2026_only_result_keys_at_a_pre_2026_version(server: SrvT): + """The handler's `resultType`/`ttlMs`/`cacheScope` are sieved out so a 2025 + client sees only schema fields.""" + + async def list_tools(ctx: Ctx, params: PaginatedRequestParams | None) -> ListToolsResult: + return ListToolsResult(tools=[Tool(name="t", input_schema={"type": "object"})], ttl_ms=5, cache_scope="public") + + server.add_request_handler("tools/list", PaginatedRequestParams, list_tools) + async with connected_runner(server) as (client, runner): + assert runner.connection.protocol_version == "2025-11-25" + result = await client.send_raw_request("tools/list", None) + assert result == {"tools": [{"name": "t", "inputSchema": {"type": "object"}}]} + + +@pytest.mark.anyio +async def test_runner_spec_method_absent_at_the_negotiated_version_is_method_not_found(server: SrvT): + """`server/discover` is a spec method (in `MONOLITH_REQUESTS`) but only at + 2026-07-28; on a 2025 session it must be METHOD_NOT_FOUND even with a + registered handler.""" + + async def discover(ctx: Ctx, params: RequestParams) -> Any: + raise NotImplementedError # the version gate rejects the request first + + server.add_request_handler("server/discover", RequestParams, discover) + async with connected_runner(server) as (client, runner): + assert runner.connection.protocol_version == "2025-11-25" + with pytest.raises(MCPError) as exc: + await client.send_raw_request("server/discover", None) + assert exc.value.error == ErrorData(code=METHOD_NOT_FOUND, message="Method not found", data="server/discover") + + +@pytest.mark.anyio +async def test_runner_middleware_short_circuit_on_a_wrong_version_spec_method_skips_the_sieve(server: SrvT): + """A server-tier middleware that returns without calling `call_next` for a + spec method absent at the negotiated version owns the result shape; the + outbound sieve has no `(method, version)` row and must not raise.""" + + async def short_circuit(ctx: Ctx, method: str, params: Any, call_next: Any) -> Any: + if method == "server/discover": + return {"ok": True} + return await call_next() + + server.middleware.append(short_circuit) + async with connected_runner(server) as (client, runner): + assert runner.connection.protocol_version == "2025-11-25" + result = await client.send_raw_request("server/discover", None) + assert result == {"ok": True} + + @pytest.mark.anyio async def test_runner_custom_method_result_is_not_surface_validated(server: SrvT): """No `SERVER_RESULTS` row for a custom method, so its result reaches the client as-is.""" diff --git a/tests/server/test_session.py b/tests/server/test_session.py index 44aab6b33b..86ec507574 100644 --- a/tests/server/test_session.py +++ b/tests/server/test_session.py @@ -219,6 +219,6 @@ async def test_protocol_version_is_none_on_stateless_connection(): seen: list[str | None] = [] async with connected_runner(_runner_server(seen), initialized=False, stateless=True) as (client, runner): result = await client.send_raw_request("tools/list", None) - assert result == {"tools": [], "resultType": "complete", "ttlMs": 0, "cacheScope": "private"} + assert result == {"tools": []} assert seen == [None] assert runner.session.protocol_version is None diff --git a/tests/test_types.py b/tests/test_types.py index d604ef1bba..8c85411eba 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -381,7 +381,7 @@ def _wire_dump(result: Result) -> dict[str, Any]: def test_concrete_wire_results_always_dump_result_type_complete(): - """Required by 2026-07-28; older peers tolerate the extra key.""" + """Required by 2026-07-28; the runner's per-version sieve drops it for older peers.""" carriers: list[Result] = [ CompleteResult(completion=Completion(values=[])), GetPromptResult(messages=[]), @@ -401,8 +401,9 @@ def test_concrete_wire_results_always_dump_result_type_complete(): assert _wire_dump(result)["resultType"] == "complete", type(result).__name__ -def test_cacheable_results_always_dump_their_caching_directives(): - """Required by 2026-07-28; older peers tolerate the extra keys.""" +def test_cacheable_results_omit_unset_caching_directives(): + """`ttl_ms`/`cache_scope` default to None: the SDK declares no caching policy + so a 2026-07-28 handler must set them explicitly.""" cacheable: list[Result] = [ ReadResourceResult(contents=[]), ListPromptsResult(prompts=[]), @@ -417,8 +418,11 @@ def test_cacheable_results_always_dump_their_caching_directives(): ] for result in cacheable: dumped = _wire_dump(result) - assert dumped["ttlMs"] == 0, type(result).__name__ - assert dumped["cacheScope"] == "private", type(result).__name__ + assert "ttlMs" not in dumped, type(result).__name__ + assert "cacheScope" not in dumped, type(result).__name__ + explicit = _wire_dump(ListToolsResult(tools=[], ttl_ms=5, cache_scope="public")) + assert explicit["ttlMs"] == 5 + assert explicit["cacheScope"] == "public" def test_empty_result_dumps_no_fields_by_default(): diff --git a/tests/types/test_methods.py b/tests/types/test_methods.py index 3033a2cf59..b115fec2d8 100644 --- a/tests/types/test_methods.py +++ b/tests/types/test_methods.py @@ -749,8 +749,8 @@ def test_validate_functions_accept_reject_and_gate_like_their_parse_siblings(): # One minimal monolith result instance per request method, dumped via the same -# `_dump_result` path the runner uses. Values are chosen so the dump satisfies -# every `SERVER_RESULTS` row that method appears under. +# `_dump_result` path the runner uses. Cacheable results set `ttl_ms`/`cache_scope` +# explicitly because the monolith no longer defaults them and 2026 requires them. MONOLITH_RESULT_FIXTURES: dict[str, types.Result] = { "completion/complete": types.CompleteResult(completion=types.Completion(values=[])), "initialize": types.InitializeResult( @@ -761,29 +761,92 @@ def test_validate_functions_accept_reject_and_gate_like_their_parse_siblings(): "logging/setLevel": types.EmptyResult(), "ping": types.EmptyResult(), "prompts/get": types.GetPromptResult(messages=[]), - "prompts/list": types.ListPromptsResult(prompts=[]), - "resources/list": types.ListResourcesResult(resources=[]), - "resources/read": types.ReadResourceResult(contents=[]), + "prompts/list": types.ListPromptsResult(prompts=[], ttl_ms=0, cache_scope="private"), + "resources/list": types.ListResourcesResult(resources=[], ttl_ms=0, cache_scope="private"), + "resources/read": types.ReadResourceResult(contents=[], ttl_ms=0, cache_scope="private"), "resources/subscribe": types.EmptyResult(), - "resources/templates/list": types.ListResourceTemplatesResult(resource_templates=[]), + "resources/templates/list": types.ListResourceTemplatesResult( + resource_templates=[], ttl_ms=0, cache_scope="private" + ), "resources/unsubscribe": types.EmptyResult(), "server/discover": types.DiscoverResult( supported_versions=["2026-07-28"], capabilities=types.ServerCapabilities(), server_info=types.Implementation(name="server", version="1.0"), + ttl_ms=0, + cache_scope="private", ), "subscriptions/listen": types.EmptyResult(result_type="complete"), "tools/call": types.CallToolResult(content=[]), - "tools/list": types.ListToolsResult(tools=[]), + "tools/list": types.ListToolsResult(tools=[], ttl_ms=0, cache_scope="private"), } +CACHEABLE_METHODS = frozenset(m for m, r in MONOLITH_RESULT_FIXTURES.items() if isinstance(r, types.CacheableResult)) + @pytest.mark.parametrize(("method", "version"), sorted(methods.SERVER_RESULTS)) -def test_dumped_monolith_results_round_trip_through_validate_server_result(method: str, version: str): - """The outbound surface check must accept every correctly-typed handler return.""" +def test_dumped_monolith_results_round_trip_through_serialize_server_result(method: str, version: str): + """The outbound sieve must accept every correctly-typed handler return and re-validate.""" + instance = MONOLITH_RESULT_FIXTURES[method] + dumped = instance.model_dump(by_alias=True, mode="json", exclude_none=True) + sieved = methods.serialize_server_result(method, version, dumped) + methods.validate_server_result(method, version, sieved) + + +PRE_2026 = [v for v in KNOWN_PROTOCOL_VERSIONS if v < "2026-07-28"] + + +@pytest.mark.parametrize(("method", "version"), [k for k in sorted(methods.SERVER_RESULTS) if k[1] in PRE_2026]) +def test_serialize_server_result_drops_2026_only_keys_at_pre_2026_versions(method: str, version: str): instance = MONOLITH_RESULT_FIXTURES[method] dumped = instance.model_dump(by_alias=True, mode="json", exclude_none=True) - methods.validate_server_result(method, version, dumped) + sieved = methods.serialize_server_result(method, version, dumped) + if getattr(instance, "result_type", None) is not None: + assert "resultType" in dumped + assert "resultType" not in sieved + if method in CACHEABLE_METHODS: + assert "ttlMs" in dumped and "cacheScope" in dumped + assert "ttlMs" not in sieved + assert "cacheScope" not in sieved + + +def test_serialize_server_result_keeps_2026_only_keys_at_2026_07_28(): + dumped = MONOLITH_RESULT_FIXTURES["tools/list"].model_dump(by_alias=True, mode="json", exclude_none=True) + sieved = methods.serialize_server_result("tools/list", "2026-07-28", dumped) + assert sieved["resultType"] == "complete" + assert sieved["ttlMs"] == 0 + assert sieved["cacheScope"] == "private" + + +@pytest.mark.parametrize("version", KNOWN_PROTOCOL_VERSIONS) +def test_serialize_server_result_preserves_arbitrary_meta_value_identically(version: str): + meta = {"custom-key": 1, "modelcontextprotocol.io/foo": "bar"} + dumped: dict[str, Any] = {"tools": [], "resultType": "complete", "ttlMs": 0, "cacheScope": "private", "_meta": meta} + sieved = methods.serialize_server_result("tools/list", version, dumped) + assert sieved["_meta"] == meta + + +def test_serialize_server_result_preserves_open_type_extras(): + """`inputSchema` and nested `_meta` are open key-value bags; the sieve must not strip them.""" + input_schema = {"type": "object", "title": "X", "additionalProperties": False, "$defs": {"Y": {"type": "string"}}} + nested_meta = {"com.example/tag": "v"} + tool = {"name": "echo", "inputSchema": input_schema, "_meta": nested_meta} + sieved = methods.serialize_server_result("tools/list", "2025-11-25", {"tools": [tool]}) + assert sieved["tools"][0]["inputSchema"] == input_schema + assert sieved["tools"][0]["_meta"] == nested_meta + + +def test_serialize_server_result_drops_an_unknown_nested_tool_field(): + tool = {"name": "echo", "inputSchema": {"type": "object"}, "unknownField": 1} + sieved = methods.serialize_server_result("tools/list", "2025-11-25", {"tools": [tool], "resultType": "complete"}) + assert sieved == {"tools": [{"name": "echo", "inputSchema": {"type": "object"}}]} + + +def test_serialize_server_result_raises_key_error_for_an_absent_row_and_value_error_for_an_unknown_version(): + with pytest.raises(KeyError): + methods.serialize_server_result("server/discover", "2025-11-25", {}) + with pytest.raises(ValueError): + methods.serialize_server_result("ping", "2099-01-01", {}) def test_importing_the_module_builds_no_adapters_and_identical_rows_share_one(): diff --git a/tests/types/test_wire_frames.py b/tests/types/test_wire_frames.py index 4badda6c31..c4d40945b0 100644 --- a/tests/types/test_wire_frames.py +++ b/tests/types/test_wire_frames.py @@ -52,7 +52,8 @@ def test_notification_frame_has_no_id_and_carries_the_dumped_params(): ) -def test_non_empty_result_frame_always_dumps_result_type_complete(): +def test_non_empty_result_dump_carries_result_type_complete_before_the_sieve(): + """The runner's per-version sieve drops `resultType` for pre-2026 peers; the raw dump carries it.""" result = CallToolResult(content=[TextContent(text="ok")]) frame = JSONRPCResponse(jsonrpc="2.0", id=1, result=_body(result)) assert _frame(frame) == snapshot( @@ -60,11 +61,12 @@ def test_non_empty_result_frame_always_dumps_result_type_complete(): ) -def test_cacheable_list_result_frame_always_dumps_its_caching_directives(): +def test_cacheable_list_result_dump_omits_unset_caching_directives(): + """`ttl_ms`/`cache_scope` default to None so the raw dump omits them; 2026 handlers set them explicitly.""" result = ListToolsResult(tools=[Tool(name="echo", input_schema={"type": "object"})]) frame = JSONRPCResponse(jsonrpc="2.0", id=2, result=_body(result)) assert _frame(frame) == snapshot( - '{"jsonrpc":"2.0","id":2,"result":{"ttlMs":0,"cacheScope":"private","tools":[{"name":"echo","inputSchema":{"type":"object"}}],"resultType":"complete"}}' + '{"jsonrpc":"2.0","id":2,"result":{"tools":[{"name":"echo","inputSchema":{"type":"object"}}],"resultType":"complete"}}' ) From 44a1e837717ba4e7b2157f8b485f67af9c7bb713 Mon Sep 17 00:00:00 2001 From: Max Isbey <224885523+maxisbey@users.noreply.github.com> Date: Tue, 16 Jun 2026 09:03:47 +0000 Subject: [PATCH 7/7] Validate client results against the surface schema in server send_request ServerSession.send_request and Connection.send_request now run the client's response through validate_client_result before parsing into the caller's result_type. KeyError (spec method without a row at the negotiated version, or a non-spec method on Connection) is tolerated and the existing model_validate proceeds; ValidationError propagates as today. Adds validate_client_result to types.methods and refactors parse_client_result to delegate its surface step to it. --- src/mcp/server/connection.py | 8 +++++ src/mcp/server/session.py | 7 +++++ src/mcp/types/methods.py | 22 ++++++++++++-- tests/server/test_connection.py | 54 +++++++++++++++++++++++++++++++-- tests/server/test_session.py | 30 ++++++++++++++++++ tests/types/test_methods.py | 5 +++ 6 files changed, 122 insertions(+), 4 deletions(-) diff --git a/src/mcp/server/connection.py b/src/mcp/server/connection.py index 1469e8bc19..8f72962023 100644 --- a/src/mcp/server/connection.py +++ b/src/mcp/server/connection.py @@ -38,6 +38,7 @@ PingRequest, Request, ) +from mcp.types import methods as _methods __all__ = ["Connection"] @@ -175,6 +176,13 @@ async def send_request( KeyError: `result_type` omitted for a non-spec request type. """ raw = await self.send_raw_request(req.method, dump_params(req.params), opts) + # Literal fallback covers pre-handshake and stateless; matches runner.py. + version = self.protocol_version or "2025-11-25" + if req.method in _methods.MONOLITH_REQUESTS: + try: + _methods.validate_client_result(req.method, version, raw) + except KeyError: + pass cls = result_type if result_type is not None else _RESULT_FOR[type(req)] return cls.model_validate(raw, by_name=False) diff --git a/src/mcp/server/session.py b/src/mcp/server/session.py index f9bf513a83..2dba81abec 100644 --- a/src/mcp/server/session.py +++ b/src/mcp/server/session.py @@ -20,6 +20,7 @@ from mcp.shared.exceptions import NoBackChannelError, StatelessModeNotSupported from mcp.shared.jsonrpc_dispatcher import JSONRPCDispatcher from mcp.shared.message import ServerMessageMetadata +from mcp.types import methods as _methods __all__ = ["ServerSession"] @@ -96,6 +97,12 @@ async def send_request( result = await self._dispatcher.send_raw_request( data["method"], data.get("params"), opts or None, _related_request_id=related ) + # Literal fallback covers pre-handshake and stateless; matches runner.py. + version = self.protocol_version or "2025-11-25" + try: + _methods.validate_client_result(request.method, version, result) + except KeyError: + pass return result_type.model_validate(result, by_name=False) async def send_notification( diff --git a/src/mcp/types/methods.py b/src/mcp/types/methods.py index 0cd4628451..3b6a448993 100644 --- a/src/mcp/types/methods.py +++ b/src/mcp/types/methods.py @@ -41,6 +41,7 @@ "serialize_server_result", "validate_client_notification", "validate_client_request", + "validate_client_result", "validate_server_result", ] @@ -652,6 +653,24 @@ def parse_server_result( return result +def validate_client_result( + method: str, + version: str, + data: Mapping[str, Any], + *, + surface: Mapping[tuple[str, str], type[BaseModel] | UnionType] = CLIENT_RESULTS, +) -> None: + """Validate a client result against `surface` only. + + Raises: + ValueError: `version` is not a known protocol version. + KeyError: `(method, version)` is not in `surface`. + pydantic.ValidationError: result fails surface validation. + """ + _check_known_version(version) + _adapter(surface[(method, version)]).validate_python(data, by_name=False) + + def parse_client_result( method: str, version: str, @@ -675,7 +694,6 @@ def parse_client_result( pydantic.ValidationError: result fails surface or monolith validation. RuntimeError: surface matched but `method` has no monolith row. """ - _check_known_version(version) - _adapter(surface[(method, version)]).validate_python(data, by_name=False) + validate_client_result(method, version, data, surface=surface) result: types.Result = _adapter(_monolith_row(monolith, method)).validate_python(data, by_name=False) return result diff --git a/tests/server/test_connection.py b/tests/server/test_connection.py index ada5a359e9..e7ec874e32 100644 --- a/tests/server/test_connection.py +++ b/tests/server/test_connection.py @@ -8,11 +8,11 @@ import logging from collections.abc import Mapping -from typing import Any +from typing import Any, Literal import anyio import pytest -from pydantic import ValidationError +from pydantic import BaseModel, ValidationError from mcp.server.connection import Connection from mcp.shared.dispatcher import CallOptions @@ -29,6 +29,8 @@ ListRootsRequest, ListRootsResult, PingRequest, + Request, + RequestParams, RootsCapability, SamplingCapability, SamplingContextCapability, @@ -144,6 +146,54 @@ async def test_connection_send_request_nonconforming_result_raises_validation_er await conn.send_request(ListRootsRequest()) +@pytest.mark.anyio +async def test_send_request_validates_the_client_result_against_the_surface_schema(): + """A spec-method result that fails the per-version surface schema raises + `ValidationError` even when the caller's `result_type` would accept it.""" + conn = Connection(StubOutbound(result={"roots": "nope"}), has_standalone_channel=True) + with pytest.raises(ValidationError): + await conn.send_request(ListRootsRequest(), result_type=EmptyResult) + + +@pytest.mark.anyio +async def test_send_request_passes_a_spec_valid_client_result(): + """A spec-valid client result passes the surface gate and parses to the typed model.""" + conn = Connection(StubOutbound(result={"roots": [{"uri": "file:///ws"}]}), has_standalone_channel=True) + conn.protocol_version = "2025-11-25" + result = await conn.send_request(ListRootsRequest()) + assert isinstance(result, ListRootsResult) + assert str(result.roots[0].uri) == "file:///ws" + + +class _CustomRequest(Request[RequestParams | None, Literal["custom/echo"]]): + method: Literal["custom/echo"] = "custom/echo" + params: RequestParams | None = None + + +class _CustomResult(BaseModel): + value: int + + +@pytest.mark.anyio +async def test_send_request_skips_the_surface_gate_when_method_absent_at_version(): + """Surface row absent for the negotiated version: gate is bypassed and only + the inferred result type validates.""" + conn = Connection(StubOutbound(result={}), has_standalone_channel=True) + conn.protocol_version = "2026-07-28" + result = await conn.send_request(PingRequest()) + assert isinstance(result, EmptyResult) + + +@pytest.mark.anyio +async def test_send_request_with_a_custom_method_skips_the_surface_gate(): + """Non-spec methods are not blocked by the surface gate; `result_type` validates.""" + conn = Connection(StubOutbound(result={"value": 7}), has_standalone_channel=True) + conn.protocol_version = "2025-11-25" + result = await conn.send_request(_CustomRequest(), result_type=_CustomResult) + assert isinstance(result, _CustomResult) + assert result.value == 7 + + @pytest.mark.anyio async def test_connection_ping_sends_ping_on_standalone(): out = StubOutbound() diff --git a/tests/server/test_session.py b/tests/server/test_session.py index 86ec507574..cddf3c19ec 100644 --- a/tests/server/test_session.py +++ b/tests/server/test_session.py @@ -9,6 +9,7 @@ from typing import Any, cast import pytest +from pydantic import ValidationError from mcp import types from mcp.server import Server, ServerRequestContext @@ -57,8 +58,10 @@ def _make_session( *, capabilities: ClientCapabilities | None = None, has_standalone_channel: bool = True, + protocol_version: str | None = None, ) -> ServerSession: conn = Connection(dispatcher, has_standalone_channel=has_standalone_channel) + conn.protocol_version = protocol_version if capabilities is not None: conn.client_params = InitializeRequestParams( protocol_version=LATEST_PROTOCOL_VERSION, @@ -128,6 +131,33 @@ async def test_send_request_without_back_channel_or_related_id_fails_fast(): assert dispatcher.requests[0][3] == 3 +@pytest.mark.anyio +async def test_send_request_validates_the_client_result_against_the_surface_schema(): + """A spec-method result that fails the per-version surface schema raises + `ValidationError` even when the caller's `result_type` would accept it.""" + session = _make_session(StubDispatcher(result={"roots": "nope"})) + with pytest.raises(ValidationError): + await session.send_request(types.ListRootsRequest(), types.EmptyResult) + + +@pytest.mark.anyio +async def test_send_request_passes_a_spec_valid_client_result(): + """A spec-valid client result passes the surface gate and parses to the typed model.""" + session = _make_session(StubDispatcher(result={"roots": [{"uri": "file:///ws"}]})) + result = await session.send_request(types.ListRootsRequest(), types.ListRootsResult) + assert isinstance(result, types.ListRootsResult) + assert str(result.roots[0].uri) == "file:///ws" + + +@pytest.mark.anyio +async def test_send_request_skips_the_surface_gate_when_method_absent_at_version(): + """Surface row absent for the negotiated version: gate is bypassed and only + `result_type` validates.""" + session = _make_session(StubDispatcher(result={}), protocol_version="2026-07-28") + result = await session.send_request(types.PingRequest(), types.EmptyResult) + assert isinstance(result, types.EmptyResult) + + @pytest.mark.anyio async def test_send_request_validates_result_alias_only(): """Peer results validate alias-only; a snake_case key from the wire is diff --git a/tests/types/test_methods.py b/tests/types/test_methods.py index b115fec2d8..3f6f2e0cb6 100644 --- a/tests/types/test_methods.py +++ b/tests/types/test_methods.py @@ -732,18 +732,23 @@ def test_validate_functions_accept_reject_and_gate_like_their_parse_siblings(): methods.validate_client_request("tools/call", "2025-11-25", {"name": "echo"}) methods.validate_client_notification("notifications/cancelled", "2025-11-25", {"requestId": 1}) methods.validate_server_result("tools/list", "2025-11-25", {"tools": []}) + methods.validate_client_result("roots/list", "2025-11-25", {"roots": []}) with pytest.raises(KeyError): methods.validate_client_request("custom/greet", "2025-11-25", None) with pytest.raises(KeyError): methods.validate_client_notification("custom/ping", "2025-11-25", None) with pytest.raises(KeyError): methods.validate_server_result("custom/greet", "2025-11-25", {}) + with pytest.raises(KeyError): + methods.validate_client_result("roots/list", "2026-07-28", {}) with pytest.raises(pydantic.ValidationError): methods.validate_client_request("tools/call", "2025-11-25", None) with pytest.raises(pydantic.ValidationError): methods.validate_client_notification("notifications/progress", "2025-11-25", {"progressToken": []}) with pytest.raises(pydantic.ValidationError): methods.validate_server_result("tools/list", "2025-11-25", {"tools": 42}) + with pytest.raises(pydantic.ValidationError): + methods.validate_client_result("roots/list", "2025-11-25", {"roots": 42}) with pytest.raises(ValueError): methods.validate_client_request("ping", "2099-01-01", None)