diff --git a/docs/migration.md b/docs/migration.md index 850e052550..a506a4a168 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -221,6 +221,20 @@ 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`, `ttlMs`, and `cacheScope` defaults + +The 2026-07-28 protocol revision requires every result to carry a `resultType` member and adds cache directives to the cacheable results. The v2 result types model these as fields with serialized defaults rather than `None`-defaulted optional fields, so serialized results now always include them: + +- `CallToolResult`, `CompleteResult`, `DiscoverResult`, `GetPromptResult`, `ListPromptsResult`, `ListResourcesResult`, `ListResourceTemplatesResult`, `ListToolsResult`, and `ReadResourceResult` serialize `"resultType": "complete"`. +- `InputRequiredResult` serializes `"resultType": "input_required"`. +- The cacheable results (`DiscoverResult`, `ListPromptsResult`, `ListResourcesResult`, `ListResourceTemplatesResult`, `ListToolsResult`, `ReadResourceResult`) also serialize `"ttlMs": 0` and `"cacheScope": "private"`. + +Modeling these as defaults keeps a single set of result types valid across protocol versions without the wire layer injecting fields at serialization time. + +In v1 these keys never appeared in serialized results. Receivers ignore unknown result fields, so the added keys interoperate with peers on any protocol version, but tests or recorded fixtures that compare exact serialized result payloads need the new keys added. + +`EmptyResult` is the deliberate exception: its `result_type` defaults to `None`, so `EmptyResult()` still dumps `{}`. Several deployed SDK implementations validate empty results strictly and reject unexpected keys, so the SDK never volunteers `resultType` on an empty result. Code answering with an empty result on a session negotiated at 2026-07-28 or later must construct `EmptyResult(result_type="complete")` explicitly (use `mcp.shared.version.is_version_at_least` for the floor check). + ### `args` parameter removed from `ClientSessionGroup.call_tool()` The deprecated `args` parameter has been removed from `ClientSessionGroup.call_tool()`. Use `arguments` instead. @@ -1166,11 +1180,18 @@ In practice, replace direct `ServerSession` use with `Server.run(read_stream, wr `BaseSession` is still used by `ClientSession`, which never relied on these members. `RequestResponder.respond()` is unchanged. -### Experimental Tasks support removed +### Experimental Tasks support removed (types restored, types-only) + +Tasks (SEP-1686) runtime support has been removed from this SDK. The `mcp.client.experimental`, `mcp.server.experimental`, `mcp.shared.experimental`, and `mcp.server.lowlevel.experimental` modules are gone, along with the `experimental` properties on `ClientSession`, `ServerSession`, `Server`, and `ServerRequestContext`. + +The 2025-11-25 protocol *types* are back in `mcp.types` so that 2025-11-25 task payloads can still be modeled: the `Task*` types, the `tasks` capability subtrees, `Tool.execution`, and the `task` field on the four task-augmentable params classes. They are types-only definitions: -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`. +- Attributes are snake_case (`task_id`, `created_at`), aliased to the camelCase wire names. +- Timestamps are plain `str` values, not `datetime`. +- `GetTaskPayloadResult` follows the default extra-field policy (`ignore`), so it retains only `_meta`; validate a tasks/result payload into the original request's result type instead. +- None of the task methods is a member of the request/notification unions, and `add_request_handler` does not dispatch them. -Tasks are expected to return as a separate MCP extension in a future release. +Tasks runtime support is expected to return as a separate MCP extension in a future release. ## Deprecations @@ -1214,6 +1235,10 @@ If you relied on extra fields round-tripping through MCP types, move that data i ## New Features +### Newer protocol fields are modeled and retained + +`mcp.types` now models the 2025-11-25 and 2026-07-28 protocol fields and types (for example `resultType`, `ttlMs`/`cacheScope` on the cacheable results, and `inputResponses`/`requestState` on retried requests). Inbound payloads carrying these keys used to lose them to the unknown-field policy on re-dump; they now parse into typed fields and survive a user-level round-trip. Most of the new fields are optional with `None` defaults, so dumps of values that do not set them are unchanged; the result directive fields (`resultType`, `ttlMs`, `cacheScope`) instead carry serialized defaults — see [Results now serialize `resultType`, `ttlMs`, and `cacheScope` defaults](#results-now-serialize-resulttype-ttlms-and-cachescope-defaults) under Breaking Changes. + ### `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..9833c1f9b4 100644 --- a/src/mcp/shared/version.py +++ b/src/mcp/shared/version.py @@ -16,8 +16,13 @@ "2025-03-26", "2025-06-18", "2025-11-25", + "2026-07-28", ) -"""Every released protocol revision, oldest to newest.""" +"""Every protocol revision this SDK knows, oldest to newest. + +Knowing a revision (its types and wire shapes are modeled) is independent of +being able to negotiate it; see SUPPORTED_PROTOCOL_VERSIONS for the latter. +""" SUPPORTED_PROTOCOL_VERSIONS: list[str] = ["2024-11-05", "2025-03-26", "2025-06-18", LATEST_PROTOCOL_VERSION] """Protocol revisions this SDK can negotiate.""" diff --git a/src/mcp/types/__init__.py b/src/mcp/types/__init__.py index cb49ff29db..75436bf3fa 100644 --- a/src/mcp/types/__init__.py +++ b/src/mcp/types/__init__.py @@ -1,22 +1,35 @@ """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 + +The models here are the version-superset types user code works with; the +schema-exact wire shapes live in the surface packages +(``mcp.types.v2025_11_25``, which serves every protocol version through +2025-11-25, and ``mcp.types.v2026_07_28``). """ # 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 +46,9 @@ CreateMessageRequestParams, CreateMessageResult, CreateMessageResultWithTools, + CreateTaskResult, + DiscoverRequest, + DiscoverResult, ElicitationCapability, ElicitationRequiredErrorData, ElicitCompleteNotification, @@ -49,6 +65,12 @@ GetPromptRequest, GetPromptRequestParams, GetPromptResult, + GetTaskPayloadRequest, + GetTaskPayloadRequestParams, + GetTaskPayloadResult, + GetTaskRequest, + GetTaskRequestParams, + GetTaskResult, Icon, IconTheme, ImageContent, @@ -58,6 +80,12 @@ InitializeRequest, InitializeRequestParams, InitializeResult, + InputRequest, + InputRequests, + InputRequiredResult, + InputResponse, + InputResponseRequestParams, + InputResponses, ListPromptsRequest, ListPromptsResult, ListResourcesRequest, @@ -66,12 +94,15 @@ ListResourceTemplatesResult, ListRootsRequest, ListRootsResult, + ListTasksRequest, + ListTasksResult, ListToolsRequest, ListToolsResult, LoggingCapability, LoggingLevel, LoggingMessageNotification, LoggingMessageNotificationParams, + MissingRequiredClientCapabilityErrorData, ModelHint, ModelPreferences, Notification, @@ -92,6 +123,7 @@ ReadResourceRequest, ReadResourceRequestParams, ReadResourceResult, + RelatedTaskMetadata, Request, RequestParams, RequestParamsMeta, @@ -105,6 +137,7 @@ ResourceUpdatedNotification, ResourceUpdatedNotificationParams, Result, + ResultType, Role, Root, RootsCapability, @@ -124,17 +157,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,10 +195,13 @@ INTERNAL_ERROR, INVALID_PARAMS, INVALID_REQUEST, + JSONRPC_VERSION, METHOD_NOT_FOUND, + MISSING_REQUIRED_CLIENT_CAPABILITY, PARSE_ERROR, REQUEST_CANCELLED, REQUEST_TIMEOUT, + UNSUPPORTED_PROTOCOL_VERSION, URL_ELICITATION_REQUIRED, ErrorData, JSONRPCError, @@ -169,17 +217,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", @@ -187,10 +246,12 @@ "Result", "RequestParams", "RequestParamsMeta", + "InputResponseRequestParams", "NotificationParams", "PaginatedRequest", "PaginatedRequestParams", "PaginatedResult", + "CacheableResult", "EmptyResult", # Capabilities "ClientCapabilities", @@ -237,27 +298,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", @@ -266,23 +340,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", @@ -298,6 +384,10 @@ "ResourceUpdatedNotification", "ResourceUpdatedNotificationParams", "RootsListChangedNotification", + "SubscriptionsAcknowledgedNotification", + "SubscriptionsAcknowledgedNotificationParams", + "TaskStatusNotification", + "TaskStatusNotificationParams", "ToolListChangedNotification", # Union types for request/response routing "ClientNotification", @@ -318,10 +408,13 @@ "INTERNAL_ERROR", "INVALID_PARAMS", "INVALID_REQUEST", + "JSONRPC_VERSION", "METHOD_NOT_FOUND", + "MISSING_REQUIRED_CLIENT_CAPABILITY", "PARSE_ERROR", "REQUEST_CANCELLED", "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..4c80e22388 100644 --- a/src/mcp/types/_types.py +++ b/src/mcp/types/_types.py @@ -1,20 +1,50 @@ +"""The version-superset ("monolith") MCP protocol models. + +One model per protocol construct, carrying every field from every supported +protocol version, so application code handles a single set of types no +matter which version a session negotiated. Per-field docstrings record where +a field's availability differs across versions. The surface packages +(``mcp.types.v2025_11_25``, ``mcp.types.v2026_07_28``) carry the +schema-exact wire shapes; emission is the plain model dump at every +version. The newest schema covered here is +https://github.com/modelcontextprotocol/modelcontextprotocol/blob/main/schema/draft/schema.json. + +Integration status: statements in this module that the session layer (or the +client session) is responsible for supplying or materializing the reserved +``io.modelcontextprotocol/*`` ``_meta`` entries on sessions negotiated at +2026-07-28 or later describe an intended integration contract, not behavior +the SDK has today. No session code supplies those entries yet, and the SDK +cannot yet negotiate 2026-07-28; ``mcp.types.methods`` carries the matching +status note for the wire-method maps. +""" + 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. + +This is deliberately `Final[str]`, not a `Literal`: the value advances when SDK +support for a newer protocol revision ships, so callers must not narrow on the +current value. """ -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 +55,23 @@ """ ProgressToken = str | int +"""A progress token, used to associate progress notifications with the original request. + +Identical in every supported protocol version: a string or a number (the JSON form of +every schema version pins the numeric kind to integer; null is never allowed). A +requester places the token in a request's optional ``_meta.progressToken`` slot; the +receiver attaches the same token as the required ``progressToken`` field of any +``notifications/progress`` it chooses to emit, correlating the notification stream +back to the original request. +""" Role = Literal["user", "assistant"] +"""The sender or recipient of messages and data in a conversation. + +The value set is identical in every protocol version (2024-11-05 through 2026-07-28). +""" IconTheme = Literal["light", "dark"] +"""Theme an icon is designed for. Wire values of ``Icon.theme`` (2025-11-25+).""" class MCPModel(BaseModel): @@ -38,8 +82,51 @@ 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: the client session is responsible for supplying it on sessions +negotiated at 2026-07-28 or later. For the HTTP transport its value must +match the `MCP-Protocol-Version` header. +""" + +CLIENT_INFO_META_KEY = "io.modelcontextprotocol/clientInfo" +"""Reserved request `_meta` key: the client `Implementation` making the request (2026-07-28). + +SDK-managed: the client session is responsible for supplying it on every +request on sessions negotiated at 2026-07-28 or later. +""" + +CLIENT_CAPABILITIES_META_KEY = "io.modelcontextprotocol/clientCapabilities" +"""Reserved request `_meta` key: the client's per-request `ClientCapabilities` (2026-07-28). + +SDK-managed: the client session is responsible for supplying it on every +request on sessions negotiated at 2026-07-28 or later; servers must not +infer capabilities from prior requests. +""" + +LOG_LEVEL_META_KEY = "io.modelcontextprotocol/logLevel" +"""Reserved request `_meta` key: the desired log level for this request (2026-07-28). + +Replaces the former `logging/setLevel` RPC, and arrives already deprecated: +2026-07-28 deprecates the logging family as a whole (SEP-2577), so the same +revision that introduces this key also deprecates it. If absent, the server +must not send log notifications for this request. +""" + class RequestParamsMeta(TypedDict, extra_items=Any): + """The `_meta` object on request params (schema name: `RequestMetaObject`). + + An open map: arbitrary `_meta` keys — including the reserved + `io.modelcontextprotocol/*` keys — are preserved on round-trip via + ``extra_items=Any``. The reserved keys are SDK-managed session/request + state: the session layer is responsible for supplying all three reserved + entries on every request it sends on sessions negotiated at 2026-07-28 + or later; inbound, the 2026-07-28-and-later surface types require them. + Read or set them via the ``*_META_KEY`` constants. + """ + progress_token: NotRequired[ProgressToken] """ If specified, the caller requests out-of-band progress notifications for @@ -50,10 +137,25 @@ class RequestParamsMeta(TypedDict, extra_items=Any): class RequestParams(MCPModel): + """Common params for any request.""" + 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 sessions negotiated at + 2026-07-28 or later, the reserved `io.modelcontextprotocol/*` keys + (protocolVersion, clientInfo, clientCapabilities, plus the deprecated + logLevel). The field is required on the wire for 2026-07-28-and-later + client requests: the session layer is responsible for supplying all + three reserved entries when it sends a request; inbound, the + 2026-07-28-and-later surface types require them. Code sending requests + through an SDK session normally leaves this field unset. + """ class PaginatedRequestParams(RequestParams): + """Common params for paginated requests.""" + cursor: str | None = None """An opaque token representing the current pagination position. @@ -62,6 +164,8 @@ class PaginatedRequestParams(RequestParams): class NotificationParams(MCPModel): + """Common params for any notification.""" + 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) @@ -75,23 +179,65 @@ class NotificationParams(MCPModel): class Request(MCPModel, Generic[RequestParamsT, MethodT]): - """Base class for JSON-RPC requests.""" + """Base class for JSON-RPC requests. + + Concrete requests subclass this as + ``Request[, Literal[""]]`` and default the method + literal. The JSON-RPC envelope (``jsonrpc``, ``id``) is not part of this + payload type; it is attached by the session layer (see ``mcp.types.jsonrpc``). + """ method: MethodT + """The protocol method name identifying this request.""" + params: RequestParamsT + """The request's parameters; concrete subclasses set the per-method type and + requiredness.""" class PaginatedRequest(Request[PaginatedRequestParams | None, MethodT], Generic[MethodT]): """Base class for paginated requests, matching the schema's PaginatedRequest interface.""" params: PaginatedRequestParams | None = None + """Pagination params. + + Required on the wire for 2026-07-28-and-later peers (because `_meta` is + required there): the session layer is responsible for materializing + ``params`` and the reserved `_meta` entries on sessions negotiated at + 2026-07-28 or later. Optional on all earlier versions. + """ class Notification(MCPModel, Generic[NotificationParamsT, MethodT]): """Base class for JSON-RPC notifications.""" method: MethodT + """The notification method name.""" + params: NotificationParamsT + """The notification's parameters. + + Optional on the wire in every protocol version; concrete subclasses narrow + this to their params model, or to `NotificationParams | None = None` for + parameterless notifications. + """ + + +# The Literal arms document the spec-defined tags; the open str arm mirrors the +# schema's open union, so type checkers see plain str (no Literal narrowing). +ResultType = Literal["complete", "input_required"] | str +"""Indicates the type of a Result object, allowing the client to determine how to parse it. + +- "complete": the request completed successfully and the result contains the final content. +- "input_required": the request requires additional input; the result contains an + InputRequiredResult with instructions for the client to provide additional input + before retrying the original request. + +Introduced in protocol 2026-07-28. The union is open: values outside the two named +literals are reserved for future protocol versions and extensions (the tasks extension +reserves "task"). Pre-2026-07-28 peers never send the carrying field; the spec defines +an absent `resultType` as equivalent to "complete". +""" class Result(MCPModel): @@ -105,6 +251,13 @@ class Result(MCPModel): class PaginatedResult(Result): + """Base class for results of paginated list operations. + + Matches the schema's PaginatedResult interface; concrete list results + (ListToolsResult, ListResourcesResult, ListResourceTemplatesResult, + ListPromptsResult) subclass it. + """ + next_cursor: str | None = None """ An opaque token representing the pagination position after the last returned result. @@ -112,15 +265,60 @@ 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 wire for 2026-07-28 peers and are always + serialized by this SDK at every version (the defaults below are the + SDK's choice; the schema declares no defaults). They do not exist in the + 2025-11-25 and earlier schemas; older peers ignore the extra keys. + """ + + ttl_ms: Annotated[int, Field(ge=0)] = 0 + """How long, in milliseconds, the client MAY cache this response before + re-fetching -- analogous to HTTP Cache-Control max-age. + + 0 (the default) means the response SHOULD be considered immediately + stale; a positive value means the client SHOULD consider the result + fresh for that many milliseconds. Must be non-negative. + """ + + cache_scope: Literal["public", "private"] = "private" + """Intended scope of the cached response, analogous to HTTP + `Cache-Control: public` vs `Cache-Control: private`. + + With "public", any client or intermediary (e.g. a shared gateway or + proxy) MAY cache the response and serve it to any user; with "private" + (the default), only the requesting user's client MAY cache it, and + shared caches MUST NOT serve a cached copy to a different user. + """ + + class EmptyResult(Result): - """A response that indicates success but carries no data.""" + """A result that indicates success but carries no data. + + `result_type` is None by default, so a freshly constructed EmptyResult + dumps as `{}`. This is the one deliberate exception to the + "results default to complete" rule: deployed TypeScript SDK clients + (every released line, EmptyResultSchema.strict()) and the Rust SDK + validate EMPTY results strictly and reject extra keys, so the SDK never + volunteers `resultType` on an empty result. The 2026-07-28 schema makes + `resultType` required on every result, empty ones included; code + answering with an empty result on a session negotiated at 2026-07-28 or + later (`mcp.shared.version.is_version_at_least`) must construct + `EmptyResult(result_type="complete")` explicitly. + """ + + result_type: ResultType | None = None + """None (the default) 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,50 +332,95 @@ class BaseMetadata(MCPModel): class Icon(MCPModel): - """An icon for display in user interfaces.""" + """An optionally-sized icon that can be displayed in a user interface. + + Added in protocol 2025-11-25; carried in the optional ``icons`` array of + tools, resources, resource templates, prompts, and implementations. Never + present on the wire in earlier protocol versions. + """ src: str - """URL or data URI for the icon.""" + """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, and SHOULD take + appropriate precautions when consuming SVGs, as they can contain + executable JavaScript. + """ mime_type: str | None = None - """Optional MIME type for the icon.""" + """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 - """Optional list of strings specifying icon dimensions (e.g., ["48x48", "96x96"]).""" + """Optional array of strings specifying sizes at which the icon can be used. - theme: IconTheme | None = None - """Optional theme specifier. + 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 the icon can be used at any size. + """ - `"light"` indicates the icon is designed for a light background, `"dark"` indicates the icon - is designed for a dark background. + theme: IconTheme | None = None + """Optional specifier for the theme this icon is designed for. - See https://modelcontextprotocol.io/specification/2025-11-25/schema#icon for more details. + ``"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 Implementation(BaseMetadata): - """Describes the name and version of an MCP implementation.""" + """Describes the MCP implementation (``clientInfo`` / ``serverInfo``). - version: str + On sessions negotiated at 2025-11-25 or earlier, this is carried once per + session: client->server as ``InitializeRequestParams.client_info`` and + server->client as ``InitializeResult.server_info``. On 2026-07-28 sessions it + is carried per request in ``_meta["io.modelcontextprotocol/clientInfo"]`` and + server->client as ``DiscoverResult.server_info``. - title: str | None = None - """An optional human-readable title for this implementation.""" + Inherits ``name`` (required) and optional ``title`` from ``BaseMetadata``; + only ``name`` and ``version`` are required on the wire in every protocol + version. + """ + + version: str + """The version of this implementation.""" description: str | None = None - """An optional human-readable description of what this implementation does.""" + """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. + """ website_url: str | None = None """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 as a whole in protocol 2026-07-28 (SEP-2577) but kept in the + specification's deprecated-features registry, so 2026-07-28 sessions still + carry it on the wire — as an empty object, since ``list_changed`` below + exists only through 2025-11-25. + """ list_changed: bool | None = None - """Whether the client supports notifications for changes to the roots list.""" + """Whether the client supports notifications for changes to the roots list. + + Removed in protocol 2026-07-28 (the 2026-07-28 `roots` capability is an + empty object); meaningful on 2025-11-25 and earlier sessions. + """ class SamplingContextCapability(MCPModel): @@ -201,7 +444,10 @@ class FormElicitationCapability(MCPModel): class UrlElicitationCapability(MCPModel): - """Capability for URL mode elicitation.""" + """Capability for URL mode elicitation. + + New in protocol 2025-11-25, with URL mode itself (no earlier wire form). + """ class ElicitationCapability(MCPModel): @@ -214,11 +460,15 @@ 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, allowing fine-grained capability advertisement. + + The `sampling` capability as a whole is deprecated in protocol 2026-07-28 + (SEP-2577) but its shape is unchanged there; used on all sessions. + """ context: SamplingContextCapability | None = None """ @@ -232,8 +482,92 @@ class SamplingCapability(MCPModel): """ +class TasksListCapability(MCPModel): + """Capability for tasks listing operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class TasksCancelCapability(MCPModel): + """Capability for tasks cancel operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class TasksCreateMessageCapability(MCPModel): + """Capability for task-augmented sampling/createMessage requests. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class TasksSamplingCapability(MCPModel): + """Capability for task-augmented sampling operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + create_message: TasksCreateMessageCapability | None = None + """Whether the client supports task-augmented sampling/createMessage.""" + + +class TasksCreateElicitationCapability(MCPModel): + """Capability for task-augmented elicitation/create requests. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class TasksElicitationCapability(MCPModel): + """Capability for task-augmented elicitation operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + create: TasksCreateElicitationCapability | None = None + """Whether the client supports task-augmented elicitation/create.""" + + +class ClientTasksRequestsCapability(MCPModel): + """Specifies which request types the client can augment with tasks. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + sampling: TasksSamplingCapability | None = None + """Task support for sampling requests.""" + + elicitation: TasksElicitationCapability | None = None + """Task support for elicitation requests.""" + + +class ClientTasksCapability(MCPModel): + """Capability for client tasks operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + list: TasksListCapability | None = None + """Whether this client supports tasks/list.""" + + cancel: TasksCancelCapability | None = None + """Whether this client supports tasks/cancel.""" + + requests: ClientTasksRequestsCapability | None = None + """Specifies which request types can be augmented with tasks.""" + + class ClientCapabilities(MCPModel): - """Capabilities a client may support.""" + """Capabilities a client may support. + + Known capabilities are defined in the spec schema, but this is not a closed + set: any client can define its own, additional capabilities. On protocol + versions through 2025-11-25 this object is sent once, in `initialize`; on + 2026-07-28 sessions the SDK carries it in every request's `_meta` under + "io.modelcontextprotocol/clientCapabilities". + """ experimental: dict[str, dict[str, Any]] | None = None """Experimental, non-standard capabilities that the client supports.""" @@ -246,6 +580,45 @@ 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 + """Optional MCP extensions that the client supports (2026-07-28). + + Keys are extension identifiers (e.g. "io.modelcontextprotocol/oauth-client-credentials"), + values are per-extension settings objects; an empty object indicates support + with 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). + + Servers return this when a request claims a protocol version they do not + support. The client should choose a mutually supported version from + ``supported`` and retry the request. + """ + + supported: list[str] + """Protocol versions the server supports. + + The client should choose a mutually supported version from this list and retry. + """ + + requested: str + """The protocol version that was requested by the client.""" + + +class MissingRequiredClientCapabilityErrorData(MCPModel): + """Error data for the 2026-07-28 MissingRequiredClientCapabilityError (-32003). + + Servers return this when processing a request requires a capability the + client did not declare in the request's `clientCapabilities`. The client + should re-send the request declaring the listed capabilities (or fail). + """ + + required_capabilities: ClientCapabilities + """The capabilities the server requires from the client to process this request.""" class PromptsCapability(MCPModel): @@ -279,14 +652,65 @@ class CompletionsCapability(MCPModel): """Capability for completions operations.""" +class TasksCallCapability(MCPModel): + """Capability for task-augmented tools/call requests. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class TasksToolsCapability(MCPModel): + """Capability for task-augmented tool operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + call: TasksCallCapability | None = None + """Whether the server supports task-augmented tools/call.""" + + +class ServerTasksRequestsCapability(MCPModel): + """Specifies which request types the server can augment with tasks. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + tools: TasksToolsCapability | None = None + """Task support for tool requests.""" + + +class ServerTasksCapability(MCPModel): + """Capability for server tasks operations. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + list: TasksListCapability | None = None + """Whether this server supports tasks/list.""" + + cancel: TasksCancelCapability | None = None + """Whether this server supports tasks/cancel.""" + + requests: ServerTasksRequestsCapability | None = None + """Specifies which request types can be augmented with tasks.""" + + class ServerCapabilities(MCPModel): - """Capabilities that a server may support.""" + """Capabilities that a server may support. + + Known capabilities are defined here, but this is not a closed set: any + server can define its own, additional capabilities. + """ 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 as of protocol version 2026-07-28 (SEP-2577); remains valid on + earlier-version sessions. + """ prompts: PromptsCapability | None = None """Present if the server offers any prompt templates.""" @@ -300,39 +724,95 @@ 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 + """Optional MCP extensions that the server supports (2026-07-28). + + Keys are extension identifiers (e.g. "io.modelcontextprotocol/tasks"); + values are per-extension settings objects. An empty object indicates + support with no settings. + """ + + tasks: ServerTasksCapability | None = None + """Present if the server supports task-augmented requests (2025-11-25 only).""" + + +# Lifecycle handshake (removed in protocol 2026-07-28). +# +# Protocol 2026-07-28 removed the initialize handshake and ping in favor of +# `server/discover` plus per-request `_meta`. The handshake types stay defined +# because earlier-version sessions still use them; every type the 2026-07-28 +# revision removed — here and elsewhere in this module — carries the same +# "Removed in protocol 2026-07-28" docstring line. +# Alternative considered: moving the removed types to a `mcp.types.legacy` module behind PEP 562 aliases. + 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.""" + """The latest version of the Model Context Protocol that the client supports. + + The client MAY decide to support older versions as well. + """ + capabilities: ClientCapabilities + """The capabilities the client supports.""" + client_info: Implementation + """Information about the client implementation.""" 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. + The `server/discover` flow plus per-request `_meta` replace the handshake there. """ method: Literal["initialize"] = "initialize" + """The protocol method name (`initialize`).""" + params: InitializeRequestParams + """The initialization parameters (required in every protocol version that + has this request).""" 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. + The 2026-07-28 revision replaces the initialize handshake with `server/discover` + (see `DiscoverResult`). + """ 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. + + This may not match the version that the client requested. If the client + cannot support this version, it MUST disconnect. + """ capabilities: ServerCapabilities + """The capabilities of the server.""" server_info: Implementation + """Information about the server 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. + + 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. + """ 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 +821,297 @@ 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"]]): + """A request from the client asking the server to advertise its supported + protocol versions, capabilities, and other metadata (2026-07-28 only). + + Servers speaking 2026-07-28 MUST implement ``server/discover``; clients MAY + call it but are not required to - version negotiation can also happen + inline via per-request ``_meta``. + """ + + method: Literal["server/discover"] = "server/discover" + params: RequestParams | None = None + """Required on the 2026-07-28 wire (its ``_meta`` must carry the reserved + ``io.modelcontextprotocol/*`` keys). The session layer is responsible + for materializing ``params`` and supplying all three reserved entries, + the same contract as for every other 2026-07-28 request; nothing is + injected at dump time (emission is the plain model dump). + """ + + +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 choose a version from this list for use in subsequent requests. + """ + + capabilities: ServerCapabilities + """The capabilities of the server.""" + + server_info: Implementation + """Information about the server software implementation.""" + + instructions: str | None = None + """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: ResultType = "complete" + """Discriminates complete results from input-required results (2026-07-28). + + Defaults to "complete" and is always serialized; pre-2026-07-28 peers + ignore the extra key on non-empty results. Inbound bodies without the + field (every pre-2026-07-28 peer) take the default, which the spec + defines as equivalent to "complete". The union is open: a value outside + the named literals (a future protocol's tag) is preserved as-is rather + than rejected. + """ + + +# Tasks (removed in protocol 2026-07-28). +# +# Protocol 2025-11-25 introduced task-augmented requests; protocol 2026-07-28 +# removed them from the core specification (tasks continue as a protocol +# extension). The 2025-11-25 task types are defined here types-only: none of +# their methods appear in the request/notification unions below or in the +# per-version method tables, so they are never dispatched. +# Alternative considered: a `mcp/extensions/tasks/` package carrying the extension's task types would attach here. + + +class ToolExecution(MCPModel): + """Execution-related properties for a tool. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating + 2025-11-25 (introduced with the experimental core tasks support; the + tasks extension has no per-tool execution declaration). + """ + + task_support: Literal["forbidden", "optional", "required"] | None = None + """ + 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 TaskMetadata(MCPModel): + """Metadata for augmenting a request with task execution. + + Include this in the `task` field of the request parameters. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating + 2025-11-25 (the tasks extension has no request-side task-creation + metadata). + """ + + ttl: int | None = None + """Requested duration in milliseconds to retain task from creation.""" + + +class RelatedTaskMetadata(MCPModel): + """Metadata for associating messages with a task. + + Include this in the ``_meta`` field under the key + ``io.modelcontextprotocol/related-task``. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + task_id: str + """The task identifier this message is associated with.""" + + +TaskStatus = Literal["working", "input_required", "completed", "failed", "cancelled"] +"""The status of a task. + +Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. +""" + + +class Task(MCPModel): + """Data associated with a task. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + task_id: str + """The task identifier.""" + + status: TaskStatus + """Current task state.""" + + status_message: str | None = 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) + """ + + 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. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + task: Task + + +class GetTaskRequestParams(RequestParams): + """Parameters for a tasks/get request.""" + + task_id: str + """The task identifier to query.""" + + +class GetTaskRequest(Request[GetTaskRequestParams, Literal["tasks/get"]]): + """A request to retrieve the state of a task. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + method: Literal["tasks/get"] = "tasks/get" + params: GetTaskRequestParams + + +class GetTaskResult(Result, Task): + """The response to a tasks/get request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class CancelTaskRequestParams(RequestParams): + """Parameters for a tasks/cancel request.""" + + task_id: str + """The task identifier to cancel.""" + + +class CancelTaskRequest(Request[CancelTaskRequestParams, Literal["tasks/cancel"]]): + """A request to cancel a task. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + method: Literal["tasks/cancel"] = "tasks/cancel" + params: CancelTaskRequestParams + + +class CancelTaskResult(Result, Task): + """The response to a tasks/cancel request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class TaskStatusNotificationParams(NotificationParams, Task): + """Parameters for a `notifications/tasks/status` notification.""" + + +class TaskStatusNotification(Notification[TaskStatusNotificationParams, Literal["notifications/tasks/status"]]): + """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. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + method: Literal["notifications/tasks/status"] = "notifications/tasks/status" + params: TaskStatusNotificationParams + + +class GetTaskPayloadRequestParams(RequestParams): + """Parameters for a tasks/result request.""" + + task_id: str + """The task identifier to retrieve results for.""" + + +class GetTaskPayloadRequest(Request[GetTaskPayloadRequestParams, Literal["tasks/result"]]): + """A request to retrieve the result of a completed task. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating + 2025-11-25 (the tasks extension delivers terminal payloads inline in + tasks/get responses instead). + """ + + method: Literal["tasks/result"] = "tasks/result" + params: GetTaskPayloadRequestParams + + +class GetTaskPayloadResult(Result): + """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. The payload arrives + as extra wire fields on this open object, which the SDK's default extra-field + policy does not retain: validating a tasks/result response into this class + keeps only ``_meta``. Callers that know the original request should validate + the response into that request's result type (e.g. ``CallToolResult``) + instead, and custom server handlers should return the original request's + result object directly rather than wrapping it in this class. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + +class ListTasksRequest(PaginatedRequest[Literal["tasks/list"]]): + """A request to retrieve a list of tasks. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating + 2025-11-25 (the tasks extension deliberately drops tasks/list). + """ + + method: Literal["tasks/list"] = "tasks/list" + + +class ListTasksResult(PaginatedResult): + """The response to a tasks/list request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating 2025-11-25. + """ + + tasks: list[Task] + """The list of tasks.""" + + class ProgressNotificationParams(NotificationParams): """Parameters for progress notifications.""" @@ -384,8 +1148,29 @@ class ListResourcesRequest(PaginatedRequest[Literal["resources/list"]]): class Annotations(MCPModel): + """Optional annotations for the client. + + The client can use annotations to inform how objects are used or displayed. + Carried as the optional ``annotations`` field of resources, resource + templates, and content blocks in every protocol version (on 2024-11-05 the + same object is carried anonymously via the schema's ``Annotated`` base + interface). + """ + 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"]``). + """ + priority: Annotated[float, Field(ge=0.0, le=1.0)] | None = None + """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 Resource(BaseMetadata): @@ -395,7 +1180,11 @@ class Resource(BaseMetadata): """The URI of this resource.""" description: str | None = None - """A description of what this resource represents.""" + """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. + """ mime_type: str | None = None """The MIME type of this resource, if known.""" @@ -407,15 +1196,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 +1212,11 @@ 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. + + 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. + """ mime_type: str | None = None """The MIME type for all resources that match this template. @@ -434,9 +1225,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 +1237,22 @@ 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] + """The list of resources the server offers.""" + + result_type: ResultType = "complete" + """Discriminates complete results from input-required results (2026-07-28). + + Defaults to "complete" and is always serialized; pre-2026-07-28 peers + ignore the extra key on non-empty results. Inbound bodies without the + field (every pre-2026-07-28 peer) take the default, which the spec + defines as equivalent to "complete". The union is open: a value outside + the named literals (a future protocol's tag) is preserved as-is rather + than rejected. + """ class ListResourceTemplatesRequest(PaginatedRequest[Literal["resources/templates/list"]]): @@ -457,19 +1261,51 @@ 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] + """The list of resource templates the server offers.""" + + result_type: ResultType = "complete" + """Discriminates complete results from input-required results (2026-07-28). + + Defaults to "complete" and is always serialized; pre-2026-07-28 peers + ignore the extra key on non-empty results. Inbound bodies without the + field (every pre-2026-07-28 peer) take the default, which the spec + defines as equivalent to "complete". The union is open: a value outside + the named literals (a future protocol's tag) is preserved as-is rather + than rejected. + """ + + +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 previously returned an InputRequiredResult, the client + retries the original request with these fields populated. Extended by + CallToolRequestParams, GetPromptRequestParams and ReadResourceRequestParams. + """ + input_responses: InputResponses | None = None + """Responses to the server's input requests from the InputRequiredResult. -class ReadResourceRequestParams(RequestParams): - """Parameters for reading a resource.""" + For each key in the InputRequiredResult's inputRequests map, the same key + must appear here with the client's result for that request. + """ + request_state: str | None = None + """Opaque request state from the InputRequiredResult, passed back to the + server verbatim when the client retries the original request.""" + + +class ReadResourceRequestParams(InputResponseRequestParams): + """Parameters for a `resources/read` request.""" 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 +1347,22 @@ 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" + """Discriminates complete results from input-required results (2026-07-28). + + Defaults to "complete" and is always serialized; pre-2026-07-28 peers + ignore the extra key on non-empty results. Inbound bodies without the + field (every pre-2026-07-28 peer) take the default, which the spec + defines as equivalent to "complete". The union is open: a value outside + the named literals (a future protocol's tag) is preserved as-is rather + than rejected. + """ class ResourceListChangedNotification( @@ -522,6 +1370,11 @@ class ResourceListChangedNotification( ): """An optional notification from the server to the client, informing it that the list of resources it can read from has changed. + + On protocol versions up to 2025-11-25, servers may send this spontaneously, + without any previous subscription from the client. On 2026-07-28 sessions, + delivery is opt-in: the server must not send it unless the client requested it + via SubscriptionFilter.resources_list_changed on a subscriptions/listen request. """ method: Literal["notifications/resources/list_changed"] = "notifications/resources/list_changed" @@ -529,7 +1382,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,60 +1397,157 @@ 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. + 2026-07-28 sessions replace per-URI subscribe with ``subscriptions/listen`` + (``SubscriptionsListenRequest``). + """ + + method: Literal["resources/subscribe"] = "resources/subscribe" + params: SubscribeRequestParams + + +class UnsubscribeRequestParams(RequestParams): + """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. This should follow a previous resources/subscribe request. + + Removed in protocol 2026-07-28; sent/received on sessions negotiating <= 2025-11-25. + 2026-07-28 peers manage resource subscriptions declaratively via subscriptions/listen + (SubscriptionsListenRequest) instead. + """ + + method: Literal["resources/unsubscribe"] = "resources/unsubscribe" + params: UnsubscribeRequestParams + + +class ResourceUpdatedNotificationParams(NotificationParams): + """Parameters for a `notifications/resources/updated` notification.""" + + 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. + """ + + +class ResourceUpdatedNotification( + Notification[ResourceUpdatedNotificationParams, Literal["notifications/resources/updated"]] +): + """A notification from the server to the client, informing it that a resource has + changed and may need to be read again. + + On sessions negotiated at 2025-11-25 or earlier, this should only be sent if the + client previously sent a `resources/subscribe` request. On 2026-07-28 sessions, + it is only sent for resources the client opted in to via the + `resourceSubscriptions` field of a `subscriptions/listen` request. + """ + + method: Literal["notifications/resources/updated"] = "notifications/resources/updated" + params: ResourceUpdatedNotificationParams + + +class SubscriptionFilter(MCPModel): + """The set of notification types a client may opt in to on a + subscriptions/listen request (2026-07-28). + + Each notification type is opt-in; the server MUST NOT send notification + types the client has not explicitly requested here. The same shape is + echoed back by the server in notifications/subscriptions/acknowledged as + the subset it agreed to honor. + + Extensions merge additional keys into this object on the wire (e.g. the + tasks extension's ``taskIds``), so unknown keys are preserved on + round-trip rather than ignored. + """ + + # Alternative considered: a codec-facing extra="allow" parse layer on all models instead of this single carve-out. + 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. + + Replaces the former resources/subscribe RPC. """ - method: Literal["resources/subscribe"] = "resources/subscribe" - params: SubscribeRequestParams +class SubscriptionsListenRequestParams(RequestParams): + """Parameters for a subscriptions/listen request (2026-07-28).""" -class UnsubscribeRequestParams(RequestParams): - """Parameters for unsubscribing from a resource.""" + notifications: SubscriptionFilter + """The notifications the client opts in to on this stream. - uri: str - """The URI of the resource to unsubscribe from.""" + The server MUST NOT send notification types the client has not explicitly + requested. + """ -class UnsubscribeRequest(Request[UnsubscribeRequestParams, Literal["resources/unsubscribe"]]): - """Sent from the client to request cancellation of resources/updated notifications from - the server. +class SubscriptionsListenRequest(Request[SubscriptionsListenRequestParams, Literal["subscriptions/listen"]]): + """Sent from the client to open a long-lived channel for receiving notifications + outside the context of a specific request (2026-07-28). + + Replaces the previous HTTP GET endpoint and ensures consistent behavior between + HTTP and STDIO. """ - method: Literal["resources/unsubscribe"] = "resources/unsubscribe" - params: UnsubscribeRequestParams + method: Literal["subscriptions/listen"] = "subscriptions/listen" + params: SubscriptionsListenRequestParams -class ResourceUpdatedNotificationParams(NotificationParams): - """Parameters for resource update notifications.""" +class SubscriptionsAcknowledgedNotificationParams(NotificationParams): + """Parameters for a notifications/subscriptions/acknowledged notification.""" - 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. + notifications: SubscriptionFilter + """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 ResourceUpdatedNotification( - Notification[ResourceUpdatedNotificationParams, Literal["notifications/resources/updated"]] +class SubscriptionsAcknowledgedNotification( + Notification[ + SubscriptionsAcknowledgedNotificationParams, + Literal["notifications/subscriptions/acknowledged"], + ] ): - """A notification from the server to the client, informing it that a resource has - changed and may need to be read again. + """Sent by the server as the first message on a subscriptions/listen stream + to acknowledge that the subscription has been established and to report + which notification types it agreed to honor (2026-07-28). """ - method: Literal["notifications/resources/updated"] = "notifications/resources/updated" - params: ResourceUpdatedNotificationParams + 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,14 +1570,26 @@ 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] + """The list of prompts and prompt templates the server offers.""" + + result_type: ResultType = "complete" + """Discriminates complete results from input-required results (2026-07-28). + + Defaults to "complete" and is always serialized; pre-2026-07-28 peers + ignore the extra key on non-empty results. Inbound bodies without the + field (every pre-2026-07-28 peer) take the default, which the spec + defines as equivalent to "complete". The union is open: a value outside + the named literals (a future protocol's tag) is preserved as-is rather + than rejected. + """ -class GetPromptRequestParams(RequestParams): - """Parameters for getting a prompt.""" +class GetPromptRequestParams(InputResponseRequestParams): + """Parameters for a prompts/get request.""" name: str """The name of the prompt or prompt template.""" @@ -640,12 +1605,14 @@ 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" + """Content-type discriminator; always "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,9 +1621,10 @@ class TextContent(MCPModel): class ImageContent(MCPModel): - """Image content for a message.""" + """An image provided to or from an LLM.""" type: Literal["image"] = "image" + """Discriminator for image content.""" data: str """The base64-encoded image data.""" mime_type: str @@ -665,17 +1633,16 @@ 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" + """Discriminator identifying this content block as audio.""" data: str """The base64-encoded audio data.""" mime_type: str @@ -684,6 +1651,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) @@ -694,9 +1662,15 @@ class AudioContent(MCPModel): class ToolUseContent(MCPModel): """Content representing an assistant's request to invoke a tool. - 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. + This content type appears in assistant messages when the LLM wants to call a + tool during sampling-with-tools: in the content of a `sampling/createMessage` + result, and in assistant-role messages replayed in subsequent + `sampling/createMessage` requests. The server should execute the tool and + return a ToolResultContent in the next user message. + + Available on 2025-11-25 and 2026-07-28 sessions only. Deprecated as of + protocol 2026-07-28 (SEP-2577) but remains in the specification for at least + twelve months and stays fully supported here. """ type: Literal["tool_use"] = "tool_use" @@ -712,48 +1686,76 @@ 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 about the tool use. + + Clients SHOULD preserve this field when including tool uses 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. - 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 (`sampling/createMessage`) as a response to a + ToolUseContent block from the assistant; `tool_use_id` MUST match the `id` of + that block. Requires the `sampling.tools` client capability (2025-11-25 and + later). Deprecated as of protocol 2026-07-28 (SEP-2577) but remains valid on + 2026-07-28 sessions for at least twelve months. """ 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 tool use this result corresponds to. - content: list[ContentBlock] = [] - """ - A list of content objects representing the tool result. - Defaults to empty list if not provided. + This MUST match the ID from a previous ToolUseContent. """ - structured_content: dict[str, Any] | None = None + content: list[ContentBlock] = [] + """The unstructured result content of the tool use. + + Same format as CallToolResult.content: text, images, audio, resource links, + and embedded resources. """ - Optional structured tool output that matches the tool's outputSchema (if defined). + + structured_content: Any = None + """An optional structured result value. + + On 2026-07-28 sessions this can be any JSON value (object, array, string, + number, boolean, or None); 2025-11-25 restricts it to a JSON object. If the + tool defined an outputSchema, this SHOULD conform to that schema. """ is_error: bool | None = None - """Whether the tool execution resulted in an error.""" + """Whether the tool use resulted in an error. - meta: Meta | None = Field(alias="_meta", default=None) + If true, the content typically describes the error that occurred. Absent is + equivalent to false. """ - See [MCP specification](https://github.com/modelcontextprotocol/modelcontextprotocol/blob/47339c03c143bb4ec01a26e721a1b8fe66634ebe/docs/specification/draft/basic/index.mdx#general-fields) - for notes on _meta usage. + + meta: Meta | None = Field(alias="_meta", default=None) + """Optional metadata about the tool result. + + Clients SHOULD preserve this field when including tool results 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 / 2026-07-28) membership. On older sessions only +a subset is legal on the wire (text/image on 2024-11-05; text/image/audio on +2025-03-26 and 2025-06-18); serialization never narrows a value to fit -- +tool blocks (and arrays of blocks) dump unchanged on 2025-06-18 and earlier, +just like audio content. Holding back a block type a peer's version predates +is the session layer's capability and version gating, not serialization's. + +Deprecated (with the rest of the sampling family) as of protocol 2026-07-28 by +SEP-2577; remains in the specification for at least twelve months and stays +fully supported here for all pre-2026-07-28 sessions. +""" SamplingContent: TypeAlias = TextContent | ImageContent | AudioContent """Basic content types for sampling responses (without tool use). @@ -766,6 +1768,7 @@ class SamplingMessage(MCPModel): """Describes a message issued to or received from an LLM API.""" role: Role + """The role of the message sender ("user" or "assistant").""" content: SamplingMessageContentBlock | list[SamplingMessageContentBlock] """ Message content. Can be a single content block or an array of content blocks @@ -792,8 +1795,11 @@ class EmbeddedResource(MCPModel): """ type: Literal["resource"] = "resource" + """Discriminator for embedded resource content blocks.""" resource: TextResourceContents | BlobResourceContents + """The text or binary contents of the embedded resource.""" 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,10 +1821,16 @@ class ResourceLink(Resource): class PromptMessage(MCPModel): - """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. + """ role: Role + """The sender or recipient of this message in the conversation.""" content: ContentBlock + """The message content: text, image, audio, a resource link, or an embedded resource.""" class GetPromptResult(Result): @@ -827,6 +1839,18 @@ 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" + """Discriminates complete results from input-required results (2026-07-28). + + Defaults to "complete" and is always serialized; pre-2026-07-28 peers + ignore the extra key on non-empty results. Inbound bodies without the + field (every pre-2026-07-28 peer) take the default, which the spec + defines as equivalent to "complete". The union is open: a value outside + the named literals (a future protocol's tag) is preserved as-is rather + than rejected. + """ class PromptListChangedNotification( @@ -834,6 +1858,12 @@ class PromptListChangedNotification( ): """An optional notification from the server to the client, informing it that the list of prompts it offers has changed. + + On sessions negotiated at 2025-11-25 or earlier, servers may send this + spontaneously, without any previous subscription from the client. On + 2026-07-28 sessions delivery is opt-in: the server MUST NOT send it unless + the client requested it via ``subscriptions/listen`` + (``SubscriptionFilter.prompts_list_changed``). """ method: Literal["notifications/prompts/list_changed"] = "notifications/prompts/list_changed" @@ -896,36 +1926,85 @@ class Tool(BaseMetadata): """Definition for a tool the client can call.""" description: str | None = None - """A human-readable description of the tool.""" + """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. + """ 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. + + Tool arguments are always JSON objects, so `type: "object"` is required at the + root. On 2026-07-28 sessions any JSON Schema 2020-12 keyword may appear + alongside `type` (composition, conditional, and reference keywords included); + earlier protocol versions define only `type`, `properties`, and `required` + (plus `$schema` from 2025-11-25). Defaults to JSON Schema 2020-12 when no + explicit `$schema` is provided. """ - An optional JSON Schema object defining the structure of the tool's output + execution: ToolExecution | None = None + """Execution-related properties for this tool. + + 2025-11-25 only; removed in protocol 2026-07-28 (tasks continue as an + extension). + """ + 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 on 2025-06-18 and 2025-11-25 + sessions; any valid JSON Schema 2020-12 on 2026-07-28. Defaults to JSON + Schema 2020-12 when no explicit `$schema` is provided. """ icons: list[Icon] | None = None - """An optional list of icons for this tool.""" + """Optional set of sized icons that the client can display in a user + interface (2025-11-25 and later).""" annotations: ToolAnnotations | None = None - """Optional additional tool information.""" - 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 additional tool information. + + Display name precedence order is: title, annotations.title, then name. """ + meta: Meta | None = Field(alias="_meta", default=None) + """See the MCP specification's general-fields documentation 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" + """Discriminates complete results from input-required results (2026-07-28). + + Defaults to "complete" and is always serialized; pre-2026-07-28 peers + ignore the extra key on non-empty results. Inbound bodies without the + field (every pre-2026-07-28 peer) take the default, which the spec + defines as equivalent to "complete". The union is open: a value outside + the named literals (a future protocol's tag) is preserved as-is rather + than rejected. + """ + -class CallToolRequestParams(RequestParams): - """Parameters for calling a tool.""" +class CallToolRequestParams(InputResponseRequestParams): + """Parameters for a `tools/call` request.""" name: str + """The name of the tool.""" + arguments: dict[str, Any] | None = None + """Arguments to use for the tool call.""" + + task: TaskMetadata | None = None + """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. + + 2025-11-25 only; removed in protocol 2026-07-28 (tasks continue as an extension). + """ class CallToolRequest(Request[CallToolRequestParams, Literal["tools/call"]]): @@ -936,17 +2015,54 @@ 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. + + Any errors that originate from the tool SHOULD be reported inside the result + object, with `is_error` 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. + """ 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.""" + # Alternative considered: an Unset-sentinel default distinguishing wire-absent from explicit null. + structured_content: Any = None + """An optional JSON value that represents the structured result of the tool call. + + On 2026-07-28 sessions this can be any JSON value (object, array, string, + number, boolean, or null) that conforms to the tool's output schema if one is + defined; 2025-06-18 and 2025-11-25 restrict it to a JSON object on the wire. + """ is_error: bool = False + """Whether the tool call ended in an error. + + If not set, this is assumed to be false (the call was successful). + """ + + result_type: ResultType = "complete" + """Discriminates complete results from input-required results (2026-07-28). + + Defaults to "complete" and is always serialized; pre-2026-07-28 peers + ignore the extra key on non-empty results. Inbound bodies without the + field (every pre-2026-07-28 peer) take the default, which the spec + defines as equivalent to "complete". The union is open: a value outside + the named literals (a future protocol's tag) is preserved as-is rather + than rejected. + """ 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. + + On protocol versions through 2025-11-25, servers may send this without any previous + subscription from the client. On 2026-07-28 sessions, delivery is opt-in: the server + sends it only if the client requested it via `subscriptions/listen` + (`SubscriptionFilter.tools_list_changed`). """ method: Literal["notifications/tools/list_changed"] = "notifications/tools/list_changed" @@ -954,24 +2070,50 @@ 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 message severities, as specified in RFC-5424: +https://datatracker.ietf.org/doc/html/rfc5424#section-6.2.1 + +The value set is identical in every protocol version (2024-11-05 through 2026-07-28). +Protocol 2026-07-28 deprecates the logging family as a whole (SEP-2577) but keeps it +fully functional for at least twelve months; the level scale itself is unchanged. +""" 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 (i.e., more severe) + to the client as notifications/message. + """ 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 sessions the client opts in to log messages + per-request via the `io.modelcontextprotocol/logLevel` key in `_meta` + instead. + """ method: Literal["logging/setLevel"] = "logging/setLevel" params: SetLevelRequestParams class LoggingMessageNotificationParams(NotificationParams): - """Parameters for logging message notifications.""" + """Parameters for a `notifications/message` notification. + + Deprecated as of protocol 2026-07-28 (SEP-2577) but still part of that + version; fully supported on all earlier protocol versions. + """ level: LoggingLevel """The severity of this log message.""" @@ -985,24 +2127,61 @@ 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. + + On protocol versions through 2025-11-25, the client subscribes via + `logging/setLevel`; if it never did, the server MAY decide which messages to + send automatically. On 2026-07-28 sessions the client instead opts in + per-request via the `io.modelcontextprotocol/logLevel` `_meta` key, and the + server MUST NOT send this notification for a request without it (a + session-layer obligation; this type does not validate the send condition). + Deprecated as of protocol 2026-07-28 (SEP-2577) but still part of that version. + """ 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. + +The "thisServer" and "allServers" values are deprecated as of protocol +2025-11-25 (SEP-2596); servers SHOULD omit the field or use "none" unless the +client declares the sampling.context capability. +""" class ModelHint(MCPModel): - """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. + + Deprecated as of protocol 2026-07-28 (SEP-2577) together with the rest of + the sampling family; remains in the specification for at least twelve + months and is still carried by embedded sampling requests on 2026-07-28 + sessions. + """ name: str | None = None - """A hint for a model name.""" + """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(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 +2193,10 @@ 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 as of protocol 2026-07-28 (SEP-2577), along with the rest of the + sampling family, but remains in the specification for at least twelve months + and stays fully supported here. """ hints: list[ModelHint] | None = None @@ -1050,63 +2233,112 @@ class ModelPreferences(MCPModel): class ToolChoice(MCPModel): - """Controls tool usage behavior during sampling. + """Controls tool selection behavior for sampling requests. - Allows the server to specify whether and how the LLM should use tools - in its response. + Sent by servers as `CreateMessageRequestParams.tool_choice` + (sampling-with-tools, protocol 2025-11-25 and later). The client MUST + return an error if it receives the carrying field without having declared + `ClientCapabilities.sampling.tools`. When the carrying field is absent, + the default is `{"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.""" + """Parameters for a sampling/createMessage request.""" 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 these preferences. """ system_prompt: str | None = None - """An optional system prompt the server wants to use for sampling.""" + """ + An optional system prompt the server wants to use for sampling. The client + MAY modify or omit this prompt. + """ 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". The "thisServer" and "allServers" values are deprecated + (SEP-2596): servers SHOULD only send them if the client declares the + sampling.context capability. """ temperature: float | None = None + """Sampling temperature requested by the server.""" max_tokens: int - """The maximum number of tokens to sample, as requested by the server.""" + """ + The requested maximum number of tokens to sample (to prevent runaway + completions). The client MAY choose to sample fewer tokens than the + requested maximum. + """ stop_sequences: list[str] | None = None + """Sequences at which the client should stop sampling.""" metadata: dict[str, Any] | None = None - """Optional metadata to pass through to the LLM provider.""" + """ + Optional metadata to pass through to the LLM provider. The format of this + metadata is provider-specific. + """ tools: list[Tool] | None = None """ - Tool definitions for the LLM to use during sampling. - Requires clientCapabilities.sampling.tools to be present. + Tools that the model may use during generation (protocol 2025-11-25 and + later). The client MUST return an error if this field is provided but the + sampling.tools client capability is not declared. """ 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 (protocol 2025-11-25 and later). The + client MUST return an error if this field is provided but the + sampling.tools client capability is not declared. Default is mode="auto". + """ + task: TaskMetadata | None = None + """If specified, the caller is requesting task-augmented execution for this request. + + 2025-11-25 sessions only; removed in protocol 2026-07-28 (tasks continue as an + extension). """ 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. 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. + + On 2024-11-05 through 2025-11-25 sessions this is a standalone JSON-RPC + server-to-client request. On 2026-07-28 sessions the same payload is + instead embedded in InputRequiredResult.input_requests and is never sent + as a JSON-RPC request. Deprecated as of protocol 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. + +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 (2025-11-25 and later) + +This is an open string to allow for provider-specific stop reasons; every protocol +version models it as an open union. +""" class CreateMessageResult(Result): @@ -1114,6 +2346,12 @@ class CreateMessageResult(Result): This is the backwards-compatible version that returns single content (no arrays). Used when the request does not include tools. + + The client should inform the user before returning the sampled message, to allow + them to inspect the response (human in the loop). On 2026-07-28 sessions this + payload travels embedded in an ``InputResponses`` map rather than as a top-level + JSON-RPC result; sampling is deprecated as of 2026-07-28 (SEP-2577) but remains + in the specification. """ role: Role @@ -1129,7 +2367,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 @@ -1158,16 +2396,28 @@ class ResourceTemplateReference(MCPModel): """A reference to a resource or resource template definition.""" type: Literal["ref/resource"] = "ref/resource" + """Reference-type discriminator; always "ref/resource".""" uri: str """The URI or URI template of the resource.""" +# Deliberately flat on MCPModel, not BaseMetadata, despite the 2025-06-18+ +# schemas declaring the BaseMetadata interface as a base: inheritance would +# reorder dump keys (type, name) -> (name, title, type), changing emitted bytes +# 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 + """ + 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. + """ class CompletionArgument(MCPModel): @@ -1187,10 +2437,12 @@ class CompletionContext(MCPModel): class CompleteRequestParams(RequestParams): - """Parameters for completion requests.""" + """Parameters for a `completion/complete` request.""" ref: ResourceTemplateReference | PromptReference + """The prompt or resource-template reference to complete against.""" argument: CompletionArgument + """The argument's information.""" context: CompletionContext | None = None """Additional, optional context for completions.""" @@ -1223,6 +2475,18 @@ 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" + """Discriminates complete results from input-required results (2026-07-28). + + Defaults to "complete" and is always serialized; pre-2026-07-28 peers + ignore the extra key on non-empty results. Inbound bodies without the + field (every pre-2026-07-28 peer) take the default, which the spec + defines as equivalent to "complete". The union is open: a value outside + the named literals (a future protocol's tag) is preserved as-is rather + than rejected. + """ class ListRootsRequest(Request[RequestParams | None, Literal["roots/list"]]): @@ -1233,26 +2497,42 @@ 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. + + On protocol versions 2024-11-05 through 2025-11-25 this is a server -> client + JSON-RPC request. On 2026-07-28 sessions there are no server -> client JSON-RPC + requests; the same payload travels embedded as an ``InputRequest`` value inside + ``InputRequiredResult.input_requests``. Deprecated as of protocol version + 2026-07-28 (SEP-2577). """ method: Literal["roots/list"] = "roots/list" params: RequestParams | None = None + """Optional request parameters. Unlike client -> server requests, ``params`` + stays optional on 2026-07-28 (the reserved client ``_meta`` keys do not apply + to server -> 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. - uri: FileUrl + Deprecated as of protocol 2026-07-28 (SEP-2577) together with the rest of + the roots family; remains in the specification for at least twelve months + and is still carried by embedded ``roots/list`` responses on 2026-07-28 + sessions. """ - 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. + + uri: FileUrl + """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. """ 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. + """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. """ meta: Meta | None = Field(alias="_meta", default=None) """ @@ -1264,11 +2544,16 @@ 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 sessions this payload is not a JSON-RPC result: it is carried + as an embedded input-response value (an ``InputResponses`` map entry) on a + retried client request, and the roots feature is deprecated (SEP-2577). """ roots: list[Root] + """The root directories or files the client exposes to the server.""" class RootsListChangedNotification( @@ -1280,6 +2565,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,21 +2574,34 @@ class RootsListChangedNotification( class CancelledNotificationParams(NotificationParams): - """Parameters for cancellation notifications.""" + """Parameters for a `notifications/cancelled` notification.""" request_id: RequestId | None = None - """ - The ID of the request to 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 correspond to the ID of a request previously issued in the same + direction. Required on the wire for protocol versions <= 2025-06-18; + optional from 2025-11-25 (where task cancellation uses the `tasks/cancel` + request, never this field). """ reason: str | None = None - """An optional string describing the reason for the cancellation.""" + """An optional string describing the reason for the cancellation. + + This MAY be logged or presented to the user. + """ 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. + """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. + + On protocol versions <= 2025-11-25, a client MUST NOT attempt to cancel its + `initialize` request (the method does not exist at 2026-07-28). """ method: Literal["notifications/cancelled"] = "notifications/cancelled" @@ -1325,39 +2625,22 @@ 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; no wire form on earlier + sessions. """ 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 ElicitRequestedSchema: TypeAlias = dict[str, Any] -"""Schema for elicitation requests.""" +"""Schema for elicitation requests. + +A restricted subset of JSON Schema: only top-level properties are allowed, +without nesting. +""" class ElicitRequestFormParams(RequestParams): @@ -1379,12 +2662,24 @@ class ElicitRequestFormParams(RequestParams): Only top-level properties are allowed, without nesting. """ + task: TaskMetadata | None = None + """If specified, the caller is requesting task-augmented execution for this request. + + 2025-11-25 sessions only; removed in protocol 2026-07-28 (tasks continue as an + extension). + """ + 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. + + New in protocol 2025-11-25: earlier schemas define no URL mode. Emission + is the plain model dump at every version; sending URL mode only to peers + that understand it is the session layer's capability and version gating, + not serialization's. """ mode: Literal["url"] = "url" @@ -1402,6 +2697,13 @@ class ElicitRequestURLParams(RequestParams): The client MUST treat this ID as an opaque value. """ + task: TaskMetadata | None = None + """If specified, the caller is requesting task-augmented execution for this request. + + 2025-11-25 sessions only; removed in protocol 2026-07-28 (tasks continue as an + extension). + """ + # Union type for elicitation request parameters ElicitRequestParams: TypeAlias = ElicitRequestURLParams | ElicitRequestFormParams @@ -1409,7 +2711,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 +2734,170 @@ 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 monolith superset leniency: the surface packages + match schema.ts, which declares no null arm, so an inbound null value + fails the surface validation step. """ class ElicitationRequiredErrorData(MCPModel): - """Error data for URLElicitationRequiredError. + """Error data for the URL-elicitation-required error (code -32042, ``URL_ELICITATION_REQUIRED``). 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 a multi-round-trip flow (2026-07-28). + +Values of the ``InputRequests`` map carried by ``InputRequiredResult.input_requests``. +On 2026-07-28 sessions these embedded payloads replace the standalone +server-to-client JSON-RPC requests of earlier protocol versions; each member's +required ``method`` literal is the discriminating tag. +""" + +InputRequests: TypeAlias = dict[str, InputRequest] +"""A map of server-initiated requests that the client must fulfill (2026-07-28). + +Keys are server-assigned identifiers; values are the embedded request payloads +(`CreateMessageRequest | ListRootsRequest | ElicitRequest`). Carried by +`InputRequiredResult.input_requests` in the multi-round-trip (MRTR) flow; the +`io.modelcontextprotocol/tasks` extension reuses the same type for its +`inputRequests` fields. +""" + +InputResponse: TypeAlias = CreateMessageResult | CreateMessageResultWithTools | ListRootsResult | ElicitResult +"""A client response to a single server-initiated input request (2026-07-28, MRTR). + +Values never travel as top-level JSON-RPC results: they appear as entries of an +``InputResponses`` map — in ``inputResponses`` on retried client requests, and in +the tasks extension's ``tasks/update`` params. ``CreateMessageResultWithTools`` is +the SDK's array-content split of the schema's single ``CreateMessageResult`` arm; +the wire union has exactly three arms. +""" + +InputResponses: TypeAlias = dict[str, InputResponse] +"""A map of client responses to server-initiated input requests (2026-07-28, MRTR). + +Keys correspond to the keys of the ``InputRequests`` map the server sent in its +``InputRequiredResult``; values are the client's result for each request. Reused +verbatim by the ``io.modelcontextprotocol/tasks`` extension (``tasks/update`` +params), which keys responses to currently-outstanding input requests. +""" + + +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 matching + responses and the echoed `request_state`. + + At least one of `input_requests` / `request_state` is present on the wire + (spec MUST; not enforced by the model — inbound stays lenient). + """ + + result_type: Literal["input_required"] = "input_required" + """Tags this result as input-required (2026-07-28). + + The literal is the discriminating tag that lets the dual-result response + unions (tools/call, prompts/get, resources/read) self-discriminate under + pydantic smart-union validation. Defaulted so server handlers construct + InputRequiredResult(...) without ceremony. + """ + + input_requests: InputRequests | None = None + """Requests issued by the server that must be completed before the client can retry the original request. + + Keys are server-assigned identifiers; values are the embedded request payloads. + """ + + request_state: str | None = None + """Opaque state to pass back to the server when the client retries the original request. + + The client must treat this as an opaque blob and must not interpret it in any way. + """ + + +# Deferred-annotation completion: InputResponseRequestParams (and its consumers) +# reference InputResponses, which is only bound above. Explicit rebuilds keep +# model completion at import time rather than first use. +InputResponseRequestParams.model_rebuild() +ReadResourceRequestParams.model_rebuild() +GetPromptRequestParams.model_rebuild() +CallToolRequestParams.model_rebuild() + +# Top-level message unions, declared last so every member class is bound. +# Membership is the superset across all known protocol versions; which members +# are valid on a given session's negotiated version is recorded in the +# wire-method maps (mcp.types.methods), never enforced here -- inbound +# parsing stays superset-lenient on every session. + +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, never +dispatched). +""" + +# Alternative considered: a method-discriminated adapter (it would reject method-less dicts). +client_request_adapter = TypeAdapter[ClientRequest](ClientRequest) + + +ClientNotification = ( + CancelledNotification | ProgressNotification | InitializedNotification | RootsListChangedNotification +) +"""Notifications sent from the client to the server. + +All four members are valid on every released version (2024-11-05 through +2025-11-25); on 2026-07-28 sessions only ``CancelledNotification`` and +``ProgressNotification`` are. The 2025-11-25 ``TaskStatusNotification`` is +deliberately excluded (types-only, never dispatched). +""" + +# Alternative considered: a method-discriminated adapter (it would reject method-less dicts). +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 on 2024-11-05 through 2025-11-25 sessions only: the 2026-07-28 protocol +removes the standalone server-to-client request channel. On 2026-07-28 +sessions, sampling, roots, and elicitation requests are instead embedded in +``InputRequiredResult.input_requests``, and ping is removed entirely, so the +server-request method set for that version is empty. +""" + +# Alternative considered: a method-discriminated adapter (it would reject method-less dicts). server_request_adapter = TypeAdapter[ServerRequest](ServerRequest) @@ -1463,13 +2910,22 @@ class ElicitationRequiredErrorData(MCPModel): | ToolListChangedNotification | PromptListChangedNotification | ElicitCompleteNotification + | SubscriptionsAcknowledgedNotification ) +"""Union of server-to-client notification payloads across all supported protocol versions. + +The 2025-11-25 ``TaskStatusNotification`` is deliberately excluded (types-only, +never dispatched). +""" + +# Alternative considered: a method-discriminated adapter (it would reject method-less dicts). server_notification_adapter = TypeAdapter[ServerNotification](ServerNotification) ServerResult = ( EmptyResult | InitializeResult + | DiscoverResult | CompleteResult | GetPromptResult | ListPromptsResult @@ -1478,5 +2934,14 @@ class ElicitationRequiredErrorData(MCPModel): | ReadResourceResult | CallToolResult | ListToolsResult + | InputRequiredResult ) +"""Union of every result payload a server can return for a client request. + +Spans all supported protocol versions: `InitializeResult` is only valid on +pre-2026-07-28 sessions; `DiscoverResult` and `InputRequiredResult` only on +2026-07-28 sessions. `InputRequiredResult` is deliberately the last member: +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..ee969b2915 --- /dev/null +++ b/src/mcp/types/_wire_base.py @@ -0,0 +1,45 @@ +"""Shared pydantic bases for the wire-shape surface packages. + +Every model in the ``mcp.types.v*`` packages builds on one of the two bases +here, so the two surface packages cannot silently diverge in model +configuration. There is deliberately no alias generator: each wire name in a +surface package is an explicit ``Field(alias=...)``, so the package file shows +exactly what goes on the wire and cannot inherit serialization behavior from +elsewhere. + +The surface packages are the validating half of a two-step boundary: the +wire-method maps in ``mcp.types.methods`` point inbound validation at them, +and the emitted bytes are always the dump of the version-free monolith +models in ``mcp.types._types``. +""" + +from pydantic import BaseModel, ConfigDict + + +class WireModel(BaseModel): + """Base for surface-package models: unknown fields validate and are ignored. + + ``extra="ignore"`` is a deliberate divergence from the schemas, which + declare most wire objects open to extra fields. The boundary only ever + parses a surface model as a check — the emitted bytes are always the + monolith dump, never a surface model's re-dump — so the policy's one + wire-visible effect is that a caller-set key the target revision never + defined can never fail that check. Unknown keys are accepted and + dropped while the declared fields validate exactly; choosing + ``extra="ignore"`` over ``extra="allow"`` keeps each model's contents + pinned to the schema-declared fields, which is what makes the surface + packages schema-exact validators for the wire-method maps. + """ + + model_config = ConfigDict(populate_by_name=True, extra="ignore") + + +class OpenWireModel(BaseModel): + """Base for ``_meta`` carrier models: unknown fields are retained. + + Unknown ``_meta`` keys must survive a validate -> re-dump round trip at + every protocol revision, so the classes a ``_meta`` field references stay + open. + """ + + model_config = ConfigDict(populate_by_name=True, extra="allow") diff --git a/src/mcp/types/jsonrpc.py b/src/mcp/types/jsonrpc.py index 14743c33b0..ff167c9ab8 100644 --- a/src/mcp/types/jsonrpc.py +++ b/src/mcp/types/jsonrpc.py @@ -2,55 +2,182 @@ 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.""" +"""A uniquely identifying ID for a request in JSON-RPC. + +Identical in every supported protocol version: a string or an integer (the JSON +form of every schema version pins the numeric kind to integer; null is never +allowed). The strict ``int`` arm disables pydantic cross-coercion so a parsed id +keeps the exact wire type the peer sent (``"7"`` stays ``str``; ``True`` is +rejected), which is what lets a response echo the id back unchanged. +""" + +JSONRPC_VERSION: Final[Literal["2.0"]] = "2.0" +"""The JSON-RPC protocol version carried by every MCP message envelope. + +Identical in every MCP protocol version (2024-11-05 through 2026-07-28): the +``jsonrpc`` field of every envelope type is ``Literal["2.0"]`` and always holds +exactly this value. +""" class JSONRPCRequest(BaseModel): """A JSON-RPC request that expects a response.""" jsonrpc: Literal["2.0"] + """The JSON-RPC protocol version. Always "2.0".""" + id: RequestId + """A uniquely identifying ID for this request, established by the sender.""" + method: str + """The name of the method being invoked.""" + params: dict[str, Any] | None = None + """The parameter object for the method, if any.""" class JSONRPCNotification(BaseModel): """A JSON-RPC notification which does not expect a response.""" jsonrpc: Literal["2.0"] + """The JSON-RPC protocol version. Always "2.0".""" + method: str + """The method name of the notification.""" + params: dict[str, Any] | None = None + """The notification's parameters as an untyped mapping. + + Typed access goes through the `Notification` payload models in `mcp.types`; + the envelope deliberately leaves this untyped. + """ -# 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. + + Wire shape is identical across all supported protocol versions. The spec named + this type ``JSONRPCResponse`` through 2025-06-18 and renamed it + ``JSONRPCResultResponse`` in 2025-11-25 (recycling ``JSONRPCResponse`` for the + success|error union); the SDK keeps its original name. + """ jsonrpc: Literal["2.0"] + """The JSON-RPC protocol version. Always "2.0".""" + id: RequestId + """The id of the request this response answers.""" + result: dict[str, Any] + """The result payload as a raw JSON object. + + The envelope deliberately leaves the payload untyped: typed result models are + validated and serialized at the session layer, then wrapped in this envelope. + """ # 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.""" - -# SDK error codes +"""Error code indicating that a URL mode elicitation is required before the request can be processed. + +Removed in protocol 2026-07-28; used on 2025-11-25 sessions (the 2026-07-28 +input-required flow delivers URL elicitations inside results instead). +""" + +MISSING_REQUIRED_CLIENT_CAPABILITY = -32003 +"""Error code returned when a server requires a client capability that was +not declared in the request's `clientCapabilities` (protocol 2026-07-28). + +The error's `data.requiredCapabilities` lists the missing capabilities; see +`MissingRequiredClientCapabilityErrorData` in `mcp.types`. For HTTP, the +response status code MUST be 400 Bad Request. +""" + +UNSUPPORTED_PROTOCOL_VERSION = -32004 +"""Error code returned when the request's protocol version is not supported by the server. + +Introduced in protocol 2026-07-28: returned when the version a request claims (the +``io.modelcontextprotocol/protocolVersion`` ``_meta`` key, which must match the HTTP +``MCP-Protocol-Version`` header) is unknown to the server or unsupported. For HTTP, +the response status code MUST be ``400 Bad Request``. The error's ``data`` member +carries an ``UnsupportedProtocolVersionErrorData`` payload listing the versions the +server supports, so the client can retry with a mutually supported one. +""" + +# SDK error codes — SDK-internal allocations in the JSON-RPC +# implementation-defined server-error range [-32000, -32099]. Not defined by any +# MCP schema version. The spec now allocates into the same range +# (URL_ELICITATION_REQUIRED = -32042 in 2025-11-25; +# MISSING_REQUIRED_CLIENT_CAPABILITY = -32003 and +# UNSUPPORTED_PROTOCOL_VERSION = -32004 in 2026-07-28), so any future SDK-internal +# code must be chosen away from values the spec has allocated or is likely to +# allocate next. CONNECTION_CLOSED = -32000 +"""SDK-only error code: the connection closed before a response arrived. + +Delivered to local request waiters when the session or dispatcher shuts down with +requests still pending; the SDK never emits this code on the wire. +""" + REQUEST_TIMEOUT = -32001 +"""SDK-only error code: a request timed out while waiting for its response. + +Raised locally as ``MCPError(code=REQUEST_TIMEOUT, ...)``. +""" + REQUEST_CANCELLED = -32002 +"""SDK-only error code: an in-flight request was cancelled. + +Written as a JSON-RPC error response when the dispatcher shuts down while the +request is still being handled. +""" # Standard JSON-RPC error codes PARSE_ERROR = -32700 +"""Standard JSON-RPC error code: invalid JSON was received. + +Returned when the receiver cannot parse the JSON text of a message. The +2026-07-28 schema also publishes a typed ``ParseError`` error-object shape for +this code; the SDK deliberately keeps the generic ``ErrorData`` envelope and +represents a parse error as ``ErrorData(code=PARSE_ERROR, message=...)``. +""" + INVALID_REQUEST = -32600 +"""Standard JSON-RPC error code: the message is not a valid request object. + +Returned when a message's 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 invalid types for those fields). +""" + METHOD_NOT_FOUND = -32601 +"""Error code: the requested method does not exist or is not available. + +Since protocol 2026-07-28 this explicitly includes methods gated behind a +server capability the server did not advertise; a request that requires a +CLIENT capability the client did not declare is signalled by code -32003 +(``MISSING_REQUIRED_CLIENT_CAPABILITY``) instead. +""" + INVALID_PARAMS = -32602 +"""Standard JSON-RPC error code: the method parameters are invalid or malformed.""" + INTERNAL_ERROR = -32603 +"""Standard JSON-RPC error code: an internal error occurred on the receiver. + +Returned when the receiver encounters an unexpected condition that prevents +it from fulfilling the request. Identical in every MCP protocol version +(2024-11-05 through 2026-07-28). The SDK's error envelope is the generic +``ErrorData`` everywhere, with ``ErrorData.code`` carrying this value; the +2026-07-28 schema's per-code wrapper interfaces (``InternalError`` and its +siblings) are mirrored as data in ``mcp.types.v2026_07_28``, and nothing in +the SDK constructs them. +""" class ErrorData(BaseModel): @@ -76,9 +203,34 @@ class JSONRPCError(BaseModel): """A response to a request that indicates an error occurred.""" jsonrpc: Literal["2.0"] + """The JSON-RPC protocol version. Always "2.0".""" + + # If the 2025-11-25 optional-id reading is adopted, `id` gains a None default here. id: RequestId | None + """The id of the request this error responds to. + + The member itself is required: ``None`` carries the JSON-RPC 2.0 + ``"id": null`` form (no request id could be determined, e.g. a parse + error), and a frame missing the key entirely does not validate. The + 2025-11-25 and 2026-07-28 schemas can be read as making the member + optional; the SDK keeps the required-but-nullable shape — which JSON-RPC + 2.0 and every earlier schema mandate — until the spec settles that + reading. SDK-generated error responses always set the member. + """ + error: ErrorData + """The error that occurred.""" JSONRPCMessage = JSONRPCRequest | JSONRPCNotification | JSONRPCResponse | JSONRPCError +"""Any valid JSON-RPC object that can be decoded off the wire, or encoded to be sent. + +One envelope for every protocol version: the 2025-11-25 schema restructure +(`JSONRPCResponse = JSONRPCResultResponse | JSONRPCErrorResponse`) changed the +schema's union nesting and member names, not the wire shape of a frame. The +2025-03-26 batch frames (JSON arrays of messages) are not members and are not +supported. +""" + jsonrpc_message_adapter: TypeAdapter[JSONRPCMessage] = TypeAdapter(JSONRPCMessage) +"""TypeAdapter for parsing wire frames into JSONRPCMessage at the transport boundary.""" diff --git a/src/mcp/types/methods.py b/src/mcp/types/methods.py new file mode 100644 index 0000000000..96e97fffb1 --- /dev/null +++ b/src/mcp/types/methods.py @@ -0,0 +1,728 @@ +"""Straight wire-method maps and two-step parse functions for the MCP protocol. + +Two families of plain maps; nothing here negotiates, dispatches, or holds +session state. + +Integration status: nothing in the SDK consults these maps or parse +functions yet -- sessions still validate traffic against the version-free +monolith types alone, and rewiring them onto this module is a follow-up +change. Statements in this module about what sessions do with a lookup or a +raised exception (the session layer mapping errors, servers and clients +gating on a map) describe that intended integration contract, not behavior +the SDK has today. + +SURFACE maps are keyed ``(method, version)`` and valued by schema-exact +surface types (``mcp.types.v2025_11_25`` / ``mcp.types.v2026_07_28``): one +entry per method per protocol version that defines it. Absence of a key IS +the version gate: the contract is for the session layer to map a failed +request lookup to JSON-RPC -32601 (METHOD_NOT_FOUND) and to drop-and-log a +failed notification lookup. + +Schema-exact comes with a granularity caveat: the method gate is +version-exact, but shape validation is only as fine-grained as the two +surface packages. Every version through 2025-11-25 validates against the +2025-11-25-shaped ``v2025_11_25`` models (those schemas evolve strictly +additively), so a 2024-11-05 session accepts later pre-2026 shapes such as +audio content or a cancellation with no ``requestId``. Across the +2025-11-25/2026-07-28 boundary the packages disagree on requiredness and +value shapes, and validation fails loudly in both directions there. + +MONOLITH maps are keyed by method alone and valued by the version-free +superset types in ``mcp.types``: what user code receives. The result side is +a two-arm union where the wire is structurally dual (``tools/call``, +``prompts/get``, ``resources/read`` may answer ``InputRequiredResult`` on +sessions negotiated at 2026-07-28 or later; ``sampling/createMessage`` may +answer with array content). The three InputRequired unions self-discriminate +through the models' ``resultType`` literals; the sampling union does not -- +a single-block body satisfies both of its arms, so it relies on pinned arm +order instead (see the ``MONOLITH_RESULTS`` docstring and its pinning test). +Either way there is no dispatch code here. + +Parsing is two steps (the ``parse_*`` functions below): the surface type +VALIDATES, then the monolith type deserializes the same payload and is +returned. Unknown keys never fail the surface check (the surface base +ignores extras); what reaches user code is what the monolith parse keeps -- +the monolith-declared fields plus ``_meta`` extras -- and a key neither +layer declares is dropped. + +Outbound serialization does not live here: emission is the model's plain +``model_dump(by_alias=True, mode="json", exclude_none=True)`` at every +version, with no version parameter, no strips, and no injections. + +Extension methods are dict unions over the built-ins, handed to the parse +functions' keyword parameters:: + + requests = {**CLIENT_REQUESTS, ("tasks/get", "2025-11-25"): v2025.GetTaskRequest} + bodies = {**MONOLITH_REQUESTS, "tasks/get": types.GetTaskRequest} + parse_client_request(method, version, params, surface=requests, monolith=bodies) + +The built-in maps are immutable (``MappingProxyType``). The ``tasks/*`` +family (four request methods and ``notifications/tasks/status``) is +deliberately absent from the built-ins: the SDK defines the task types but +never dispatches them; extensions register them. + +``initialize`` and ``ping`` are ordinary rows, not special cases: they +appear at every version whose schema defines them (every version through +2025-11-25; neither exists at 2026-07-28, so plain key absence gates them +there like any removed method), and a lookup at a defining version +validates through its row like any other. What bypasses the maps is the +pre-negotiation window only: before a version is negotiated there is +nothing to key a lookup on, so the contract validates that exchange (the +``initialize`` handshake and any early ``ping``) directly against the +monolith types. +""" + +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 direction (servers validate inbound) --- + +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 (the four tasks/* request methods are 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, + } +) +"""Requests clients send, per protocol version. Servers gate and validate inbound requests here.""" + +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 (notifications/tasks/status is 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 removed with the lifecycle; roots/list_changed removed with the roots channel) + ("notifications/cancelled", "2026-07-28"): v2026.CancelledNotification, + ("notifications/progress", "2026-07-28"): v2026.ProgressNotification, + } +) +"""Notifications clients send, per protocol version. Servers gate and validate inbound notifications here.""" + + +# --- Surface maps: server-to-client direction (clients validate inbound) --- + +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 (the four tasks/* request methods are 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: no entries. The standalone server-to-client request + # channel does not exist at this version (no ServerRequest union in + # the schema); ANY server-initiated request on a 2026-07-28 session + # is therefore gated to -32601 by plain key absence. + } +) +"""Requests servers send, per protocol version. Clients gate and validate inbound requests here.""" + +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; notifications/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, + } +) +"""Notifications servers send, per protocol version. Clients gate and validate inbound notifications here.""" + + +# --- Surface maps: responses, keyed by the (method, version) the sender used --- + +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 point at the version's union aliases: + # which result arms a session can see is row DATA, never dispatch code) + ("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 client request's (method, version). Clients validate inbound responses here.""" + +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: no entries (no server-to-client requests, so no + # client-sent results; embedded InputResponses travel inside retried + # client requests and are validated by the request rows above). + } +) +"""Results clients send, keyed by the server request's (method, version). Servers validate inbound responses here.""" + + +# --- Monolith maps: version-free, what user code receives --- + +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 (ping appears once; the types are identical).""" + +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: complete arm first. A single-block body satisfies + # both arms (CreateMessageResultWithTools.content also accepts a single + # block) and pydantic smart-union ties resolve leftmost; reversing the + # arms silently changes wire-visible parsing. Pinned by + # tests/types/test_methods.py::test_sampling_union_keeps_the_complete_arm_first_because_order_is_load_bearing. + "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 self-discriminating two-arm union) per request method. + +Membership is version-free superset data: which arm a given session can +actually see is the response surface maps' job. The InputRequired arms +discriminate through the models' resultType literals and are order-insensitive +(verified both orders); the sampling arms discriminate through content shape +(single block vs array / tool-use blocks) and their order IS load-bearing: +the complete arm must stay first, because a single-block body satisfies both +arms and smart-union ties resolve leftmost. No dispatch code anywhere. +""" + + +# --- Two-step parse functions ----------------------------------------------- + +_REQUEST_STUB: Final[Mapping[str, Any]] = MappingProxyType({"jsonrpc": "2.0", "id": 0}) +"""Envelope stub merged for surface request checks (surface classes are full frames).""" + +_NOTIFICATION_STUB: Final[Mapping[str, Any]] = MappingProxyType({"jsonrpc": "2.0"}) +"""Envelope stub merged for surface notification checks.""" + + +def _check_known_version(version: str) -> None: + """Reject version strings outside KNOWN_PROTOCOL_VERSIONS with ValueError. + + The negotiated session version is always a member (negotiation validates + against SUPPORTED_PROTOCOL_VERSIONS, a subset). Anything else is + programmer error: a silent miss would turn a typo into every method + answering -32601. + """ + 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]: + """The JSON-RPC body for a request or notification. + + The params key is omitted entirely when params is None, so a method + whose params model has required fields rejects absent params. + """ + 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]: + """A cached TypeAdapter for a result row (a model class or a union). + + Built lazily on first use, so importing this module constructs no + pydantic adapters. The cache key is the row object itself: identical + rows shared across versions build one adapter, and extension rows are + cached the same way (classes and union expressions are both hashable). + """ + return TypeAdapter(target) + + +_MonolithT = TypeVar("_MonolithT") + + +def _monolith_row(monolith: Mapping[str, _MonolithT], method: str) -> _MonolithT: + """The monolith row for a method whose surface row already matched. + + A miss here is never the version gate (the surface lookup already + passed): it means the surface and monolith maps disagree -- inconsistent + extension maps, programmer error. Raised as RuntimeError, not KeyError, + so the gate handling the contract assigns to the session layer + (``except KeyError`` -> -32601 or drop-and-log) cannot swallow it. + """ + 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]: + """Parse an inbound client-to-server request body (server side). + + Two steps: the surface type for (method, version) VALIDATES the request + (a constant envelope stub supplies jsonrpc/id), then the monolith type + for method deserializes the same body and is returned -- user handlers + are typed against mcp.types. The method gate is version-exact; shape + validation is as fine-grained as the surface package serving the + negotiated version (every version through 2025-11-25 validates + 2025-11-25-shaped -- see the module docstring). Unknown keys never fail + the surface check (surface ignores extras); the returned monolith model + carries its declared fields plus ``_meta`` extras, and a key neither + layer declares is dropped. + + Pass extended maps (dict unions over the built-ins) to serve extension + methods; the built-ins never change. + + Raises: + ValueError: version is not a known protocol version. + KeyError: (method, version) is not in surface -- the method does not + exist at the negotiated version (or at all). The session-layer + contract maps this to JSON-RPC -32601. + pydantic.ValidationError: the body is invalid at this version, or + passes the surface step but fails the monolith parse -- possible + where the monolith declares a stricter constrained type than the + surfaces (the one audited built-in instance: the monolith's + Root.uri is file-scheme FileUrl while the surfaces accept any + string, so a 2026-07-28 retried request whose inputResponses + embed a roots response with a non-file URI passes the surface + and rejects here), or with shape-mismatched extension maps. The + session-layer contract maps this to JSON-RPC -32602. + RuntimeError: the surface row matched but the method has no monolith + row (inconsistent extension maps; programmer error). + """ + _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]: + """Parse an inbound server-to-client request body (client side). + + Same contract as parse_client_request, gated by SERVER_REQUESTS. On + 2026-07-28 sessions that map has no rows, so every server-initiated + request raises KeyError (-32601 under the session-layer contract) with + no special-casing: the table encodes the channel removal. + + Raises: + ValueError: version is not a known protocol version. + KeyError: (method, version) is not in surface. + pydantic.ValidationError: the body is invalid at this version. + RuntimeError: the surface row matched but the method has no monolith + row (inconsistent extension maps; programmer error). + """ + _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]: + """Parse an inbound client-to-server notification body (server side). + + Same two steps as the request functions (the notification stub has no + id). The session-layer contract maps KeyError to drop-and-log, never an + error to the peer. + + Raises: + ValueError: version is not a known protocol version. + KeyError: (method, version) is not in surface. + pydantic.ValidationError: the body is invalid at this version. + RuntimeError: the surface row matched but the method has no monolith + row (inconsistent extension maps; programmer error). + """ + _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]: + """Parse an inbound server-to-client notification body (client side). + + Same contract as parse_client_notification, gated by SERVER_NOTIFICATIONS. + + Raises: + ValueError: version is not a known protocol version. + KeyError: (method, version) is not in surface. + pydantic.ValidationError: the body is invalid at this version. + RuntimeError: the surface row matched but the method has no monolith + row (inconsistent extension maps; programmer error). + """ + _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: + """Parse the result of a client-to-server request this side sent (client side). + + data is the JSON-RPC result member. The surface row for the request's + (method, version) VALIDATES it (2025-11-25-shaped on every version + through 2025-11-25; a server answering with shapes its surface package + does not define fails loudly here), then the monolith row deserializes + it. Dual-result methods return whichever arm the body discriminates to; + isinstance narrows at the caller. + + Raises: + ValueError: version is not a known protocol version. + KeyError: (method, version) is not in surface -- the caller sent a + request that does not exist at the negotiated version. + pydantic.ValidationError: the result body is invalid at this version. + RuntimeError: the surface row matched but the method has no monolith + row (inconsistent extension maps; programmer error). + """ + _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: + """Parse the result of a server-to-client request this side sent (server side). + + Same contract as parse_server_result, gated by CLIENT_RESULTS. Empty on + 2026-07-28 (no server-to-client requests exist there). + + Raises: + ValueError: version is not a known protocol version. + KeyError: (method, version) is not in surface. + pydantic.ValidationError: the result body is invalid at this version, + at either step -- the monolith's Root.uri (file-scheme FileUrl) + is stricter than the surfaces' plain-string uri, so a roots/list + result carrying a non-file URI passes the surface step and + rejects at the monolith step. + RuntimeError: the surface row matched but the method has no monolith + row (inconsistent extension maps; programmer error). + """ + _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..e214e51546 --- /dev/null +++ b/src/mcp/types/v2025_11_25/__init__.py @@ -0,0 +1,2860 @@ +"""Internal wire-shape models for protocol 2025-11-25. Not part of the public API. + +One of the two schema-exact validator surfaces behind ``mcp.types.methods``, +and the one that serves every protocol version through 2025-11-25: the +schemas from 2024-11-05 to 2025-11-25 evolve strictly additively (each +defines a subset of the types and fields here), so the newest of them +describes them all. The wire-method maps point inbound validation for the +served versions at these models; emission is the plain monolith dump, so +this package's job is to be the reviewable record of the 2025-11-25 wire +shapes. + +Initially generated from schema/2025-11-25/schema.json @ 6d441518de8a9d5adbab0b10a76a667a63f90665 +(datamodel-code-generator 0.57.0), then hand-validated against the pinned +schema. Maintained as ordinary hand-edited source from here on; keep +definitions aligned with the pinned schema revision. + +The models deliberately use ``extra="ignore"`` even where the schema declares +an object open to extra fields: unknown keys are accepted and dropped, and +the declared fields validate exactly — see ``mcp.types._wire_base`` for the +rationale. The classes kept open (``extra="allow"``) are commented in place. + +Models live in this package's ``__init__.py`` so the whole version reads as +one file beside its pinned schema; the package form leaves room for a future +per-family split without import-path churn. +""" + +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) properties.""" + + 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 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 + """ + 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): + 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")] + """ + The task identifier to cancel. + """ + + +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 + """ + 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 + """ + 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 + """ + 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. 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'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, optional context for completions""" + + arguments: dict[str, str] | None = None + """ + 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] + """ + An array of completion values. 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 + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + 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 + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + action: Literal["accept", "cancel", "decline"] + """ + 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 + """ + # Deliberate deviation from the pinned schema.json, which renders the + # value union's number arm as "integer" — its schema.ts source types form + # answers string | number | boolean | string[], so fractional answers are + # legal wire values. The float arm follows schema.ts. + content: dict[str, list[str] | str | int | float | bool] | None = None + """ + 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): + """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"] + params: ElicitationCompleteNotificationParams + + +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): + """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 + """ + 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 + """ + 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 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): + """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. + 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 + """ + (Legacy) Display names for enum values. + Non-standard according to JSON schema 2020-12. + """ + title: str | None = None + type: Literal["string"] + + +LoggingLevel: TypeAlias = Literal["alert", "critical", "debug", "emergency", "error", "info", "notice", "warning"] + + +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. + + Keys not declared here are currently left unspecified by the spec and are up + to the client to interpret. + """ + + name: str | None = None + """ + 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 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 + """ + 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): + method: str + params: dict[str, Any] | None = None + + +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): + # Deliberate deviation from the pinned schema.json, which renders the + # default and the bounds as "integer" — schema.ts types them number + # (JSON Schema minimum/maximum/default are numbers; the schema describes + # number fields too). The float arms 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 + """ + 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. + """ + + +ProgressToken: TypeAlias = str | int + + +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): + """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"] + params: NotificationParams | None = None + + +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): + """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 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): + """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): + method: str + params: dict[str, Any] | None = None + + +RequestId: TypeAlias = str | int + + +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): + """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"] + params: NotificationParams | None = None + + +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): + """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): + """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 + """ + 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): + """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 + """ + 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): + """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"] + params: NotificationParams | None = None + + +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 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 + """ + 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): + 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): + """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. + Include this in the `task` field of the request parameters. + """ + + ttl: int | None = None + """ + Requested duration in milliseconds to retain task from creation. + """ + + +TaskStatus: TypeAlias = Literal["cancelled", "completed", "failed", "input_required", "working"] + + +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): + """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): + """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.""" + + # Stays open: schema keywords beyond the declared properties 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): + """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", + ) + 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): + """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 + """ + 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 + """ + 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 + """ + 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 + """ + 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 + """ + 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): + """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"] + 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 + """ + 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 + """ + 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): + """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): + """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. 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 + """ + 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 + """ + 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 + """ + 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 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): + """A request to cancel a task.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tasks/cancel"] + params: CancelTaskRequestParams + + +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 + """ + 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): + """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")] + """ + 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 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. + + 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"] + + +EmptyResult: TypeAlias = Result + + +EnumSchema: TypeAlias = ( + UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +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): + """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 + """ + 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 + """ + 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 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.""" + + 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. + + 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. + """ + server_info: Annotated[Implementation, Field(alias="serverInfo")] + + +class InitializedNotification(WireModel): + """This notification is 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. 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"] + method: Literal["roots/list"] + params: RequestParams | None = None + + +class ListRootsResult(WireModel): + """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): + """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"] + params: LoggingMessageNotificationParams + + +MultiSelectEnumSchema: TypeAlias = UntitledMultiSelectEnumSchema | TitledMultiSelectEnumSchema + + +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 + """ + 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 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"] + method: Literal["ping"] + params: RequestParams | None = None + + +PrimitiveSchemaDefinition: TypeAlias = ( + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +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 + """ + 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 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 the server, 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 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] + + +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 + """ + 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 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 + """ + 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 + """ + 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")] + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ + + +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. + """ + + 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.""" + + 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 from the server whenever a particular resource + changes. + """ + + 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 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. + """ + + +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 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 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. + """ + + +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"] + + +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): + """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): + """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 + 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. + """ + + 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 + """ + 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 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. + """ + + +class CancelledNotification(WireModel): + """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"] + params: CancelledNotificationParams + + +class CompleteRequest(WireModel): + """A request from the client to the server, 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 + """ + See [General fields: `_meta`](/specification/2025-11-25/basic/index#meta) for notes on `_meta` usage. + """ + task: Task + + +class RequestedSchema(WireModel): + """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] + required: list[str] | None = None + type: Literal["object"] + + +class ElicitRequestFormParams(WireModel): + """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 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 + + +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 + """ + 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 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. + """ + + +class InitializeRequest(WireModel): + """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"] + 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 the server has.""" + + 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 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 the server has.""" + + 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 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 the server has.""" + + 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 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] + + +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 + """ + 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 the server has.""" + + 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 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] + + +class PaginatedRequest(WireModel): + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: PaginatedRequestParams | None = None + + +class ProgressNotification(WireModel): + """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"] + params: ProgressNotificationParams + + +class PromptMessage(WireModel): + """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 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"] + 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 + """ + 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 + """ + 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 + """ + 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")] + """ + 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): + """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 + """ + 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[Any | None, Field(alias="structuredContent")] = None + """ + An optional JSON object that represents the structured result of the tool call. + """ + + +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 via the client.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["elicitation/create"] + params: ElicitRequestParams + + +class GetPromptResult(WireModel): + """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] + + +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 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 + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + model: str + """ + The name of the model that generated the message. + """ + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """ + 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): + """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 + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + role: Role + + +ClientResult: TypeAlias = ( + Result + | GetTaskResult + | GetTaskPayloadResult + | CancelTaskResult + | ListTasksResult + | CreateMessageResult + | ListRootsResult + | ElicitResult +) + + +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 + """ + 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 + """ + 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 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. 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"] + 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..c464b6add2 --- /dev/null +++ b/src/mcp/types/v2026_07_28/__init__.py @@ -0,0 +1,2916 @@ +"""Internal wire-shape models for protocol 2026-07-28. Not part of the public API. + +One of the two schema-exact validator surfaces behind ``mcp.types.methods``: +the wire-method maps point inbound validation for 2026-07-28 sessions at +these models. The emitted bytes are always the plain monolith dump, never a +re-dump of a model here, and a monolith type with no counterpart in this +package has no 2026-07-28 wire form at all. + +Initially generated from schema/draft/schema.json @ 6d441518de8a9d5adbab0b10a76a667a63f90665 +(datamodel-code-generator 0.57.0), then hand-validated against the pinned +schema. Maintained as ordinary hand-edited source from here on; keep +definitions aligned with the pinned schema revision. + +The models deliberately use ``extra="ignore"`` even where the schema declares +an object open to extra fields: unknown keys are accepted and dropped, and +the declared fields validate exactly — see ``mcp.types._wire_base`` for the +rationale. The classes kept open (``extra="allow"``) are commented in place. +Because unknown keys are ignored, a caller-set field this schema does not +define can never fail validation here: requiredness and value shapes are +what these models enforce. + +At this revision the ``clientInfo``/``clientCapabilities`` values inside a +request's ``_meta`` validate through typed models, but vendor extra keys +nested inside those two values pass through emission: the reserved keys and +their values are caller data, never reshaped by the SDK. + +Models live in this package's ``__init__.py`` so the whole version reads as +one file beside its pinned schema; the package form leaves room for a future +per-family split without import-path churn. +""" + +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 + +# Deliberate deviation from the pinned schema.json, which renders JSONValue's +# primitive branch as ["string", "integer", "boolean"] — its schema.ts source +# defines all six JSON types (string | number | boolean | null | object | +# array), so the render is missing fractional numbers and null. This alias +# follows the schema.ts definition: capability values like {"ratio": 0.5} or +# nested nulls must survive revalidation. +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) properties.""" + + 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 BooleanSchema(WireModel): + default: bool | None = None + description: str | None = None + title: str | None = None + type: Literal["boolean"] + + +class Argument(WireModel): + """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, optional context for completions""" + + arguments: dict[str, str] | None = None + """ + 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: Annotated[list[str], Field(max_length=100)] + """ + An array of completion values. Must not exceed 100 items. + """ + + +Cursor: TypeAlias = str + + +class ElicitRequestURLParams(WireModel): + """The parameters for a request to elicit information from the user via a URL in the client.""" + + elicitation_id: Annotated[str, Field(alias="elicitationId")] + """ + 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 + """ + The URL that the user should navigate to. + """ + + +class ElicitResult(WireModel): + """The result returned by the client for an `ElicitRequest` (`elicitation/create`) request.""" + + action: Literal["accept", "cancel", "decline"] + """ + 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 + """ + # Deliberate deviation from the pinned schema.json, which renders the + # value union's number arm as "integer" — its schema.ts source types form + # answers string | number | boolean | string[], so fractional answers are + # legal wire values (the same render artifact fixed for JSONValue above). + # The float arm follows schema.ts. + content: dict[str, list[str] | str | int | float | bool] | None = None + """ + 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): + """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"] + params: ElicitationCompleteNotificationParams + + +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 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 + """ + 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 + """ + 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 + """ + 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 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 + """ + 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): + """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): + """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): + """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.""" + + jsonrpc: Literal["2.0"] + method: str + params: dict[str, Any] | None = None + + +class LegacyTitledEnumSchema(WireModel): + """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 + """ + (Legacy) Display names for enum values. + Non-standard according to 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): + """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): + """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 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 + """ + 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 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 + """ + 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): + method: str + params: dict[str, Any] | None = None + + +class NotificationParams(WireModel): + """Common params for any notification.""" + + 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 + """ + 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")] + """ + 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): + """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 + + +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): + """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"] + params: NotificationParams | None = None + + +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 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 + """ + The MIME type of this resource, if known. + """ + uri: str + """ + 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. + """ + + 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 + """ + 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 + """ + 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.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + result_type: Annotated[str, Field(alias="resultType")] + """ + 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 + + +Role: TypeAlias = Literal["assistant", "user"] + + +class Root(WireModel): + """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 + """ + 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 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): + """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 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): + """The set of notification types a client may opt in to on a + `SubscriptionsListenRequest` (`subscriptions/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 `PromptListChangedNotification` (`notifications/prompts/list_changed`). + """ + resource_subscriptions: Annotated[list[str] | None, Field(alias="resourceSubscriptions")] = None + """ + Subscribe to `ResourceUpdatedNotification` (`notifications/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 `ResourceListChangedNotification` (`notifications/resources/list_changed`). + """ + tools_list_changed: Annotated[bool | None, Field(alias="toolsListChanged")] = None + """ + If true, receive `ToolListChangedNotification` (`notifications/tools/list_changed`). + """ + + +class SubscriptionsAcknowledgedNotificationParams(WireModel): + """Parameters for a `SubscriptionsAcknowledgedNotification` + (`notifications/subscriptions/acknowledged`) notification. + """ + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + notifications: SubscriptionFilter + """ + 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): + """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): + """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): + """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. + + 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: schema keywords beyond the declared properties ride extra fields. + model_config = ConfigDict( + extra="allow", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + type: Literal["object"] + + +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. + + 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", + ) + schema_: Annotated[str | None, Field(alias="$schema")] = None + + +class ToolAnnotations(WireModel): + """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 + """ + 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 + """ + 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 + """ + 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 + """ + 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 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. + """ + + 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. + """ + id: str + """ + 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 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 + """ + The protocol version that was requested by the client. + """ + supported: list[str] + """ + 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): + """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 + jsonrpc: Literal["2.0"] + + +class Items1(WireModel): + """Schema for the array items.""" + + enum: list[str] + """ + Array of enum values to choose from. + """ + type: Literal["string"] + + +class UntitledMultiSelectEnumSchema(WireModel): + """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): + """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. 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 + """ + 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 + """ + 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[MetaObject | None, Field(alias="_meta")] = None + annotations: Annotations | None = None + """ + Optional annotations for the client. + """ + data: str + """ + 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 + """ + 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 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")] + """ + 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")] + """ + 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)] + """ + 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 + """ + 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): + """The result returned by the server for a `CompleteRequest` (`completion/complete`) request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + completion: Completion + result_type: Annotated[str, Field(alias="resultType")] + """ + 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): + """A successful response from the server for a `CompleteRequest` (`completion/complete`) request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: CompleteResult + + +class EmbeddedResource(WireModel): + """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"] + + +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 + """ + Optional annotations for the client. + """ + data: str + """ + 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): + """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 ListRootsResult(WireModel): + """The result returned by the client for a `ListRootsRequest` (`roots/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 + """ + 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 + + +PrimitiveSchemaDefinition: TypeAlias = ( + StringSchema + | NumberSchema + | BooleanSchema + | UntitledSingleSelectEnumSchema + | TitledSingleSelectEnumSchema + | UntitledMultiSelectEnumSchema + | TitledMultiSelectEnumSchema + | LegacyTitledEnumSchema +) + + +class ProgressNotificationParams(WireModel): + """Parameters for a `ProgressNotification` (`notifications/progress`) notification.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + message: str | None = None + """ + An optional message describing the current progress. + """ + progress: float + """ + 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 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[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): + """The result returned by the server for a `ReadResourceRequest` (`resources/read`) request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + 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")] + """ + 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)] + """ + 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 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 + """ + 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 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 + `ListResourcesRequest` (`resources/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 + """ + 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[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 + """ + 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")] + """ + A URI template (according to RFC 6570) that can be used to construct resource URIs. + """ + + +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 + `SubscriptionsListenRequest` (`subscriptions/listen`) request. + """ + + jsonrpc: Literal["2.0"] + method: Literal["notifications/resources/updated"] + params: ResourceUpdatedNotificationParams + + +SingleSelectEnumSchema: TypeAlias = UntitledSingleSelectEnumSchema | TitledSingleSelectEnumSchema + + +class SubscriptionsAcknowledgedNotification(WireModel): + """Sent by the server as the first message on a + `SubscriptionsListenRequest` (`subscriptions/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"] + params: SubscriptionsAcknowledgedNotificationParams + + +class TextContent(WireModel): + """Text provided to or from an LLM.""" + + 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"] + + +class Tool(WireModel): + """Definition for a tool the client can call.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + 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. + """ + 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): + """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"] + params: CancelledNotificationParams + + +ContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ResourceLink | EmbeddedResource + + +class RequestedSchema(WireModel): + """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] + required: list[str] | None = None + type: Literal["object"] + + +class ElicitRequestFormParams(WireModel): + """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 + + +JSONRPCMessage: TypeAlias = JSONRPCRequest | JSONRPCNotification | JSONRPCResultResponse | JSONRPCErrorResponse + + +JSONRPCResponse: TypeAlias = JSONRPCResultResponse | JSONRPCErrorResponse + + +class ListPromptsResult(WireModel): + """The result returned by the server for a `ListPromptsRequest` (`prompts/list`) request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + 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 + """ + 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")] + """ + 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)] + """ + 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): + """A successful response from the server for a `ListPromptsRequest` (`prompts/list`) request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListPromptsResult + + +class ListResourceTemplatesResult(WireModel): + """The result returned by the server for a `ListResourceTemplatesRequest` (`resources/templates/list`) request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + 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 + """ + 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")] + """ + 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)] + """ + 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): + """A successful response from the server for a `ListResourceTemplatesRequest` (`resources/templates/list`) + request. + """ + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListResourceTemplatesResult + + +class ListResourcesResult(WireModel): + """The result returned by the server for a `ListResourcesRequest` (`resources/list`) request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + 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 + """ + 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")] + """ + 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)] + """ + 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): + """A successful response from the server for a `ListResourcesRequest` (`resources/list`) request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: ListResourcesResult + + +class ListToolsResult(WireModel): + """The result returned by the server for a `ListToolsRequest` (`tools/list`) request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + 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 + """ + 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")] + """ + 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)] + """ + 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): + """A successful response from the server for a `ListToolsRequest` (`tools/list`) request.""" + + 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`. + """ + + 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.""" + + jsonrpc: Literal["2.0"] + method: Literal["notifications/progress"] + params: ProgressNotificationParams + + +class PromptMessage(WireModel): + """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 + + +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 + """ + 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] + """ + 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 + """ + 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 + """ + An optional structured result value. + + This can be any JSON value (object, array, string, number, boolean, or null). + If the tool defined a `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. + """ + type: Literal["tool_result"] + + +class CallToolResult(WireModel): + """The result returned by the server for a `CallToolRequest` (`tools/call`) request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + 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 + """ + 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")] + """ + 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 + """ + 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): + """A request from the server to elicit additional information from the user via the client.""" + + method: Literal["elicitation/create"] + params: ElicitRequestParams + + +class GetPromptResult(WireModel): + """The result returned by the server for a `GetPromptRequest` (`prompts/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")] + """ + 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): + """The result returned by the client for a `CreateMessageRequest` (`sampling/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: ( + TextContent + | ImageContent + | AudioContent + | ToolUseContent + | ToolResultContent + | list[SamplingMessageContentBlock] + ) + model: str + """ + The name of the model that generated the message. + """ + role: Role + stop_reason: Annotated[str | None, Field(alias="stopReason")] = None + """ + 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] +"""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): + """Describes 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): + """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 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): + """A successful response from the server for a `CallToolRequest` (`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 + """ + 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 + """ + Whether the client supports tool use via `tools` and `toolChoice` parameters. + """ + + +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. + """ + + 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 + """ + 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): + """A request from the client to the server, to ask for completion options.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["completion/complete"] + params: CompleteRequestParams + + +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): + """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 + """ + 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")] + """ + 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 + """ + 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 + """ + 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 DiscoverRequest(WireModel): + """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"] + method: Literal["server/discover"] + params: RequestParams + + +class DiscoverResult(WireModel): + """The result returned by the server for a `DiscoverRequest` (`server/discover`) request.""" + + meta: Annotated[MetaObject | None, Field(alias="_meta")] = None + cache_scope: Annotated[Literal["private", "public"], Field(alias="cacheScope")] + """ + 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 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")] + """ + 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")] + """ + 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)] + """ + 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): + """A successful response from the server for a `DiscoverRequest` (`server/discover`) request.""" + + id: RequestId + jsonrpc: Literal["2.0"] + result: DiscoverResult + + +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 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): + """A successful response from the server for a `GetPromptRequest` (`prompts/get`) request.""" + + 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 + 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")] + """ + 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): + 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.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["prompts/list"] + params: PaginatedRequestParams + + +class ListResourceTemplatesRequest(WireModel): + """Sent from the client to request a list of resource templates the server has.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/templates/list"] + params: PaginatedRequestParams + + +class ListResourcesRequest(WireModel): + """Sent from the client to request a list of resources the server has.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/list"] + params: PaginatedRequestParams + + +class ListRootsRequest(WireModel): + """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): + """Sent from the client to request a list of tools the server has.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["tools/list"] + params: PaginatedRequestParams + + +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")] + """ + 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): + """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 + jsonrpc: Literal["2.0"] + + +class PaginatedRequest(WireModel): + id: RequestId + jsonrpc: Literal["2.0"] + method: str + params: PaginatedRequestParams + + +class PaginatedRequestParams(WireModel): + """Common params for paginated requests.""" + + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + cursor: str | None = None + """ + An opaque token representing the current pagination position. + If provided, the server should return results starting after this cursor. + """ + + +class ReadResourceRequest(WireModel): + """Sent from the client to the server, to read a specific resource URI.""" + + id: RequestId + jsonrpc: Literal["2.0"] + method: Literal["resources/read"] + params: ReadResourceRequestParams + + +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 + """ + 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 `ReadResourceRequest` (`resources/read`) request.""" + + 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.""" + + io_modelcontextprotocol_client_capabilities: Annotated[ + ClientCapabilities, Field(alias="io.modelcontextprotocol/clientCapabilities") + ] + """ + 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. 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 + """ + The desired log level for this request. Optional. + + If absent, the server MUST NOT send any `LoggingMessageNotification` (`notifications/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")] + """ + 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 + """ + If specified, the caller is requesting out-of-band progress notifications for this request (as represented by + `ProgressNotification` (`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 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 + """ + 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. + """ + + 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 + """ + 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): + """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"] + method: Literal["subscriptions/listen"] + params: SubscriptionsListenRequestParams + + +class SubscriptionsListenRequestParams(WireModel): + """Parameters for a `SubscriptionsListenRequest` (`subscriptions/listen`) request.""" + + meta: Annotated[RequestMetaObject, Field(alias="_meta")] + notifications: SubscriptionFilter + """ + The notifications the client opts in to on this stream. The server + **MUST NOT** send notification types the client has not explicitly + requested. + """ + + +AnyCallToolResult: TypeAlias = CallToolResult | InputRequiredResult +"""Everything a tools/call response's result member may be at this version. + +The same union the generated CallToolResultResponse.result field carries, +exported as a named alias so the wire-method maps (mcp.types.methods) can +reference it as a value. +""" + +AnyGetPromptResult: TypeAlias = GetPromptResult | InputRequiredResult +"""Everything a prompts/get response's result member may be at this version.""" + +AnyReadResourceResult: TypeAlias = ReadResourceResult | InputRequiredResult +"""Everything a resources/read response's result member 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] +"""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"] + + +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/README.md b/tests/interaction/README.md index be68c3b0f1..6d2a18b4c1 100644 --- a/tests/interaction/README.md +++ b/tests/interaction/README.md @@ -37,7 +37,7 @@ flows — with a single subprocess test for stdio. ```text tests/interaction/ _requirements.py the requirements manifest (see below) - _helpers.py shared type aliases + the wire-recording transport + _helpers.py shared type aliases _connect.py the transport-parametrized connection factories conftest.py the connect fixture (the transport matrix) test_coverage.py enforces the manifest ↔ test contract @@ -59,11 +59,10 @@ directly, and therefore run once per transport: over the in-memory transport, ov real streamable HTTP app driven in-process through the streaming bridge, and over the legacy SSE transport the same way. A test connects with `async with connect(server, ...) as client:` and asserts the same output on every leg, because the transport is not supposed to change observable -behaviour. Tests that are tied to one transport do not use the fixture: the wire-recording tests -(their seam is the in-memory stream pair), the bare-`ClientSession` lifecycle tests, the -real-clock timeout tests (the timeout machinery is transport-independent and must not race -transport latency), and everything under `transports/`, which pins behaviour only observable on -that transport. +behaviour. Tests that are tied to one transport do not use the fixture: the bare-`ClientSession` +lifecycle tests, the real-clock timeout tests (the timeout machinery is transport-independent and +must not race transport latency), and everything under `transports/`, which pins behaviour only +observable on that transport. A transport conformance test in `transports/` speaks raw `httpx` against the mounted ASGI app **only** when its assertion is about HTTP semantics that `Client` cannot observe — status codes, diff --git a/tests/interaction/_helpers.py b/tests/interaction/_helpers.py index 25833b0ca5..38035129ea 100644 --- a/tests/interaction/_helpers.py +++ b/tests/interaction/_helpers.py @@ -1,18 +1,10 @@ """Shared helpers for the interaction suite. -Keep this module small: it exists only for (a) types that every test would otherwise have to -assemble from the SDK's internals to annotate a client callback, and (b) the recording transport -used by the wire-level tests. Server fixtures and assertion helpers belong in the test that uses -them. +Keep this module small: it exists only for types that every test would otherwise have to +assemble from the SDK's internals to annotate a client callback. Server fixtures and +assertion helpers belong in the test that uses them. """ -from types import TracebackType - -import anyio -from typing_extensions import Self - -from mcp.client._transport import ReadStream, Transport, TransportStreams, WriteStream -from mcp.shared.message import SessionMessage from mcp.shared.session import RequestResponder from mcp.types import ClientResult, ServerNotification, ServerRequest @@ -23,85 +15,3 @@ # for the request callbacks), at which point this alias can be deleted. IncomingMessage = RequestResponder[ServerRequest, ClientResult] | ServerNotification | Exception """Everything a client message handler can receive.""" - - -class _RecordingReadStream: - """Delegates to a read stream, appending every received message to a log.""" - - def __init__(self, inner: ReadStream[SessionMessage | Exception], log: list[SessionMessage | Exception]) -> None: - self._inner = inner - self._log = log - - async def receive(self) -> SessionMessage | Exception: - item = await self._inner.receive() - self._log.append(item) - return item - - async def aclose(self) -> None: - await self._inner.aclose() - - def __aiter__(self) -> Self: - return self - - async def __anext__(self) -> SessionMessage | Exception: - try: - return await self.receive() - except anyio.EndOfStream: - raise StopAsyncIteration from None - - async def __aenter__(self) -> Self: - return self - - async def __aexit__( - self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None - ) -> bool | None: - await self.aclose() - return None - - -class _RecordingWriteStream: - """Delegates to a write stream, appending every sent message to a log.""" - - def __init__(self, inner: WriteStream[SessionMessage], log: list[SessionMessage]) -> None: - self._inner = inner - self._log = log - - async def send(self, item: SessionMessage, /) -> None: - self._log.append(item) - await self._inner.send(item) - - async def aclose(self) -> None: - await self._inner.aclose() - - async def __aenter__(self) -> Self: - return self - - async def __aexit__( - self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None - ) -> bool | None: - await self.aclose() - return None - - -class RecordingTransport: - """Wraps a Transport and records every message crossing the client's transport boundary. - - `sent` holds everything the client wrote towards the server; `received` holds everything the - server delivered to the client. The recording sits at the transport seam -- the exact payloads - a real transport would serialise -- and never touches the session, so wire-level assertions - written against it survive changes to the receive path. - """ - - def __init__(self, inner: Transport) -> None: - self.inner = inner - self.sent: list[SessionMessage] = [] - self.received: list[SessionMessage | Exception] = [] - - async def __aenter__(self) -> TransportStreams: - read_stream, write_stream = await self.inner.__aenter__() - return _RecordingReadStream(read_stream, self.received), _RecordingWriteStream(write_stream, self.sent) - - async def __aexit__( - self, exc_type: type[BaseException] | None, exc_val: BaseException | None, exc_tb: TracebackType | None - ) -> bool | None: - return await self.inner.__aexit__(exc_type, exc_val, exc_tb) diff --git a/tests/interaction/_requirements.py b/tests/interaction/_requirements.py index caed8905d0..f7d9d3cbb5 100644 --- a/tests/interaction/_requirements.py +++ b/tests/interaction/_requirements.py @@ -54,6 +54,13 @@ "unimplemented." ) +_WIRE_FRAME_DEFERRAL = ( + "Not yet covered here: previously exercised by lowlevel wire-frame round-trip tests " + "that were removed in the wire-method maps rework (their inline-snapshot frames pinned " + "wire shapes the rework changes); interaction-level coverage returns when the session " + "layer is rewired to the maps." +) + @dataclass(frozen=True, kw_only=True) class Divergence: @@ -158,6 +165,7 @@ def __post_init__(self) -> None: "After successful initialization, the client sends exactly one initialized notification, " "before any non-ping request." ), + deferred=_WIRE_FRAME_DEFERRAL, ), "lifecycle:ping": Requirement( source=f"{SPEC_BASE_URL}/basic/utilities/ping#behavior-requirements", @@ -235,6 +243,7 @@ def __post_init__(self) -> None: "Every request sent on a session carries a unique, non-null string or integer id; ids are " "never reused within the session." ), + deferred=_WIRE_FRAME_DEFERRAL, ), "protocol:notifications:no-response": Requirement( source=f"{SPEC_BASE_URL}/basic#notifications", @@ -242,6 +251,7 @@ def __post_init__(self) -> None: "Notifications are never answered: every message the server delivers is either the response " "to a request the client sent or a notification carrying no id." ), + deferred=_WIRE_FRAME_DEFERRAL, ), "protocol:cancel:abort-signal": Requirement( source=f"{SPEC_BASE_URL}/basic/utilities/cancellation#cancellation-flow", @@ -341,6 +351,7 @@ def __post_init__(self) -> None: "protocol:error:connection-closed": Requirement( source="sdk", behavior="Closing the transport fails all in-flight requests with a connection-closed error.", + deferred=_WIRE_FRAME_DEFERRAL, ), "protocol:error:internal-error": Requirement( source=f"{SPEC_BASE_URL}/basic#responses", @@ -358,6 +369,7 @@ def __post_init__(self) -> None: "protocol:error:invalid-params": Requirement( source=f"{SPEC_BASE_URL}/basic#responses", behavior="A request with malformed params is answered with JSON-RPC error -32602 Invalid params.", + deferred=_WIRE_FRAME_DEFERRAL, ), "protocol:error:method-not-found": Requirement( source=f"{SPEC_BASE_URL}/basic#responses", @@ -1094,6 +1106,7 @@ def __post_init__(self) -> None: "logging:set-level:invalid-level": Requirement( source=f"{SPEC_BASE_URL}/server/utilities/logging#error-handling", behavior="logging/setLevel with an invalid level value returns JSON-RPC error -32602 (Invalid params).", + deferred=_WIRE_FRAME_DEFERRAL, ), # ═══════════════════════════════════════════════════════════════════════════ # Sampling (server → client) diff --git a/tests/interaction/lowlevel/test_wire.py b/tests/interaction/lowlevel/test_wire.py deleted file mode 100644 index 0f9c58aa7a..0000000000 --- a/tests/interaction/lowlevel/test_wire.py +++ /dev/null @@ -1,309 +0,0 @@ -"""Wire-level invariants observed at the client's transport boundary. - -These behaviours are invisible to API callers -- they are properties of the raw JSON-RPC frames. -The tests wrap the in-memory transport in a RecordingTransport, which tees every message crossing -the transport seam into a list without touching the session, so the assertions hold for whatever -the session implementation sends rather than for what its API returns. - -The later tests drive the wire by hand instead: one closes the server-to-client stream while a -request is in flight to pin the connection-closed teardown, and the last two send deliberately -malformed JSON-RPC requests that the typed client API cannot produce. -""" - -import anyio -import pytest -from inline_snapshot import snapshot - -from mcp import MCPError, types -from mcp.client import ClientRequestContext, ClientSession -from mcp.client._memory import InMemoryTransport -from mcp.client.client import Client -from mcp.server import Server, ServerRequestContext -from mcp.shared.memory import create_client_server_memory_streams -from mcp.shared.message import SessionMessage -from mcp.types import ( - CONNECTION_CLOSED, - INVALID_PARAMS, - CallToolRequest, - CallToolRequestParams, - CallToolResult, - EmptyResult, - ErrorData, - JSONRPCError, - JSONRPCNotification, - JSONRPCRequest, - JSONRPCResponse, - ListRootsResult, - TextContent, -) -from tests.interaction._helpers import RecordingTransport, _RecordingReadStream -from tests.interaction._requirements import requirement - -pytestmark = pytest.mark.anyio - - -def _echo_server() -> Server: - """A server with one echo tool, used by every test in this module.""" - - async def list_tools( - ctx: ServerRequestContext, params: types.PaginatedRequestParams | None - ) -> types.ListToolsResult: - return types.ListToolsResult(tools=[types.Tool(name="echo", input_schema={"type": "object"})]) - - async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult: - assert params.name == "echo" - return CallToolResult(content=[TextContent(text="ok")]) - - return Server("wire", on_list_tools=list_tools, on_call_tool=call_tool) - - -@requirement("protocol:request-id:unique") -async def test_request_ids_are_unique_and_never_null() -> None: - """Every request the client sends carries a distinct, non-null id. - - The id sequence is pinned: sequential integers from zero, in send order. - """ - recording = RecordingTransport(InMemoryTransport(_echo_server())) - - async with Client(recording) as client: - await client.list_tools() - await client.call_tool("echo", {}) - await client.call_tool("echo", {}) - await client.send_ping() - - sent = [message.message for message in recording.sent] - request_ids = [message.id for message in sent if isinstance(message, JSONRPCRequest)] - assert all(request_id is not None for request_id in request_ids) - assert len(request_ids) == len(set(request_ids)) - # initialize, tools/list, tools/call, tools/call, ping -- the client does not issue a - # schema-cache refresh here because the explicit tools/list already populated the cache. - assert request_ids == snapshot([0, 1, 2, 3, 4]) - - -@requirement("protocol:notifications:no-response") -async def test_notifications_are_never_answered() -> None: - """A notification produces no response: everything the server sends back answers a request. - - The client sends two notifications (initialized and roots/list_changed) and several requests; - the messages received from the server must be exactly one response per request, each carrying - the id of the request it answers, and nothing else. - """ - - async def list_roots(context: ClientRequestContext) -> ListRootsResult: - """Registered so the client declares the roots capability; the server never asks for roots.""" - raise NotImplementedError - - recording = RecordingTransport(InMemoryTransport(_echo_server())) - - async with Client(recording, list_roots_callback=list_roots) as client: - await client.send_roots_list_changed() - await client.send_ping() - - sent = [message.message for message in recording.sent] - sent_request_ids = [message.id for message in sent if isinstance(message, JSONRPCRequest)] - sent_notifications = [message for message in sent if isinstance(message, JSONRPCNotification)] - received = [message.message for message in recording.received if isinstance(message, SessionMessage)] - received_responses = [message for message in received if isinstance(message, JSONRPCResponse)] - - assert len(sent_notifications) == 2 # notifications/initialized and notifications/roots/list_changed - assert len(received_responses) == len(received) # nothing the server sent was anything but a response - assert [message.id for message in received_responses] == sent_request_ids - - -async def test_recording_read_stream_ends_iteration_when_the_sender_closes() -> None: - """The recording wrapper preserves the end-of-stream behaviour of the stream it wraps. - - This exercises the helper itself rather than an interaction-model behaviour: a transport whose - far end closes must end the client's receive loop cleanly, and the wrapper must not swallow or - mistranslate that. - """ - send_stream, receive_stream = anyio.create_memory_object_stream[SessionMessage | Exception](1) - log: list[SessionMessage | Exception] = [] - async with send_stream, _RecordingReadStream(receive_stream, log) as wrapped: - await send_stream.aclose() - items = [item async for item in wrapped] - assert items == [] - assert log == [] - - -@requirement("lifecycle:initialized-notification") -async def test_exactly_one_initialized_notification_is_sent_after_the_handshake() -> None: - """The client sends initialized exactly once, between the initialize response and its first request. - - The full method sequence the client puts on the wire is pinned in send order. - """ - recording = RecordingTransport(InMemoryTransport(_echo_server())) - - async with Client(recording) as client: - await client.list_tools() - - sent_methods = [ - message.message.method - for message in recording.sent - if isinstance(message.message, JSONRPCRequest | JSONRPCNotification) - ] - assert sent_methods.count("notifications/initialized") == 1 - assert sent_methods == snapshot(["initialize", "notifications/initialized", "tools/list"]) - - -@requirement("protocol:error:connection-closed") -async def test_closing_the_transport_fails_in_flight_requests_with_connection_closed() -> None: - """When the server-to-client stream closes, every in-flight client request fails with CONNECTION_CLOSED. - - Driven over a bare ClientSession against a real Server so the test holds the transport stream - pair directly: once the request is in flight (the server handler signals it has started) the - test closes the server's write stream, which ends the client's receive loop and triggers the - teardown that fails the pending request. - """ - handler_started = anyio.Event() - - async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult: - assert params.name == "block" - handler_started.set() - await anyio.Event().wait() # blocks until cancelled; nothing ever sets this event - raise NotImplementedError # unreachable: the wait above never completes normally - - server = Server("blocker", on_call_tool=call_tool) - - async with create_client_server_memory_streams() as (client_streams, server_streams): - client_read, client_write = client_streams - server_read, server_write = server_streams - errors: list[ErrorData] = [] - - async with anyio.create_task_group() as server_task_group: - server_task_group.start_soon(server.run, server_read, server_write, server.create_initialization_options()) - - async with ClientSession(client_read, client_write) as session: - with anyio.fail_after(5): - await session.initialize() - - async def call_and_capture_error() -> None: - with pytest.raises(MCPError) as exc_info: - await session.send_request( - CallToolRequest(params=CallToolRequestParams(name="block")), CallToolResult - ) - errors.append(exc_info.value.error) - - async with anyio.create_task_group() as task_group: # pragma: no branch - task_group.start_soon(call_and_capture_error) - await handler_started.wait() - await server_write.aclose() - - server_task_group.cancel_scope.cancel() - - assert errors == snapshot([ErrorData(code=CONNECTION_CLOSED, message="Connection closed")]) - - -@requirement("protocol:error:invalid-params") -async def test_malformed_request_params_are_answered_with_invalid_params() -> None: - """A request whose params fail validation is answered with -32602 Invalid params. - - The typed client API cannot construct a request with the wrong parameter types, so the test - plays the client's side of the wire by hand against a real Server: it completes the - initialization handshake at the JSON-RPC layer and then sends a tools/call whose `name` is an - integer. Reserve this pattern for behaviour the typed API cannot produce. - """ - server = Server("strict") - errors: list[ErrorData] = [] - - async with create_client_server_memory_streams() as (client_streams, server_streams): - client_read, client_write = client_streams - server_read, server_write = server_streams - - async with anyio.create_task_group() as server_task_group: - server_task_group.start_soon(server.run, server_read, server_write, server.create_initialization_options()) - - with anyio.fail_after(5): - await client_write.send( - SessionMessage( - JSONRPCRequest( - jsonrpc="2.0", - id=0, - method="initialize", - params={ - "protocolVersion": "2025-11-25", - "capabilities": {}, - "clientInfo": {"name": "raw", "version": "0.0.1"}, - }, - ) - ) - ) - init_response = await client_read.receive() - assert isinstance(init_response, SessionMessage) - assert isinstance(init_response.message, JSONRPCResponse) - await client_write.send( - SessionMessage(JSONRPCNotification(jsonrpc="2.0", method="notifications/initialized")) - ) - - await client_write.send( - SessionMessage(JSONRPCRequest(jsonrpc="2.0", id=1, method="tools/call", params={"name": 42})) - ) - error_response = await client_read.receive() - assert isinstance(error_response, SessionMessage) - assert isinstance(error_response.message, JSONRPCError) - errors.append(error_response.message.error) - - server_task_group.cancel_scope.cancel() - - assert errors == snapshot([ErrorData(code=INVALID_PARAMS, message="Invalid request parameters", data="")]) - - -@requirement("logging:set-level:invalid-level") -async def test_set_level_with_an_unrecognized_value_is_answered_with_invalid_params() -> None: - """logging/setLevel with a value outside the spec's level enum is answered with -32602 Invalid params. - - The typed client API cannot construct a setLevel request with an unrecognized level (pyright and - the client-side model both reject it), so the test plays the client's side of the wire by hand - against a real Server. Reserve this pattern for behaviour the typed API cannot produce. - """ - - async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> EmptyResult: - """Registered so the logging capability is advertised; never called -- params validation fails first.""" - raise NotImplementedError - - server = Server("logger", on_set_logging_level=set_logging_level) - errors: list[ErrorData] = [] - - async with create_client_server_memory_streams() as (client_streams, server_streams): - client_read, client_write = client_streams - server_read, server_write = server_streams - - async with anyio.create_task_group() as server_task_group: - server_task_group.start_soon(server.run, server_read, server_write, server.create_initialization_options()) - - with anyio.fail_after(5): - await client_write.send( - SessionMessage( - JSONRPCRequest( - jsonrpc="2.0", - id=0, - method="initialize", - params={ - "protocolVersion": "2025-11-25", - "capabilities": {}, - "clientInfo": {"name": "raw", "version": "0.0.1"}, - }, - ) - ) - ) - init_response = await client_read.receive() - assert isinstance(init_response, SessionMessage) - assert isinstance(init_response.message, JSONRPCResponse) - await client_write.send( - SessionMessage(JSONRPCNotification(jsonrpc="2.0", method="notifications/initialized")) - ) - - await client_write.send( - SessionMessage( - JSONRPCRequest(jsonrpc="2.0", id=1, method="logging/setLevel", params={"level": "loud"}) - ) - ) - error_response = await client_read.receive() - assert isinstance(error_response, SessionMessage) - assert isinstance(error_response.message, JSONRPCError) - errors.append(error_response.message.error) - - server_task_group.cancel_scope.cancel() - - assert len(errors) == 1 - assert errors[0].code == INVALID_PARAMS diff --git a/tests/interaction/test_coverage.py b/tests/interaction/test_coverage.py index 7821c1eed5..4f4cf32117 100644 --- a/tests/interaction/test_coverage.py +++ b/tests/interaction/test_coverage.py @@ -25,7 +25,6 @@ # Tests that exercise the suite's own helpers rather than an interaction-model behaviour. # Anything listed here is exempt from the every-test-has-a-requirement check. _HARNESS_SELF_TESTS = { - "tests.interaction.lowlevel.test_wire.test_recording_read_stream_ends_iteration_when_the_sender_closes", "tests.interaction.transports.test_bridge.test_response_chunks_arrive_as_the_application_sends_them", "tests.interaction.transports.test_bridge.test_closing_the_response_delivers_a_disconnect_to_the_application", "tests.interaction.transports.test_bridge.test_an_application_failure_before_the_response_starts_fails_the_request", 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 6279eb4f54..a855370aff 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..1eff6df903 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,76 @@ 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]: + """The SDK's outbound serialization: one plain dump at every protocol version.""" + return result.model_dump(by_alias=True, mode="json", exclude_none=True) + + +def test_concrete_wire_results_always_dump_result_type_complete(): + """Every concrete result the SDK answers with on the wire dumps resultType "complete" by default. + + SDK-defined always-serialized default for the field the 2026-07-28 schema + requires on results; earlier peers ignore the extra key on non-empty results. + """ + 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(): + """The six cacheable results dump ttlMs 0 and cacheScope "private" by default at every version. + + SDK-defined always-serialized defaults for the fields the 2026-07-28 + schema requires on these results; earlier peers ignore 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(): + """A freshly constructed EmptyResult dumps as an empty object. + + SDK-defined carve-out: deployed peers validate empty results strictly and + reject extra keys, so the SDK never volunteers resultType on them. + """ + assert _wire_dump(EmptyResult()) == snapshot({}) + + +def test_empty_result_dumps_result_type_only_when_explicitly_tagged(): + """EmptyResult dumps resultType only when constructed with it, as code answering 2026-07-28 sessions must.""" + assert _wire_dump(EmptyResult(result_type="complete")) == snapshot({"resultType": "complete"}) + + +def test_input_required_result_dumps_its_discriminating_tag(): + """InputRequiredResult always dumps the input_required tag that discriminates the dual-result unions.""" + 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..ccc8a596d7 --- /dev/null +++ b/tests/types/test_methods.py @@ -0,0 +1,873 @@ +"""Tests for the wire-method maps and two-step parse functions in `mcp.types.methods`. + +The maps are data: these tests pin every row (methods per version, response +row values, class provenance) against expected sets transcribed from the +per-version ClientRequest/ServerRequest/ClientNotification/ServerNotification +union memberships in the pinned schemas, and exercise every behavior branch +of the six parse functions. +""" + +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 + +# Expected method sets per protocol version, transcribed from each pinned +# schema's ClientRequest/ServerRequest/ClientNotification/ServerNotification +# union memberships minus the tasks family (the four tasks/* request methods and +# notifications/tasks/status): the SDK defines the task types but never +# dispatches them; extensions register them. The version axis is derived from +# KNOWN_PROTOCOL_VERSIONS so that adding a version without map rows fails here +# instead of silently gating every method at runtime. +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"}), + # The standalone server-to-client request channel does not exist at + # this version; the empty set is deliberate, not an omission. + "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", + } + ), + }, +} + +# Response row values pinned per (method, version): identity for class-valued +# rows, exact arm tuple (classes and order) for union rows. Without the value +# pin, mistyping a response row as the bare surface EmptyResult (all fields +# optional, extras ignored) would silently disable strict response validation +# for that row while every key-level test stays green. +EXPECTED_SERVER_RESULTS: dict[tuple[str, str], type[WireModel] | tuple[type[WireModel], ...]] = { + # 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 + ("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], ...]] = { + # 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, +} + +# The only response rows legitimately valued by the bare surface EmptyResult. +EMPTY_SERVER_RESPONSE_METHODS = frozenset( + {"logging/setLevel", "ping", "resources/subscribe", "resources/unsubscribe", "subscriptions/listen"} +) +EMPTY_CLIENT_RESPONSE_METHODS = frozenset({"ping"}) + +# Which surface package each version's rows must come from. The pre-2026 +# versions share the 2025-11-25 package (the SDK's only earlier surface; +# the method gate stays version-exact through key absence even though shape +# validation for those versions is 2025-11-25-shaped). +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", +} + +# Requests on sessions negotiated at 2026-07-28 or later must carry the three +# reserved identity entries in params._meta; the 2026 surface types require them. +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. Fixtures are +# keyed by the surface class, not by (method, version): rows sharing a value +# share a fixture, so a future version's diff here is proportional to actual +# shape changes. Root URIs use the file scheme because the monolith's Root.uri +# is file-scheme only (see the companion divergence test below). +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). +# The 2026 bodies carry resultType and, on the cacheable results, ttlMs and +# cacheScope, because the 2026 surface requires all three on the wire. +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(): + """Each known protocol version's map rows match the transcribed method sets exactly. + + Spec-mandated method matrix. The version axis is derived from + KNOWN_PROTOCOL_VERSIONS: a version added there without an EXPECTED_METHODS + entry (or without map rows) fails here, instead of answering + METHOD_NOT_FOUND to every request on a successfully negotiated session. + """ + 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(): + """Every request that can be sent has exactly one response row at exactly the versions it exists.""" + 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(): + """Response row values are pinned per (method, version): identity for classes, exact arm order for unions. + + SDK-defined data pin: response rows carry no method literal, so a mistyped + row would otherwise silently weaken response validation. Also asserts that + only the known empty-response methods are 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(): + """Every surface key's method matches its class's method literal, its monolith row, and its version's package. + + SDK-defined cross-map invariants: (a) each surface method has a monolith + row, (b) the surface class declares the key's method literal, (c) so does + the monolith class, (d) each row's value comes from the package that + serves its version. + """ + 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: + """Attempt the item assignment the map types already forbid statically; the runtime must forbid it too.""" + mapping["new-key"] = None + + +def test_built_in_maps_are_immutable(): + """All nine built-in maps are mapping proxies that raise TypeError on mutation; extension is dict union.""" + 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(): + """A minimal valid body per surface class parses into a monolith request through every request row. + + SDK-defined property, scoped to these fixtures: what the surface step + admits, the monolith step parses (the one audited divergence is pinned in + the non-file Root.uri test below). + """ + 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(): + """A minimal valid body per surface class parses into a monolith notification 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(): + """A minimal valid body per response row value parses into a monolith result through every response 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(): + """A non-file root URI is the one built-in shape the monolith rejects after the surface admits it. + + Pins a known divergence: the monolith's Root.uri is file-scheme only while + both surface packages declare a plain string (matching the schema), so the + monolith step's ValidationError is reachable for built-in rows. Outcome is + identical to validating roots through the monolith directly. + """ + non_file_roots = {"roots": [{"uri": "https://example.com/x"}]} + # The surface step alone admits the body... + pydantic.TypeAdapter(v2025.ListRootsResult).validate_python(non_file_roots) + # ...so the two-step parse must fail at the monolith step. + with pytest.raises(pydantic.ValidationError): + methods.parse_client_result("roots/list", "2025-11-25", non_file_roots) + + # Same divergence through the one 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) + + # A file-scheme root URI parses end to end on both paths. + 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(): + """A missing (method, version) key raises KeyError for removed, future, extension, and wrong-direction methods. + + Spec-mandated gate: absence of a key IS the version gate; the session + layer maps it to METHOD_NOT_FOUND or drop-and-log. + """ + gated = [ + ("resources/subscribe", "2026-07-28"), # removed at this version + ("server/discover", "2025-11-25"), # does not exist yet at this version + ("tasks/get", "2025-11-25"), # never built-in; extensions register it + ("sampling/createMessage", "2025-11-25"), # wrong direction for this map + ] + for method, version in gated: + with pytest.raises(KeyError): + methods.parse_client_request(method, version, None) + # No server-to-client request exists at 2026-07-28: every method gates. + 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(): + """An unknown version string raises ValueError naming it, even for a method that exists at known versions. + + SDK-defined guard: a typo must crash loudly instead of silently gating + every method to METHOD_NOT_FOUND. + """ + 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(): + """A 2026-07-28 request missing any reserved _meta identity entry rejects with a missing error at that entry. + + Spec-mandated: the 2026-07-28 schema requires all three entries on every + client request; the surface types enforce it. + """ + 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(): + """A 2026-07-28 result body without resultType rejects, including the empty body. + + Spec-mandated: the 2026-07-28 schema requires resultType on every result. + """ + 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(): + """An empty result body parses as EmptyResult where the version's response row is the empty result.""" + 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(): + """A result carrying resultType/ttlMs/cacheScope parses at 2025-11-25, extras flowing into the monolith fields. + + Pins documented leniency: the earlier surface ignores unknown keys, so + 2026-07-28-shaped extras never reject on earlier sessions; the monolith + preserves the values on classes that declare the fields. + """ + 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(): + """An inputRequests entry with no method rejects at the surface step, which the monolith alone would admit. + + Spec-mandated: the 2026-07-28 embedded request shapes require method. The + monolith's embedded request classes default their method literals, so the + rejection can only come from the surface step; this asserts both halves. + """ + 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(): + """params=None omits the params key entirely: methods with required params reject, optional ones parse.""" + 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: a required aliased field sent in its snake-case spelling rejects as missing. + + SDK-defined contract (matches the session layer's by_name=False parsing). + Both steps enforce it; the surface step fires first through the parse + function, and the monolith class enforces the same rule directly. + """ + 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(): + """Dict-union extension rows parse end to end through the unchanged parse 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(): + """A surface row whose method has no monolith row raises RuntimeError, never the gate's KeyError. + + SDK-defined: the session layer treats KeyError as the version gate, so + inconsistent extension maps must fail distinguishably. + """ + 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(): + """The three dual-result unions resolve every body shape to the same arm regardless of arm order. + + SDK-defined: discrimination rides on the models' resultType literals and + required fields, so these unions are order-insensitive (unlike the + sampling union, pinned separately below). + """ + 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, # absent tag takes the "complete" default + {**complete_body, "resultType": "complete"}, # explicit tag + *shared_bodies, # input-required via inputRequests and via requestState + {**complete_body, "resultType": "task"}, # open tag is preserved, not rejected + {**complete_body, "resultType": "input_required"}, # pathological: 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(): + """The sampling result union pins CreateMessageResult first: a single-block body satisfies both arms. + + SDK-defined and order-sensitive: smart-union ties resolve leftmost, so + reversing the arms silently reparses single-block bodies as the + with-tools class. Array and tool-use bodies discriminate by shape. + """ + 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 + # The regression a reorder causes, demonstrated on the reversed union. + 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(): + """Import constructs zero result adapters; the first parse builds them and identical rows share a cache entry. + + SDK-defined import-cost contract. Executes a fresh copy of the module (so + the assertion is order-independent) and reads its private adapter cache, + because lazy construction is exactly the contract under test. + """ + 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 + # One result parse builds exactly two adapters: the surface row and the monolith row. + fresh.parse_server_result("ping", "2025-11-25", {}) + assert fresh._adapter.cache_info().currsize == 2 + # The same rows at another version are identical objects: no new adapters. + fresh.parse_server_result("ping", "2024-11-05", {}) + assert fresh._adapter.cache_info().currsize == 2 diff --git a/tests/types/test_public_surface.py b/tests/types/test_public_surface.py new file mode 100644 index 0000000000..f5a6e72908 --- /dev/null +++ b/tests/types/test_public_surface.py @@ -0,0 +1,374 @@ +"""Pin the public type surface against the fork-point baseline. + +``mcp.types.__all__`` is a one-way compatibility ratchet: every name the +module exported at the fork point is still exported (zero removals), and this +branch adds exactly the names in ``_ADDED_EXPORTS`` — nothing else. The +curated top-level surface (``mcp.__all__``) gains nothing. Negotiation +defaults are pinned unchanged: modeling the 2026-07-28 protocol revision must +not change which protocol versions the SDK advertises or negotiates. +""" + +from __future__ import annotations + +from typing import Any + +import mcp +import mcp.types +from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS +from mcp.types import CallToolRequestParams, Tool + +_BASELINE_EXPORTS: tuple[str, ...] = ( + # `mcp.types.__all__` at the fork point (153 names, sorted). Removing any + # of these is a breaking change; this tuple is never edited, only the + # additions tuple below grows. + "Annotations", + "AudioContent", + "BaseMetadata", + "BlobResourceContents", + "CONNECTION_CLOSED", + "CallToolRequest", + "CallToolRequestParams", + "CallToolResult", + "CancelledNotification", + "CancelledNotificationParams", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "CompleteRequest", + "CompleteRequestParams", + "CompleteResult", + "Completion", + "CompletionArgument", + "CompletionContext", + "CompletionsCapability", + "ContentBlock", + "CreateMessageRequest", + "CreateMessageRequestParams", + "CreateMessageResult", + "CreateMessageResultWithTools", + "DEFAULT_NEGOTIATED_VERSION", + "ElicitCompleteNotification", + "ElicitCompleteNotificationParams", + "ElicitRequest", + "ElicitRequestFormParams", + "ElicitRequestParams", + "ElicitRequestURLParams", + "ElicitRequestedSchema", + "ElicitResult", + "ElicitationCapability", + "ElicitationRequiredErrorData", + "EmbeddedResource", + "EmptyResult", + "ErrorData", + "FormElicitationCapability", + "GetPromptRequest", + "GetPromptRequestParams", + "GetPromptResult", + "INTERNAL_ERROR", + "INVALID_PARAMS", + "INVALID_REQUEST", + "Icon", + "IconTheme", + "ImageContent", + "Implementation", + "IncludeContext", + "InitializeRequest", + "InitializeRequestParams", + "InitializeResult", + "InitializedNotification", + "JSONRPCError", + "JSONRPCMessage", + "JSONRPCNotification", + "JSONRPCRequest", + "JSONRPCResponse", + "LATEST_PROTOCOL_VERSION", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourceTemplatesRequest", + "ListResourceTemplatesResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListRootsRequest", + "ListRootsResult", + "ListToolsRequest", + "ListToolsResult", + "LoggingCapability", + "LoggingLevel", + "LoggingMessageNotification", + "LoggingMessageNotificationParams", + "METHOD_NOT_FOUND", + "ModelHint", + "ModelPreferences", + "Notification", + "NotificationParams", + "PARSE_ERROR", + "PaginatedRequest", + "PaginatedRequestParams", + "PaginatedResult", + "PingRequest", + "ProgressNotification", + "ProgressNotificationParams", + "ProgressToken", + "Prompt", + "PromptArgument", + "PromptListChangedNotification", + "PromptMessage", + "PromptReference", + "PromptsCapability", + "REQUEST_CANCELLED", + "REQUEST_TIMEOUT", + "ReadResourceRequest", + "ReadResourceRequestParams", + "ReadResourceResult", + "Request", + "RequestId", + "RequestParams", + "RequestParamsMeta", + "Resource", + "ResourceContents", + "ResourceLink", + "ResourceListChangedNotification", + "ResourceTemplate", + "ResourceTemplateReference", + "ResourceUpdatedNotification", + "ResourceUpdatedNotificationParams", + "ResourcesCapability", + "Result", + "Role", + "Root", + "RootsCapability", + "RootsListChangedNotification", + "SamplingCapability", + "SamplingContent", + "SamplingContextCapability", + "SamplingMessage", + "SamplingMessageContentBlock", + "SamplingToolsCapability", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "SetLevelRequest", + "SetLevelRequestParams", + "StopReason", + "SubscribeRequest", + "SubscribeRequestParams", + "TextContent", + "TextResourceContents", + "Tool", + "ToolAnnotations", + "ToolChoice", + "ToolListChangedNotification", + "ToolResultContent", + "ToolUseContent", + "ToolsCapability", + "URL_ELICITATION_REQUIRED", + "UnsubscribeRequest", + "UnsubscribeRequestParams", + "UrlElicitationCapability", + "client_notification_adapter", + "client_request_adapter", + "client_result_adapter", + "jsonrpc_message_adapter", + "server_notification_adapter", + "server_request_adapter", + "server_result_adapter", +) + +_ADDED_EXPORTS: tuple[str, ...] = ( + # Everything this branch adds to `mcp.types.__all__` (43 names). Grouped + # by the protocol feature that introduces each name. + # + # JSON-RPC protocol identifier, re-exported from mcp.types.jsonrpc. + "JSONRPC_VERSION", + # Result completion state, added in 2026-07-28 (absent means complete). + "ResultType", + # Client-side caching directives on results, added in 2026-07-28. + "CacheableResult", + # The server/discover lifecycle request, added in 2026-07-28. + "DiscoverRequest", + "DiscoverResult", + # Filtered resource subscriptions, added in 2026-07-28. + "SubscriptionFilter", + "SubscriptionsAcknowledgedNotification", + "SubscriptionsAcknowledgedNotificationParams", + "SubscriptionsListenRequest", + "SubscriptionsListenRequestParams", + # Server-initiated input requests during sampling, added in 2026-07-28. + "InputRequest", + "InputRequests", + "InputRequiredResult", + "InputResponse", + "InputResponseRequestParams", + "InputResponses", + # Error payloads and codes, added in 2026-07-28. + "MISSING_REQUIRED_CLIENT_CAPABILITY", + "MissingRequiredClientCapabilityErrorData", + "UNSUPPORTED_PROTOCOL_VERSION", + "UnsupportedProtocolVersionErrorData", + # Reserved `_meta` key names, added in 2026-07-28. + "CLIENT_CAPABILITIES_META_KEY", + "CLIENT_INFO_META_KEY", + "LOG_LEVEL_META_KEY", + "PROTOCOL_VERSION_META_KEY", + # Task types from 2025-11-25, modeled again so that sessions negotiating + # that version can exchange them (the methods were removed in 2026-07-28). + "CancelTaskRequest", + "CancelTaskRequestParams", + "CancelTaskResult", + "CreateTaskResult", + "GetTaskPayloadRequest", + "GetTaskPayloadRequestParams", + "GetTaskPayloadResult", + "GetTaskRequest", + "GetTaskRequestParams", + "GetTaskResult", + "ListTasksRequest", + "ListTasksResult", + "RelatedTaskMetadata", + "Task", + "TaskMetadata", + "TaskStatus", + "TaskStatusNotification", + "TaskStatusNotificationParams", + "ToolExecution", +) + +_TOP_LEVEL_EXPORTS: tuple[str, ...] = ( + # `mcp.__all__` at the fork point (66 names, original order). This branch + # adds nothing to the curated top-level surface. + "CallToolRequest", + "Client", + "ClientCapabilities", + "ClientNotification", + "ClientRequest", + "ClientResult", + "ClientSession", + "ClientSessionGroup", + "CompleteRequest", + "CreateMessageRequest", + "CreateMessageResult", + "CreateMessageResultWithTools", + "ErrorData", + "GetPromptRequest", + "GetPromptResult", + "Implementation", + "IncludeContext", + "InitializeRequest", + "InitializeResult", + "InitializedNotification", + "JSONRPCError", + "JSONRPCRequest", + "JSONRPCResponse", + "ListPromptsRequest", + "ListPromptsResult", + "ListResourcesRequest", + "ListResourcesResult", + "ListToolsResult", + "LoggingLevel", + "LoggingMessageNotification", + "MCPError", + "Notification", + "PingRequest", + "ProgressNotification", + "PromptsCapability", + "ReadResourceRequest", + "ReadResourceResult", + "Resource", + "ResourcesCapability", + "ResourceUpdatedNotification", + "RootsCapability", + "SamplingCapability", + "SamplingContent", + "SamplingContextCapability", + "SamplingMessage", + "SamplingMessageContentBlock", + "SamplingRole", + "SamplingToolsCapability", + "ServerCapabilities", + "ServerNotification", + "ServerRequest", + "ServerResult", + "ServerSession", + "SetLevelRequest", + "StdioServerParameters", + "StopReason", + "SubscribeRequest", + "Tool", + "ToolChoice", + "ToolResultContent", + "ToolsCapability", + "ToolUseContent", + "UnsubscribeRequest", + "UrlElicitationRequiredError", + "stdio_client", + "stdio_server", +) + + +def test_pinned_lists_are_internally_consistent() -> None: + """Self-check on the pinned data: counts, no duplicates, no overlap.""" + assert len(_BASELINE_EXPORTS) == 153 + assert len(set(_BASELINE_EXPORTS)) == 153 + assert len(_ADDED_EXPORTS) == 43 + assert len(set(_ADDED_EXPORTS)) == 43 + assert set(_BASELINE_EXPORTS) & set(_ADDED_EXPORTS) == set() + assert len(_TOP_LEVEL_EXPORTS) == 66 + + +def test_types_exports_are_baseline_plus_exactly_the_additions() -> None: + """`mcp.types.__all__` keeps every fork-point name and adds only the pinned names.""" + exported = set(mcp.types.__all__) + removed = set(_BASELINE_EXPORTS) - exported + assert removed == set(), f"fork-point exports must never be removed: {sorted(removed)}" + assert exported - set(_BASELINE_EXPORTS) == set(_ADDED_EXPORTS) + + +def test_types_export_list_has_no_duplicates() -> None: + """`mcp.types.__all__` lists each name exactly once.""" + assert len(mcp.types.__all__) == len(set(mcp.types.__all__)) + + +def test_every_types_export_resolves() -> None: + """Every name in `mcp.types.__all__` is an attribute of the module.""" + missing = [name for name in mcp.types.__all__ if not hasattr(mcp.types, name)] + assert missing == [] + + +def test_top_level_exports_unchanged() -> None: + """`mcp.__all__` is exactly the fork-point list, in the same order.""" + assert list(mcp.__all__) == list(_TOP_LEVEL_EXPORTS) + + +def test_negotiation_defaults_unchanged() -> None: + """The SDK advertises and negotiates the same versions as at the fork point. + + 2026-07-28 types are modeled, but the version is not offered during + negotiation; enabling it is a separate, deliberate change. + """ + assert mcp.types.LATEST_PROTOCOL_VERSION == "2025-11-25" + assert mcp.types.DEFAULT_NEGOTIATED_VERSION == "2025-03-26" + assert SUPPORTED_PROTOCOL_VERSIONS == ["2024-11-05", "2025-03-26", "2025-06-18", "2025-11-25"] + + +def test_wire_name_constructor_kwargs_still_work() -> None: + """Models keep accepting wire-name (camelCase) constructor kwargs. + + The static signature lists only the snake_case field names, so the + wire-name spelling is passed as an unpacked dict; at runtime both + spellings construct the same model. + """ + wire_name_kwargs: dict[str, Any] = {"name": "t", "inputSchema": {"type": "object"}} + assert Tool(**wire_name_kwargs) == Tool(name="t", input_schema={"type": "object"}) + + +def test_meta_constructor_kwargs_still_work() -> None: + """Request params accept both the `_meta` wire alias and the `meta` field name. + + The static signature lists only the `_meta` spelling, so the field-name + spelling is passed as an unpacked dict; at runtime both spellings + construct the same model. + """ + field_name_kwargs: dict[str, Any] = {"name": "t", "meta": {"k": "v"}} + assert CallToolRequestParams(**field_name_kwargs) == CallToolRequestParams(name="t", _meta={"k": "v"}) diff --git a/tests/types/test_wire_frames.py b/tests/types/test_wire_frames.py new file mode 100644 index 0000000000..6581264334 --- /dev/null +++ b/tests/types/test_wire_frames.py @@ -0,0 +1,120 @@ +"""Frame-level pins for the SDK's outbound JSON-RPC serialization. + +Each test builds a monolith payload model, dumps it with the session layer's +outbound convention (`model_dump(by_alias=True, mode="json", exclude_none=True)`), +wraps it in the matching `mcp.types.jsonrpc` envelope class, and pins the +frame exactly as the transports serialize it +(`model_dump_json(by_alias=True, exclude_unset=True)`, see the stdio +transports). The pinned strings are the SDK-defined serialization contract -- +the spec constrains JSON content, not key order or the SDK's +always-serialized defaults -- so a diff here is a wire-visible change that +needs a deliberate decision, not necessarily a bug. +""" + +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]: + """The session layer's outbound dump: one plain dump at every protocol version.""" + return model.model_dump(by_alias=True, mode="json", exclude_none=True) + + +def _frame(envelope: BaseModel) -> str: + """The transports' frame serialization (see the stdio transports).""" + return envelope.model_dump_json(by_alias=True, exclude_unset=True) + + +def test_request_frame_carries_the_envelope_and_the_dumped_request_body(): + """A request frame is the JSON-RPC envelope around the monolith request dump, aliases applied.""" + 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(): + """A notification frame carries method and params only: the id key never appears.""" + 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(): + """A non-empty result frame carries resultType "complete" without the handler setting it. + + SDK-defined always-serialized default for the field the 2026-07-28 schema + requires on results; earlier peers ignore the extra key. + """ + 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(): + """A cacheable list result frame carries ttlMs 0 and cacheScope "private" without the handler setting them. + + SDK-defined always-serialized defaults for the fields the 2026-07-28 + schema requires on cacheable results; earlier peers ignore the extra keys. + """ + 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(): + """A default EmptyResult frame carries result {} with no resultType. + + SDK-defined carve-out: deployed peers validate empty results strictly and + reject extra keys, so the SDK never volunteers resultType on them. + """ + 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(): + """An input-required frame travels as a plain result whose body carries the discriminating tag. + + The embedded server-initiated request dumps inside inputRequests; no + JSON-RPC request frame exists for it (2026-07-28 MRTR flow). + """ + 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(): + """An error frame carries the ErrorData object under the error key; unset data never appears.""" + 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"}}')