Completely drop RootModel from types module (#1910)

This commit is contained in:
Marcelo Trylesinski 2026-01-19 14:29:15 +01:00 • committed by GitHub
commit 5fdd48a0d3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 640 additions and 800 deletions

View file

@ -19,7 +19,7 @@ from starlette.routing import Route
from mcp import ClientSession, types
from mcp.client.streamable_http import streamable_http_client
from mcp.shared.session import RequestResponder
from mcp.types import ClientNotification, RootsListChangedNotification
from mcp.types import RootsListChangedNotification
from tests.test_helpers import wait_for_server
@ -135,9 +135,7 @@ async def test_non_compliant_notification_response(non_sdk_server: None, non_sdk
await session.initialize()
# The test server returns a 204 instead of the expected 202
await session.send_notification(
ClientNotification(RootsListChangedNotification(method="notifications/roots/list_changed"))
)
await session.send_notification(RootsListChangedNotification(method="notifications/roots/list_changed"))
if returned_exception: # pragma: no cover
pytest.fail(f"Server encountered an exception: {returned_exception}")

View file

@ -3,6 +3,7 @@ from unittest.mock import patch
import anyio
import pytest
from pydantic import TypeAdapter
from mcp.shared.message import SessionMessage
from mcp.shared.session import BaseSession, RequestId, SendResultT
@ -23,20 +24,23 @@ async def test_send_request_stream_cleanup():
) -> None: # pragma: no cover
pass
@property
def _receive_request_adapter(self) -> TypeAdapter[Any]:
return TypeAdapter(object) # pragma: no cover
@property
def _receive_notification_adapter(self) -> TypeAdapter[Any]:
return TypeAdapter(object) # pragma: no cover
# Create streams
write_stream_send, write_stream_receive = anyio.create_memory_object_stream[SessionMessage](1)
read_stream_send, read_stream_receive = anyio.create_memory_object_stream[SessionMessage](1)
# Create the session
session = TestSession(
read_stream_receive,
write_stream_send,
object, # Request type doesn't matter for this test
object, # Notification type doesn't matter for this test
)
session = TestSession(read_stream_receive, write_stream_send)
# Create a test request
request = ClientRequest(PingRequest())
request = PingRequest()
# Patch the _write_stream.send method to raise an exception
async def mock_send(*args: Any, **kwargs: Any):

View file

@ -12,8 +12,6 @@ from mcp.shared.version import SUPPORTED_PROTOCOL_VERSIONS
from mcp.types import (
LATEST_PROTOCOL_VERSION,
CallToolResult,
ClientNotification,
ClientRequest,
Implementation,
InitializedNotification,
InitializeRequest,
@ -22,8 +20,9 @@ from mcp.types import (
JSONRPCRequest,
JSONRPCResponse,
ServerCapabilities,
ServerResult,
TextContent,
client_notification_adapter,
client_request_adapter,
)
@ -41,24 +40,22 @@ async def test_client_session_initialize():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
assert isinstance(request, InitializeRequest)
result = ServerResult(
InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(
logging=None,
resources=None,
tools=None,
experimental=None,
prompts=None,
),
server_info=Implementation(name="mock-server", version="0.1.0"),
instructions="The server instructions.",
)
result = InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(
logging=None,
resources=None,
tools=None,
experimental=None,
prompts=None,
),
server_info=Implementation(name="mock-server", version="0.1.0"),
instructions="The server instructions.",
)
async with server_to_client_send:
@ -74,7 +71,7 @@ async def test_client_session_initialize():
session_notification = await client_to_server_receive.receive()
jsonrpc_notification = session_notification.message
assert isinstance(jsonrpc_notification, JSONRPCNotification)
initialized_notification = ClientNotification.model_validate(
initialized_notification = client_notification_adapter.validate_python(
jsonrpc_notification.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -109,7 +106,7 @@ async def test_client_session_initialize():
# Check that the client sent the initialized notification
assert initialized_notification
assert isinstance(initialized_notification.root, InitializedNotification)
assert isinstance(initialized_notification, InitializedNotification)
@pytest.mark.anyio
@ -126,18 +123,16 @@ async def test_client_session_custom_client_info():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
received_client_info = request.root.params.client_info
assert isinstance(request, InitializeRequest)
received_client_info = request.params.client_info
result = ServerResult(
InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
async with server_to_client_send:
@ -185,18 +180,16 @@ async def test_client_session_default_client_info():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
received_client_info = request.root.params.client_info
assert isinstance(request, InitializeRequest)
received_client_info = request.params.client_info
result = ServerResult(
InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
async with server_to_client_send:
@ -238,21 +231,19 @@ async def test_client_session_version_negotiation_success():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
assert isinstance(request, InitializeRequest)
# Verify client sent the latest protocol version
assert request.root.params.protocol_version == LATEST_PROTOCOL_VERSION
assert request.params.protocol_version == LATEST_PROTOCOL_VERSION
# Server responds with a supported older version
result = ServerResult(
InitializeResult(
protocol_version="2024-11-05",
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version="2024-11-05",
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
async with server_to_client_send:
@ -295,18 +286,16 @@ async def test_client_session_version_negotiation_failure():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
assert isinstance(request, InitializeRequest)
# Server responds with an unsupported version
result = ServerResult(
InitializeResult(
protocol_version="2020-01-01", # Unsupported old version
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version="2020-01-01", # Unsupported old version
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
async with server_to_client_send:
@ -349,18 +338,16 @@ async def test_client_capabilities_default():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
received_capabilities = request.root.params.capabilities
assert isinstance(request, InitializeRequest)
received_capabilities = request.params.capabilities
result = ServerResult(
InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
async with server_to_client_send:
@ -422,18 +409,16 @@ async def test_client_capabilities_with_custom_callbacks():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
received_capabilities = request.root.params.capabilities
assert isinstance(request, InitializeRequest)
received_capabilities = request.params.capabilities
result = ServerResult(
InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
async with server_to_client_send:
@ -503,18 +488,16 @@ async def test_client_capabilities_with_sampling_tools():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
received_capabilities = request.root.params.capabilities
assert isinstance(request, InitializeRequest)
received_capabilities = request.params.capabilities
result = ServerResult(
InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
async with server_to_client_send:
@ -572,17 +555,15 @@ async def test_get_server_capabilities():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
assert isinstance(request, InitializeRequest)
result = ServerResult(
InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=expected_capabilities,
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=expected_capabilities,
server_info=Implementation(name="mock-server", version="0.1.0"),
)
async with server_to_client_send:
@ -639,17 +620,15 @@ async def test_client_tool_call_with_meta(meta: dict[str, Any] | None):
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
assert isinstance(request, InitializeRequest)
result = ServerResult(
InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
# Answer initialization request
@ -678,9 +657,7 @@ async def test_client_tool_call_with_meta(meta: dict[str, Any] | None):
assert "_meta" in jsonrpc_request.params
assert jsonrpc_request.params["_meta"] == meta
result = ServerResult(
CallToolResult(content=[TextContent(type="text", text="Called successfully")], is_error=False)
)
result = CallToolResult(content=[TextContent(type="text", text="Called successfully")], is_error=False)
# Send the tools/call result
await server_to_client_send.send(

View file

@ -11,14 +11,13 @@ from mcp.shared.context import RequestContext
from mcp.shared.message import SessionMessage
from mcp.types import (
LATEST_PROTOCOL_VERSION,
ClientRequest,
Implementation,
InitializeRequest,
InitializeResult,
JSONRPCRequest,
JSONRPCResponse,
ServerCapabilities,
ServerResult,
client_request_adapter,
)
@ -36,18 +35,16 @@ async def test_client_capabilities_without_tasks():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
received_capabilities = request.root.params.capabilities
assert isinstance(request, InitializeRequest)
received_capabilities = request.params.capabilities
result = ServerResult(
InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
async with server_to_client_send:
@ -108,18 +105,16 @@ async def test_client_capabilities_with_tasks():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
received_capabilities = request.root.params.capabilities
assert isinstance(request, InitializeRequest)
received_capabilities = request.params.capabilities
result = ServerResult(
InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
async with server_to_client_send:
@ -190,18 +185,16 @@ async def test_client_capabilities_auto_built_from_handlers():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
received_capabilities = request.root.params.capabilities
assert isinstance(request, InitializeRequest)
received_capabilities = request.params.capabilities
result = ServerResult(
InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
async with server_to_client_send:
@ -268,18 +261,16 @@ async def test_client_capabilities_with_task_augmented_handlers():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
request = client_request_adapter.validate_python(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
assert isinstance(request.root, InitializeRequest)
received_capabilities = request.root.params.capabilities
assert isinstance(request, InitializeRequest)
received_capabilities = request.params.capabilities
result = ServerResult(
InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
result = InitializeResult(
protocol_version=LATEST_PROTOCOL_VERSION,
capabilities=ServerCapabilities(),
server_info=Implementation(name="mock-server", version="0.1.0"),
)
async with server_to_client_send:

View file

@ -23,7 +23,6 @@ from mcp.types import (
CallToolResult,
CancelTaskRequest,
CancelTaskResult,
ClientRequest,
ClientResult,
CreateTaskResult,
GetTaskPayloadRequest,
@ -134,13 +133,11 @@ async def test_session_experimental_get_task() -> None:
# Create a task
create_result = await client_session.send_request(
ClientRequest(
CallToolRequest(
params=CallToolRequestParams(
name="test_tool",
arguments={},
task=TaskMetadata(ttl=60000),
)
CallToolRequest(
params=CallToolRequestParams(
name="test_tool",
arguments={},
task=TaskMetadata(ttl=60000),
)
),
CreateTaskResult,
@ -240,13 +237,11 @@ async def test_session_experimental_get_task_result() -> None:
# Create a task
create_result = await client_session.send_request(
ClientRequest(
CallToolRequest(
params=CallToolRequestParams(
name="test_tool",
arguments={},
task=TaskMetadata(ttl=60000),
)
CallToolRequest(
params=CallToolRequestParams(
name="test_tool",
arguments={},
task=TaskMetadata(ttl=60000),
)
),
CreateTaskResult,
@ -343,13 +338,11 @@ async def test_session_experimental_list_tasks() -> None:
# Create two tasks
for _ in range(2):
create_result = await client_session.send_request(
ClientRequest(
CallToolRequest(
params=CallToolRequestParams(
name="test_tool",
arguments={},
task=TaskMetadata(ttl=60000),
)
CallToolRequest(
params=CallToolRequestParams(
name="test_tool",
arguments={},
task=TaskMetadata(ttl=60000),
)
),
CreateTaskResult,
@ -456,13 +449,11 @@ async def test_session_experimental_cancel_task() -> None:
# Create a task (but don't complete it)
create_result = await client_session.send_request(
ClientRequest(
CallToolRequest(
params=CallToolRequestParams(
name="test_tool",
arguments={},
task=TaskMetadata(ttl=60000),
)
CallToolRequest(
params=CallToolRequestParams(
name="test_tool",
arguments={},
task=TaskMetadata(ttl=60000),
)
),
CreateTaskResult,

View file

@ -30,7 +30,6 @@ from mcp.types import (
CallToolRequest,
CallToolRequestParams,
CallToolResult,
ClientRequest,
ClientResult,
CreateTaskResult,
GetTaskPayloadRequest,
@ -190,14 +189,12 @@ async def test_task_lifecycle_with_task_execution() -> None:
# === Step 1: Send task-augmented tool call ===
create_result = await client_session.send_request(
ClientRequest(
CallToolRequest(
params=CallToolRequestParams(
name="process_data",
arguments={"input": "hello world"},
task=TaskMetadata(ttl=60000),
),
)
CallToolRequest(
params=CallToolRequestParams(
name="process_data",
arguments={"input": "hello world"},
task=TaskMetadata(ttl=60000),
),
),
CreateTaskResult,
)
@ -210,7 +207,7 @@ async def test_task_lifecycle_with_task_execution() -> None:
await app_context.task_done_events[task_id].wait()
task_status = await client_session.send_request(
ClientRequest(GetTaskRequest(params=GetTaskRequestParams(task_id=task_id))),
GetTaskRequest(params=GetTaskRequestParams(task_id=task_id)),
GetTaskResult,
)
@ -219,7 +216,7 @@ async def test_task_lifecycle_with_task_execution() -> None:
# === Step 3: Retrieve the actual result ===
task_result = await client_session.send_request(
ClientRequest(GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id=task_id))),
GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id=task_id)),
CallToolResult,
)
@ -327,14 +324,12 @@ async def test_task_auto_fails_on_exception() -> None:
# Send task request
create_result = await client_session.send_request(
ClientRequest(
CallToolRequest(
params=CallToolRequestParams(
name="failing_task",
arguments={},
task=TaskMetadata(ttl=60000),
),
)
CallToolRequest(
params=CallToolRequestParams(
name="failing_task",
arguments={},
task=TaskMetadata(ttl=60000),
),
),
CreateTaskResult,
)
@ -346,8 +341,7 @@ async def test_task_auto_fails_on_exception() -> None:
# Check that task was auto-failed
task_status = await client_session.send_request(
ClientRequest(GetTaskRequest(params=GetTaskRequestParams(task_id=task_id))),
GetTaskResult,
GetTaskRequest(params=GetTaskRequestParams(task_id=task_id)), GetTaskResult
)
assert task_status.status == "failed"

View file

@ -26,7 +26,6 @@ from mcp.types import (
CancelTaskRequest,
CancelTaskRequestParams,
CancelTaskResult,
ClientRequest,
ClientResult,
ErrorData,
GetTaskPayloadRequest,
@ -89,10 +88,10 @@ async def test_list_tasks_handler() -> None:
result = await handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, ListTasksResult)
assert len(result.root.tasks) == 2
assert result.root.tasks[0].task_id == "task-1"
assert result.root.tasks[1].task_id == "task-2"
assert isinstance(result, ListTasksResult)
assert len(result.tasks) == 2
assert result.tasks[0].task_id == "task-1"
assert result.tasks[1].task_id == "task-2"
@pytest.mark.anyio
@ -120,9 +119,9 @@ async def test_get_task_handler() -> None:
result = await handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, GetTaskResult)
assert result.root.task_id == "test-task-123"
assert result.root.status == "working"
assert isinstance(result, GetTaskResult)
assert result.task_id == "test-task-123"
assert result.status == "working"
@pytest.mark.anyio
@ -142,7 +141,7 @@ async def test_get_task_result_handler() -> None:
result = await handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, GetTaskPayloadResult)
assert isinstance(result, GetTaskPayloadResult)
@pytest.mark.anyio
@ -169,9 +168,9 @@ async def test_cancel_task_handler() -> None:
result = await handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, CancelTaskResult)
assert result.root.task_id == "test-task-123"
assert result.root.status == "cancelled"
assert isinstance(result, CancelTaskResult)
assert result.task_id == "test-task-123"
assert result.status == "cancelled"
@pytest.mark.anyio
@ -253,8 +252,8 @@ async def test_tool_with_task_execution_metadata() -> None:
result = await tools_handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, ListToolsResult)
tools = result.root.tools
assert isinstance(result, ListToolsResult)
tools = result.tools
assert tools[0].execution is not None
assert tools[0].execution.task_support == TASK_FORBIDDEN
@ -330,14 +329,12 @@ async def test_task_metadata_in_call_tool_request() -> None:
# Call tool with task metadata
await client_session.send_request(
ClientRequest(
CallToolRequest(
params=CallToolRequestParams(
name="long_task",
arguments={},
task=TaskMetadata(ttl=60000),
),
)
CallToolRequest(
params=CallToolRequestParams(
name="long_task",
arguments={},
task=TaskMetadata(ttl=60000),
),
),
CallToolResult,
)
@ -411,24 +408,14 @@ async def test_task_metadata_is_task_property() -> None:
# Call without task metadata
await client_session.send_request(
ClientRequest(
CallToolRequest(
params=CallToolRequestParams(name="test_tool", arguments={}),
)
),
CallToolRequest(params=CallToolRequestParams(name="test_tool", arguments={})),
CallToolResult,
)
# Call with task metadata
await client_session.send_request(
ClientRequest(
CallToolRequest(
params=CallToolRequestParams(
name="test_tool",
arguments={},
task=TaskMetadata(ttl=60000),
),
)
CallToolRequest(
params=CallToolRequestParams(name="test_tool", arguments={}, task=TaskMetadata(ttl=60000)),
),
CallToolResult,
)
@ -507,16 +494,13 @@ async def test_default_task_handlers_via_enable_tasks() -> None:
task = await store.create_task(TaskMetadata(ttl=60000))
# Test list_tasks (default handler)
list_result = await client_session.send_request(
ClientRequest(ListTasksRequest()),
ListTasksResult,
)
list_result = await client_session.send_request(ListTasksRequest(), ListTasksResult)
assert len(list_result.tasks) == 1
assert list_result.tasks[0].task_id == task.task_id
# Test get_task (default handler - found)
get_result = await client_session.send_request(
ClientRequest(GetTaskRequest(params=GetTaskRequestParams(task_id=task.task_id))),
GetTaskRequest(params=GetTaskRequestParams(task_id=task.task_id)),
GetTaskResult,
)
assert get_result.task_id == task.task_id
@ -525,7 +509,7 @@ async def test_default_task_handlers_via_enable_tasks() -> None:
# Test get_task (default handler - not found path)
with pytest.raises(McpError, match="not found"):
await client_session.send_request(
ClientRequest(GetTaskRequest(params=GetTaskRequestParams(task_id="nonexistent-task"))),
GetTaskRequest(params=GetTaskRequestParams(task_id="nonexistent-task")),
GetTaskResult,
)
@ -538,9 +522,7 @@ async def test_default_task_handlers_via_enable_tasks() -> None:
# Test get_task_result (default handler)
payload_result = await client_session.send_request(
ClientRequest(
GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id=completed_task.task_id))
),
GetTaskPayloadRequest(params=GetTaskPayloadRequestParams(task_id=completed_task.task_id)),
GetTaskPayloadResult,
)
# The result should have the related-task metadata
@ -549,8 +531,7 @@ async def test_default_task_handlers_via_enable_tasks() -> None:
# Test cancel_task (default handler)
cancel_result = await client_session.send_request(
ClientRequest(CancelTaskRequest(params=CancelTaskRequestParams(task_id=task.task_id))),
CancelTaskResult,
CancelTaskRequest(params=CancelTaskRequestParams(task_id=task.task_id)), CancelTaskResult
)
assert cancel_result.task_id == task.task_id
assert cancel_result.status == "cancelled"
@ -568,11 +549,7 @@ async def test_build_elicit_form_request() -> None:
async with ServerSession(
client_to_server_receive,
server_to_client_send,
InitializationOptions(
server_name="test-server",
server_version="1.0.0",
capabilities=ServerCapabilities(),
),
InitializationOptions(server_name="test-server", server_version="1.0.0", capabilities=ServerCapabilities()),
) as server_session:
# Test without task_id
request = server_session._build_elicit_form_request(
@ -613,11 +590,7 @@ async def test_build_elicit_url_request() -> None:
async with ServerSession(
client_to_server_receive,
server_to_client_send,
InitializationOptions(
server_name="test-server",
server_version="1.0.0",
capabilities=ServerCapabilities(),
),
InitializationOptions(server_name="test-server", server_version="1.0.0", capabilities=ServerCapabilities()),
) as server_session:
# Test without related_task_id
request = server_session._build_elicit_url_request(

View file

@ -26,8 +26,8 @@ async def test_resource_templates():
result = await mcp._mcp_server.request_handlers[types.ListResourceTemplatesRequest](
types.ListResourceTemplatesRequest(params=None)
)
assert isinstance(result.root, types.ListResourceTemplatesResult)
templates = result.root.resource_templates
assert isinstance(result, types.ListResourceTemplatesResult)
templates = result.resource_templates
# Verify we get both templates back
assert len(templates) == 2

View file

@ -60,7 +60,7 @@ async def test_server_base64_encoding_issue():
result: ServerResult = await handler(request)
# After (fixed code):
read_result: ReadResourceResult = cast(ReadResourceResult, result.root)
read_result: ReadResourceResult = cast(ReadResourceResult, result)
blob_content = read_result.contents[0]
# First verify our test data actually produces different encodings

View file

@ -77,14 +77,14 @@ class NotificationCollector:
) -> None:
"""Handle any server notification and route to appropriate handler."""
if isinstance(message, ServerNotification): # pragma: no branch
if isinstance(message.root, ProgressNotification):
self.progress_notifications.append(message.root.params)
elif isinstance(message.root, LoggingMessageNotification):
self.log_messages.append(message.root.params)
elif isinstance(message.root, ResourceListChangedNotification):
self.resource_notifications.append(message.root.params)
elif isinstance(message.root, ToolListChangedNotification): # pragma: no cover
self.tool_notifications.append(message.root.params)
if isinstance(message, ProgressNotification):
self.progress_notifications.append(message.params)
elif isinstance(message, LoggingMessageNotification):
self.log_messages.append(message.params)
elif isinstance(message, ResourceListChangedNotification):
self.resource_notifications.append(message.params)
elif isinstance(message, ToolListChangedNotification): # pragma: no cover
self.tool_notifications.append(message.params)
# Common fixtures

View file

@ -41,8 +41,8 @@ async def test_list_prompts_basic() -> None:
result = await handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, ListPromptsResult)
assert result.root.prompts == test_prompts
assert isinstance(result, ListPromptsResult)
assert result.prompts == test_prompts
@pytest.mark.anyio
@ -67,8 +67,8 @@ async def test_list_resources_basic() -> None:
result = await handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, ListResourcesResult)
assert result.root.resources == test_resources
assert isinstance(result, ListResourcesResult)
assert result.resources == test_resources
@pytest.mark.anyio
@ -114,8 +114,8 @@ async def test_list_tools_basic() -> None:
result = await handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, ListToolsResult)
assert result.root.tools == test_tools
assert isinstance(result, ListToolsResult)
assert result.tools == test_tools
@pytest.mark.anyio
@ -135,8 +135,8 @@ async def test_list_prompts_empty() -> None:
result = await handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, ListPromptsResult)
assert result.root.prompts == []
assert isinstance(result, ListPromptsResult)
assert result.prompts == []
@pytest.mark.anyio
@ -156,8 +156,8 @@ async def test_list_resources_empty() -> None:
result = await handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, ListResourcesResult)
assert result.root.resources == []
assert isinstance(result, ListResourcesResult)
assert result.resources == []
@pytest.mark.anyio
@ -177,5 +177,5 @@ async def test_list_tools_empty() -> None:
result = await handler(request)
assert isinstance(result, ServerResult)
assert isinstance(result.root, ListToolsResult)
assert result.root.tools == []
assert isinstance(result, ListToolsResult)
assert result.tools == []

View file

@ -15,8 +15,6 @@ from mcp.types import (
CallToolResult,
CancelledNotification,
CancelledNotificationParams,
ClientNotification,
ClientRequest,
Tool,
)
@ -59,11 +57,7 @@ async def test_server_remains_functional_after_cancel():
async def first_request():
try:
await client.session.send_request(
ClientRequest(
CallToolRequest(
params=CallToolRequestParams(name="test_tool", arguments={}),
)
),
CallToolRequest(params=CallToolRequestParams(name="test_tool", arguments={})),
CallToolResult,
)
pytest.fail("First request should have been cancelled") # pragma: no cover
@ -80,13 +74,8 @@ async def test_server_remains_functional_after_cancel():
# Cancel it
assert first_request_id is not None
await client.session.send_notification(
ClientNotification(
CancelledNotification(
params=CancelledNotificationParams(
request_id=first_request_id,
reason="Testing server recovery",
),
)
CancelledNotification(
params=CancelledNotificationParams(request_id=first_request_id, reason="Testing server recovery"),
)
)

View file

@ -60,7 +60,7 @@ async def test_normal_message_handling_not_affected():
# Create a mock RequestResponder
responder = Mock(spec=RequestResponder)
responder.request = types.ClientRequest(root=types.PingRequest(method="ping"))
responder.request = types.PingRequest(method="ping")
responder.__enter__ = Mock(return_value=responder)
responder.__exit__ = Mock(return_value=None)

View file

@ -39,10 +39,10 @@ async def test_read_resource_text(temp_file: Path):
# Call the handler
result = await handler(request)
assert isinstance(result.root, types.ReadResourceResult)
assert len(result.root.contents) == 1
assert isinstance(result, types.ReadResourceResult)
assert len(result.contents) == 1
content = result.root.contents[0]
content = result.contents[0]
assert isinstance(content, types.TextResourceContents)
assert content.text == "Hello World"
assert content.mime_type == "text/plain"
@ -66,10 +66,10 @@ async def test_read_resource_binary(temp_file: Path):
# Call the handler
result = await handler(request)
assert isinstance(result.root, types.ReadResourceResult)
assert len(result.root.contents) == 1
assert isinstance(result, types.ReadResourceResult)
assert len(result.contents) == 1
content = result.root.contents[0]
content = result.contents[0]
assert isinstance(content, types.BlobResourceContents)
assert content.mime_type == "application/octet-stream"
@ -97,10 +97,10 @@ async def test_read_resource_default_mime(temp_file: Path):
# Call the handler
result = await handler(request)
assert isinstance(result.root, types.ReadResourceResult)
assert len(result.root.contents) == 1
assert isinstance(result, types.ReadResourceResult)
assert len(result.contents) == 1
content = result.root.contents[0]
content = result.contents[0]
assert isinstance(content, types.TextResourceContents)
assert content.text == "Hello World"
assert content.mime_type == "text/plain"

View file

@ -60,7 +60,7 @@ async def test_server_session_initialize():
raise message
if isinstance(message, ClientNotification) and isinstance(
message.root, InitializedNotification
message, InitializedNotification
): # pragma: no branch
received_initialized = True
return
@ -158,7 +158,7 @@ async def test_server_session_initialize_with_older_protocol_version():
raise message
if isinstance(message, types.ClientNotification) and isinstance(
message.root, InitializedNotification
message, InitializedNotification
): # pragma: no branch
received_initialized = True
return
@ -236,11 +236,11 @@ async def test_ping_request_before_initialization():
# We should receive a ping request before initialization
if isinstance(message, RequestResponder) and isinstance(
message.request.root, types.PingRequest
message.request, types.PingRequest
): # pragma: no branch
# Respond to the ping
with message:
await message.respond(types.ServerResult(types.EmptyResult()))
await message.respond(types.EmptyResult())
return
async def mock_client():

View file

@ -57,27 +57,25 @@ async def test_request_immediately_after_initialize_response():
# Handle tools/list request
if isinstance(message, RequestResponder):
if isinstance(message.request.root, types.ListToolsRequest): # pragma: no branch
if isinstance(message.request, types.ListToolsRequest): # pragma: no branch
tools_list_success = True
# Respond with a tool list
with message:
await message.respond(
types.ServerResult(
types.ListToolsResult(
tools=[
Tool(
name="example_tool",
description="An example tool",
input_schema={"type": "object", "properties": {}},
)
]
)
types.ListToolsResult(
tools=[
Tool(
name="example_tool",
description="An example tool",
input_schema={"type": "object", "properties": {}},
)
]
)
)
# Handle InitializedNotification
if isinstance(message, types.ClientNotification):
if isinstance(message.root, types.InitializedNotification): # pragma: no branch
if isinstance(message, types.InitializedNotification): # pragma: no branch
# Done - exit gracefully
return

View file

@ -135,8 +135,8 @@ async def test_bidirectional_progress_notifications():
raise message
if isinstance(message, types.ServerNotification): # pragma: no branch
if isinstance(message.root, types.ProgressNotification): # pragma: no branch
params = message.root.params
if isinstance(message, types.ProgressNotification): # pragma: no branch
params = message.params
client_progress_updates.append(
{
"token": params.progress_token,
@ -332,7 +332,7 @@ async def test_progress_callback_exception_logging():
# Track logged warnings
logged_errors: list[str] = []
def mock_log_error(msg: str, *args: Any) -> None:
def mock_log_exception(msg: str, *args: Any, **kwargs: Any) -> None:
logged_errors.append(msg % args if args else msg)
# Create a progress callback that raises an exception
@ -368,7 +368,7 @@ async def test_progress_callback_exception_logging():
]
# Test with mocked logging
with patch("mcp.shared.session.logging.error", side_effect=mock_log_error):
with patch("mcp.shared.session.logging.exception", side_effect=mock_log_exception):
async with Client(server) as client:
# Call tool with a failing progress callback
result = await client.call_tool(

View file

@ -13,8 +13,6 @@ from mcp.shared.message import SessionMessage
from mcp.types import (
CancelledNotification,
CancelledNotificationParams,
ClientNotification,
ClientRequest,
EmptyResult,
ErrorData,
JSONRPCError,
@ -73,10 +71,8 @@ async def test_request_cancellation():
nonlocal ev_cancelled
try:
await client.session.send_request(
ClientRequest(
types.CallToolRequest(
params=types.CallToolRequestParams(name="slow_tool", arguments={}),
)
types.CallToolRequest(
params=types.CallToolRequestParams(name="slow_tool", arguments={}),
),
types.CallToolResult,
)
@ -97,11 +93,7 @@ async def test_request_cancellation():
# Send cancellation notification
assert request_id is not None
await client.session.send_notification(
ClientNotification(
CancelledNotification(
params=CancelledNotificationParams(request_id=request_id),
)
)
CancelledNotification(params=CancelledNotificationParams(request_id=request_id))
)
# Give cancellation time to process
@ -252,7 +244,7 @@ async def test_response_id_non_numeric_string_no_match():
try:
# Use a short timeout since we expect this to fail
await client_session.send_request(
ClientRequest(types.PingRequest()),
types.PingRequest(),
types.EmptyResult,
request_read_timeout_seconds=0.5,
)

View file

@ -1125,8 +1125,8 @@ async def test_streamable_http_client_get_stream(basic_server: None, basic_serve
# Verify the notification is a ResourceUpdatedNotification
resource_update_found = False
for notif in notifications_received:
if isinstance(notif.root, types.ResourceUpdatedNotification): # pragma: no branch
assert str(notif.root.params.uri) == "http://test_resource"
if isinstance(notif, types.ResourceUpdatedNotification): # pragma: no branch
assert str(notif.params.uri) == "http://test_resource"
resource_update_found = True
assert resource_update_found, "ResourceUpdatedNotification not received via GET stream"
@ -1167,10 +1167,7 @@ async def test_streamable_http_client_session_termination(basic_server: None, ba
):
async with ClientSession(read_stream, write_stream) as session: # pragma: no branch
# Attempt to make a request after termination
with pytest.raises( # pragma: no branch
McpError,
match="Session terminated",
):
with pytest.raises(McpError, match="Session terminated"): # pragma: no branch
await session.list_tools()
@ -1259,8 +1256,8 @@ async def test_streamable_http_client_resumption(event_server: tuple[SimpleEvent
if isinstance(message, types.ServerNotification): # pragma: no branch
captured_notifications.append(message)
# Look for our first notification
if isinstance(message.root, types.LoggingMessageNotification): # pragma: no branch
if message.root.params.data == "First notification before lock":
if isinstance(message, types.LoggingMessageNotification): # pragma: no branch
if message.params.data == "First notification before lock":
nonlocal first_notification_received
first_notification_received = True
@ -1291,12 +1288,8 @@ async def test_streamable_http_client_resumption(event_server: tuple[SimpleEvent
on_resumption_token_update=on_resumption_token_update,
)
await session.send_request(
types.ClientRequest(
types.CallToolRequest(
params=types.CallToolRequestParams(
name="wait_for_lock_with_notification", arguments={}
),
)
types.CallToolRequest(
params=types.CallToolRequestParams(name="wait_for_lock_with_notification", arguments={}),
),
types.CallToolResult,
metadata=metadata,
@ -1313,8 +1306,8 @@ async def test_streamable_http_client_resumption(event_server: tuple[SimpleEvent
# Verify we received exactly one notification
assert len(captured_notifications) == 1 # pragma: no cover
assert isinstance(captured_notifications[0].root, types.LoggingMessageNotification) # pragma: no cover
assert captured_notifications[0].root.params.data == "First notification before lock" # pragma: no cover
assert isinstance(captured_notifications[0], types.LoggingMessageNotification) # pragma: no cover
assert captured_notifications[0].params.data == "First notification before lock" # pragma: no cover
# Clear notifications for the second phase
captured_notifications = [] # pragma: no cover
@ -1334,11 +1327,7 @@ async def test_streamable_http_client_resumption(event_server: tuple[SimpleEvent
):
async with ClientSession(read_stream, write_stream, message_handler=message_handler) as session:
result = await session.send_request(
types.ClientRequest(
types.CallToolRequest(
params=types.CallToolRequestParams(name="release_lock", arguments={}),
)
),
types.CallToolRequest(params=types.CallToolRequestParams(name="release_lock", arguments={})),
types.CallToolResult,
)
metadata = ClientMessageMetadata(
@ -1346,10 +1335,8 @@ async def test_streamable_http_client_resumption(event_server: tuple[SimpleEvent
)
result = await session.send_request(
types.ClientRequest(
types.CallToolRequest(
params=types.CallToolRequestParams(name="wait_for_lock_with_notification", arguments={}),
)
types.CallToolRequest(
params=types.CallToolRequestParams(name="wait_for_lock_with_notification", arguments={}),
),
types.CallToolResult,
metadata=metadata,
@ -1361,8 +1348,8 @@ async def test_streamable_http_client_resumption(event_server: tuple[SimpleEvent
# We should have received the remaining notifications
assert len(captured_notifications) == 1
assert isinstance(captured_notifications[0].root, types.LoggingMessageNotification) # pragma: no cover
assert captured_notifications[0].root.params.data == "Second notification after lock" # pragma: no cover
assert isinstance(captured_notifications[0], types.LoggingMessageNotification) # pragma: no cover
assert captured_notifications[0].params.data == "Second notification after lock" # pragma: no cover
@pytest.mark.anyio
@ -1905,11 +1892,7 @@ async def test_streamable_http_client_receives_priming_event(
on_resumption_token_update=on_resumption_token_update,
)
result = await session.send_request(
types.ClientRequest(
types.CallToolRequest(
params=types.CallToolRequestParams(name="test_tool", arguments={}),
)
),
types.CallToolRequest(params=types.CallToolRequestParams(name="test_tool", arguments={})),
types.CallToolResult,
metadata=metadata,
)
@ -1967,8 +1950,8 @@ async def test_streamable_http_client_auto_reconnects(
if isinstance(message, Exception): # pragma: no branch
return # pragma: no cover
if isinstance(message, types.ServerNotification): # pragma: no branch
if isinstance(message.root, types.LoggingMessageNotification): # pragma: no branch
captured_notifications.append(str(message.root.params.data))
if isinstance(message, types.LoggingMessageNotification): # pragma: no branch
captured_notifications.append(str(message.params.data))
async with streamable_http_client(f"{server_url}/mcp") as (
read_stream,
@ -2043,8 +2026,8 @@ async def test_streamable_http_sse_polling_full_cycle(
if isinstance(message, Exception): # pragma: no branch
return # pragma: no cover
if isinstance(message, types.ServerNotification): # pragma: no branch
if isinstance(message.root, types.LoggingMessageNotification): # pragma: no branch
all_notifications.append(str(message.root.params.data))
if isinstance(message, types.LoggingMessageNotification): # pragma: no branch
all_notifications.append(str(message.params.data))
async with streamable_http_client(f"{server_url}/mcp") as (
read_stream,
@ -2091,8 +2074,8 @@ async def test_streamable_http_events_replayed_after_disconnect(
if isinstance(message, Exception): # pragma: no branch
return # pragma: no cover
if isinstance(message, types.ServerNotification): # pragma: no branch
if isinstance(message.root, types.LoggingMessageNotification): # pragma: no branch
notification_data.append(str(message.root.params.data))
if isinstance(message, types.LoggingMessageNotification): # pragma: no branch
notification_data.append(str(message.params.data))
async with streamable_http_client(f"{server_url}/mcp") as (
read_stream,
@ -2153,15 +2136,13 @@ async def test_streamable_http_multiple_reconnections(
# Use send_request with metadata to track resumption tokens
metadata = ClientMessageMetadata(on_resumption_token_update=on_resumption_token)
result = await session.send_request(
types.ClientRequest(
types.CallToolRequest(
method="tools/call",
params=types.CallToolRequestParams(
name="tool_with_multiple_stream_closes",
# retry_interval=500ms, so sleep 600ms to ensure reconnect completes
arguments={"checkpoints": 3, "sleep_time": 0.6},
),
)
types.CallToolRequest(
method="tools/call",
params=types.CallToolRequestParams(
name="tool_with_multiple_stream_closes",
# retry_interval=500ms, so sleep 600ms to ensure reconnect completes
arguments={"checkpoints": 3, "sleep_time": 0.6},
),
),
types.CallToolResult,
metadata=metadata,
@ -2202,8 +2183,8 @@ async def test_standalone_get_stream_reconnection(
if isinstance(message, Exception):
return # pragma: no cover
if isinstance(message, types.ServerNotification): # pragma: no branch
if isinstance(message.root, types.ResourceUpdatedNotification): # pragma: no branch
received_notifications.append(str(message.root.params.uri))
if isinstance(message, types.ResourceUpdatedNotification): # pragma: no branch
received_notifications.append(str(message.params.uri))
async with streamable_http_client(f"{server_url}/mcp") as (
read_stream,

View file

@ -5,7 +5,6 @@ import pytest
from mcp.types import (
LATEST_PROTOCOL_VERSION,
ClientCapabilities,
ClientRequest,
CreateMessageRequestParams,
CreateMessageResult,
CreateMessageResultWithTools,
@ -21,6 +20,7 @@ from mcp.types import (
ToolChoice,
ToolResultContent,
ToolUseContent,
client_request_adapter,
jsonrpc_message_adapter,
)
@ -40,7 +40,7 @@ async def test_jsonrpc_request():
request = jsonrpc_message_adapter.validate_python(json_data)
assert isinstance(request, JSONRPCRequest)
ClientRequest.model_validate(request.model_dump(by_alias=True, exclude_none=True))
client_request_adapter.validate_python(request.model_dump(by_alias=True, exclude_none=True))
assert request.jsonrpc == "2.0"
assert request.id == 1