diff --git a/README.v2.md b/README.v2.md index 25cf5ac959..1d0e96d79f 100644 --- a/README.v2.md +++ b/README.v2.md @@ -45,7 +45,6 @@ - [Context Properties and Methods](#context-properties-and-methods) - [Completions](#completions) - [Elicitation](#elicitation) - - [Sampling](#sampling) - [Logging and Notifications](#logging-and-notifications) - [Authentication](#authentication) - [MCPServer Properties](#mcpserver-properties) @@ -931,42 +930,6 @@ The `elicit()` method returns an `ElicitationResult` with: If the client returns data that doesn't match the schema, `elicit()` raises a `pydantic.ValidationError`. -### Sampling - -Tools can interact with LLMs through sampling (generating text): - - -```python -from mcp.server.mcpserver import Context, MCPServer -from mcp.types import SamplingMessage, TextContent - -mcp = MCPServer(name="Sampling Example") - - -@mcp.tool() -async def generate_poem(topic: str, ctx: Context) -> str: - """Generate a poem using LLM sampling.""" - prompt = f"Write a short poem about {topic}" - - result = await ctx.session.create_message( - messages=[ - SamplingMessage( - role="user", - content=TextContent(type="text", text=prompt), - ) - ], - max_tokens=100, - ) - - # Since we're not passing tools param, result.content is single content - if result.content.type == "text": - return result.content.text - return str(result.content) -``` - -_Full example: [examples/snippets/servers/sampling.py](https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/snippets/servers/sampling.py)_ - - ### Logging and Notifications Tools can send logs and notifications through the context: @@ -2108,7 +2071,6 @@ import asyncio import os from mcp import ClientSession, StdioServerParameters, types -from mcp.client.context import ClientRequestContext from mcp.client.stdio import stdio_client # Create server parameters for stdio connection @@ -2119,25 +2081,9 @@ server_params = StdioServerParameters( ) -# Optional: create a sampling callback -async def handle_sampling_message( - context: ClientRequestContext, params: types.CreateMessageRequestParams -) -> types.CreateMessageResult: - print(f"Sampling request: {params.messages}") - return types.CreateMessageResult( - role="assistant", - content=types.TextContent( - type="text", - text="Hello, world! from model", - ), - model="gpt-3.5-turbo", - stop_reason="endTurn", - ) - - async def run(): async with stdio_client(server_params) as (read, write): - async with ClientSession(read, write, sampling_callback=handle_sampling_message) as session: + async with ClientSession(read, write) as session: # Initialize the connection await session.initialize() diff --git a/docs/migration.md b/docs/migration.md index 9fbbbf2ed2..167e183936 100644 --- a/docs/migration.md +++ b/docs/migration.md @@ -1199,7 +1199,13 @@ Tasks are expected to return as a separate MCP extension in a future release. ## Deprecations - +### Roots, Sampling, and Logging deprecated (SEP-2577) + +[SEP-2577](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2577) deprecates the Roots, Sampling, and Logging features as of the 2026-07-28 spec. This is advisory only: there are no wire-level changes, capability negotiation is unchanged, and every type remains fully functional for sessions negotiating 2025-11-25 and earlier. + +The deprecated capability fields (`ClientCapabilities.roots`, `ClientCapabilities.sampling`, `ClientCapabilities.tasks.requests.sampling`, `ServerCapabilities.logging`) and the associated types (`Root`, `ListRootsRequest`, `ListRootsResult`, `RootsListChangedNotification`, `CreateMessageRequest`/`Params`/`Result`, `SamplingMessage`, `ToolChoice`, `ToolUseContent`, `ToolResultContent`, `ModelPreferences`, `ModelHint`, `SetLevelRequest`/`Params`, `LoggingMessageNotification`/`Params`) are marked with `typing_extensions.deprecated`. Type checkers and IDEs surface a deprecation warning where downstream code uses them; at runtime, accessing a deprecated capability field or constructing a deprecated type emits a `DeprecationWarning`. + +No migration is required during the deprecation window. New code should avoid building on these features, since they may be removed in a future spec version. ## Bug Fixes diff --git a/examples/servers/everything-server/mcp_everything_server/server.py b/examples/servers/everything-server/mcp_everything_server/server.py index b37ff3e950..b6ea3cd169 100644 --- a/examples/servers/everything-server/mcp_everything_server/server.py +++ b/examples/servers/everything-server/mcp_everything_server/server.py @@ -25,8 +25,8 @@ JSONRPCMessage, PromptReference, ResourceTemplateReference, - SamplingMessage, - SetLevelRequestParams, + SamplingMessage, # pyright: ignore[reportDeprecated] + SetLevelRequestParams, # pyright: ignore[reportDeprecated] SubscribeRequestParams, TextContent, TextResourceContents, @@ -177,7 +177,7 @@ async def test_sampling(prompt: str, ctx: Context) -> str: try: # Request sampling from client result = await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))], + messages=[SamplingMessage(role="user", content=TextContent(type="text", text=prompt))], # pyright: ignore[reportDeprecated] max_tokens=100, ) @@ -397,7 +397,7 @@ def test_prompt_with_image() -> list[UserMessage]: # Custom request handlers # TODO(felix): Add public APIs to MCPServer for subscribe_resource, unsubscribe_resource, # and set_logging_level to avoid accessing protected _lowlevel_server attribute. -async def handle_set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult: +async def handle_set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult: # pyright: ignore[reportDeprecated] """Handle logging level changes""" logger.info(f"Log level set to: {params.level}") return EmptyResult() @@ -418,7 +418,9 @@ async def handle_unsubscribe(ctx: ServerRequestContext, params: UnsubscribeReque mcp._lowlevel_server.add_request_handler( # pyright: ignore[reportPrivateUsage] - "logging/setLevel", SetLevelRequestParams, handle_set_logging_level + "logging/setLevel", + SetLevelRequestParams, # pyright: ignore[reportDeprecated] + handle_set_logging_level, ) mcp._lowlevel_server.add_request_handler( # pyright: ignore[reportPrivateUsage] "resources/subscribe", SubscribeRequestParams, handle_subscribe diff --git a/examples/snippets/clients/stdio_client.py b/examples/snippets/clients/stdio_client.py index 3f7c4b981b..56446d051a 100644 --- a/examples/snippets/clients/stdio_client.py +++ b/examples/snippets/clients/stdio_client.py @@ -6,7 +6,6 @@ import os from mcp import ClientSession, StdioServerParameters, types -from mcp.client.context import ClientRequestContext from mcp.client.stdio import stdio_client # Create server parameters for stdio connection @@ -17,25 +16,9 @@ ) -# Optional: create a sampling callback -async def handle_sampling_message( - context: ClientRequestContext, params: types.CreateMessageRequestParams -) -> types.CreateMessageResult: - print(f"Sampling request: {params.messages}") - return types.CreateMessageResult( - role="assistant", - content=types.TextContent( - type="text", - text="Hello, world! from model", - ), - model="gpt-3.5-turbo", - stop_reason="endTurn", - ) - - async def run(): async with stdio_client(server_params) as (read, write): - async with ClientSession(read, write, sampling_callback=handle_sampling_message) as session: + async with ClientSession(read, write) as session: # Initialize the connection await session.initialize() diff --git a/examples/snippets/servers/__init__.py b/examples/snippets/servers/__init__.py index f132f875f5..d8ed79eee2 100644 --- a/examples/snippets/servers/__init__.py +++ b/examples/snippets/servers/__init__.py @@ -21,7 +21,7 @@ def run_server(): if len(sys.argv) < 2: print("Usage: server [transport]") print("Available servers: basic_tool, basic_resource, basic_prompt, tool_progress,") - print(" sampling, elicitation, completion, notifications,") + print(" elicitation, completion, notifications,") print(" mcpserver_quickstart, structured_output, images") print("Available transports: stdio (default), sse, streamable-http") sys.exit(1) diff --git a/examples/snippets/servers/sampling.py b/examples/snippets/servers/sampling.py deleted file mode 100644 index 43259589a4..0000000000 --- a/examples/snippets/servers/sampling.py +++ /dev/null @@ -1,25 +0,0 @@ -from mcp.server.mcpserver import Context, MCPServer -from mcp.types import SamplingMessage, TextContent - -mcp = MCPServer(name="Sampling Example") - - -@mcp.tool() -async def generate_poem(topic: str, ctx: Context) -> str: - """Generate a poem using LLM sampling.""" - prompt = f"Write a short poem about {topic}" - - result = await ctx.session.create_message( - messages=[ - SamplingMessage( - role="user", - content=TextContent(type="text", text=prompt), - ) - ], - max_tokens=100, - ) - - # Since we're not passing tools param, result.content is single content - if result.content.type == "text": - return result.content.text - return str(result.content) diff --git a/pyproject.toml b/pyproject.toml index 8f8b8c37e1..31acb3da61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -213,6 +213,10 @@ filterwarnings = [ "error", # pywin32 internal deprecation warning "ignore:getargs.*The 'u' format is deprecated:DeprecationWarning", + # SEP-2577 deprecates roots/sampling/logging types; the SDK still builds them + # internally to serve <= 2025-11-25 sessions, so the @deprecated runtime warning + # is advisory only. Tests asserting it opt back in locally with pytest.warns. + "ignore:`.*` is deprecated as of 2026-07-28 \\(SEP-2577\\).:DeprecationWarning", ] [tool.markdown.lint] diff --git a/src/mcp/__init__.py b/src/mcp/__init__.py index 4b5caa9cca..636f896f85 100644 --- a/src/mcp/__init__.py +++ b/src/mcp/__init__.py @@ -12,8 +12,8 @@ ClientRequest, ClientResult, CompleteRequest, - CreateMessageRequest, - CreateMessageResult, + CreateMessageRequest, # pyright: ignore[reportDeprecated] + CreateMessageResult, # pyright: ignore[reportDeprecated] CreateMessageResultWithTools, ErrorData, GetPromptRequest, @@ -32,7 +32,7 @@ ListResourcesResult, ListToolsResult, LoggingLevel, - LoggingMessageNotification, + LoggingMessageNotification, # pyright: ignore[reportDeprecated] Notification, PingRequest, ProgressNotification, @@ -46,21 +46,21 @@ SamplingCapability, SamplingContent, SamplingContextCapability, - SamplingMessage, + SamplingMessage, # pyright: ignore[reportDeprecated] SamplingMessageContentBlock, SamplingToolsCapability, ServerCapabilities, ServerNotification, ServerRequest, ServerResult, - SetLevelRequest, + SetLevelRequest, # pyright: ignore[reportDeprecated] StopReason, SubscribeRequest, Tool, - ToolChoice, - ToolResultContent, + ToolChoice, # pyright: ignore[reportDeprecated] + ToolResultContent, # pyright: ignore[reportDeprecated] ToolsCapability, - ToolUseContent, + ToolUseContent, # pyright: ignore[reportDeprecated] UnsubscribeRequest, ) from .types import Role as SamplingRole diff --git a/src/mcp/client/session.py b/src/mcp/client/session.py index cd18a67541..fe95d3983f 100644 --- a/src/mcp/client/session.py +++ b/src/mcp/client/session.py @@ -53,8 +53,10 @@ class SamplingFnT(Protocol): async def __call__( self, context: ClientRequestContext, - params: types.CreateMessageRequestParams, - ) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: ... # pragma: no branch + params: types.CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> ( + types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData # pyright: ignore[reportDeprecated] + ): ... # pragma: no branch class ElicitationFnT(Protocol): @@ -68,11 +70,14 @@ async def __call__( class ListRootsFnT(Protocol): async def __call__( self, context: ClientRequestContext - ) -> types.ListRootsResult | types.ErrorData: ... # pragma: no branch + ) -> types.ListRootsResult | types.ErrorData: ... # pragma: no branch # pyright: ignore[reportDeprecated] class LoggingFnT(Protocol): - async def __call__(self, params: types.LoggingMessageNotificationParams) -> None: ... # pragma: no branch + async def __call__( + self, + params: types.LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated] + ) -> None: ... # pragma: no branch class MessageHandlerFnT(Protocol): @@ -90,8 +95,8 @@ async def _default_message_handler( async def _default_sampling_callback( context: ClientRequestContext, - params: types.CreateMessageRequestParams, -) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: + params: types.CreateMessageRequestParams, # pyright: ignore[reportDeprecated] +) -> types.CreateMessageResult | types.CreateMessageResultWithTools | types.ErrorData: # pyright: ignore[reportDeprecated] return types.ErrorData( code=types.INVALID_REQUEST, message="Sampling not supported", @@ -110,7 +115,7 @@ async def _default_elicitation_callback( async def _default_list_roots_callback( context: ClientRequestContext, -) -> types.ListRootsResult | types.ErrorData: +) -> types.ListRootsResult | types.ErrorData: # pyright: ignore[reportDeprecated] return types.ErrorData( code=types.INVALID_REQUEST, message="List roots not supported", @@ -118,7 +123,7 @@ async def _default_list_roots_callback( async def _default_logging_callback( - params: types.LoggingMessageNotificationParams, + params: types.LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated] ) -> None: pass @@ -394,7 +399,7 @@ async def set_logging_level( ) -> types.EmptyResult: """Send a logging/setLevel request.""" return await self.send_request( - types.SetLevelRequest(params=types.SetLevelRequestParams(level=level, _meta=meta)), + types.SetLevelRequest(params=types.SetLevelRequestParams(level=level, _meta=meta)), # pyright: ignore[reportDeprecated] types.EmptyResult, ) @@ -552,7 +557,7 @@ async def list_tools(self, *, params: types.PaginatedRequestParams | None = None async def send_roots_list_changed(self) -> None: """Send a roots/list_changed notification.""" - await self.send_notification(types.RootsListChangedNotification()) + await self.send_notification(types.RootsListChangedNotification()) # pyright: ignore[reportDeprecated] async def _on_request( self, dctx: DispatchContext[TransportContext], method: str, params: Mapping[str, Any] | None @@ -577,11 +582,11 @@ async def _on_request( session=self, request_id=dctx.request_id, meta=request.params.meta if request.params else None ) match request: - case types.CreateMessageRequest(params=sampling_params): + case types.CreateMessageRequest(params=sampling_params): # pyright: ignore[reportDeprecated] response = await self._sampling_callback(ctx, sampling_params) case types.ElicitRequest(params=elicit_params): response = await self._elicitation_callback(ctx, elicit_params) - case types.ListRootsRequest(): # pragma: no branch + case types.ListRootsRequest(): # pragma: no branch # pyright: ignore[reportDeprecated] response = await self._list_roots_callback(ctx) client_response = ClientResponse.validate_python(response) if isinstance(client_response, types.ErrorData): @@ -612,7 +617,7 @@ async def _on_notify( # The dispatcher already applied the cancellation; not surfaced to message_handler. return try: - if isinstance(notification, types.LoggingMessageNotification): + if isinstance(notification, types.LoggingMessageNotification): # pyright: ignore[reportDeprecated] await self._logging_callback(notification.params) await self._message_handler(notification) except Exception: diff --git a/src/mcp/server/connection.py b/src/mcp/server/connection.py index e0f406a200..b04f163997 100644 --- a/src/mcp/server/connection.py +++ b/src/mcp/server/connection.py @@ -26,14 +26,14 @@ from mcp.shared.peer import Meta, dump_params from mcp.types import ( ClientCapabilities, - CreateMessageRequest, - CreateMessageResult, + CreateMessageRequest, # pyright: ignore[reportDeprecated] + CreateMessageResult, # pyright: ignore[reportDeprecated] ElicitRequest, ElicitResult, EmptyResult, InitializeRequestParams, - ListRootsRequest, - ListRootsResult, + ListRootsRequest, # pyright: ignore[reportDeprecated] + ListRootsResult, # pyright: ignore[reportDeprecated] LoggingLevel, PingRequest, Request, @@ -52,9 +52,9 @@ # request types themselves (a `__mcp_result__` ClassVar read via a structural # protocol) so this table and the overload ladder don't need maintaining. _RESULT_FOR: dict[type[Request[Any, Any]], type[BaseModel]] = { - CreateMessageRequest: CreateMessageResult, + CreateMessageRequest: CreateMessageResult, # pyright: ignore[reportDeprecated] ElicitRequest: ElicitResult, - ListRootsRequest: ListRootsResult, + ListRootsRequest: ListRootsResult, # pyright: ignore[reportDeprecated] PingRequest: EmptyResult, } @@ -145,12 +145,15 @@ async def send_raw_request( @overload async def send_request( - self, req: CreateMessageRequest, *, opts: CallOptions | None = None - ) -> CreateMessageResult: ... + self, + req: CreateMessageRequest, # pyright: ignore[reportDeprecated] + *, + opts: CallOptions | None = None, + ) -> CreateMessageResult: ... # pyright: ignore[reportDeprecated] @overload async def send_request(self, req: ElicitRequest, *, opts: CallOptions | None = None) -> ElicitResult: ... @overload - async def send_request(self, req: ListRootsRequest, *, opts: CallOptions | None = None) -> ListRootsResult: ... + async def send_request(self, req: ListRootsRequest, *, opts: CallOptions | None = None) -> ListRootsResult: ... # pyright: ignore[reportDeprecated] @overload async def send_request(self, req: PingRequest, *, opts: CallOptions | None = None) -> EmptyResult: ... @overload diff --git a/src/mcp/server/lowlevel/server.py b/src/mcp/server/lowlevel/server.py index d2536189d0..8f0f04960f 100644 --- a/src/mcp/server/lowlevel/server.py +++ b/src/mcp/server/lowlevel/server.py @@ -184,7 +184,7 @@ def __init__( ] | None = None, on_set_logging_level: Callable[ - [ServerRequestContext[LifespanResultT], types.SetLevelRequestParams], + [ServerRequestContext[LifespanResultT], types.SetLevelRequestParams], # pyright: ignore[reportDeprecated] Awaitable[types.EmptyResult], ] | None = None, @@ -235,7 +235,7 @@ def __init__( ("resources/unsubscribe", types.UnsubscribeRequestParams, on_unsubscribe_resource), ("tools/list", types.PaginatedRequestParams, on_list_tools), ("tools/call", types.CallToolRequestParams, on_call_tool), - ("logging/setLevel", types.SetLevelRequestParams, on_set_logging_level), + ("logging/setLevel", types.SetLevelRequestParams, on_set_logging_level), # pyright: ignore[reportDeprecated] ("completion/complete", types.CompleteRequestParams, on_completion), ] self._request_handlers.update({m: HandlerEntry(pt, h) for m, pt, h in _spec_requests if h is not None}) diff --git a/src/mcp/server/session.py b/src/mcp/server/session.py index f56b87c9a3..df09f84b83 100644 --- a/src/mcp/server/session.py +++ b/src/mcp/server/session.py @@ -135,8 +135,8 @@ async def send_log_message( ) -> None: """Send a log message notification.""" await self.send_notification( - types.LoggingMessageNotification( - params=types.LoggingMessageNotificationParams( + types.LoggingMessageNotification( # pyright: ignore[reportDeprecated] + params=types.LoggingMessageNotificationParams( # pyright: ignore[reportDeprecated] level=level, data=data, logger=logger, @@ -156,7 +156,7 @@ async def send_resource_updated(self, uri: str | AnyUrl) -> None: @overload async def create_message( self, - messages: list[types.SamplingMessage], + messages: list[types.SamplingMessage], # pyright: ignore[reportDeprecated] *, max_tokens: int, system_prompt: str | None = None, @@ -164,18 +164,18 @@ async def create_message( temperature: float | None = None, stop_sequences: list[str] | None = None, metadata: dict[str, Any] | None = None, - model_preferences: types.ModelPreferences | None = None, + model_preferences: types.ModelPreferences | None = None, # pyright: ignore[reportDeprecated] tools: None = None, - tool_choice: types.ToolChoice | None = None, + tool_choice: types.ToolChoice | None = None, # pyright: ignore[reportDeprecated] related_request_id: types.RequestId | None = None, - ) -> types.CreateMessageResult: + ) -> types.CreateMessageResult: # pyright: ignore[reportDeprecated] """Overload: Without tools, returns single content.""" ... @overload async def create_message( self, - messages: list[types.SamplingMessage], + messages: list[types.SamplingMessage], # pyright: ignore[reportDeprecated] *, max_tokens: int, system_prompt: str | None = None, @@ -183,9 +183,9 @@ async def create_message( temperature: float | None = None, stop_sequences: list[str] | None = None, metadata: dict[str, Any] | None = None, - model_preferences: types.ModelPreferences | None = None, + model_preferences: types.ModelPreferences | None = None, # pyright: ignore[reportDeprecated] tools: list[types.Tool], - tool_choice: types.ToolChoice | None = None, + tool_choice: types.ToolChoice | None = None, # pyright: ignore[reportDeprecated] related_request_id: types.RequestId | None = None, ) -> types.CreateMessageResultWithTools: """Overload: With tools, returns array-capable content.""" @@ -193,7 +193,7 @@ async def create_message( async def create_message( self, - messages: list[types.SamplingMessage], + messages: list[types.SamplingMessage], # pyright: ignore[reportDeprecated] *, max_tokens: int, system_prompt: str | None = None, @@ -201,11 +201,11 @@ async def create_message( temperature: float | None = None, stop_sequences: list[str] | None = None, metadata: dict[str, Any] | None = None, - model_preferences: types.ModelPreferences | None = None, + model_preferences: types.ModelPreferences | None = None, # pyright: ignore[reportDeprecated] tools: list[types.Tool] | None = None, - tool_choice: types.ToolChoice | None = None, + tool_choice: types.ToolChoice | None = None, # pyright: ignore[reportDeprecated] related_request_id: types.RequestId | None = None, - ) -> types.CreateMessageResult | types.CreateMessageResultWithTools: + ) -> types.CreateMessageResult | types.CreateMessageResultWithTools: # pyright: ignore[reportDeprecated] """Send a sampling/create_message request. Args: @@ -239,8 +239,8 @@ async def create_message( validate_sampling_tools(client_caps, tools, tool_choice) validate_tool_use_result_messages(messages) - request = types.CreateMessageRequest( - params=types.CreateMessageRequestParams( + request = types.CreateMessageRequest( # pyright: ignore[reportDeprecated] + params=types.CreateMessageRequestParams( # pyright: ignore[reportDeprecated] messages=messages, system_prompt=system_prompt, include_context=include_context, @@ -263,17 +263,17 @@ async def create_message( ) return await self.send_request( request=request, - result_type=types.CreateMessageResult, + result_type=types.CreateMessageResult, # pyright: ignore[reportDeprecated] metadata=metadata_obj, ) - async def list_roots(self) -> types.ListRootsResult: + async def list_roots(self) -> types.ListRootsResult: # pyright: ignore[reportDeprecated] """Send a roots/list request.""" if self._stateless: raise StatelessModeNotSupported(method="list_roots") return await self.send_request( - types.ListRootsRequest(), - types.ListRootsResult, + types.ListRootsRequest(), # pyright: ignore[reportDeprecated] + types.ListRootsResult, # pyright: ignore[reportDeprecated] ) async def elicit( diff --git a/src/mcp/server/validation.py b/src/mcp/server/validation.py index 08f5754f1e..6e4c502ad9 100644 --- a/src/mcp/server/validation.py +++ b/src/mcp/server/validation.py @@ -4,7 +4,13 @@ """ from mcp.shared.exceptions import MCPError -from mcp.types import INVALID_PARAMS, ClientCapabilities, SamplingMessage, Tool, ToolChoice +from mcp.types import ( + INVALID_PARAMS, + ClientCapabilities, + SamplingMessage, # pyright: ignore[reportDeprecated] + Tool, + ToolChoice, # pyright: ignore[reportDeprecated] +) def check_sampling_tools_capability(client_caps: ClientCapabilities | None) -> bool: @@ -28,7 +34,7 @@ def check_sampling_tools_capability(client_caps: ClientCapabilities | None) -> b def validate_sampling_tools( client_caps: ClientCapabilities | None, tools: list[Tool] | None, - tool_choice: ToolChoice | None, + tool_choice: ToolChoice | None, # pyright: ignore[reportDeprecated] ) -> None: """Validate that the client supports sampling tools if tools are being used. @@ -45,7 +51,7 @@ def validate_sampling_tools( raise MCPError(code=INVALID_PARAMS, message="Client does not support sampling tools capability") -def validate_tool_use_result_messages(messages: list[SamplingMessage]) -> None: +def validate_tool_use_result_messages(messages: list[SamplingMessage]) -> None: # pyright: ignore[reportDeprecated] """Validate tool_use/tool_result message structure per SEP-1577. This validation ensures: diff --git a/src/mcp/shared/peer.py b/src/mcp/shared/peer.py index 410b96a975..74a5024a80 100644 --- a/src/mcp/shared/peer.py +++ b/src/mcp/shared/peer.py @@ -16,21 +16,21 @@ from mcp.shared.dispatcher import CallOptions, Outbound from mcp.types import ( - CreateMessageRequestParams, - CreateMessageResult, + CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + CreateMessageResult, # pyright: ignore[reportDeprecated] CreateMessageResultWithTools, ElicitRequestedSchema, ElicitRequestFormParams, ElicitRequestURLParams, ElicitResult, IncludeContext, - ListRootsResult, - ModelPreferences, + ListRootsResult, # pyright: ignore[reportDeprecated] + ModelPreferences, # pyright: ignore[reportDeprecated] RequestParams, RequestParamsMeta, - SamplingMessage, + SamplingMessage, # pyright: ignore[reportDeprecated] Tool, - ToolChoice, + ToolChoice, # pyright: ignore[reportDeprecated] ) __all__ = ["ClientPeer", "Meta"] @@ -85,7 +85,7 @@ async def notify(self, method: str, params: Mapping[str, Any] | None) -> None: @overload async def sample( self, - messages: list[SamplingMessage], + messages: list[SamplingMessage], # pyright: ignore[reportDeprecated] *, max_tokens: int, system_prompt: str | None = None, @@ -93,16 +93,16 @@ async def sample( temperature: float | None = None, stop_sequences: list[str] | None = None, metadata: dict[str, Any] | None = None, - model_preferences: ModelPreferences | None = None, + model_preferences: ModelPreferences | None = None, # pyright: ignore[reportDeprecated] tools: None = None, - tool_choice: ToolChoice | None = None, + tool_choice: ToolChoice | None = None, # pyright: ignore[reportDeprecated] meta: Meta | None = None, opts: CallOptions | None = None, - ) -> CreateMessageResult: ... + ) -> CreateMessageResult: ... # pyright: ignore[reportDeprecated] @overload async def sample( self, - messages: list[SamplingMessage], + messages: list[SamplingMessage], # pyright: ignore[reportDeprecated] *, max_tokens: int, system_prompt: str | None = None, @@ -110,15 +110,15 @@ async def sample( temperature: float | None = None, stop_sequences: list[str] | None = None, metadata: dict[str, Any] | None = None, - model_preferences: ModelPreferences | None = None, + model_preferences: ModelPreferences | None = None, # pyright: ignore[reportDeprecated] tools: list[Tool], - tool_choice: ToolChoice | None = None, + tool_choice: ToolChoice | None = None, # pyright: ignore[reportDeprecated] meta: Meta | None = None, opts: CallOptions | None = None, ) -> CreateMessageResultWithTools: ... async def sample( self, - messages: list[SamplingMessage], + messages: list[SamplingMessage], # pyright: ignore[reportDeprecated] *, max_tokens: int, system_prompt: str | None = None, @@ -126,12 +126,12 @@ async def sample( temperature: float | None = None, stop_sequences: list[str] | None = None, metadata: dict[str, Any] | None = None, - model_preferences: ModelPreferences | None = None, + model_preferences: ModelPreferences | None = None, # pyright: ignore[reportDeprecated] tools: list[Tool] | None = None, - tool_choice: ToolChoice | None = None, + tool_choice: ToolChoice | None = None, # pyright: ignore[reportDeprecated] meta: Meta | None = None, opts: CallOptions | None = None, - ) -> CreateMessageResult | CreateMessageResultWithTools: + ) -> CreateMessageResult | CreateMessageResultWithTools: # pyright: ignore[reportDeprecated] """Send a `sampling/createMessage` request to the peer. Raises: @@ -139,7 +139,7 @@ async def sample( NoBackChannelError: No back-channel for server-initiated requests. pydantic.ValidationError: The peer's result does not match the expected result type. """ - params = CreateMessageRequestParams( + params = CreateMessageRequestParams( # pyright: ignore[reportDeprecated] messages=messages, system_prompt=system_prompt, include_context=include_context, @@ -154,7 +154,7 @@ async def sample( result = await self.send_raw_request("sampling/createMessage", dump_params(params, meta), opts) if tools is not None: return CreateMessageResultWithTools.model_validate(result, by_name=False) - return CreateMessageResult.model_validate(result, by_name=False) + return CreateMessageResult.model_validate(result, by_name=False) # pyright: ignore[reportDeprecated] async def elicit_form( self, @@ -195,7 +195,7 @@ async def elicit_url( result = await self.send_raw_request("elicitation/create", dump_params(params, meta), opts) return ElicitResult.model_validate(result, by_name=False) - async def list_roots(self, *, meta: Meta | None = None, opts: CallOptions | None = None) -> ListRootsResult: + async def list_roots(self, *, meta: Meta | None = None, opts: CallOptions | None = None) -> ListRootsResult: # pyright: ignore[reportDeprecated] """Send a `roots/list` request. Raises: @@ -204,7 +204,7 @@ async def list_roots(self, *, meta: Meta | None = None, opts: CallOptions | None pydantic.ValidationError: The peer's result does not match the expected result type. """ result = await self.send_raw_request("roots/list", dump_params(None, meta), opts) - return ListRootsResult.model_validate(result, by_name=False) + return ListRootsResult.model_validate(result, by_name=False) # pyright: ignore[reportDeprecated] async def ping(self, *, meta: Meta | None = None, opts: CallOptions | None = None) -> None: """Send a `ping` request and ignore the result. diff --git a/src/mcp/types/__init__.py b/src/mcp/types/__init__.py index 992d584687..199ccf1e8b 100644 --- a/src/mcp/types/__init__.py +++ b/src/mcp/types/__init__.py @@ -39,9 +39,9 @@ CompletionContext, CompletionsCapability, ContentBlock, - CreateMessageRequest, - CreateMessageRequestParams, - CreateMessageResult, + CreateMessageRequest, # pyright: ignore[reportDeprecated] + CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + CreateMessageResult, # pyright: ignore[reportDeprecated] CreateMessageResultWithTools, CreateTaskResult, DiscoverRequest, @@ -89,19 +89,19 @@ ListResourcesResult, ListResourceTemplatesRequest, ListResourceTemplatesResult, - ListRootsRequest, - ListRootsResult, + ListRootsRequest, # pyright: ignore[reportDeprecated] + ListRootsResult, # pyright: ignore[reportDeprecated] ListTasksRequest, ListTasksResult, ListToolsRequest, ListToolsResult, LoggingCapability, LoggingLevel, - LoggingMessageNotification, - LoggingMessageNotificationParams, + LoggingMessageNotification, # pyright: ignore[reportDeprecated] + LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated] MissingRequiredClientCapabilityErrorData, - ModelHint, - ModelPreferences, + ModelHint, # pyright: ignore[reportDeprecated] + ModelPreferences, # pyright: ignore[reportDeprecated] Notification, NotificationParams, PaginatedRequest, @@ -136,13 +136,13 @@ Result, ResultType, Role, - Root, + Root, # pyright: ignore[reportDeprecated] RootsCapability, - RootsListChangedNotification, + RootsListChangedNotification, # pyright: ignore[reportDeprecated] SamplingCapability, SamplingContent, SamplingContextCapability, - SamplingMessage, + SamplingMessage, # pyright: ignore[reportDeprecated] SamplingMessageContentBlock, SamplingToolsCapability, ServerCapabilities, @@ -151,8 +151,8 @@ ServerResult, ServerTasksCapability, ServerTasksRequestsCapability, - SetLevelRequest, - SetLevelRequestParams, + SetLevelRequest, # pyright: ignore[reportDeprecated] + SetLevelRequestParams, # pyright: ignore[reportDeprecated] StopReason, SubscribeRequest, SubscribeRequestParams, @@ -178,12 +178,12 @@ TextResourceContents, Tool, ToolAnnotations, - ToolChoice, + ToolChoice, # pyright: ignore[reportDeprecated] ToolExecution, ToolListChangedNotification, - ToolResultContent, + ToolResultContent, # pyright: ignore[reportDeprecated] ToolsCapability, - ToolUseContent, + ToolUseContent, # pyright: ignore[reportDeprecated] UnsubscribeRequest, UnsubscribeRequestParams, UnsupportedProtocolVersionErrorData, diff --git a/src/mcp/types/_types.py b/src/mcp/types/_types.py index 82b4a084d5..d84b31a017 100644 --- a/src/mcp/types/_types.py +++ b/src/mcp/types/_types.py @@ -18,7 +18,7 @@ TypeAdapter, ) from pydantic.alias_generators import to_camel -from typing_extensions import NotRequired, TypedDict +from typing_extensions import NotRequired, TypedDict, deprecated from mcp.types.jsonrpc import RequestId @@ -361,7 +361,10 @@ class TasksElicitationCapability(MCPModel): class ClientTasksRequestsCapability(MCPModel): """Specifies which request types the client can augment with tasks (2025-11-25 only).""" - sampling: TasksSamplingCapability | None = None + sampling: Annotated[ + TasksSamplingCapability | None, + deprecated("`tasks.requests.sampling` is deprecated as of 2026-07-28 (SEP-2577)."), + ] = None elicitation: TasksElicitationCapability | None = None @@ -382,15 +385,18 @@ class ClientCapabilities(MCPModel): experimental: dict[str, dict[str, Any]] | None = None """Experimental, non-standard capabilities that the client supports.""" - sampling: SamplingCapability | None = None + sampling: Annotated[ + SamplingCapability | None, deprecated("`sampling` is deprecated as of 2026-07-28 (SEP-2577).") + ] = None """ Present if the client supports sampling from an LLM. Can contain fine-grained capabilities like context and tools support. + Deprecated in 2026-07-28 (SEP-2577). """ elicitation: ElicitationCapability | None = None """Present if the client supports elicitation from the user.""" - roots: RootsCapability | None = None - """Present if the client supports listing roots.""" + roots: Annotated[RootsCapability | None, deprecated("`roots` is deprecated as of 2026-07-28 (SEP-2577).")] = None + """Present if the client supports listing roots. Deprecated in 2026-07-28 (SEP-2577).""" extensions: dict[str, dict[str, Any]] | None = None """MCP extensions the client supports (2026-07-28). Keys are extension identifiers; values are per-extension settings (empty object = no settings).""" @@ -475,7 +481,9 @@ class ServerCapabilities(MCPModel): experimental: dict[str, dict[str, Any]] | None = None """Experimental, non-standard capabilities that the server supports.""" - logging: LoggingCapability | None = None + logging: Annotated[LoggingCapability | None, deprecated("`logging` is deprecated as of 2026-07-28 (SEP-2577).")] = ( + None + ) """Present if the server supports sending log messages to the client. Deprecated in 2026-07-28 (SEP-2577).""" @@ -1173,6 +1181,7 @@ class AudioContent(MCPModel): """ +@deprecated("`ToolUseContent` is deprecated as of 2026-07-28 (SEP-2577).") class ToolUseContent(MCPModel): """An assistant's request to invoke a tool during sampling (2025-11-25+). @@ -1198,6 +1207,7 @@ class ToolUseContent(MCPModel): requests to enable caching optimizations.""" +@deprecated("`ToolResultContent` is deprecated as of 2026-07-28 (SEP-2577).") class ToolResultContent(MCPModel): """The result of a tool use, provided by the user back to the assistant (2025-11-25+). @@ -1226,7 +1236,7 @@ class ToolResultContent(MCPModel): requests to enable caching optimizations.""" -SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent +SamplingMessageContentBlock: TypeAlias = TextContent | ImageContent | AudioContent | ToolUseContent | ToolResultContent # pyright: ignore[reportDeprecated] """Content block types allowed in sampling messages. This is the widest (2025-11-25+) membership; older sessions allow only a subset @@ -1241,8 +1251,9 @@ class ToolResultContent(MCPModel): """ +@deprecated("`SamplingMessage` is deprecated as of 2026-07-28 (SEP-2577).") class SamplingMessage(MCPModel): - """Describes a message issued to or received from an LLM API.""" + """Describes a message issued to or received from an LLM API. Deprecated in 2026-07-28 (SEP-2577).""" role: Role content: SamplingMessageContentBlock | list[SamplingMessageContentBlock] @@ -1478,6 +1489,7 @@ class ToolListChangedNotification(Notification[NotificationParams | None, Litera """ +@deprecated("`SetLevelRequestParams` is deprecated as of 2026-07-28 (SEP-2577).") class SetLevelRequestParams(RequestParams): """Parameters for setting the logging level. @@ -1489,7 +1501,8 @@ class SetLevelRequestParams(RequestParams): The server should send all logs at this level and higher (more severe).""" -class SetLevelRequest(Request[SetLevelRequestParams, Literal["logging/setLevel"]]): +@deprecated("`SetLevelRequest` is deprecated as of 2026-07-28 (SEP-2577).") +class SetLevelRequest(Request[SetLevelRequestParams, Literal["logging/setLevel"]]): # pyright: ignore[reportDeprecated] """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. @@ -1497,9 +1510,10 @@ class SetLevelRequest(Request[SetLevelRequestParams, Literal["logging/setLevel"] """ method: Literal["logging/setLevel"] = "logging/setLevel" - params: SetLevelRequestParams + params: SetLevelRequestParams # pyright: ignore[reportDeprecated] +@deprecated("`LoggingMessageNotificationParams` is deprecated as of 2026-07-28 (SEP-2577).") class LoggingMessageNotificationParams(NotificationParams): level: LoggingLevel """The severity of this log message.""" @@ -1512,7 +1526,8 @@ class LoggingMessageNotificationParams(NotificationParams): """ -class LoggingMessageNotification(Notification[LoggingMessageNotificationParams, Literal["notifications/message"]]): +@deprecated("`LoggingMessageNotification` is deprecated as of 2026-07-28 (SEP-2577).") +class LoggingMessageNotification(Notification[LoggingMessageNotificationParams, Literal["notifications/message"]]): # pyright: ignore[reportDeprecated] """Notification of a log message passed from server to client. Through 2025-11-25 the client subscribes via `logging/setLevel`. On @@ -1521,7 +1536,7 @@ class LoggingMessageNotification(Notification[LoggingMessageNotificationParams, """ method: Literal["notifications/message"] = "notifications/message" - params: LoggingMessageNotificationParams + params: LoggingMessageNotificationParams # pyright: ignore[reportDeprecated] IncludeContext = Literal["none", "thisServer", "allServers"] @@ -1531,6 +1546,7 @@ class LoggingMessageNotification(Notification[LoggingMessageNotificationParams, """ +@deprecated("`ModelHint` is deprecated as of 2026-07-28 (SEP-2577).") class ModelHint(MCPModel): """Hints to use for model selection. @@ -1547,6 +1563,7 @@ class ModelHint(MCPModel): """ +@deprecated("`ModelPreferences` is deprecated as of 2026-07-28 (SEP-2577).") class ModelPreferences(MCPModel): """The server's preferences for model selection, requested of the client during sampling. @@ -1564,7 +1581,7 @@ class ModelPreferences(MCPModel): Deprecated in 2026-07-28 (SEP-2577) with the rest of sampling. """ - hints: list[ModelHint] | None = None + hints: list[ModelHint] | None = None # pyright: ignore[reportDeprecated] """ Optional hints to use for model selection. @@ -1597,6 +1614,7 @@ class ModelPreferences(MCPModel): """ +@deprecated("`ToolChoice` is deprecated as of 2026-07-28 (SEP-2577).") class ToolChoice(MCPModel): """Controls tool selection behavior for sampling requests (2025-11-25+). @@ -1613,10 +1631,11 @@ class ToolChoice(MCPModel): """ +@deprecated("`CreateMessageRequestParams` is deprecated as of 2026-07-28 (SEP-2577).") class CreateMessageRequestParams(RequestParams): - messages: list[SamplingMessage] + messages: list[SamplingMessage] # pyright: ignore[reportDeprecated] """The conversation to sample from.""" - model_preferences: ModelPreferences | None = None + model_preferences: ModelPreferences | None = None # pyright: ignore[reportDeprecated] """ The server's preferences for which model to select. The client MAY ignore these preferences. @@ -1638,14 +1657,15 @@ class CreateMessageRequestParams(RequestParams): tools: list[Tool] | None = None """Tools the model may use during generation (2025-11-25+). Requires the `sampling.tools` client capability.""" - tool_choice: ToolChoice | None = None + tool_choice: ToolChoice | None = None # pyright: ignore[reportDeprecated] """Controls how the model uses tools (2025-11-25+). Requires the `sampling.tools` client capability.""" task: TaskMetadata | None = None """If specified, the caller requests task-augmented execution (2025-11-25 only).""" -class CreateMessageRequest(Request[CreateMessageRequestParams, Literal["sampling/createMessage"]]): +@deprecated("`CreateMessageRequest` is deprecated as of 2026-07-28 (SEP-2577).") +class CreateMessageRequest(Request[CreateMessageRequestParams, Literal["sampling/createMessage"]]): # pyright: ignore[reportDeprecated] """A request from the server to sample an LLM via the client. The client has full discretion over which model to select and should inform @@ -1655,7 +1675,7 @@ class CreateMessageRequest(Request[CreateMessageRequestParams, Literal["sampling """ method: Literal["sampling/createMessage"] = "sampling/createMessage" - params: CreateMessageRequestParams + params: CreateMessageRequestParams # pyright: ignore[reportDeprecated] StopReason = Literal["endTurn", "stopSequence", "maxTokens", "toolUse"] | str @@ -1665,6 +1685,7 @@ class CreateMessageRequest(Request[CreateMessageRequestParams, Literal["sampling """ +@deprecated("`CreateMessageResult` is deprecated as of 2026-07-28 (SEP-2577).") class CreateMessageResult(Result): """The client's response to a sampling/createMessage request from the server. @@ -1790,6 +1811,7 @@ class CompleteResult(Result): """See `ResultType`. Always serialized; older peers ignore it.""" +@deprecated("`ListRootsRequest` is deprecated as of 2026-07-28 (SEP-2577).") class ListRootsRequest(Request[RequestParams | None, Literal["roots/list"]]): """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 @@ -1809,6 +1831,7 @@ class ListRootsRequest(Request[RequestParams | None, Literal["roots/list"]]): to server-to-client payloads).""" +@deprecated("`Root` is deprecated as of 2026-07-28 (SEP-2577).") class Root(MCPModel): """Represents a root directory or file that the server can operate on. @@ -1834,6 +1857,7 @@ class Root(MCPModel): """ +@deprecated("`ListRootsResult` is deprecated as of 2026-07-28 (SEP-2577).") class ListRootsResult(Result): """The client's response to a roots/list request from the server. @@ -1844,9 +1868,10 @@ class ListRootsResult(Result): result. Deprecated in 2026-07-28 (SEP-2577). """ - roots: list[Root] + roots: list[Root] # pyright: ignore[reportDeprecated] +@deprecated("`RootsListChangedNotification` is deprecated as of 2026-07-28 (SEP-2577).") class RootsListChangedNotification( Notification[NotificationParams | None, Literal["notifications/roots/list_changed"]] ): @@ -2016,7 +2041,7 @@ class ElicitationRequiredErrorData(MCPModel): """List of URL mode elicitations that must be completed.""" -InputRequest: TypeAlias = CreateMessageRequest | ListRootsRequest | ElicitRequest +InputRequest: TypeAlias = CreateMessageRequest | ListRootsRequest | ElicitRequest # pyright: ignore[reportDeprecated] """A single server-initiated input request embedded in `InputRequiredResult` (2026-07-28). Discriminated by `method`. On 2026-07-28 these embedded payloads take the place @@ -2030,7 +2055,7 @@ class ElicitationRequiredErrorData(MCPModel): and by the tasks extension. """ -InputResponse: TypeAlias = CreateMessageResult | CreateMessageResultWithTools | ListRootsResult | ElicitResult +InputResponse: TypeAlias = CreateMessageResult | CreateMessageResultWithTools | ListRootsResult | ElicitResult # pyright: ignore[reportDeprecated] """A client response to a single server-initiated input request (2026-07-28). `CreateMessageResultWithTools` is this SDK's array-content split of the schema's @@ -2078,7 +2103,7 @@ class InputRequiredResult(Result): PingRequest | InitializeRequest | CompleteRequest - | SetLevelRequest + | SetLevelRequest # pyright: ignore[reportDeprecated] | GetPromptRequest | ListPromptsRequest | ListResourcesRequest @@ -2100,7 +2125,7 @@ class InputRequiredResult(Result): ClientNotification = ( - CancelledNotification | ProgressNotification | InitializedNotification | RootsListChangedNotification + CancelledNotification | ProgressNotification | InitializedNotification | RootsListChangedNotification # pyright: ignore[reportDeprecated] ) """Notifications sent from the client to the server. @@ -2110,11 +2135,11 @@ class InputRequiredResult(Result): client_notification_adapter = TypeAdapter[ClientNotification](ClientNotification) -ClientResult = EmptyResult | CreateMessageResult | CreateMessageResultWithTools | ListRootsResult | ElicitResult +ClientResult = EmptyResult | CreateMessageResult | CreateMessageResultWithTools | ListRootsResult | ElicitResult # pyright: ignore[reportDeprecated] client_result_adapter = TypeAdapter[ClientResult](ClientResult) -ServerRequest = PingRequest | CreateMessageRequest | ListRootsRequest | ElicitRequest +ServerRequest = PingRequest | CreateMessageRequest | ListRootsRequest | ElicitRequest # pyright: ignore[reportDeprecated] """Union of standalone JSON-RPC requests a server can send to a client. Live through 2025-11-25 only: 2026-07-28 has no server-to-client JSON-RPC @@ -2127,7 +2152,7 @@ class InputRequiredResult(Result): ServerNotification = ( CancelledNotification | ProgressNotification - | LoggingMessageNotification + | LoggingMessageNotification # pyright: ignore[reportDeprecated] | ResourceUpdatedNotification | ResourceListChangedNotification | ToolListChangedNotification diff --git a/src/mcp/types/methods.py b/src/mcp/types/methods.py index 10bded166d..f277a4fa42 100644 --- a/src/mcp/types/methods.py +++ b/src/mcp/types/methods.py @@ -341,7 +341,7 @@ "completion/complete": types.CompleteRequest, "elicitation/create": types.ElicitRequest, "initialize": types.InitializeRequest, - "logging/setLevel": types.SetLevelRequest, + "logging/setLevel": types.SetLevelRequest, # pyright: ignore[reportDeprecated] "ping": types.PingRequest, "prompts/get": types.GetPromptRequest, "prompts/list": types.ListPromptsRequest, @@ -350,8 +350,8 @@ "resources/subscribe": types.SubscribeRequest, "resources/templates/list": types.ListResourceTemplatesRequest, "resources/unsubscribe": types.UnsubscribeRequest, - "roots/list": types.ListRootsRequest, - "sampling/createMessage": types.CreateMessageRequest, + "roots/list": types.ListRootsRequest, # pyright: ignore[reportDeprecated] + "sampling/createMessage": types.CreateMessageRequest, # pyright: ignore[reportDeprecated] "server/discover": types.DiscoverRequest, "subscriptions/listen": types.SubscriptionsListenRequest, "tools/call": types.CallToolRequest, @@ -365,12 +365,12 @@ "notifications/cancelled": types.CancelledNotification, "notifications/elicitation/complete": types.ElicitCompleteNotification, "notifications/initialized": types.InitializedNotification, - "notifications/message": types.LoggingMessageNotification, + "notifications/message": types.LoggingMessageNotification, # pyright: ignore[reportDeprecated] "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/roots/list_changed": types.RootsListChangedNotification, # pyright: ignore[reportDeprecated] "notifications/subscriptions/acknowledged": types.SubscriptionsAcknowledgedNotification, "notifications/tools/list_changed": types.ToolListChangedNotification, } @@ -391,10 +391,10 @@ "resources/subscribe": types.EmptyResult, "resources/templates/list": types.ListResourceTemplatesResult, "resources/unsubscribe": types.EmptyResult, - "roots/list": types.ListRootsResult, + "roots/list": types.ListRootsResult, # pyright: ignore[reportDeprecated] # Arm order load-bearing: a single-block body satisfies both arms and # smart-union ties resolve leftmost. Pinned by tests/types/test_methods.py. - "sampling/createMessage": types.CreateMessageResult | types.CreateMessageResultWithTools, + "sampling/createMessage": types.CreateMessageResult | types.CreateMessageResultWithTools, # pyright: ignore[reportDeprecated] "server/discover": types.DiscoverResult, "subscriptions/listen": types.EmptyResult, "tools/call": types.CallToolResult | types.InputRequiredResult, diff --git a/tests/client/test_client.py b/tests/client/test_client.py index 64b6666eca..0c1d961a08 100644 --- a/tests/client/test_client.py +++ b/tests/client/test_client.py @@ -60,7 +60,7 @@ async def handle_unsubscribe_resource( ) -> EmptyResult: return EmptyResult() - async def handle_set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> EmptyResult: + async def handle_set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> EmptyResult: # pyright: ignore[reportDeprecated] return EmptyResult() async def handle_completion(ctx: ServerRequestContext, params: types.CompleteRequestParams) -> types.CompleteResult: diff --git a/tests/client/test_list_roots_callback.py b/tests/client/test_list_roots_callback.py index a26ef45b27..7232799b2a 100644 --- a/tests/client/test_list_roots_callback.py +++ b/tests/client/test_list_roots_callback.py @@ -4,23 +4,23 @@ from mcp import Client from mcp.client import ClientRequestContext from mcp.server.mcpserver import Context, MCPServer -from mcp.types import ListRootsResult, Root, TextContent +from mcp.types import ListRootsResult, Root, TextContent # pyright: ignore[reportDeprecated] @pytest.mark.anyio async def test_list_roots_callback(): server = MCPServer("test") - callback_return = ListRootsResult( + callback_return = ListRootsResult( # pyright: ignore[reportDeprecated] roots=[ - Root(uri=FileUrl("file://users/fake/test"), name="Test Root 1"), - Root(uri=FileUrl("file://users/fake/test/2"), name="Test Root 2"), + Root(uri=FileUrl("file://users/fake/test"), name="Test Root 1"), # pyright: ignore[reportDeprecated] + Root(uri=FileUrl("file://users/fake/test/2"), name="Test Root 2"), # pyright: ignore[reportDeprecated] ] ) async def list_roots_callback( context: ClientRequestContext, - ) -> ListRootsResult: + ) -> ListRootsResult: # pyright: ignore[reportDeprecated] return callback_return @server.tool("test_list_roots") diff --git a/tests/client/test_logging_callback.py b/tests/client/test_logging_callback.py index 454c1d3382..1e8bf87ece 100644 --- a/tests/client/test_logging_callback.py +++ b/tests/client/test_logging_callback.py @@ -6,16 +6,16 @@ from mcp.server.mcpserver import Context, MCPServer from mcp.shared.session import RequestResponder from mcp.types import ( - LoggingMessageNotificationParams, + LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated] TextContent, ) class LoggingCollector: def __init__(self): - self.log_messages: list[LoggingMessageNotificationParams] = [] + self.log_messages: list[LoggingMessageNotificationParams] = [] # pyright: ignore[reportDeprecated] - async def __call__(self, params: LoggingMessageNotificationParams) -> None: + async def __call__(self, params: LoggingMessageNotificationParams) -> None: # pyright: ignore[reportDeprecated] self.log_messages.append(params) diff --git a/tests/client/test_notification_response.py b/tests/client/test_notification_response.py index 69c8afeb84..e3d4b22835 100644 --- a/tests/client/test_notification_response.py +++ b/tests/client/test_notification_response.py @@ -16,7 +16,7 @@ from mcp import ClientSession, MCPError, types from mcp.client.streamable_http import streamable_http_client from mcp.shared.session import RequestResponder -from mcp.types import RootsListChangedNotification +from mcp.types import RootsListChangedNotification # pyright: ignore[reportDeprecated] pytestmark = pytest.mark.anyio @@ -94,7 +94,7 @@ async def message_handler( # pragma: no cover await session.initialize() # The test server returns a 204 instead of the expected 202 - await session.send_notification(RootsListChangedNotification(method="notifications/roots/list_changed")) + await session.send_notification(RootsListChangedNotification(method="notifications/roots/list_changed")) # pyright: ignore[reportDeprecated] if returned_exception: # pragma: no cover pytest.fail(f"Server encountered an exception: {returned_exception}") @@ -165,7 +165,7 @@ async def test_http_error_on_notification_does_not_hang() -> None: await session.initialize() # Should not raise or hang — the error is silently ignored for notifications - await session.send_notification(RootsListChangedNotification(method="notifications/roots/list_changed")) + await session.send_notification(RootsListChangedNotification(method="notifications/roots/list_changed")) # pyright: ignore[reportDeprecated] def _create_invalid_json_response_app() -> Starlette: diff --git a/tests/client/test_sampling_callback.py b/tests/client/test_sampling_callback.py index 2b90b00afa..145a07710b 100644 --- a/tests/client/test_sampling_callback.py +++ b/tests/client/test_sampling_callback.py @@ -4,12 +4,12 @@ from mcp.client import ClientRequestContext from mcp.server.mcpserver import Context, MCPServer from mcp.types import ( - CreateMessageRequestParams, - CreateMessageResult, + CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + CreateMessageResult, # pyright: ignore[reportDeprecated] CreateMessageResultWithTools, - SamplingMessage, + SamplingMessage, # pyright: ignore[reportDeprecated] TextContent, - ToolUseContent, + ToolUseContent, # pyright: ignore[reportDeprecated] ) @@ -17,7 +17,7 @@ async def test_sampling_callback(): server = MCPServer("test") - callback_return = CreateMessageResult( + callback_return = CreateMessageResult( # pyright: ignore[reportDeprecated] role="assistant", content=TextContent(type="text", text="This is a response from the sampling callback"), model="test-model", @@ -26,14 +26,14 @@ async def test_sampling_callback(): async def sampling_callback( context: ClientRequestContext, - params: CreateMessageRequestParams, - ) -> CreateMessageResult: + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] return callback_return @server.tool("test_sampling") async def test_sampling_tool(message: str, ctx: Context) -> bool: value = await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=TextContent(type="text", text=message))], + messages=[SamplingMessage(role="user", content=TextContent(type="text", text=message))], # pyright: ignore[reportDeprecated] max_tokens=100, ) assert value == callback_return @@ -62,7 +62,7 @@ async def test_create_message_backwards_compat_single_content(): server = MCPServer("test") # Callback returns single content (text) - callback_return = CreateMessageResult( + callback_return = CreateMessageResult( # pyright: ignore[reportDeprecated] role="assistant", content=TextContent(type="text", text="Hello from LLM"), model="test-model", @@ -71,19 +71,19 @@ async def test_create_message_backwards_compat_single_content(): async def sampling_callback( context: ClientRequestContext, - params: CreateMessageRequestParams, - ) -> CreateMessageResult: + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] return callback_return @server.tool("test_backwards_compat") async def test_tool(message: str, ctx: Context) -> bool: # Call create_message WITHOUT tools result = await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=TextContent(type="text", text=message))], + messages=[SamplingMessage(role="user", content=TextContent(type="text", text=message))], # pyright: ignore[reportDeprecated] max_tokens=100, ) # Backwards compat: result should be CreateMessageResult - assert isinstance(result, CreateMessageResult) + assert isinstance(result, CreateMessageResult) # pyright: ignore[reportDeprecated] # Content should be single (not a list) - this is the key backwards compat check assert isinstance(result.content, TextContent) assert result.content.text == "Hello from LLM" @@ -104,7 +104,7 @@ async def test_create_message_result_with_tools_type(): # Test the type itself, not the overload (overload requires client capability setup) result = CreateMessageResultWithTools( role="assistant", - content=ToolUseContent(type="tool_use", id="call_123", name="get_weather", input={"city": "SF"}), + content=ToolUseContent(type="tool_use", id="call_123", name="get_weather", input={"city": "SF"}), # pyright: ignore[reportDeprecated] model="test-model", stop_reason="toolUse", ) @@ -119,7 +119,7 @@ async def test_create_message_result_with_tools_type(): role="assistant", content=[ TextContent(type="text", text="Let me check the weather"), - ToolUseContent(type="tool_use", id="call_456", name="get_weather", input={"city": "NYC"}), + ToolUseContent(type="tool_use", id="call_456", name="get_weather", input={"city": "NYC"}), # pyright: ignore[reportDeprecated] ], model="test-model", stop_reason="toolUse", diff --git a/tests/client/test_session.py b/tests/client/test_session.py index c171360de2..da82027b68 100644 --- a/tests/client/test_session.py +++ b/tests/client/test_session.py @@ -430,9 +430,9 @@ async def test_client_capabilities_with_custom_callbacks(): async def custom_sampling_callback( # pragma: no cover context: ClientRequestContext, - params: types.CreateMessageRequestParams, - ) -> types.CreateMessageResult | types.ErrorData: - return types.CreateMessageResult( + params: types.CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> types.CreateMessageResult | types.ErrorData: # pyright: ignore[reportDeprecated] + return types.CreateMessageResult( # pyright: ignore[reportDeprecated] role="assistant", content=types.TextContent(type="text", text="test"), model="test-model", @@ -440,8 +440,8 @@ async def custom_sampling_callback( # pragma: no cover async def custom_list_roots_callback( # pragma: no cover context: ClientRequestContext, - ) -> types.ListRootsResult | types.ErrorData: - return types.ListRootsResult(roots=[]) + ) -> types.ListRootsResult | types.ErrorData: # pyright: ignore[reportDeprecated] + return types.ListRootsResult(roots=[]) # pyright: ignore[reportDeprecated] async def mock_server(): nonlocal received_capabilities @@ -514,9 +514,9 @@ async def test_client_capabilities_with_sampling_tools(): async def custom_sampling_callback( # pragma: no cover context: ClientRequestContext, - params: types.CreateMessageRequestParams, - ) -> types.CreateMessageResult | types.ErrorData: - return types.CreateMessageResult( + params: types.CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> types.CreateMessageResult | types.ErrorData: # pyright: ignore[reportDeprecated] + return types.CreateMessageResult( # pyright: ignore[reportDeprecated] role="assistant", content=types.TextContent(type="text", text="test"), model="test-model", @@ -811,12 +811,13 @@ async def test_on_request_validates_the_callback_result_against_the_surface_sche """A surface-valid callback result reaches the wire as the dump dict unchanged.""" async def sampling( - ctx: ClientRequestContext, params: types.CreateMessageRequestParams - ) -> types.CreateMessageResult: - return types.CreateMessageResult(role="assistant", content=types.TextContent(type="text", text="hi"), model="m") + ctx: ClientRequestContext, + params: types.CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> types.CreateMessageResult: # pyright: ignore[reportDeprecated] + return types.CreateMessageResult(role="assistant", content=types.TextContent(type="text", text="hi"), model="m") # pyright: ignore[reportDeprecated] - request_params = types.CreateMessageRequestParams( - messages=[types.SamplingMessage(role="user", content=types.TextContent(type="text", text="q"))], + request_params = types.CreateMessageRequestParams( # pyright: ignore[reportDeprecated] + messages=[types.SamplingMessage(role="user", content=types.TextContent(type="text", text="q"))], # pyright: ignore[reportDeprecated] max_tokens=10, ).model_dump(by_alias=True, mode="json", exclude_none=True) async with raw_client_session(sampling_callback=sampling) as (_session, to_client, from_client): @@ -836,8 +837,8 @@ async def test_on_request_callback_returning_a_surface_invalid_result_is_interna `EmptyResult` is a `ClientResult` arm so the union accepts it, but `roots/list` requires a `roots` array.""" - async def list_roots(ctx: ClientRequestContext) -> types.ListRootsResult | types.ErrorData: - return cast("types.ListRootsResult", types.EmptyResult()) + async def list_roots(ctx: ClientRequestContext) -> types.ListRootsResult | types.ErrorData: # pyright: ignore[reportDeprecated] + return cast("types.ListRootsResult", types.EmptyResult()) # pyright: ignore[reportDeprecated] async with raw_client_session(list_roots_callback=list_roots) as (_session, to_client, from_client): await to_client.send(SessionMessage(JSONRPCRequest(jsonrpc="2.0", id=3, method="roots/list"))) @@ -949,11 +950,11 @@ async def test_raising_sampling_callback_answers_with_code_zero(): """A raising sampling callback is answered with code 0 and `str(exc)` (SDK-defined). Raw streams because the assertion is the outbound `JSONRPCError` envelope itself.""" - async def boom(ctx: object, params: object) -> types.CreateMessageResult: + async def boom(ctx: object, params: object) -> types.CreateMessageResult: # pyright: ignore[reportDeprecated] raise RuntimeError("sampling boom") - params = types.CreateMessageRequestParams( - messages=[types.SamplingMessage(role="user", content=types.TextContent(type="text", text="hi"))], + params = types.CreateMessageRequestParams( # pyright: ignore[reportDeprecated] + messages=[types.SamplingMessage(role="user", content=types.TextContent(type="text", text="hi"))], # pyright: ignore[reportDeprecated] max_tokens=10, ).model_dump(by_alias=True, mode="json", exclude_none=True) async with raw_client_session(sampling_callback=boom) as (_session, to_client, from_client): @@ -1159,7 +1160,7 @@ async def server_on_notify( results.append(await session.send_ping(meta=None)) # Server-to-client: direct dispatch delivers ping with no params member (no _meta injection). assert await server_side.send_raw_request("ping", None) == {} - await session.send_notification(types.RootsListChangedNotification()) + await session.send_notification(types.RootsListChangedNotification()) # pyright: ignore[reportDeprecated] server_side.close() assert results == [types.EmptyResult()] assert notified == ["notifications/roots/list_changed"] @@ -1173,9 +1174,9 @@ async def test_direct_dispatch_roots_list_reaches_callback_with_synthesized_requ client_side, server_side = create_direct_dispatcher_pair() contexts: list[ClientRequestContext] = [] - async def list_roots(context: ClientRequestContext) -> types.ListRootsResult: + async def list_roots(context: ClientRequestContext) -> types.ListRootsResult: # pyright: ignore[reportDeprecated] contexts.append(context) - return types.ListRootsResult(roots=[types.Root(uri=FileUrl("file:///workspace"))]) + return types.ListRootsResult(roots=[types.Root(uri=FileUrl("file:///workspace"))]) # pyright: ignore[reportDeprecated] async def server_on_request( ctx: DispatchContext[TransportContext], method: str, params: dict[str, object] | None @@ -1211,7 +1212,7 @@ async def test_raising_notification_callbacks_over_direct_dispatch_cost_only_tha client_side, server_side = create_direct_dispatcher_pair() teed: list[types.ServerNotification] = [] - async def logging_callback(params: types.LoggingMessageNotificationParams) -> None: + async def logging_callback(params: types.LoggingMessageNotificationParams) -> None: # pyright: ignore[reportDeprecated] raise ValueError("logging callback boom") async def message_handler( @@ -1470,7 +1471,7 @@ async def test_send_notification_after_close_is_dropped_silently(): async with ClientSession(s2c_recv, c2s_send) as session: pass with anyio.fail_after(5): - await session.send_notification(types.RootsListChangedNotification()) + await session.send_notification(types.RootsListChangedNotification()) # pyright: ignore[reportDeprecated] with pytest.raises(anyio.EndOfStream): c2s_recv.receive_nowait() # nothing reached the wire finally: diff --git a/tests/client/test_session_concurrency.py b/tests/client/test_session_concurrency.py index dc91bee258..b139c66c7c 100644 --- a/tests/client/test_session_concurrency.py +++ b/tests/client/test_session_concurrency.py @@ -9,9 +9,9 @@ from mcp.server.mcpserver import Context, MCPServer from mcp.types import ( CallToolResult, - CreateMessageRequestParams, - CreateMessageResult, - SamplingMessage, + CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + CreateMessageResult, # pyright: ignore[reportDeprecated] + SamplingMessage, # pyright: ignore[reportDeprecated] TextContent, ) @@ -96,7 +96,7 @@ async def fan_out(ctx: Context) -> str: async def sample(tag: str) -> None: result = await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=TextContent(text=tag))], + messages=[SamplingMessage(role="user", content=TextContent(text=tag))], # pyright: ignore[reportDeprecated] max_tokens=10, ) assert isinstance(result.content, TextContent) @@ -108,13 +108,14 @@ async def sample(tag: str) -> None: return f"{echoes['x']} {echoes['y']}" async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams - ) -> CreateMessageResult: + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] content = params.messages[0].content assert isinstance(content, TextContent) sampling_started[content.text].set() await sampling_release.wait() - return CreateMessageResult( + return CreateMessageResult( # pyright: ignore[reportDeprecated] role="assistant", content=TextContent(text=f"echo:{content.text}"), model="test-model", diff --git a/tests/interaction/lowlevel/test_cancellation.py b/tests/interaction/lowlevel/test_cancellation.py index 6e6c2b6f60..ae29f1b911 100644 --- a/tests/interaction/lowlevel/test_cancellation.py +++ b/tests/interaction/lowlevel/test_cancellation.py @@ -163,8 +163,9 @@ async def test_abandoned_server_request_cancels_the_client_callback(connect: Con callback_cancelled = anyio.Event() async def sampling_callback( - context: ClientRequestContext, params: types.CreateMessageRequestParams - ) -> types.CreateMessageResult: + context: ClientRequestContext, + params: types.CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> types.CreateMessageResult: # pyright: ignore[reportDeprecated] callback_started.set() try: await anyio.Event().wait() # blocks until the cancellation interrupts it @@ -180,16 +181,16 @@ async def list_tools( async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult: assert params.name == "impatient" - request = types.CreateMessageRequest( - params=types.CreateMessageRequestParams( - messages=[types.SamplingMessage(role="user", content=TextContent(text="Say hello."))], + request = types.CreateMessageRequest( # pyright: ignore[reportDeprecated] + params=types.CreateMessageRequestParams( # pyright: ignore[reportDeprecated] + messages=[types.SamplingMessage(role="user", content=TextContent(text="Say hello."))], # pyright: ignore[reportDeprecated] max_tokens=8, ) ) async with anyio.create_task_group() as abandon_scope: async def sample() -> None: - await ctx.session.send_request(request, types.CreateMessageResult) + await ctx.session.send_request(request, types.CreateMessageResult) # pyright: ignore[reportDeprecated] raise NotImplementedError # unreachable: the scope is cancelled abandon_scope.start_soon(sample) diff --git a/tests/interaction/lowlevel/test_flows.py b/tests/interaction/lowlevel/test_flows.py index 8d96582341..0f3b73e4b0 100644 --- a/tests/interaction/lowlevel/test_flows.py +++ b/tests/interaction/lowlevel/test_flows.py @@ -168,7 +168,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara ) return CallToolResult(content=[TextContent(text="contents")]) - async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> EmptyResult: + async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> EmptyResult: # pyright: ignore[reportDeprecated] """Registered so the logging capability is advertised; the client never sets a level.""" raise NotImplementedError diff --git a/tests/interaction/lowlevel/test_initialize.py b/tests/interaction/lowlevel/test_initialize.py index 91adbf5611..e303780564 100644 --- a/tests/interaction/lowlevel/test_initialize.py +++ b/tests/interaction/lowlevel/test_initialize.py @@ -118,7 +118,7 @@ async def list_prompts( """Registered only so the prompts capability is advertised; never called.""" raise NotImplementedError - async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> types.EmptyResult: + async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> types.EmptyResult: # pyright: ignore[reportDeprecated] """Registered only so the logging capability is advertised; never called.""" raise NotImplementedError @@ -211,7 +211,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara declared.append(f"roots(list_changed={capabilities.roots.list_changed})") return CallToolResult(content=[TextContent(text=",".join(declared) or "none")]) - async def list_roots(context: ClientRequestContext) -> types.ListRootsResult: + async def list_roots(context: ClientRequestContext) -> types.ListRootsResult: # pyright: ignore[reportDeprecated] """Registered only so the client declares the roots capability; never called.""" raise NotImplementedError diff --git a/tests/interaction/lowlevel/test_logging.py b/tests/interaction/lowlevel/test_logging.py index b8f9d3d776..514afced2f 100644 --- a/tests/interaction/lowlevel/test_logging.py +++ b/tests/interaction/lowlevel/test_logging.py @@ -10,7 +10,12 @@ from mcp import types from mcp.server import Server, ServerRequestContext -from mcp.types import CallToolResult, EmptyResult, LoggingMessageNotificationParams, TextContent +from mcp.types import ( + CallToolResult, + EmptyResult, + LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated] + TextContent, +) from tests.interaction._connect import Connect from tests.interaction._requirements import requirement @@ -32,7 +37,7 @@ async def test_set_logging_level_reaches_handler(connect: Connect) -> None: """The level requested by the client is delivered to the server's handler verbatim.""" - async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> EmptyResult: + async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> EmptyResult: # pyright: ignore[reportDeprecated] assert params.level == "warning" return EmptyResult() @@ -52,9 +57,9 @@ async def test_log_messages_reach_logging_callback_in_order(connect: Connect) -> The two messages pin the full notification shape: severity, optional logger name, and both string and structured data payloads. """ - received: list[LoggingMessageNotificationParams] = [] + received: list[LoggingMessageNotificationParams] = [] # pyright: ignore[reportDeprecated] - async def collect(params: LoggingMessageNotificationParams) -> None: + async def collect(params: LoggingMessageNotificationParams) -> None: # pyright: ignore[reportDeprecated] received.append(params) async def list_tools( @@ -72,7 +77,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara ) return CallToolResult(content=[TextContent(text="done")]) - async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> EmptyResult: + async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> EmptyResult: # pyright: ignore[reportDeprecated] """Registered so the logging capability is advertised; the client never sets a level.""" raise NotImplementedError @@ -84,8 +89,8 @@ async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelReq assert result == snapshot(CallToolResult(content=[TextContent(text="done")])) assert received == snapshot( [ - LoggingMessageNotificationParams(level="info", logger="app.lifecycle", data="starting up"), - LoggingMessageNotificationParams(level="error", data={"code": 502, "retryable": True}), + LoggingMessageNotificationParams(level="info", logger="app.lifecycle", data="starting up"), # pyright: ignore[reportDeprecated] + LoggingMessageNotificationParams(level="error", data={"code": 502, "retryable": True}), # pyright: ignore[reportDeprecated] ] ) @@ -93,9 +98,9 @@ async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelReq @requirement("logging:message:all-levels") async def test_log_messages_at_every_severity_level(connect: Connect) -> None: """Each of the eight RFC 5424 severity levels is deliverable as a log message notification.""" - received: list[LoggingMessageNotificationParams] = [] + received: list[LoggingMessageNotificationParams] = [] # pyright: ignore[reportDeprecated] - async def collect(params: LoggingMessageNotificationParams) -> None: + async def collect(params: LoggingMessageNotificationParams) -> None: # pyright: ignore[reportDeprecated] received.append(params) async def list_tools( @@ -111,7 +116,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara ) return CallToolResult(content=[TextContent(text="logged")]) - async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> EmptyResult: + async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> EmptyResult: # pyright: ignore[reportDeprecated] """Registered so the logging capability is advertised; the client never sets a level.""" raise NotImplementedError diff --git a/tests/interaction/lowlevel/test_roots.py b/tests/interaction/lowlevel/test_roots.py index deb24cbf55..2d6845d14f 100644 --- a/tests/interaction/lowlevel/test_roots.py +++ b/tests/interaction/lowlevel/test_roots.py @@ -8,7 +8,14 @@ from mcp import MCPError, types from mcp.client import ClientRequestContext from mcp.server import Server, ServerRequestContext -from mcp.types import INTERNAL_ERROR, CallToolResult, ErrorData, ListRootsResult, Root, TextContent +from mcp.types import ( + INTERNAL_ERROR, + CallToolResult, + ErrorData, + ListRootsResult, # pyright: ignore[reportDeprecated] + Root, # pyright: ignore[reportDeprecated] + TextContent, +) from tests.interaction._connect import Connect from tests.interaction._requirements import requirement @@ -35,11 +42,11 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("rooted", on_list_tools=list_tools, on_call_tool=call_tool) - async def list_roots(context: ClientRequestContext) -> ListRootsResult: - return ListRootsResult( + async def list_roots(context: ClientRequestContext) -> ListRootsResult: # pyright: ignore[reportDeprecated] + return ListRootsResult( # pyright: ignore[reportDeprecated] roots=[ - Root(uri=FileUrl("file:///home/alice/project"), name="project"), - Root(uri=FileUrl("file:///home/alice/scratch")), + Root(uri=FileUrl("file:///home/alice/project"), name="project"), # pyright: ignore[reportDeprecated] + Root(uri=FileUrl("file:///home/alice/scratch")), # pyright: ignore[reportDeprecated] ] ) @@ -69,8 +76,8 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("rooted", on_list_tools=list_tools, on_call_tool=call_tool) - async def list_roots(context: ClientRequestContext) -> ListRootsResult: - return ListRootsResult(roots=[]) + async def list_roots(context: ClientRequestContext) -> ListRootsResult: # pyright: ignore[reportDeprecated] + return ListRootsResult(roots=[]) # pyright: ignore[reportDeprecated] async with connect(server, list_roots_callback=list_roots) as client: result = await client.call_tool("count_roots", {}) @@ -154,7 +161,7 @@ async def roots_list_changed(ctx: ServerRequestContext, params: types.Notificati server = Server("rooted", on_roots_list_changed=roots_list_changed) - async def list_roots(context: ClientRequestContext) -> ListRootsResult: + async def list_roots(context: ClientRequestContext) -> ListRootsResult: # pyright: ignore[reportDeprecated] """Registered so the client declares the roots capability; the server never asks for roots.""" raise NotImplementedError diff --git a/tests/interaction/lowlevel/test_sampling.py b/tests/interaction/lowlevel/test_sampling.py index 260e564192..620c592752 100644 --- a/tests/interaction/lowlevel/test_sampling.py +++ b/tests/interaction/lowlevel/test_sampling.py @@ -15,18 +15,18 @@ from mcp.types import ( AudioContent, CallToolResult, - CreateMessageRequestParams, - CreateMessageResult, + CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + CreateMessageResult, # pyright: ignore[reportDeprecated] CreateMessageResultWithTools, ErrorData, ImageContent, - ModelHint, - ModelPreferences, + ModelHint, # pyright: ignore[reportDeprecated] + ModelPreferences, # pyright: ignore[reportDeprecated] SamplingCapability, - SamplingMessage, + SamplingMessage, # pyright: ignore[reportDeprecated] TextContent, - ToolResultContent, - ToolUseContent, + ToolResultContent, # pyright: ignore[reportDeprecated] + ToolUseContent, # pyright: ignore[reportDeprecated] ) from tests.interaction._connect import Connect from tests.interaction._requirements import requirement @@ -40,7 +40,7 @@ async def test_create_message_round_trip(connect: Connect) -> None: """A handler's sampling request is answered by the client callback, and the callback's result (role, content, model, stop reason) is returned to the handler. """ - received: list[CreateMessageRequestParams] = [] + received: list[CreateMessageRequestParams] = [] # pyright: ignore[reportDeprecated] async def list_tools( ctx: ServerRequestContext, params: types.PaginatedRequestParams | None @@ -50,7 +50,7 @@ async def list_tools( async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult: assert params.name == "ask_model" result = await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=TextContent(text="Say hello."))], + messages=[SamplingMessage(role="user", content=TextContent(text="Say hello."))], # pyright: ignore[reportDeprecated] max_tokens=100, ) assert isinstance(result.content, TextContent) @@ -59,10 +59,11 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("sampler", on_list_tools=list_tools, on_call_tool=call_tool) async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams - ) -> CreateMessageResult: + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] received.append(params) - return CreateMessageResult( + return CreateMessageResult( # pyright: ignore[reportDeprecated] role="assistant", content=TextContent(text="Hello to you too."), model="mock-llm-1", @@ -75,9 +76,9 @@ async def sampling_callback( assert result == snapshot(CallToolResult(content=[TextContent(text="mock-llm-1/endTurn: Hello to you too.")])) assert received == snapshot( [ - CreateMessageRequestParams( + CreateMessageRequestParams( # pyright: ignore[reportDeprecated] _meta={}, - messages=[SamplingMessage(role="user", content=TextContent(text="Say hello."))], + messages=[SamplingMessage(role="user", content=TextContent(text="Say hello."))], # pyright: ignore[reportDeprecated] max_tokens=100, ) ] @@ -95,7 +96,7 @@ async def test_create_message_params_reach_callback(connect: Connect) -> None: include_context="thisServer" reaches the callback regardless: the spec's SHOULD NOT is not enforced. See the divergence note on `sampling:context:server-gated-by-capability`. """ - received: list[CreateMessageRequestParams] = [] + received: list[CreateMessageRequestParams] = [] # pyright: ignore[reportDeprecated] async def list_tools( ctx: ServerRequestContext, params: types.PaginatedRequestParams | None @@ -105,14 +106,14 @@ async def list_tools( async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult: assert params.name == "ask_model" result = await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=TextContent(text="Pick a model."))], + messages=[SamplingMessage(role="user", content=TextContent(text="Pick a model."))], # pyright: ignore[reportDeprecated] max_tokens=50, system_prompt="You are terse.", include_context="thisServer", temperature=0.7, stop_sequences=["\n\n", "END"], - model_preferences=ModelPreferences( - hints=[ModelHint(name="claude"), ModelHint(name="gpt")], + model_preferences=ModelPreferences( # pyright: ignore[reportDeprecated] + hints=[ModelHint(name="claude"), ModelHint(name="gpt")], # pyright: ignore[reportDeprecated] cost_priority=0.2, speed_priority=0.3, intelligence_priority=0.9, @@ -124,10 +125,11 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("sampler", on_list_tools=list_tools, on_call_tool=call_tool) async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams - ) -> CreateMessageResult: + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] received.append(params) - return CreateMessageResult(role="assistant", content=TextContent(text="ok"), model="mock-llm-1") + return CreateMessageResult(role="assistant", content=TextContent(text="ok"), model="mock-llm-1") # pyright: ignore[reportDeprecated] async with connect(server, sampling_callback=sampling_callback) as client: result = await client.call_tool("ask_model", {}) @@ -135,11 +137,11 @@ async def sampling_callback( assert result == snapshot(CallToolResult(content=[TextContent(text="ok")])) assert received == snapshot( [ - CreateMessageRequestParams( + CreateMessageRequestParams( # pyright: ignore[reportDeprecated] _meta={}, - messages=[SamplingMessage(role="user", content=TextContent(text="Pick a model."))], - model_preferences=ModelPreferences( - hints=[ModelHint(name="claude"), ModelHint(name="gpt")], + messages=[SamplingMessage(role="user", content=TextContent(text="Pick a model."))], # pyright: ignore[reportDeprecated] + model_preferences=ModelPreferences( # pyright: ignore[reportDeprecated] + hints=[ModelHint(name="claude"), ModelHint(name="gpt")], # pyright: ignore[reportDeprecated] cost_priority=0.2, speed_priority=0.3, intelligence_priority=0.9, @@ -161,7 +163,7 @@ async def test_create_message_request_with_image_content_reaches_callback(connec This is the server-to-client direction: the server includes an image in the conversation it asks the client to sample from. """ - received: list[CreateMessageRequestParams] = [] + received: list[CreateMessageRequestParams] = [] # pyright: ignore[reportDeprecated] async def list_tools( ctx: ServerRequestContext, params: types.PaginatedRequestParams | None @@ -171,7 +173,7 @@ async def list_tools( async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult: assert params.name == "describe_image" result = await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=ImageContent(data="aW1n", mime_type="image/png"))], + messages=[SamplingMessage(role="user", content=ImageContent(data="aW1n", mime_type="image/png"))], # pyright: ignore[reportDeprecated] max_tokens=100, ) assert isinstance(result.content, TextContent) @@ -180,12 +182,13 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("sampler", on_list_tools=list_tools, on_call_tool=call_tool) async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams - ) -> CreateMessageResult: + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] received.append(params) image = params.messages[0].content assert isinstance(image, ImageContent) - return CreateMessageResult( + return CreateMessageResult( # pyright: ignore[reportDeprecated] role="assistant", content=TextContent(text=f"described {image.mime_type} ({image.data})"), model="mock-vision-1", @@ -197,9 +200,9 @@ async def sampling_callback( assert result == snapshot(CallToolResult(content=[TextContent(text="described image/png (aW1n)")])) assert received == snapshot( [ - CreateMessageRequestParams( + CreateMessageRequestParams( # pyright: ignore[reportDeprecated] _meta={}, - messages=[SamplingMessage(role="user", content=ImageContent(data="aW1n", mime_type="image/png"))], + messages=[SamplingMessage(role="user", content=ImageContent(data="aW1n", mime_type="image/png"))], # pyright: ignore[reportDeprecated] max_tokens=100, ) ] @@ -221,7 +224,7 @@ async def list_tools( async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult: assert params.name == "draw" result = await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=TextContent(text="Draw a cat."))], + messages=[SamplingMessage(role="user", content=TextContent(text="Draw a cat."))], # pyright: ignore[reportDeprecated] max_tokens=100, ) image = result.content @@ -231,9 +234,10 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("sampler", on_list_tools=list_tools, on_call_tool=call_tool) async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams - ) -> CreateMessageResult: - return CreateMessageResult( + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] + return CreateMessageResult( # pyright: ignore[reportDeprecated] role="assistant", content=ImageContent(data="Y2F0", mime_type="image/png"), model="mock-vision-1", @@ -262,7 +266,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara assert params.name == "ask_model" try: await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=TextContent(text="Say hello."))], + messages=[SamplingMessage(role="user", content=TextContent(text="Say hello."))], # pyright: ignore[reportDeprecated] max_tokens=100, ) except MCPError as exc: @@ -271,7 +275,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("sampler", on_list_tools=list_tools, on_call_tool=call_tool) - async def sampling_callback(context: ClientRequestContext, params: CreateMessageRequestParams) -> ErrorData: + async def sampling_callback(context: ClientRequestContext, params: CreateMessageRequestParams) -> ErrorData: # pyright: ignore[reportDeprecated] return ErrorData(code=-1, message="User rejected sampling request") async with connect(server, sampling_callback=sampling_callback) as client: @@ -293,7 +297,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara assert params.name == "ask_model" try: await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=TextContent(text="Say hello."))], + messages=[SamplingMessage(role="user", content=TextContent(text="Say hello."))], # pyright: ignore[reportDeprecated] max_tokens=100, ) except MCPError as exc: @@ -325,7 +329,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara assert params.name == "ask_model" try: await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=TextContent(text="What is the weather?"))], + messages=[SamplingMessage(role="user", content=TextContent(text="What is the weather?"))], # pyright: ignore[reportDeprecated] max_tokens=100, tools=[types.Tool(name="get_weather", input_schema={"type": "object"})], ) @@ -336,8 +340,9 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("sampler", on_list_tools=list_tools, on_call_tool=call_tool) async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams - ) -> CreateMessageResult: + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] """Declares the plain sampling capability; never invoked because the request is rejected first.""" raise NotImplementedError @@ -368,10 +373,10 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara try: await ctx.session.create_message( messages=[ - SamplingMessage( + SamplingMessage( # pyright: ignore[reportDeprecated] role="user", content=[ - ToolResultContent(tool_use_id="call-1", content=[TextContent(text="42")]), + ToolResultContent(tool_use_id="call-1", content=[TextContent(text="42")]), # pyright: ignore[reportDeprecated] TextContent(text="Also, a comment alongside the result."), ], ) @@ -385,8 +390,9 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("sampler", on_list_tools=list_tools, on_call_tool=call_tool) async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams - ) -> CreateMessageResult: + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] """Declares the sampling capability; never invoked because the request is rejected first.""" raise NotImplementedError @@ -425,8 +431,9 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("introspector", on_list_tools=list_tools, on_call_tool=call_tool) async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams - ) -> CreateMessageResult: + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] """Registered only so the sampling capability is advertised; never called.""" raise NotImplementedError @@ -443,7 +450,7 @@ async def test_create_message_request_with_audio_content_reaches_callback(connec This is the server-to-client direction: the server includes audio in the conversation it asks the client to sample from. """ - received: list[CreateMessageRequestParams] = [] + received: list[CreateMessageRequestParams] = [] # pyright: ignore[reportDeprecated] async def list_tools( ctx: ServerRequestContext, params: types.PaginatedRequestParams | None @@ -453,7 +460,7 @@ async def list_tools( async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult: assert params.name == "transcribe" result = await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=AudioContent(data="c25k", mime_type="audio/wav"))], + messages=[SamplingMessage(role="user", content=AudioContent(data="c25k", mime_type="audio/wav"))], # pyright: ignore[reportDeprecated] max_tokens=100, ) assert isinstance(result.content, TextContent) @@ -462,12 +469,13 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("sampler", on_list_tools=list_tools, on_call_tool=call_tool) async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams - ) -> CreateMessageResult: + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] received.append(params) audio = params.messages[0].content assert isinstance(audio, AudioContent) - return CreateMessageResult( + return CreateMessageResult( # pyright: ignore[reportDeprecated] role="assistant", content=TextContent(text=f"transcribed {audio.mime_type} ({audio.data})"), model="mock-audio-1", @@ -479,9 +487,9 @@ async def sampling_callback( assert result == snapshot(CallToolResult(content=[TextContent(text="transcribed audio/wav (c25k)")])) assert received == snapshot( [ - CreateMessageRequestParams( + CreateMessageRequestParams( # pyright: ignore[reportDeprecated] _meta={}, - messages=[SamplingMessage(role="user", content=AudioContent(data="c25k", mime_type="audio/wav"))], + messages=[SamplingMessage(role="user", content=AudioContent(data="c25k", mime_type="audio/wav"))], # pyright: ignore[reportDeprecated] max_tokens=100, ) ] @@ -503,7 +511,7 @@ async def list_tools( async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult: assert params.name == "speak" result = await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=TextContent(text="Say hello, aloud."))], + messages=[SamplingMessage(role="user", content=TextContent(text="Say hello, aloud."))], # pyright: ignore[reportDeprecated] max_tokens=100, ) audio = result.content @@ -513,9 +521,10 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("sampler", on_list_tools=list_tools, on_call_tool=call_tool) async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams - ) -> CreateMessageResult: - return CreateMessageResult( + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] + return CreateMessageResult( # pyright: ignore[reportDeprecated] role="assistant", content=AudioContent(data="aGVsbG8=", mime_type="audio/wav"), model="mock-audio-1", @@ -530,7 +539,7 @@ async def sampling_callback( @requirement("sampling:message:content-cardinality") async def test_create_message_with_list_valued_message_content_reaches_callback(connect: Connect) -> None: """A sampling message whose content is a list of blocks arrives at the client callback as a list.""" - received: list[CreateMessageRequestParams] = [] + received: list[CreateMessageRequestParams] = [] # pyright: ignore[reportDeprecated] async def list_tools( ctx: ServerRequestContext, params: types.PaginatedRequestParams | None @@ -541,7 +550,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara assert params.name == "caption" result = await ctx.session.create_message( messages=[ - SamplingMessage( + SamplingMessage( # pyright: ignore[reportDeprecated] role="user", content=[ TextContent(text="Caption this image."), @@ -557,12 +566,13 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("sampler", on_list_tools=list_tools, on_call_tool=call_tool) async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams - ) -> CreateMessageResult: + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] received.append(params) content = params.messages[0].content assert isinstance(content, list) - return CreateMessageResult( + return CreateMessageResult( # pyright: ignore[reportDeprecated] role="assistant", content=TextContent(text=f"{len(content)} blocks"), model="mock-llm-1" ) @@ -572,10 +582,10 @@ async def sampling_callback( assert result == snapshot(CallToolResult(content=[TextContent(text="2 blocks")])) assert received == snapshot( [ - CreateMessageRequestParams( + CreateMessageRequestParams( # pyright: ignore[reportDeprecated] _meta={}, messages=[ - SamplingMessage( + SamplingMessage( # pyright: ignore[reportDeprecated] role="user", content=[ TextContent(text="Caption this image."), @@ -608,13 +618,13 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara try: await ctx.session.create_message( messages=[ - SamplingMessage( + SamplingMessage( # pyright: ignore[reportDeprecated] role="assistant", - content=[ToolUseContent(id="call-1", name="weather", input={})], + content=[ToolUseContent(id="call-1", name="weather", input={})], # pyright: ignore[reportDeprecated] ), - SamplingMessage( + SamplingMessage( # pyright: ignore[reportDeprecated] role="user", - content=[ToolResultContent(tool_use_id="call-WRONG", content=[TextContent(text="42")])], + content=[ToolResultContent(tool_use_id="call-WRONG", content=[TextContent(text="42")])], # pyright: ignore[reportDeprecated] ), ], max_tokens=100, @@ -626,8 +636,9 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("sampler", on_list_tools=list_tools, on_call_tool=call_tool) async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams - ) -> CreateMessageResult: + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> CreateMessageResult: # pyright: ignore[reportDeprecated] """Declares the sampling capability; never invoked because the request is rejected first.""" raise NotImplementedError @@ -663,7 +674,7 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara assert params.name == "ask_model" try: await ctx.session.create_message( - messages=[SamplingMessage(role="user", content=TextContent(text="Two thoughts, please."))], + messages=[SamplingMessage(role="user", content=TextContent(text="Two thoughts, please."))], # pyright: ignore[reportDeprecated] max_tokens=100, ) except pydantic.ValidationError as exc: @@ -673,7 +684,8 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara server = Server("sampler", on_list_tools=list_tools, on_call_tool=call_tool) async def sampling_callback( - context: ClientRequestContext, params: CreateMessageRequestParams + context: ClientRequestContext, + params: CreateMessageRequestParams, # pyright: ignore[reportDeprecated] ) -> CreateMessageResultWithTools: return CreateMessageResultWithTools( role="assistant", diff --git a/tests/interaction/lowlevel/test_timeouts.py b/tests/interaction/lowlevel/test_timeouts.py index d73e66f32f..d9b6e63922 100644 --- a/tests/interaction/lowlevel/test_timeouts.py +++ b/tests/interaction/lowlevel/test_timeouts.py @@ -83,14 +83,14 @@ async def list_tools( async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestParams) -> CallToolResult: assert params.name == "impatient" - request = types.CreateMessageRequest( - params=types.CreateMessageRequestParams( - messages=[types.SamplingMessage(role="user", content=TextContent(text="Say hello."))], + request = types.CreateMessageRequest( # pyright: ignore[reportDeprecated] + params=types.CreateMessageRequestParams( # pyright: ignore[reportDeprecated] + messages=[types.SamplingMessage(role="user", content=TextContent(text="Say hello."))], # pyright: ignore[reportDeprecated] max_tokens=8, ) ) with pytest.raises(MCPError) as exc_info: - await ctx.session.send_request(request, types.CreateMessageResult, request_read_timeout_seconds=0.000001) + await ctx.session.send_request(request, types.CreateMessageResult, request_read_timeout_seconds=0.000001) # pyright: ignore[reportDeprecated] errors.append(exc_info.value.error) release.set() return CallToolResult(content=[TextContent(text="gave up")]) @@ -99,12 +99,13 @@ async def call_tool(ctx: ServerRequestContext, params: types.CallToolRequestPara recording = RecordingTransport(InMemoryTransport(server)) async def sampling_callback( - context: ClientRequestContext, params: types.CreateMessageRequestParams - ) -> types.CreateMessageResult: + context: ClientRequestContext, + params: types.CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> types.CreateMessageResult: # pyright: ignore[reportDeprecated] callback_started.set() with anyio.fail_after(5): await release.wait() - return types.CreateMessageResult(role="assistant", content=TextContent(text="too late"), model="test-model") + return types.CreateMessageResult(role="assistant", content=TextContent(text="too late"), model="test-model") # pyright: ignore[reportDeprecated] async with Client(recording, sampling_callback=sampling_callback) as client: result = await client.call_tool("impatient", {}) diff --git a/tests/interaction/lowlevel/test_wire.py b/tests/interaction/lowlevel/test_wire.py index 178c2c1c38..ee8a8ca57c 100644 --- a/tests/interaction/lowlevel/test_wire.py +++ b/tests/interaction/lowlevel/test_wire.py @@ -33,7 +33,7 @@ JSONRPCNotification, JSONRPCRequest, JSONRPCResponse, - ListRootsResult, + ListRootsResult, # pyright: ignore[reportDeprecated] TextContent, ) from tests.interaction._helpers import RecordingTransport, _RecordingReadStream @@ -89,7 +89,7 @@ async def test_notifications_are_never_answered() -> None: the id of the request it answers, and nothing else. """ - async def list_roots(context: ClientRequestContext) -> ListRootsResult: + async def list_roots(context: ClientRequestContext) -> ListRootsResult: # pyright: ignore[reportDeprecated] """Registered so the client declares the roots capability; the server never asks for roots.""" raise NotImplementedError @@ -257,7 +257,7 @@ async def test_set_level_with_an_unrecognized_value_is_answered_with_invalid_par 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: + async def set_logging_level(ctx: ServerRequestContext, params: types.SetLevelRequestParams) -> EmptyResult: # pyright: ignore[reportDeprecated] """Registered so the logging capability is advertised; never called -- params validation fails first.""" raise NotImplementedError diff --git a/tests/interaction/mcpserver/test_context.py b/tests/interaction/mcpserver/test_context.py index 3e6bb542e6..175b7e195b 100644 --- a/tests/interaction/mcpserver/test_context.py +++ b/tests/interaction/mcpserver/test_context.py @@ -16,8 +16,8 @@ ElicitResult, ErrorData, Implementation, - LoggingMessageNotification, - LoggingMessageNotificationParams, + LoggingMessageNotification, # pyright: ignore[reportDeprecated] + LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated] TextContent, ) from tests.interaction._connect import Connect @@ -36,7 +36,7 @@ async def test_context_logging_helpers_send_log_notifications(connect: Connect) of them carry a logger name unless one is passed explicitly. The server emits these without advertising the logging capability (see the divergence note on logging:capability). """ - received: list[LoggingMessageNotificationParams] = [] + received: list[LoggingMessageNotificationParams] = [] # pyright: ignore[reportDeprecated] mcp = MCPServer("chatty") @mcp.tool() @@ -47,7 +47,7 @@ async def narrate(ctx: Context) -> str: await ctx.error("e") return "done" - async def collect(params: LoggingMessageNotificationParams) -> None: + async def collect(params: LoggingMessageNotificationParams) -> None: # pyright: ignore[reportDeprecated] received.append(params) async with connect(mcp, logging_callback=collect) as client: @@ -57,10 +57,10 @@ async def collect(params: LoggingMessageNotificationParams) -> None: assert result == snapshot(CallToolResult(content=[TextContent(text="done")], structured_content={"result": "done"})) assert received == snapshot( [ - LoggingMessageNotificationParams(level="debug", data="d"), - LoggingMessageNotificationParams(level="info", data="i"), - LoggingMessageNotificationParams(level="warning", data="w"), - LoggingMessageNotificationParams(level="error", data="e"), + LoggingMessageNotificationParams(level="debug", data="d"), # pyright: ignore[reportDeprecated] + LoggingMessageNotificationParams(level="info", data="i"), # pyright: ignore[reportDeprecated] + LoggingMessageNotificationParams(level="warning", data="w"), # pyright: ignore[reportDeprecated] + LoggingMessageNotificationParams(level="error", data="e"), # pyright: ignore[reportDeprecated] ] ) # The spec requires servers that emit log notifications to declare the logging capability. @@ -149,7 +149,7 @@ async def collect(message: IncomingMessage) -> None: CallToolResult(content=[TextContent(text="milled")], structured_content={"result": "milled"}) ) assert received == snapshot( - [LoggingMessageNotification(params=LoggingMessageNotificationParams(level="info", data="milling done"))] + [LoggingMessageNotification(params=LoggingMessageNotificationParams(level="info", data="milling done"))] # pyright: ignore[reportDeprecated] ) @@ -245,7 +245,7 @@ async def test_set_logging_level_is_rejected_and_messages_are_never_filtered(con should only send messages at or above the configured level; with no way to configure one, everything is sent. """ - received: list[LoggingMessageNotificationParams] = [] + received: list[LoggingMessageNotificationParams] = [] # pyright: ignore[reportDeprecated] mcp = MCPServer("unfilterable") @mcp.tool() @@ -254,7 +254,7 @@ async def chatter(ctx: Context) -> str: await ctx.error("signal") return "done" - async def collect(params: LoggingMessageNotificationParams) -> None: + async def collect(params: LoggingMessageNotificationParams) -> None: # pyright: ignore[reportDeprecated] received.append(params) async with connect(mcp, logging_callback=collect) as client: @@ -268,7 +268,7 @@ async def collect(params: LoggingMessageNotificationParams) -> None: ) assert received == snapshot( [ - LoggingMessageNotificationParams(level="debug", data="noise"), - LoggingMessageNotificationParams(level="error", data="signal"), + LoggingMessageNotificationParams(level="debug", data="noise"), # pyright: ignore[reportDeprecated] + LoggingMessageNotificationParams(level="error", data="signal"), # pyright: ignore[reportDeprecated] ] ) diff --git a/tests/interaction/mcpserver/test_tools.py b/tests/interaction/mcpserver/test_tools.py index 05135c1286..6a128fa3a7 100644 --- a/tests/interaction/mcpserver/test_tools.py +++ b/tests/interaction/mcpserver/test_tools.py @@ -16,8 +16,8 @@ CallToolResult, ElicitRequestURLParams, ErrorData, - LoggingMessageNotification, - LoggingMessageNotificationParams, + LoggingMessageNotification, # pyright: ignore[reportDeprecated] + LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated] TextContent, ) from tests.interaction._connect import Connect @@ -428,5 +428,5 @@ async def collect(message: IncomingMessage) -> None: assert [tool.name for tool in before.tools] == ["doomed", "grow"] assert [tool.name for tool in after.tools] == ["grow", "extra"] assert received == snapshot( - [LoggingMessageNotification(params=LoggingMessageNotificationParams(level="info", data="tool set changed"))] + [LoggingMessageNotification(params=LoggingMessageNotificationParams(level="info", data="tool set changed"))] # pyright: ignore[reportDeprecated] ) diff --git a/tests/interaction/transports/_stdio_server.py b/tests/interaction/transports/_stdio_server.py index a6dad4772d..c7617e162c 100644 --- a/tests/interaction/transports/_stdio_server.py +++ b/tests/interaction/transports/_stdio_server.py @@ -19,7 +19,7 @@ EmptyResult, ListToolsResult, PaginatedRequestParams, - SetLevelRequestParams, + SetLevelRequestParams, # pyright: ignore[reportDeprecated] TextContent, Tool, ) @@ -44,7 +44,7 @@ async def call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> return CallToolResult(content=[TextContent(text=text)]) -async def set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult: +async def set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult: # pyright: ignore[reportDeprecated] """Registered so the logging capability is advertised; the client never sets a level.""" raise NotImplementedError diff --git a/tests/interaction/transports/test_flows.py b/tests/interaction/transports/test_flows.py index c428fe2d68..de72e29f4a 100644 --- a/tests/interaction/transports/test_flows.py +++ b/tests/interaction/transports/test_flows.py @@ -12,7 +12,7 @@ from mcp.client.session import LoggingFnT from mcp.server.mcpserver import Context, MCPServer -from mcp.types import CallToolResult, LoggingMessageNotificationParams, TextContent +from mcp.types import CallToolResult, LoggingMessageNotificationParams, TextContent # pyright: ignore[reportDeprecated] from tests.interaction._connect import client_via_http, connect_over_sse, mounted_app from tests.interaction._requirements import requirement @@ -38,10 +38,10 @@ async def announce(label: str, ctx: Context) -> str: received_a: list[object] = [] received_b: list[object] = [] - async def collect_a(params: LoggingMessageNotificationParams) -> None: + async def collect_a(params: LoggingMessageNotificationParams) -> None: # pyright: ignore[reportDeprecated] received_a.append(params.data) - async def collect_b(params: LoggingMessageNotificationParams) -> None: + async def collect_b(params: LoggingMessageNotificationParams) -> None: # pyright: ignore[reportDeprecated] received_b.append(params.data) async with mounted_app(mcp) as (http, _): diff --git a/tests/interaction/transports/test_hosting_http.py b/tests/interaction/transports/test_hosting_http.py index e4a92ea7e4..365e37979d 100644 --- a/tests/interaction/transports/test_hosting_http.py +++ b/tests/interaction/transports/test_hosting_http.py @@ -27,7 +27,7 @@ ListResourcesResult, ListToolsResult, PaginatedRequestParams, - SetLevelRequestParams, + SetLevelRequestParams, # pyright: ignore[reportDeprecated] SubscribeRequestParams, TextContent, ) @@ -56,7 +56,7 @@ async def call_tool(ctx: ServerRequestContext, params: CallToolRequestParams) -> await ctx.session.send_resource_updated("file:///watched.txt") return CallToolResult(content=[TextContent(text="done")]) - async def set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult: + async def set_logging_level(ctx: ServerRequestContext, params: SetLevelRequestParams) -> EmptyResult: # pyright: ignore[reportDeprecated] """Registered so the logging capability is advertised; the client never sets a level.""" raise NotImplementedError diff --git a/tests/interaction/transports/test_hosting_resume.py b/tests/interaction/transports/test_hosting_resume.py index c7945d56c3..50e5de29e3 100644 --- a/tests/interaction/transports/test_hosting_resume.py +++ b/tests/interaction/transports/test_hosting_resume.py @@ -29,7 +29,7 @@ JSONRPCNotification, JSONRPCRequest, JSONRPCResponse, - LoggingMessageNotificationParams, + LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated] TextContent, jsonrpc_message_adapter, ) @@ -270,7 +270,7 @@ async def interrupt(ctx: Context) -> str: done.set() return "resumed" - async def collect(params: LoggingMessageNotificationParams) -> None: + async def collect(params: LoggingMessageNotificationParams) -> None: # pyright: ignore[reportDeprecated] received.append(params.data) if params.data == "before close": before_seen.set() @@ -331,7 +331,7 @@ async def on_token(token: str) -> None: if len(captured) >= 2: token_seen.set() - async def collect(params: LoggingMessageNotificationParams) -> None: + async def collect(params: LoggingMessageNotificationParams) -> None: # pyright: ignore[reportDeprecated] received.append(params.data) first_seen.set() diff --git a/tests/interaction/transports/test_stdio.py b/tests/interaction/transports/test_stdio.py index 8aac551c67..45fed8928f 100644 --- a/tests/interaction/transports/test_stdio.py +++ b/tests/interaction/transports/test_stdio.py @@ -31,7 +31,7 @@ JSONRPCNotification, JSONRPCRequest, JSONRPCResponse, - LoggingMessageNotificationParams, + LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated] TextContent, ) from mcp.types.jsonrpc import jsonrpc_message_adapter @@ -66,9 +66,9 @@ async def test_tool_call_and_notification_round_trip_over_a_stdio_subprocess( # child exits promptly. Not under test. monkeypatch.setattr(stdio, "PROCESS_TERMINATION_TIMEOUT", 20.0) - received: list[LoggingMessageNotificationParams] = [] + received: list[LoggingMessageNotificationParams] = [] # pyright: ignore[reportDeprecated] - async def collect(params: LoggingMessageNotificationParams) -> None: + async def collect(params: LoggingMessageNotificationParams) -> None: # pyright: ignore[reportDeprecated] received.append(params) with tempfile.TemporaryFile(mode="w+") as errlog: @@ -100,7 +100,7 @@ async def collect(params: LoggingMessageNotificationParams) -> None: # stdio carries one ordered server-to-client stream, so the same notification-before-response # guarantee holds here as for the in-memory transport. assert received == snapshot( - [LoggingMessageNotificationParams(level="info", logger="echo", data="echoing across\nprocesses")] + [LoggingMessageNotificationParams(level="info", logger="echo", data="echoing across\nprocesses")] # pyright: ignore[reportDeprecated] ) # The server writes this line only after its run loop returns on stdin close: seeing it proves # a self-exit, not the terminate escalation. The capture itself proves stderr passthrough. diff --git a/tests/interaction/transports/test_streamable_http.py b/tests/interaction/transports/test_streamable_http.py index d38e2a0bb3..41168ce164 100644 --- a/tests/interaction/transports/test_streamable_http.py +++ b/tests/interaction/transports/test_streamable_http.py @@ -19,8 +19,8 @@ CallToolResult, ElicitRequestParams, ElicitResult, - LoggingMessageNotification, - LoggingMessageNotificationParams, + LoggingMessageNotification, # pyright: ignore[reportDeprecated] + LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated] ResourceUpdatedNotification, ResourceUpdatedNotificationParams, TextContent, @@ -133,8 +133,8 @@ async def collect(message: IncomingMessage) -> None: ) # The related log notification rides the call's stream; the unrelated resource-updated # notification rides the standalone stream. Both arrive, nothing else does. - assert [message for message in received if isinstance(message, LoggingMessageNotification)] == snapshot( - [LoggingMessageNotification(params=LoggingMessageNotificationParams(level="info", data="about to announce"))] + assert [message for message in received if isinstance(message, LoggingMessageNotification)] == snapshot( # pyright: ignore[reportDeprecated] + [LoggingMessageNotification(params=LoggingMessageNotificationParams(level="info", data="about to announce"))] # pyright: ignore[reportDeprecated] ) assert [message for message in received if isinstance(message, ResourceUpdatedNotification)] == snapshot( [ResourceUpdatedNotification(params=ResourceUpdatedNotificationParams(uri="file:///watched.txt"))] diff --git a/tests/server/mcpserver/test_integration.py b/tests/server/mcpserver/test_integration.py index 5bac39dfee..d45b6b20c0 100644 --- a/tests/server/mcpserver/test_integration.py +++ b/tests/server/mcpserver/test_integration.py @@ -22,7 +22,6 @@ elicitation, mcpserver_quickstart, notifications, - sampling, structured_output, tool_progress, ) @@ -30,13 +29,11 @@ from mcp.shared.session import RequestResponder from mcp.types import ( ClientResult, - CreateMessageRequestParams, - CreateMessageResult, ElicitRequestParams, ElicitResult, GetPromptResult, - LoggingMessageNotification, - LoggingMessageNotificationParams, + LoggingMessageNotification, # pyright: ignore[reportDeprecated] + LoggingMessageNotificationParams, # pyright: ignore[reportDeprecated] NotificationParams, ProgressNotification, ProgressNotificationParams, @@ -59,7 +56,7 @@ class NotificationCollector: def __init__(self): self.progress_notifications: list[ProgressNotificationParams] = [] - self.log_messages: list[LoggingMessageNotificationParams] = [] + self.log_messages: list[LoggingMessageNotificationParams] = [] # pyright: ignore[reportDeprecated] self.resource_notifications: list[NotificationParams | None] = [] self.tool_notifications: list[NotificationParams | None] = [] @@ -70,7 +67,7 @@ async def handle_generic_notification( if isinstance(message, ServerNotification): # pragma: no branch if isinstance(message, ProgressNotification): self.progress_notifications.append(message.params) - elif isinstance(message, LoggingMessageNotification): + elif isinstance(message, LoggingMessageNotification): # pyright: ignore[reportDeprecated] self.log_messages.append(message.params) elif isinstance(message, ResourceListChangedNotification): self.resource_notifications.append(message.params) @@ -78,18 +75,6 @@ async def handle_generic_notification( self.tool_notifications.append(message.params) -async def sampling_callback(context: ClientRequestContext, params: CreateMessageRequestParams) -> CreateMessageResult: - """Sampling callback for tests.""" - return CreateMessageResult( - role="assistant", - content=TextContent( - type="text", - text="This is a simulated LLM response for testing", - ), - model="test-model", - ) - - async def elicitation_callback(context: ClientRequestContext, params: ElicitRequestParams): """Elicitation callback for tests.""" # For restaurant booking test @@ -213,18 +198,6 @@ async def progress_callback(progress: float, total: float | None, message: str | assert len(collector.log_messages) > 0 -async def test_sampling() -> None: - """Test sampling (LLM interaction) functionality.""" - async with Client(sampling.mcp, sampling_callback=sampling_callback) as client: - assert client.initialize_result.capabilities.tools is not None - - # Test sampling tool - sampling_result = await client.call_tool("generate_poem", {"topic": "nature"}) - assert len(sampling_result.content) == 1 - assert isinstance(sampling_result.content[0], TextContent) - assert "This is a simulated LLM response" in sampling_result.content[0].text - - async def test_elicitation() -> None: """Test elicitation (user interaction) functionality.""" async with Client(elicitation.mcp, elicitation_callback=elicitation_callback) as client: diff --git a/tests/server/test_connection.py b/tests/server/test_connection.py index e7ec874e32..dc48e40e14 100644 --- a/tests/server/test_connection.py +++ b/tests/server/test_connection.py @@ -20,14 +20,14 @@ from mcp.types import ( LATEST_PROTOCOL_VERSION, ClientCapabilities, - CreateMessageRequest, - CreateMessageRequestParams, + CreateMessageRequest, # pyright: ignore[reportDeprecated] + CreateMessageRequestParams, # pyright: ignore[reportDeprecated] ElicitationCapability, EmptyResult, Implementation, InitializeRequestParams, - ListRootsRequest, - ListRootsResult, + ListRootsRequest, # pyright: ignore[reportDeprecated] + ListRootsResult, # pyright: ignore[reportDeprecated] PingRequest, Request, RequestParams, @@ -114,10 +114,10 @@ async def test_connection_send_raw_request_forwards_when_standalone_channel_pres async def test_connection_send_request_with_spec_type_infers_result_type(): out = StubOutbound(result={"roots": [{"uri": "file:///ws"}]}) conn = Connection(out, has_standalone_channel=True) - result = await conn.send_request(ListRootsRequest()) + result = await conn.send_request(ListRootsRequest()) # pyright: ignore[reportDeprecated] method, _ = out.requests[0] assert method == "roots/list" - assert isinstance(result, ListRootsResult) + assert isinstance(result, ListRootsResult) # pyright: ignore[reportDeprecated] assert str(result.roots[0].uri) == "file:///ws" @@ -127,7 +127,7 @@ async def test_connection_send_request_validates_result_alias_only(): ignored as extra, not populated by Python field name.""" snake = {"role": "assistant", "content": {"type": "text", "text": "x"}, "model": "m", "stop_reason": "endTurn"} conn = Connection(StubOutbound(result=snake), has_standalone_channel=True) - result = await conn.send_request(CreateMessageRequest(params=CreateMessageRequestParams(messages=[], max_tokens=1))) + result = await conn.send_request(CreateMessageRequest(params=CreateMessageRequestParams(messages=[], max_tokens=1))) # pyright: ignore[reportDeprecated] assert result.stop_reason is None @@ -143,7 +143,7 @@ async def test_connection_send_request_with_result_type_kwarg_validates_custom_t async def test_connection_send_request_nonconforming_result_raises_validation_error(): conn = Connection(StubOutbound(result={"bogus": 1}), has_standalone_channel=True) with pytest.raises(ValidationError): - await conn.send_request(ListRootsRequest()) + await conn.send_request(ListRootsRequest()) # pyright: ignore[reportDeprecated] @pytest.mark.anyio @@ -152,7 +152,7 @@ async def test_send_request_validates_the_client_result_against_the_surface_sche `ValidationError` even when the caller's `result_type` would accept it.""" conn = Connection(StubOutbound(result={"roots": "nope"}), has_standalone_channel=True) with pytest.raises(ValidationError): - await conn.send_request(ListRootsRequest(), result_type=EmptyResult) + await conn.send_request(ListRootsRequest(), result_type=EmptyResult) # pyright: ignore[reportDeprecated] @pytest.mark.anyio @@ -160,8 +160,8 @@ async def test_send_request_passes_a_spec_valid_client_result(): """A spec-valid client result passes the surface gate and parses to the typed model.""" conn = Connection(StubOutbound(result={"roots": [{"uri": "file:///ws"}]}), has_standalone_channel=True) conn.protocol_version = "2025-11-25" - result = await conn.send_request(ListRootsRequest()) - assert isinstance(result, ListRootsResult) + result = await conn.send_request(ListRootsRequest()) # pyright: ignore[reportDeprecated] + assert isinstance(result, ListRootsResult) # pyright: ignore[reportDeprecated] assert str(result.roots[0].uri) == "file:///ws" diff --git a/tests/server/test_runner.py b/tests/server/test_runner.py index c4d10aea08..28ba393174 100644 --- a/tests/server/test_runner.py +++ b/tests/server/test_runner.py @@ -44,7 +44,7 @@ ProgressNotificationParams, RequestParams, RequestParamsMeta, - SetLevelRequestParams, + SetLevelRequestParams, # pyright: ignore[reportDeprecated] Tool, ) @@ -446,10 +446,10 @@ async def boom(ctx: Ctx, params: NotificationParams | None) -> None: async def test_runner_on_notify_drops_malformed_params(server: SrvT, caplog: pytest.LogCaptureFixture): """Malformed notification params are logged and dropped, not raised.""" - async def on_level(ctx: Ctx, params: SetLevelRequestParams) -> None: + async def on_level(ctx: Ctx, params: SetLevelRequestParams) -> None: # pyright: ignore[reportDeprecated] raise NotImplementedError - server.add_notification_handler("notifications/roots/list_changed", SetLevelRequestParams, on_level) + server.add_notification_handler("notifications/roots/list_changed", SetLevelRequestParams, on_level) # pyright: ignore[reportDeprecated] async with connected_runner(server) as (client, _): await client.notify("notifications/roots/list_changed", {"level": "not-a-level"}) result = await client.send_raw_request("tools/list", None) @@ -756,10 +756,10 @@ async def mw(ctx: Ctx, method: str, params: Any, call_next: Any) -> Any: @pytest.mark.anyio async def test_runner_handler_returning_none_yields_empty_result(server: SrvT): - async def set_level(ctx: Ctx, params: SetLevelRequestParams) -> None: + async def set_level(ctx: Ctx, params: SetLevelRequestParams) -> None: # pyright: ignore[reportDeprecated] return None - server.add_request_handler("logging/setLevel", SetLevelRequestParams, set_level) + server.add_request_handler("logging/setLevel", SetLevelRequestParams, set_level) # pyright: ignore[reportDeprecated] async with connected_runner(server) as (client, _): result = await client.send_raw_request("logging/setLevel", {"level": "info"}) assert result == {} @@ -770,10 +770,10 @@ async def test_runner_handler_returning_error_data_produces_jsonrpc_error(server """A handler returning `ErrorData` reaches the client as a JSON-RPC error, not a success result, matching `BaseSession._send_response`.""" - async def set_level(ctx: Ctx, params: SetLevelRequestParams) -> ErrorData: + async def set_level(ctx: Ctx, params: SetLevelRequestParams) -> ErrorData: # pyright: ignore[reportDeprecated] return ErrorData(code=INVALID_PARAMS, message="bad level", data={"got": params.level}) - server.add_request_handler("logging/setLevel", SetLevelRequestParams, set_level) + server.add_request_handler("logging/setLevel", SetLevelRequestParams, set_level) # pyright: ignore[reportDeprecated] async with connected_runner(server) as (client, _): with pytest.raises(MCPError) as exc: await client.send_raw_request("logging/setLevel", {"level": "info"}) @@ -794,11 +794,11 @@ async def observe(ctx: Ctx, method: str, params: Any, call_next: Any) -> Any: seen.append(e) raise - async def set_level(ctx: Ctx, params: SetLevelRequestParams) -> ErrorData: + async def set_level(ctx: Ctx, params: SetLevelRequestParams) -> ErrorData: # pyright: ignore[reportDeprecated] return ErrorData(code=INVALID_PARAMS, message="bad level") server.middleware.append(observe) - server.add_request_handler("logging/setLevel", SetLevelRequestParams, set_level) + server.add_request_handler("logging/setLevel", SetLevelRequestParams, set_level) # pyright: ignore[reportDeprecated] async with connected_runner(server) as (client, _): with pytest.raises(MCPError) as exc: await client.send_raw_request("logging/setLevel", {"level": "info"}) diff --git a/tests/server/test_session.py b/tests/server/test_session.py index cddf3c19ec..1c81ef0e82 100644 --- a/tests/server/test_session.py +++ b/tests/server/test_session.py @@ -82,13 +82,13 @@ async def on_progress(progress: float, total: float | None, message: str | None) raise NotImplementedError result = await session.send_request( - types.ListRootsRequest(), - types.ListRootsResult, + types.ListRootsRequest(), # pyright: ignore[reportDeprecated] + types.ListRootsResult, # pyright: ignore[reportDeprecated] request_read_timeout_seconds=2.5, metadata=ServerMessageMetadata(related_request_id=7), progress_callback=on_progress, ) - assert isinstance(result, types.ListRootsResult) + assert isinstance(result, types.ListRootsResult) # pyright: ignore[reportDeprecated] method, _params, opts, related = dispatcher.requests[0] assert method == "roots/list" assert opts == {"timeout": 2.5, "on_progress": on_progress} @@ -99,7 +99,7 @@ async def on_progress(progress: float, total: float | None, message: str | None) async def test_send_request_omits_call_options_when_none_given(): dispatcher = StubDispatcher(result={"roots": []}) session = _make_session(dispatcher) - await session.send_request(types.ListRootsRequest(), types.ListRootsResult) + await session.send_request(types.ListRootsRequest(), types.ListRootsResult) # pyright: ignore[reportDeprecated] _method, _params, opts, related = dispatcher.requests[0] assert opts is None assert related is None @@ -137,15 +137,15 @@ async def test_send_request_validates_the_client_result_against_the_surface_sche `ValidationError` even when the caller's `result_type` would accept it.""" session = _make_session(StubDispatcher(result={"roots": "nope"})) with pytest.raises(ValidationError): - await session.send_request(types.ListRootsRequest(), types.EmptyResult) + await session.send_request(types.ListRootsRequest(), types.EmptyResult) # pyright: ignore[reportDeprecated] @pytest.mark.anyio async def test_send_request_passes_a_spec_valid_client_result(): """A spec-valid client result passes the surface gate and parses to the typed model.""" session = _make_session(StubDispatcher(result={"roots": [{"uri": "file:///ws"}]})) - result = await session.send_request(types.ListRootsRequest(), types.ListRootsResult) - assert isinstance(result, types.ListRootsResult) + result = await session.send_request(types.ListRootsRequest(), types.ListRootsResult) # pyright: ignore[reportDeprecated] + assert isinstance(result, types.ListRootsResult) # pyright: ignore[reportDeprecated] assert str(result.roots[0].uri) == "file:///ws" @@ -164,8 +164,8 @@ async def test_send_request_validates_result_alias_only(): ignored as extra, not populated by Python field name.""" snake = {"role": "assistant", "content": {"type": "text", "text": "x"}, "model": "m", "stop_reason": "endTurn"} session = _make_session(StubDispatcher(result=snake)) - request = types.CreateMessageRequest(params=types.CreateMessageRequestParams(messages=[], max_tokens=1)) - result = await session.send_request(request, types.CreateMessageResult) + request = types.CreateMessageRequest(params=types.CreateMessageRequestParams(messages=[], max_tokens=1)) # pyright: ignore[reportDeprecated] + result = await session.send_request(request, types.CreateMessageResult) # pyright: ignore[reportDeprecated] assert result.stop_reason is None @@ -176,7 +176,7 @@ async def test_create_message_with_tools_returns_with_tools_result(): dispatcher, capabilities=ClientCapabilities(sampling=SamplingCapability(tools=SamplingToolsCapability())) ) result = await session.create_message( - messages=[types.SamplingMessage(role="user", content=types.TextContent(type="text", text="hi"))], + messages=[types.SamplingMessage(role="user", content=types.TextContent(type="text", text="hi"))], # pyright: ignore[reportDeprecated] max_tokens=10, tools=[types.Tool(name="t", input_schema={"type": "object"})], ) diff --git a/tests/server/test_stateless_mode.py b/tests/server/test_stateless_mode.py index 1b628e2388..15104fef51 100644 --- a/tests/server/test_stateless_mode.py +++ b/tests/server/test_stateless_mode.py @@ -51,7 +51,7 @@ async def test_create_message_fails_in_stateless_mode(stateless_session: ServerS with pytest.raises(StatelessModeNotSupported, match="sampling"): await stateless_session.create_message( messages=[ - types.SamplingMessage( + types.SamplingMessage( # pyright: ignore[reportDeprecated] role="user", content=types.TextContent(type="text", text="hello"), ) @@ -131,10 +131,10 @@ async def test_stateful_mode_does_not_raise_stateless_error( """ send_request_called = False - async def mock_send_request(*_: Any, **__: Any) -> types.ListRootsResult: + async def mock_send_request(*_: Any, **__: Any) -> types.ListRootsResult: # pyright: ignore[reportDeprecated] nonlocal send_request_called send_request_called = True - return types.ListRootsResult(roots=[]) + return types.ListRootsResult(roots=[]) # pyright: ignore[reportDeprecated] monkeypatch.setattr(stateful_session, "send_request", mock_send_request) @@ -142,7 +142,7 @@ async def mock_send_request(*_: Any, **__: Any) -> types.ListRootsResult: result = await stateful_session.list_roots() assert send_request_called - assert isinstance(result, types.ListRootsResult) + assert isinstance(result, types.ListRootsResult) # pyright: ignore[reportDeprecated] @pytest.mark.anyio diff --git a/tests/server/test_validation.py b/tests/server/test_validation.py index 19f4eb1088..16654e2480 100644 --- a/tests/server/test_validation.py +++ b/tests/server/test_validation.py @@ -11,13 +11,13 @@ from mcp.types import ( ClientCapabilities, SamplingCapability, - SamplingMessage, + SamplingMessage, # pyright: ignore[reportDeprecated] SamplingToolsCapability, TextContent, Tool, - ToolChoice, - ToolResultContent, - ToolUseContent, + ToolChoice, # pyright: ignore[reportDeprecated] + ToolResultContent, # pyright: ignore[reportDeprecated] + ToolUseContent, # pyright: ignore[reportDeprecated] ) # Tests for check_sampling_tools_capability function @@ -65,7 +65,7 @@ def test_validate_sampling_tools_raises_when_tools_provided_but_no_capability() def test_validate_sampling_tools_raises_when_tool_choice_provided_but_no_capability() -> None: """Raises MCPError when tool_choice provided but client doesn't support.""" with pytest.raises(MCPError) as exc_info: - validate_sampling_tools(None, None, ToolChoice(mode="auto")) + validate_sampling_tools(None, None, ToolChoice(mode="auto")) # pyright: ignore[reportDeprecated] assert "sampling tools capability" in str(exc_info.value) @@ -73,7 +73,11 @@ def test_validate_sampling_tools_no_error_when_capability_present() -> None: """No error when client has sampling.tools capability.""" caps = ClientCapabilities(sampling=SamplingCapability(tools=SamplingToolsCapability())) tool = Tool(name="test", input_schema={"type": "object"}) - validate_sampling_tools(caps, [tool], ToolChoice(mode="auto")) # Should not raise + validate_sampling_tools( + caps, + [tool], + ToolChoice(mode="auto"), # Should not raise # pyright: ignore[reportDeprecated] + ) # Tests for validate_tool_use_result_messages function @@ -87,8 +91,8 @@ def test_validate_tool_use_result_messages_no_error_for_empty_messages() -> None def test_validate_tool_use_result_messages_no_error_for_simple_text_messages() -> None: """No error for simple text messages.""" messages = [ - SamplingMessage(role="user", content=TextContent(type="text", text="Hello")), - SamplingMessage(role="assistant", content=TextContent(type="text", text="Hi")), + SamplingMessage(role="user", content=TextContent(type="text", text="Hello")), # pyright: ignore[reportDeprecated] + SamplingMessage(role="assistant", content=TextContent(type="text", text="Hi")), # pyright: ignore[reportDeprecated] ] validate_tool_use_result_messages(messages) # Should not raise @@ -96,10 +100,10 @@ def test_validate_tool_use_result_messages_no_error_for_simple_text_messages() - def test_validate_tool_use_result_messages_raises_when_tool_result_mixed_with_other_content() -> None: """Raises when tool_result is mixed with other content types.""" messages = [ - SamplingMessage( + SamplingMessage( # pyright: ignore[reportDeprecated] role="user", content=[ - ToolResultContent(type="tool_result", tool_use_id="123"), + ToolResultContent(type="tool_result", tool_use_id="123"), # pyright: ignore[reportDeprecated] TextContent(type="text", text="also this"), ], ), @@ -111,9 +115,9 @@ def test_validate_tool_use_result_messages_raises_when_tool_result_mixed_with_ot def test_validate_tool_use_result_messages_raises_when_tool_result_without_previous_tool_use() -> None: """Raises when tool_result appears without preceding tool_use.""" messages = [ - SamplingMessage( + SamplingMessage( # pyright: ignore[reportDeprecated] role="user", - content=ToolResultContent(type="tool_result", tool_use_id="123"), + content=ToolResultContent(type="tool_result", tool_use_id="123"), # pyright: ignore[reportDeprecated] ), ] with pytest.raises(ValueError, match="previous message containing tool_use"): @@ -123,8 +127,8 @@ def test_validate_tool_use_result_messages_raises_when_tool_result_without_previ def test_validate_tool_use_result_messages_raises_when_previous_message_has_no_tool_use() -> None: """Raises when tool_result follows a message that has content but no tool_use.""" messages = [ - SamplingMessage(role="assistant", content=TextContent(type="text", text="just text")), - SamplingMessage(role="user", content=ToolResultContent(type="tool_result", tool_use_id="tool-1")), + SamplingMessage(role="assistant", content=TextContent(type="text", text="just text")), # pyright: ignore[reportDeprecated] + SamplingMessage(role="user", content=ToolResultContent(type="tool_result", tool_use_id="tool-1")), # pyright: ignore[reportDeprecated] ] with pytest.raises(ValueError, match="do not match any tool_use in the previous message"): validate_tool_use_result_messages(messages) @@ -133,13 +137,13 @@ def test_validate_tool_use_result_messages_raises_when_previous_message_has_no_t def test_validate_tool_use_result_messages_raises_when_tool_result_ids_dont_match_tool_use() -> None: """Raises when tool_result IDs don't match tool_use IDs.""" messages = [ - SamplingMessage( + SamplingMessage( # pyright: ignore[reportDeprecated] role="assistant", - content=ToolUseContent(type="tool_use", id="tool-1", name="test", input={}), + content=ToolUseContent(type="tool_use", id="tool-1", name="test", input={}), # pyright: ignore[reportDeprecated] ), - SamplingMessage( + SamplingMessage( # pyright: ignore[reportDeprecated] role="user", - content=ToolResultContent(type="tool_result", tool_use_id="tool-2"), + content=ToolResultContent(type="tool_result", tool_use_id="tool-2"), # pyright: ignore[reportDeprecated] ), ] with pytest.raises(ValueError, match="do not match"): @@ -149,13 +153,13 @@ def test_validate_tool_use_result_messages_raises_when_tool_result_ids_dont_matc def test_validate_tool_use_result_messages_no_error_when_tool_result_matches_tool_use() -> None: """No error when tool_result IDs match tool_use IDs.""" messages = [ - SamplingMessage( + SamplingMessage( # pyright: ignore[reportDeprecated] role="assistant", - content=ToolUseContent(type="tool_use", id="tool-1", name="test", input={}), + content=ToolUseContent(type="tool_use", id="tool-1", name="test", input={}), # pyright: ignore[reportDeprecated] ), - SamplingMessage( + SamplingMessage( # pyright: ignore[reportDeprecated] role="user", - content=ToolResultContent(type="tool_result", tool_use_id="tool-1"), + content=ToolResultContent(type="tool_result", tool_use_id="tool-1"), # pyright: ignore[reportDeprecated] ), ] validate_tool_use_result_messages(messages) # Should not raise diff --git a/tests/shared/test_peer.py b/tests/shared/test_peer.py index a839991a6c..3dc6a147bd 100644 --- a/tests/shared/test_peer.py +++ b/tests/shared/test_peer.py @@ -15,11 +15,11 @@ from mcp.shared.peer import ClientPeer, dump_params from mcp.shared.transport_context import TransportContext from mcp.types import ( - CreateMessageResult, + CreateMessageResult, # pyright: ignore[reportDeprecated] CreateMessageResultWithTools, ElicitResult, - ListRootsResult, - SamplingMessage, + ListRootsResult, # pyright: ignore[reportDeprecated] + SamplingMessage, # pyright: ignore[reportDeprecated] TextContent, Tool, ) @@ -47,13 +47,13 @@ async def test_peer_sample_sends_create_message_and_returns_typed_result(): peer = ClientPeer(client) with anyio.fail_after(5): result = await peer.sample( - [SamplingMessage(role="user", content=TextContent(type="text", text="hello"))], + [SamplingMessage(role="user", content=TextContent(type="text", text="hello"))], # pyright: ignore[reportDeprecated] max_tokens=10, ) method, params = rec.seen[0] assert method == "sampling/createMessage" assert params is not None and params["maxTokens"] == 10 - assert isinstance(result, CreateMessageResult) + assert isinstance(result, CreateMessageResult) # pyright: ignore[reportDeprecated] assert result.model == "m" @@ -67,9 +67,10 @@ async def test_peer_sample_validates_result_alias_only(): peer = ClientPeer(client) with anyio.fail_after(5): result = await peer.sample( - [SamplingMessage(role="user", content=TextContent(type="text", text="q"))], max_tokens=1 + [SamplingMessage(role="user", content=TextContent(type="text", text="q"))], # pyright: ignore[reportDeprecated] + max_tokens=1, ) - assert isinstance(result, CreateMessageResult) + assert isinstance(result, CreateMessageResult) # pyright: ignore[reportDeprecated] assert result.stop_reason is None @@ -80,7 +81,7 @@ async def test_peer_sample_with_tools_returns_with_tools_result(): peer = ClientPeer(client) with anyio.fail_after(5): result = await peer.sample( - [SamplingMessage(role="user", content=TextContent(type="text", text="q"))], + [SamplingMessage(role="user", content=TextContent(type="text", text="q"))], # pyright: ignore[reportDeprecated] max_tokens=5, tools=[Tool(name="t", input_schema={"type": "object"})], ) @@ -127,7 +128,7 @@ async def test_peer_list_roots_sends_roots_list_and_returns_typed_result(): result = await peer.list_roots() method, _ = rec.seen[0] assert method == "roots/list" - assert isinstance(result, ListRootsResult) + assert isinstance(result, ListRootsResult) # pyright: ignore[reportDeprecated] assert len(result.roots) == 1 assert str(result.roots[0].uri) == "file:///workspace" diff --git a/tests/shared/test_streamable_http.py b/tests/shared/test_streamable_http.py index 6aadf6ff88..adc2cfaaaf 100644 --- a/tests/shared/test_streamable_http.py +++ b/tests/shared/test_streamable_http.py @@ -214,7 +214,7 @@ async def _handle_call_tool(ctx: ServerRequestContext[ServerState], params: Call elif name == "test_sampling_tool": sampling_result = await ctx.session.create_message( messages=[ - types.SamplingMessage( + types.SamplingMessage( # pyright: ignore[reportDeprecated] role="user", content=types.TextContent(type="text", text="Server needs client sampling"), ) @@ -1127,7 +1127,10 @@ async def message_handler( # pragma: no branch if isinstance(message, types.ServerNotification): # pragma: no branch captured_notifications.append(message) # Look for our first notification - if isinstance(message, types.LoggingMessageNotification): # pragma: no branch + if isinstance( + message, + types.LoggingMessageNotification, # pyright: ignore[reportDeprecated] + ): # pragma: no branch if message.params.data == "First notification before lock": first_notification_received.set() @@ -1188,7 +1191,7 @@ async def run_tool(): # after appending to captured_notifications. The server tool is # blocked on its lock, so nothing else can arrive before we cancel. assert len(captured_notifications) == 1 - assert isinstance(captured_notifications[0], types.LoggingMessageNotification) + assert isinstance(captured_notifications[0], types.LoggingMessageNotification) # pyright: ignore[reportDeprecated] assert captured_notifications[0].params.data == "First notification before lock" # Reset for phase 2 before cancelling captured_notifications.clear() @@ -1227,7 +1230,7 @@ async def run_tool(): # We should have received the remaining notifications assert len(captured_notifications) == 1 - assert isinstance(captured_notifications[0], types.LoggingMessageNotification) + assert isinstance(captured_notifications[0], types.LoggingMessageNotification) # pyright: ignore[reportDeprecated] assert captured_notifications[0].params.data == "Second notification after lock" @@ -1241,15 +1244,15 @@ async def test_streamablehttp_server_sampling(basic_app: Starlette) -> None: # Define sampling callback that returns a mock response async def sampling_callback( context: ClientRequestContext, - params: types.CreateMessageRequestParams, - ) -> types.CreateMessageResult: + params: types.CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + ) -> types.CreateMessageResult: # pyright: ignore[reportDeprecated] nonlocal sampling_callback_invoked, captured_message_params sampling_callback_invoked = True captured_message_params = params msg_content = params.messages[0].content_as_list[0] message_received = msg_content.text if msg_content.type == "text" else None - return types.CreateMessageResult( + return types.CreateMessageResult( # pyright: ignore[reportDeprecated] role="assistant", content=types.TextContent( type="text", @@ -1912,7 +1915,10 @@ async def message_handler( if isinstance(message, Exception): # pragma: no branch return # pragma: no cover if isinstance(message, types.ServerNotification): # pragma: no branch - if isinstance(message, types.LoggingMessageNotification): # pragma: no branch + if isinstance( + message, + types.LoggingMessageNotification, # pyright: ignore[reportDeprecated] + ): # pragma: no branch captured_notifications.append(str(message.params.data)) async with ( @@ -1980,7 +1986,10 @@ async def message_handler( if isinstance(message, Exception): # pragma: no branch return # pragma: no cover if isinstance(message, types.ServerNotification): # pragma: no branch - if isinstance(message, types.LoggingMessageNotification): # pragma: no branch + if isinstance( + message, + types.LoggingMessageNotification, # pyright: ignore[reportDeprecated] + ): # pragma: no branch all_notifications.append(str(message.params.data)) async with ( @@ -2023,7 +2032,10 @@ async def message_handler( if isinstance(message, Exception): # pragma: no branch return # pragma: no cover if isinstance(message, types.ServerNotification): # pragma: no branch - if isinstance(message, types.LoggingMessageNotification): # pragma: no branch + if isinstance( + message, + types.LoggingMessageNotification, # pyright: ignore[reportDeprecated] + ): # pragma: no branch notification_data.append(str(message.params.data)) async with ( diff --git a/tests/test_types.py b/tests/test_types.py index 3756bd893d..0f141c23c7 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -1,3 +1,4 @@ +import warnings from typing import Any import pytest @@ -9,8 +10,8 @@ ClientCapabilities, CompleteResult, Completion, - CreateMessageRequestParams, - CreateMessageResult, + CreateMessageRequestParams, # pyright: ignore[reportDeprecated] + CreateMessageResult, # pyright: ignore[reportDeprecated] CreateMessageResultWithTools, DiscoverResult, EmptyResult, @@ -26,14 +27,15 @@ ListToolsResult, ReadResourceResult, Result, + RootsCapability, SamplingCapability, - SamplingMessage, + SamplingMessage, # pyright: ignore[reportDeprecated] ServerCapabilities, TextContent, Tool, - ToolChoice, - ToolResultContent, - ToolUseContent, + ToolChoice, # pyright: ignore[reportDeprecated] + ToolResultContent, # pyright: ignore[reportDeprecated] + ToolUseContent, # pyright: ignore[reportDeprecated] client_request_adapter, jsonrpc_message_adapter, ) @@ -94,7 +96,7 @@ async def test_tool_use_content(): "input": {"location": "San Francisco", "unit": "celsius"}, } - tool_use = ToolUseContent.model_validate(tool_use_data) + tool_use = ToolUseContent.model_validate(tool_use_data) # pyright: ignore[reportDeprecated] assert tool_use.type == "tool_use" assert tool_use.name == "get_weather" assert tool_use.id == "call_abc123" @@ -116,7 +118,7 @@ async def test_tool_result_content(): "isError": False, } - tool_result = ToolResultContent.model_validate(tool_result_data) + tool_result = ToolResultContent.model_validate(tool_result_data) # pyright: ignore[reportDeprecated] assert tool_result.type == "tool_result" assert tool_result.tool_use_id == "call_abc123" assert len(tool_result.content) == 1 @@ -124,7 +126,7 @@ async def test_tool_result_content(): # Test with empty content (should default to []) minimal_result_data = {"type": "tool_result", "toolUseId": "call_xyz"} - minimal_result = ToolResultContent.model_validate(minimal_result_data) + minimal_result = ToolResultContent.model_validate(minimal_result_data) # pyright: ignore[reportDeprecated] assert minimal_result.content == [] @@ -133,18 +135,18 @@ async def test_tool_choice(): """Test ToolChoice type for SEP-1577.""" # Test with mode tool_choice_data = {"mode": "required"} - tool_choice = ToolChoice.model_validate(tool_choice_data) + tool_choice = ToolChoice.model_validate(tool_choice_data) # pyright: ignore[reportDeprecated] assert tool_choice.mode == "required" # Test with minimal data (all fields optional) - minimal_choice = ToolChoice.model_validate({}) + minimal_choice = ToolChoice.model_validate({}) # pyright: ignore[reportDeprecated] assert minimal_choice.mode is None # Test different modes - auto_choice = ToolChoice.model_validate({"mode": "auto"}) + auto_choice = ToolChoice.model_validate({"mode": "auto"}) # pyright: ignore[reportDeprecated] assert auto_choice.mode == "auto" - none_choice = ToolChoice.model_validate({"mode": "none"}) + none_choice = ToolChoice.model_validate({"mode": "none"}) # pyright: ignore[reportDeprecated] assert none_choice.mode == "none" @@ -153,7 +155,7 @@ async def test_sampling_message_with_user_role(): """Test SamplingMessage with user role for SEP-1577.""" # Test with single content user_msg_data = {"role": "user", "content": {"type": "text", "text": "Hello"}} - user_msg = SamplingMessage.model_validate(user_msg_data) + user_msg = SamplingMessage.model_validate(user_msg_data) # pyright: ignore[reportDeprecated] assert user_msg.role == "user" assert isinstance(user_msg.content, TextContent) @@ -165,7 +167,7 @@ async def test_sampling_message_with_user_role(): {"type": "tool_result", "toolUseId": "call_123", "content": []}, ], } - multi_msg = SamplingMessage.model_validate(multi_content_data) + multi_msg = SamplingMessage.model_validate(multi_content_data) # pyright: ignore[reportDeprecated] assert multi_msg.role == "user" assert isinstance(multi_msg.content, list) assert len(multi_msg.content) == 2 @@ -184,9 +186,9 @@ async def test_sampling_message_with_assistant_role(): "input": {"query": "MCP protocol"}, }, } - assistant_msg = SamplingMessage.model_validate(assistant_msg_data) + assistant_msg = SamplingMessage.model_validate(assistant_msg_data) # pyright: ignore[reportDeprecated] assert assistant_msg.role == "assistant" - assert isinstance(assistant_msg.content, ToolUseContent) + assert isinstance(assistant_msg.content, ToolUseContent) # pyright: ignore[reportDeprecated] # Test with array of mixed content multi_content_data: dict[str, Any] = { @@ -196,7 +198,7 @@ async def test_sampling_message_with_assistant_role(): {"type": "tool_use", "name": "search", "id": "call_789", "input": {}}, ], } - multi_msg = SamplingMessage.model_validate(multi_content_data) + multi_msg = SamplingMessage.model_validate(multi_content_data) # pyright: ignore[reportDeprecated] assert isinstance(multi_msg.content, list) assert len(multi_msg.content) == 2 @@ -206,7 +208,7 @@ async def test_sampling_message_backward_compatibility(): """Test that SamplingMessage maintains backward compatibility.""" # Old-style message (single content, no tools) old_style_data = {"role": "user", "content": {"type": "text", "text": "Hello"}} - old_msg = SamplingMessage.model_validate(old_style_data) + old_msg = SamplingMessage.model_validate(old_style_data) # pyright: ignore[reportDeprecated] assert old_msg.role == "user" assert isinstance(old_msg.content, TextContent) @@ -215,16 +217,16 @@ async def test_sampling_message_backward_compatibility(): "role": "assistant", "content": {"type": "tool_use", "name": "test", "id": "call_1", "input": {}}, } - new_msg = SamplingMessage.model_validate(new_style_data) + new_msg = SamplingMessage.model_validate(new_style_data) # pyright: ignore[reportDeprecated] assert new_msg.role == "assistant" - assert isinstance(new_msg.content, ToolUseContent) + assert isinstance(new_msg.content, ToolUseContent) # pyright: ignore[reportDeprecated] # Array content array_style_data: dict[str, Any] = { "role": "user", "content": [{"type": "text", "text": "Result:"}, {"type": "tool_result", "toolUseId": "call_1", "content": []}], } - array_msg = SamplingMessage.model_validate(array_style_data) + array_msg = SamplingMessage.model_validate(array_style_data) # pyright: ignore[reportDeprecated] assert isinstance(array_msg.content, list) @@ -237,11 +239,11 @@ async def test_create_message_request_params_with_tools(): input_schema={"type": "object", "properties": {"location": {"type": "string"}}}, ) - params = CreateMessageRequestParams( - messages=[SamplingMessage(role="user", content=TextContent(type="text", text="What's the weather?"))], + params = CreateMessageRequestParams( # pyright: ignore[reportDeprecated] + messages=[SamplingMessage(role="user", content=TextContent(type="text", text="What's the weather?"))], # pyright: ignore[reportDeprecated] max_tokens=1000, tools=[tool], - tool_choice=ToolChoice(mode="auto"), + tool_choice=ToolChoice(mode="auto"), # pyright: ignore[reportDeprecated] ) assert params.tools is not None @@ -264,7 +266,7 @@ async def test_create_message_result_with_tool_use(): # Tool use content uses CreateMessageResultWithTools result = CreateMessageResultWithTools.model_validate(result_data) assert result.role == "assistant" - assert isinstance(result.content, ToolUseContent) + assert isinstance(result.content, ToolUseContent) # pyright: ignore[reportDeprecated] assert result.stop_reason == "toolUse" assert result.model == "claude-3" @@ -285,7 +287,7 @@ async def test_create_message_result_basic(): } # Basic content uses CreateMessageResult (single content, no arrays) - result = CreateMessageResult.model_validate(result_data) + result = CreateMessageResult.model_validate(result_data) # pyright: ignore[reportDeprecated] assert result.role == "assistant" assert isinstance(result.content, TextContent) assert result.content.text == "Hello!" @@ -437,3 +439,21 @@ def test_empty_result_dumps_result_type_only_when_explicitly_tagged(): def test_input_required_result_dumps_its_discriminating_tag(): assert _wire_dump(InputRequiredResult()) == snapshot({"resultType": "input_required"}) + + +def test_sep_2577_deprecated_class_warns_on_instantiation(): + with pytest.warns(DeprecationWarning, match=r"`SamplingMessage` is deprecated as of 2026-07-28 \(SEP-2577\)\."): + SamplingMessage(role="user", content=TextContent(type="text", text="hi")) # pyright: ignore[reportDeprecated] + + +def test_sep_2577_deprecated_capability_field_warns_on_access_not_construction(): + with warnings.catch_warnings(): + warnings.simplefilter("error") + caps = ClientCapabilities(roots=RootsCapability(list_changed=True)) + with pytest.warns(DeprecationWarning, match=r"`roots` is deprecated as of 2026-07-28 \(SEP-2577\)\."): + _ = caps.roots # pyright: ignore[reportDeprecated] + + +def test_sep_2577_deprecated_capability_sets_json_schema_flag(): + assert ClientCapabilities.model_json_schema()["properties"]["roots"]["deprecated"] is True + assert ServerCapabilities.model_json_schema()["properties"]["logging"]["deprecated"] is True diff --git a/tests/types/test_methods.py b/tests/types/test_methods.py index c6d6823d72..315be640be 100644 --- a/tests/types/test_methods.py +++ b/tests/types/test_methods.py @@ -591,7 +591,7 @@ def test_non_file_root_uri_passes_the_surface_step_and_rejects_at_the_monolith_s methods.parse_client_request("tools/call", "2026-07-28", retry_params) file_roots = {"roots": [{"uri": "file:///workspace"}]} - assert isinstance(methods.parse_client_result("roots/list", "2025-11-25", file_roots), types.ListRootsResult) + assert isinstance(methods.parse_client_result("roots/list", "2025-11-25", file_roots), types.ListRootsResult) # pyright: ignore[reportDeprecated] retried = methods.parse_client_request( "tools/call", "2026-07-28", {"_meta": META_TRIPLE, "name": "echo", "inputResponses": {"r1": file_roots}} ) @@ -715,7 +715,7 @@ def test_snake_case_spellings_of_required_aliased_fields_reject_as_missing(): 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( + types.CreateMessageRequest.model_validate( # pyright: ignore[reportDeprecated] {"method": "sampling/createMessage", "params": snake_params}, by_name=False ) @@ -772,13 +772,13 @@ def test_input_required_unions_discriminate_identically_in_both_arm_orders(): def test_sampling_union_keeps_the_complete_arm_first_because_order_is_load_bearing(): """A single-block body satisfies both arms; smart-union ties resolve leftmost.""" assert get_args(methods.MONOLITH_RESULTS["sampling/createMessage"]) == ( - types.CreateMessageResult, + types.CreateMessageResult, # pyright: ignore[reportDeprecated] types.CreateMessageResultWithTools, ) single_block: dict[str, Any] = {"role": "assistant", "content": {"type": "text", "text": "hi"}, "model": "m"} through_row = methods.parse_client_result("sampling/createMessage", "2025-11-25", single_block) - assert type(through_row) is types.CreateMessageResult - reversed_union = pydantic.TypeAdapter[Any](types.CreateMessageResultWithTools | types.CreateMessageResult) + assert type(through_row) is types.CreateMessageResult # pyright: ignore[reportDeprecated] + reversed_union = pydantic.TypeAdapter[Any](types.CreateMessageResultWithTools | types.CreateMessageResult) # pyright: ignore[reportDeprecated] 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"} diff --git a/tests/types/test_wire_frames.py b/tests/types/test_wire_frames.py index a48180634d..8899eec9b4 100644 --- a/tests/types/test_wire_frames.py +++ b/tests/types/test_wire_frames.py @@ -17,7 +17,7 @@ JSONRPCNotification, JSONRPCRequest, JSONRPCResponse, - ListRootsRequest, + ListRootsRequest, # pyright: ignore[reportDeprecated] ListToolsResult, ProgressNotification, ProgressNotificationParams, @@ -78,7 +78,7 @@ def test_empty_result_frame_dumps_an_empty_result_object(): def test_input_required_result_frame_carries_the_tag_and_the_embedded_requests(): - result = InputRequiredResult(input_requests={"r1": ListRootsRequest()}, request_state="s1") + result = InputRequiredResult(input_requests={"r1": ListRootsRequest()}, request_state="s1") # pyright: ignore[reportDeprecated] 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"}}'