Drop RootModel from JSONRPCMessage (#1908)

This commit is contained in:
Marcelo Trylesinski 2026-01-19 14:04:15 +01:00 • committed by GitHub
commit f4672c5084
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 314 additions and 476 deletions

View file

@ -43,35 +43,33 @@ class StreamSpyCollection:
def get_client_requests(self, method: str | None = None) -> list[JSONRPCRequest]: # pragma: no cover
"""Get client-sent requests, optionally filtered by method."""
return [
req.message.root
req.message
for req in self.client.sent_messages
if isinstance(req.message.root, JSONRPCRequest) and (method is None or req.message.root.method == method)
if isinstance(req.message, JSONRPCRequest) and (method is None or req.message.method == method)
]
def get_server_requests(self, method: str | None = None) -> list[JSONRPCRequest]: # pragma: no cover
"""Get server-sent requests, optionally filtered by method."""
return [ # pragma: no cover
req.message.root
req.message
for req in self.server.sent_messages
if isinstance(req.message.root, JSONRPCRequest) and (method is None or req.message.root.method == method)
if isinstance(req.message, JSONRPCRequest) and (method is None or req.message.method == method)
]
def get_client_notifications(self, method: str | None = None) -> list[JSONRPCNotification]: # pragma: no cover
"""Get client-sent notifications, optionally filtered by method."""
return [
notif.message.root
notif.message
for notif in self.client.sent_messages
if isinstance(notif.message.root, JSONRPCNotification)
and (method is None or notif.message.root.method == method)
if isinstance(notif.message, JSONRPCNotification) and (method is None or notif.message.method == method)
]
def get_server_notifications(self, method: str | None = None) -> list[JSONRPCNotification]: # pragma: no cover
"""Get server-sent notifications, optionally filtered by method."""
return [
notif.message.root
notif.message
for notif in self.server.sent_messages
if isinstance(notif.message.root, JSONRPCNotification)
and (method is None or notif.message.root.method == method)
if isinstance(notif.message, JSONRPCNotification) and (method is None or notif.message.method == method)
]

View file

@ -18,7 +18,6 @@ from mcp.types import (
InitializedNotification,
InitializeRequest,
InitializeResult,
JSONRPCMessage,
JSONRPCNotification,
JSONRPCRequest,
JSONRPCResponse,
@ -41,7 +40,7 @@ async def test_client_session_initialize():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -65,18 +64,16 @@ async def test_client_session_initialize():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
session_notification = await client_to_server_receive.receive()
jsonrpc_notification = session_notification.message
assert isinstance(jsonrpc_notification.root, JSONRPCNotification)
assert isinstance(jsonrpc_notification, JSONRPCNotification)
initialized_notification = ClientNotification.model_validate(
jsonrpc_notification.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -128,7 +125,7 @@ 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.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -146,12 +143,10 @@ async def test_client_session_custom_client_info():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -189,7 +184,7 @@ 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.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -207,12 +202,10 @@ async def test_client_session_default_client_info():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -220,10 +213,7 @@ async def test_client_session_default_client_info():
await client_to_server_receive.receive()
async with (
ClientSession(
server_to_client_receive,
client_to_server_send,
) as session,
ClientSession(server_to_client_receive, client_to_server_send) as session,
anyio.create_task_group() as tg,
client_to_server_send,
client_to_server_receive,
@ -247,7 +237,7 @@ async def test_client_session_version_negotiation_success():
async def mock_server():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -268,12 +258,10 @@ async def test_client_session_version_negotiation_success():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -281,10 +269,7 @@ async def test_client_session_version_negotiation_success():
await client_to_server_receive.receive()
async with (
ClientSession(
server_to_client_receive,
client_to_server_send,
) as session,
ClientSession(server_to_client_receive, client_to_server_send) as session,
anyio.create_task_group() as tg,
client_to_server_send,
client_to_server_receive,
@ -309,7 +294,7 @@ async def test_client_session_version_negotiation_failure():
async def mock_server():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -327,21 +312,16 @@ async def test_client_session_version_negotiation_failure():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
async with (
ClientSession(
server_to_client_receive,
client_to_server_send,
) as session,
ClientSession(server_to_client_receive, client_to_server_send) as session,
anyio.create_task_group() as tg,
client_to_server_send,
client_to_server_receive,
@ -368,7 +348,7 @@ async def test_client_capabilities_default():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -386,12 +366,10 @@ async def test_client_capabilities_default():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -399,10 +377,7 @@ async def test_client_capabilities_default():
await client_to_server_receive.receive()
async with (
ClientSession(
server_to_client_receive,
client_to_server_send,
) as session,
ClientSession(server_to_client_receive, client_to_server_send) as session,
anyio.create_task_group() as tg,
client_to_server_send,
client_to_server_receive,
@ -446,7 +421,7 @@ 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.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -464,12 +439,10 @@ async def test_client_capabilities_with_custom_callbacks():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -529,7 +502,7 @@ 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.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -547,12 +520,10 @@ async def test_client_capabilities_with_sampling_tools():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -600,7 +571,7 @@ async def test_get_server_capabilities():
async def mock_server():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -617,12 +588,10 @@ async def test_get_server_capabilities():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -669,7 +638,7 @@ async def test_client_tool_call_with_meta(meta: dict[str, Any] | None):
# Receive initialization request from client
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -686,12 +655,10 @@ async def test_client_tool_call_with_meta(meta: dict[str, Any] | None):
# Answer initialization request
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -702,14 +669,14 @@ async def test_client_tool_call_with_meta(meta: dict[str, Any] | None):
# Wait for the client to send a 'tools/call' request
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
assert jsonrpc_request.root.method == "tools/call"
assert jsonrpc_request.method == "tools/call"
if meta is not None:
assert jsonrpc_request.root.params
assert "_meta" in jsonrpc_request.root.params
assert jsonrpc_request.root.params["_meta"] == meta
assert jsonrpc_request.params
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)
@ -718,12 +685,10 @@ async def test_client_tool_call_with_meta(meta: dict[str, Any] | None):
# Send the tools/call result
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -732,20 +697,18 @@ async def test_client_tool_call_with_meta(meta: dict[str, Any] | None):
# The client requires this step to validate the tool output schema
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
assert jsonrpc_request.root.method == "tools/list"
assert jsonrpc_request.method == "tools/list"
result = types.ListToolsResult(tools=[mocked_tool])
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -753,10 +716,7 @@ async def test_client_tool_call_with_meta(meta: dict[str, Any] | None):
server_to_client_send.close()
async with (
ClientSession(
server_to_client_receive,
client_to_server_send,
) as session,
ClientSession(server_to_client_receive, client_to_server_send) as session,
anyio.create_task_group() as tg,
client_to_server_send,
client_to_server_receive,

View file

@ -47,8 +47,8 @@ async def test_stdio_client():
async with stdio_client(server_parameters) as (read_stream, write_stream):
# Test sending and receiving messages
messages = [
JSONRPCMessage(root=JSONRPCRequest(jsonrpc="2.0", id=1, method="ping")),
JSONRPCMessage(root=JSONRPCResponse(jsonrpc="2.0", id=2, result={})),
JSONRPCRequest(jsonrpc="2.0", id=1, method="ping"),
JSONRPCResponse(jsonrpc="2.0", id=2, result={}),
]
async with write_stream:
@ -67,8 +67,8 @@ async def test_stdio_client():
break
assert len(read_messages) == 2
assert read_messages[0] == JSONRPCMessage(root=JSONRPCRequest(jsonrpc="2.0", id=1, method="ping"))
assert read_messages[1] == JSONRPCMessage(root=JSONRPCResponse(jsonrpc="2.0", id=2, result={}))
assert read_messages[0] == JSONRPCRequest(jsonrpc="2.0", id=1, method="ping")
assert read_messages[1] == JSONRPCResponse(jsonrpc="2.0", id=2, result={})
@pytest.mark.anyio

View file

@ -15,7 +15,6 @@ from mcp.types import (
Implementation,
InitializeRequest,
InitializeResult,
JSONRPCMessage,
JSONRPCRequest,
JSONRPCResponse,
ServerCapabilities,
@ -36,7 +35,7 @@ async def test_client_capabilities_without_tasks():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -54,12 +53,10 @@ async def test_client_capabilities_without_tasks():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -110,7 +107,7 @@ async def test_client_capabilities_with_tasks():
session_message = await client_to_server_receive.receive()
jsonrpc_request = session_message.message
assert isinstance(jsonrpc_request.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -128,12 +125,10 @@ async def test_client_capabilities_with_tasks():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -194,7 +189,7 @@ 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.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -212,12 +207,10 @@ async def test_client_capabilities_auto_built_from_handlers():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
@ -274,7 +267,7 @@ 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.root, JSONRPCRequest)
assert isinstance(jsonrpc_request, JSONRPCRequest)
request = ClientRequest.model_validate(
jsonrpc_request.model_dump(by_alias=True, mode="json", exclude_none=True)
)
@ -292,12 +285,10 @@ async def test_client_capabilities_with_task_augmented_handlers():
async with server_to_client_send:
await server_to_client_send.send(
SessionMessage(
JSONRPCMessage(
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.root.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
JSONRPCResponse(
jsonrpc="2.0",
id=jsonrpc_request.id,
result=result.model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)

View file

@ -151,15 +151,11 @@ async def test_client_handles_get_task_request(client_streams: ClientTestStreams
await client_ready.wait()
typed_request = GetTaskRequest(params=GetTaskRequestParams(task_id="test-task-123"))
request = types.JSONRPCRequest(
jsonrpc="2.0",
id="req-1",
**typed_request.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(request)))
request = types.JSONRPCRequest(jsonrpc="2.0", id="req-1", **typed_request.model_dump(by_alias=True))
await client_streams.server_send.send(SessionMessage(request))
response_msg = await client_streams.server_receive.receive()
response = response_msg.message.root
response = response_msg.message
assert isinstance(response, types.JSONRPCResponse)
assert response.id == "req-1"
@ -219,10 +215,10 @@ async def test_client_handles_get_task_result_request(client_streams: ClientTest
id="req-2",
**typed_request.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(request)))
await client_streams.server_send.send(SessionMessage(request))
response_msg = await client_streams.server_receive.receive()
response = response_msg.message.root
response = response_msg.message
assert isinstance(response, types.JSONRPCResponse)
assert isinstance(response.result, dict)
@ -277,10 +273,10 @@ async def test_client_handles_list_tasks_request(client_streams: ClientTestStrea
id="req-3",
**typed_request.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(request)))
await client_streams.server_send.send(SessionMessage(request))
response_msg = await client_streams.server_receive.receive()
response = response_msg.message.root
response = response_msg.message
assert isinstance(response, types.JSONRPCResponse)
result = ListTasksResult.model_validate(response.result)
@ -340,10 +336,10 @@ async def test_client_handles_cancel_task_request(client_streams: ClientTestStre
id="req-4",
**typed_request.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(request)))
await client_streams.server_send.send(SessionMessage(request))
response_msg = await client_streams.server_receive.receive()
response = response_msg.message.root
response = response_msg.message
assert isinstance(response, types.JSONRPCResponse)
result = CancelTaskResult.model_validate(response.result)
@ -448,11 +444,11 @@ async def test_client_task_augmented_sampling(client_streams: ClientTestStreams)
id="req-sampling",
**typed_request.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(request)))
await client_streams.server_send.send(SessionMessage(request))
# Step 2: Client responds with CreateTaskResult
response_msg = await client_streams.server_receive.receive()
response = response_msg.message.root
response = response_msg.message
assert isinstance(response, types.JSONRPCResponse)
task_result = CreateTaskResult.model_validate(response.result)
@ -469,10 +465,10 @@ async def test_client_task_augmented_sampling(client_streams: ClientTestStreams)
id="req-poll",
**typed_poll.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(poll_request)))
await client_streams.server_send.send(SessionMessage(poll_request))
poll_response_msg = await client_streams.server_receive.receive()
poll_response = poll_response_msg.message.root
poll_response = poll_response_msg.message
assert isinstance(poll_response, types.JSONRPCResponse)
status = GetTaskResult.model_validate(poll_response.result)
@ -485,10 +481,10 @@ async def test_client_task_augmented_sampling(client_streams: ClientTestStreams)
id="req-result",
**typed_result_req.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(result_request)))
await client_streams.server_send.send(SessionMessage(result_request))
result_response_msg = await client_streams.server_receive.receive()
result_response = result_response_msg.message.root
result_response = result_response_msg.message
assert isinstance(result_response, types.JSONRPCResponse)
assert isinstance(result_response.result, dict)
@ -588,11 +584,11 @@ async def test_client_task_augmented_elicitation(client_streams: ClientTestStrea
id="req-elicit",
**typed_request.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(request)))
await client_streams.server_send.send(SessionMessage(request))
# Step 2: Client responds with CreateTaskResult
response_msg = await client_streams.server_receive.receive()
response = response_msg.message.root
response = response_msg.message
assert isinstance(response, types.JSONRPCResponse)
task_result = CreateTaskResult.model_validate(response.result)
@ -609,10 +605,10 @@ async def test_client_task_augmented_elicitation(client_streams: ClientTestStrea
id="req-poll",
**typed_poll.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(poll_request)))
await client_streams.server_send.send(SessionMessage(poll_request))
poll_response_msg = await client_streams.server_receive.receive()
poll_response = poll_response_msg.message.root
poll_response = poll_response_msg.message
assert isinstance(poll_response, types.JSONRPCResponse)
status = GetTaskResult.model_validate(poll_response.result)
@ -625,10 +621,10 @@ async def test_client_task_augmented_elicitation(client_streams: ClientTestStrea
id="req-result",
**typed_result_req.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(result_request)))
await client_streams.server_send.send(SessionMessage(result_request))
result_response_msg = await client_streams.server_receive.receive()
result_response = result_response_msg.message.root
result_response = result_response_msg.message
assert isinstance(result_response, types.JSONRPCResponse)
# Verify the elicitation result
@ -667,10 +663,10 @@ async def test_client_returns_error_for_unhandled_task_request(client_streams: C
id="req-unhandled",
**typed_request.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(request)))
await client_streams.server_send.send(SessionMessage(request))
response_msg = await client_streams.server_receive.receive()
response = response_msg.message.root
response = response_msg.message
assert isinstance(response, types.JSONRPCError)
assert (
"not supported" in response.error.message.lower()
@ -706,10 +702,10 @@ async def test_client_returns_error_for_unhandled_task_result_request(client_str
id="req-result",
**typed_request.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(request)))
await client_streams.server_send.send(SessionMessage(request))
response_msg = await client_streams.server_receive.receive()
response = response_msg.message.root
response = response_msg.message
assert isinstance(response, types.JSONRPCError)
assert "not supported" in response.error.message.lower()
@ -742,10 +738,10 @@ async def test_client_returns_error_for_unhandled_list_tasks_request(client_stre
id="req-list",
**typed_request.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(request)))
await client_streams.server_send.send(SessionMessage(request))
response_msg = await client_streams.server_receive.receive()
response = response_msg.message.root
response = response_msg.message
assert isinstance(response, types.JSONRPCError)
assert "not supported" in response.error.message.lower()
@ -778,10 +774,10 @@ async def test_client_returns_error_for_unhandled_cancel_task_request(client_str
id="req-cancel",
**typed_request.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(request)))
await client_streams.server_send.send(SessionMessage(request))
response_msg = await client_streams.server_receive.receive()
response = response_msg.message.root
response = response_msg.message
assert isinstance(response, types.JSONRPCError)
assert "not supported" in response.error.message.lower()
@ -822,10 +818,10 @@ async def test_client_returns_error_for_unhandled_task_augmented_sampling(client
id="req-sampling",
**typed_request.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(request)))
await client_streams.server_send.send(SessionMessage(request))
response_msg = await client_streams.server_receive.receive()
response = response_msg.message.root
response = response_msg.message
assert isinstance(response, types.JSONRPCError)
assert "not supported" in response.error.message.lower()
@ -868,10 +864,10 @@ async def test_client_returns_error_for_unhandled_task_augmented_elicitation(
id="req-elicit",
**typed_request.model_dump(by_alias=True),
)
await client_streams.server_send.send(SessionMessage(types.JSONRPCMessage(request)))
await client_streams.server_send.send(SessionMessage(request))
response_msg = await client_streams.server_receive.receive()
response = response_msg.message.root
response = response_msg.message
assert isinstance(response, types.JSONRPCError)
assert "not supported" in response.error.message.lower()

View file

@ -36,7 +36,6 @@ from mcp.types import (
GetTaskRequestParams,
GetTaskResult,
JSONRPCError,
JSONRPCMessage,
JSONRPCNotification,
JSONRPCResponse,
ListTasksRequest,
@ -724,7 +723,7 @@ async def test_send_message() -> None:
# Create a test message
notification = JSONRPCNotification(jsonrpc="2.0", method="test/notification")
message = SessionMessage(
message=JSONRPCMessage(notification),
message=notification,
metadata=ServerMessageMetadata(related_request_id="test-req-1"),
)
@ -733,8 +732,8 @@ async def test_send_message() -> None:
# Verify it was sent to the stream
received = await server_to_client_receive.receive()
assert isinstance(received.message.root, JSONRPCNotification)
assert received.message.root.method == "test/notification"
assert isinstance(received.message, JSONRPCNotification)
assert received.message.method == "test/notification"
finally: # pragma: no cover
await server_to_client_send.aclose()
await server_to_client_receive.aclose()
@ -776,7 +775,7 @@ async def test_response_routing_success() -> None:
# Simulate receiving a response from client
response = JSONRPCResponse(jsonrpc="2.0", id="test-req-1", result={"status": "ok"})
message = SessionMessage(message=JSONRPCMessage(response))
message = SessionMessage(message=response)
# Send from "client" side
await client_to_server_send.send(message)
@ -831,7 +830,7 @@ async def test_response_routing_error() -> None:
# Simulate receiving an error response from client
error_data = ErrorData(code=INVALID_REQUEST, message="Test error")
error_response = JSONRPCError(jsonrpc="2.0", id="test-req-2", error=error_data)
message = SessionMessage(message=JSONRPCMessage(error_response))
message = SessionMessage(message=error_response)
# Send from "client" side
await client_to_server_send.send(message)
@ -894,7 +893,7 @@ async def test_response_routing_skips_non_matching_routers() -> None:
# Send a response - should skip first router and be handled by second
response = JSONRPCResponse(jsonrpc="2.0", id="test-req-1", result={"status": "ok"})
message = SessionMessage(message=JSONRPCMessage(response))
message = SessionMessage(message=response)
await client_to_server_send.send(message)
with anyio.fail_after(5):
@ -953,7 +952,7 @@ async def test_error_routing_skips_non_matching_routers() -> None:
# Send an error - should skip first router and be handled by second
error_data = ErrorData(code=INVALID_REQUEST, message="Test error")
error_response = JSONRPCError(jsonrpc="2.0", id="test-req-2", error=error_data)
message = SessionMessage(message=JSONRPCMessage(error_response))
message = SessionMessage(message=error_response)
await client_to_server_send.send(message)
with anyio.fail_after(5):

View file

@ -66,7 +66,7 @@ async def test_request_id_match() -> None:
jsonrpc="2.0",
)
await client_writer.send(SessionMessage(JSONRPCMessage(root=init_req)))
await client_writer.send(SessionMessage(init_req))
response = await server_reader.receive() # Get init response but don't need to check it
# Send initialized notification
@ -75,12 +75,12 @@ async def test_request_id_match() -> None:
params=NotificationParams().model_dump(by_alias=True, exclude_none=True),
jsonrpc="2.0",
)
await client_writer.send(SessionMessage(JSONRPCMessage(root=initialized_notification)))
await client_writer.send(SessionMessage(initialized_notification))
# Send ping request with custom ID
ping_request = JSONRPCRequest(id=custom_request_id, method="ping", params={}, jsonrpc="2.0")
await client_writer.send(SessionMessage(JSONRPCMessage(root=ping_request)))
await client_writer.send(SessionMessage(ping_request))
# Read response
response = await server_reader.receive()
@ -88,8 +88,8 @@ async def test_request_id_match() -> None:
# Verify response ID matches request ID
assert isinstance(response, SessionMessage)
assert isinstance(response.message, JSONRPCMessage)
assert isinstance(response.message.root, JSONRPCResponse)
assert response.message.root.id == custom_request_id, "Response ID should match request ID"
assert isinstance(response.message, JSONRPCResponse)
assert response.message.id == custom_request_id, "Response ID should match request ID"
# Cancel server task
tg.cancel_scope.cancel()

View file

@ -1,21 +1,13 @@
# Claude Debug
"""Test for HackerOne vulnerability report #3156202 - malformed input DOS."""
from typing import Any
import anyio
import pytest
from mcp.server.models import InitializationOptions
from mcp.server.session import ServerSession
from mcp.shared.message import SessionMessage
from mcp.types import (
INVALID_PARAMS,
JSONRPCError,
JSONRPCMessage,
JSONRPCRequest,
ServerCapabilities,
)
from mcp.types import INVALID_PARAMS, JSONRPCError, JSONRPCMessage, JSONRPCRequest, ServerCapabilities
@pytest.mark.anyio
@ -37,7 +29,7 @@ async def test_malformed_initialize_request_does_not_crash_server():
)
# Wrap in session message
request_message = SessionMessage(message=JSONRPCMessage(malformed_request))
request_message = SessionMessage(message=malformed_request)
# Start a server session
async with ServerSession(
@ -58,7 +50,7 @@ async def test_malformed_initialize_request_does_not_crash_server():
# Check that we received an error response instead of a crash
try:
response_message = write_receive_stream.receive_nowait()
response = response_message.message.root
response = response_message.message
# Verify it's a proper JSON-RPC error response
assert isinstance(response, JSONRPCError)
@ -75,14 +67,14 @@ async def test_malformed_initialize_request_does_not_crash_server():
method="tools/call",
# params=None # Missing required params
)
another_request_message = SessionMessage(message=JSONRPCMessage(another_malformed_request))
another_request_message = SessionMessage(message=another_malformed_request)
await read_send_stream.send(another_request_message)
await anyio.sleep(0.1)
# Should get another error response, not a crash
second_response_message = write_receive_stream.receive_nowait()
second_response = second_response_message.message.root
second_response = second_response_message.message
assert isinstance(second_response, JSONRPCError)
assert second_response.id == "test_id_2"
@ -125,7 +117,7 @@ async def test_multiple_concurrent_malformed_requests():
method="initialize",
# params=None # Missing required params
)
request_message = SessionMessage(message=JSONRPCMessage(malformed_request))
request_message = SessionMessage(message=malformed_request)
malformed_requests.append(request_message)
# Send all requests
@ -136,11 +128,11 @@ async def test_multiple_concurrent_malformed_requests():
await anyio.sleep(0.2)
# Verify we get error responses for all requests
error_responses: list[Any] = []
error_responses: list[JSONRPCMessage] = []
try:
while True:
response_message = write_receive_stream.receive_nowait()
error_responses.append(response_message.message.root)
error_responses.append(response_message.message)
except anyio.WouldBlock:
pass # No more messages

View file

@ -82,13 +82,11 @@ async def test_lowlevel_server_lifespan():
)
await send_stream1.send(
SessionMessage(
JSONRPCMessage(
root=JSONRPCRequest(
jsonrpc="2.0",
id=1,
method="initialize",
params=TypeAdapter(InitializeRequestParams).dump_python(params),
)
JSONRPCRequest(
jsonrpc="2.0",
id=1,
method="initialize",
params=TypeAdapter(InitializeRequestParams).dump_python(params),
)
)
)
@ -96,27 +94,16 @@ async def test_lowlevel_server_lifespan():
response = response.message
# Send initialized notification
await send_stream1.send(
SessionMessage(
JSONRPCMessage(
root=JSONRPCNotification(
jsonrpc="2.0",
method="notifications/initialized",
)
)
)
)
await send_stream1.send(SessionMessage(JSONRPCNotification(jsonrpc="2.0", method="notifications/initialized")))
# Call the tool to verify lifespan context
await send_stream1.send(
SessionMessage(
JSONRPCMessage(
root=JSONRPCRequest(
jsonrpc="2.0",
id=2,
method="tools/call",
params={"name": "check_lifespan", "arguments": {}},
)
JSONRPCRequest(
jsonrpc="2.0",
id=2,
method="tools/call",
params={"name": "check_lifespan", "arguments": {}},
)
)
)
@ -125,8 +112,8 @@ async def test_lowlevel_server_lifespan():
response = await receive_stream2.receive()
response = response.message
assert isinstance(response, JSONRPCMessage)
assert isinstance(response.root, JSONRPCResponse)
assert response.root.result["content"][0]["text"] == "true"
assert isinstance(response, JSONRPCResponse)
assert response.result["content"][0]["text"] == "true"
# Cancel server task
tg.cancel_scope.cancel()
@ -162,13 +149,7 @@ async def test_fastmcp_server_lifespan():
return True
# Run server in background task
async with (
anyio.create_task_group() as tg,
send_stream1,
receive_stream1,
send_stream2,
receive_stream2,
):
async with anyio.create_task_group() as tg, send_stream1, receive_stream1, send_stream2, receive_stream2:
async def run_server():
await server._mcp_server.run(
@ -188,13 +169,11 @@ async def test_fastmcp_server_lifespan():
)
await send_stream1.send(
SessionMessage(
JSONRPCMessage(
root=JSONRPCRequest(
jsonrpc="2.0",
id=1,
method="initialize",
params=TypeAdapter(InitializeRequestParams).dump_python(params),
)
JSONRPCRequest(
jsonrpc="2.0",
id=1,
method="initialize",
params=TypeAdapter(InitializeRequestParams).dump_python(params),
)
)
)
@ -202,27 +181,16 @@ async def test_fastmcp_server_lifespan():
response = response.message
# Send initialized notification
await send_stream1.send(
SessionMessage(
JSONRPCMessage(
root=JSONRPCNotification(
jsonrpc="2.0",
method="notifications/initialized",
)
)
)
)
await send_stream1.send(SessionMessage(JSONRPCNotification(jsonrpc="2.0", method="notifications/initialized")))
# Call the tool to verify lifespan context
await send_stream1.send(
SessionMessage(
JSONRPCMessage(
root=JSONRPCRequest(
jsonrpc="2.0",
id=2,
method="tools/call",
params={"name": "check_lifespan", "arguments": {}},
)
JSONRPCRequest(
jsonrpc="2.0",
id=2,
method="tools/call",
params={"name": "check_lifespan", "arguments": {}},
)
)
)
@ -231,8 +199,8 @@ async def test_fastmcp_server_lifespan():
response = await receive_stream2.receive()
response = response.message
assert isinstance(response, JSONRPCMessage)
assert isinstance(response.root, JSONRPCResponse)
assert response.root.result["content"][0]["text"] == "true"
assert isinstance(response, JSONRPCResponse)
assert response.result["content"][0]["text"] == "true"
# Cancel server task
tg.cancel_scope.cancel()

View file

@ -169,25 +169,23 @@ async def test_server_session_initialize_with_older_protocol_version():
# Send initialization request with older protocol version (2024-11-05)
await client_to_server_send.send(
SessionMessage(
types.JSONRPCMessage(
types.JSONRPCRequest(
jsonrpc="2.0",
id=1,
method="initialize",
params=types.InitializeRequestParams(
protocol_version="2024-11-05",
capabilities=types.ClientCapabilities(),
client_info=types.Implementation(name="test-client", version="1.0.0"),
).model_dump(by_alias=True, mode="json", exclude_none=True),
)
types.JSONRPCRequest(
jsonrpc="2.0",
id=1,
method="initialize",
params=types.InitializeRequestParams(
protocol_version="2024-11-05",
capabilities=types.ClientCapabilities(),
client_info=types.Implementation(name="test-client", version="1.0.0"),
).model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
# Wait for the initialize response
init_response_message = await server_to_client_receive.receive()
assert isinstance(init_response_message.message.root, types.JSONRPCResponse)
result_data = init_response_message.message.root.result
assert isinstance(init_response_message.message, types.JSONRPCResponse)
result_data = init_response_message.message.result
init_result = types.InitializeResult.model_validate(result_data)
# Check that the server responded with the requested protocol version
@ -196,14 +194,7 @@ async def test_server_session_initialize_with_older_protocol_version():
# Send initialized notification
await client_to_server_send.send(
SessionMessage(
types.JSONRPCMessage(
types.JSONRPCNotification(
jsonrpc="2.0",
method="notifications/initialized",
)
)
)
SessionMessage(types.JSONRPCNotification(jsonrpc="2.0", method="notifications/initialized"))
)
async with (
@ -256,24 +247,14 @@ async def test_ping_request_before_initialization():
nonlocal ping_response_received, ping_response_id
# Send ping request before any initialization
await client_to_server_send.send(
SessionMessage(
types.JSONRPCMessage(
types.JSONRPCRequest(
jsonrpc="2.0",
id=42,
method="ping",
)
)
)
)
await client_to_server_send.send(SessionMessage(types.JSONRPCRequest(jsonrpc="2.0", id=42, method="ping")))
# Wait for the ping response
ping_response_message = await server_to_client_receive.receive()
assert isinstance(ping_response_message.message.root, types.JSONRPCResponse)
assert isinstance(ping_response_message.message, types.JSONRPCResponse)
ping_response_received = True
ping_response_id = ping_response_message.message.root.id
ping_response_id = ping_response_message.message.id
async with (
client_to_server_send,
@ -493,22 +474,14 @@ async def test_other_requests_blocked_before_initialization():
# Try to send a non-ping request before initialization
await client_to_server_send.send(
SessionMessage(
types.JSONRPCMessage(
types.JSONRPCRequest(
jsonrpc="2.0",
id=1,
method="prompts/list",
)
)
)
SessionMessage(types.JSONRPCRequest(jsonrpc="2.0", id=1, method="prompts/list"))
)
# Wait for the error response
error_message = await server_to_client_receive.receive()
if isinstance(error_message.message.root, types.JSONRPCError): # pragma: no branch
if isinstance(error_message.message, types.JSONRPCError): # pragma: no branch
error_response_received = True
error_code = error_message.message.root.error.code
error_code = error_message.message.error.code
async with (
client_to_server_send,

View file

@ -87,54 +87,35 @@ async def test_request_immediately_after_initialize_response():
# Step 1: Send InitializeRequest
await client_to_server_send.send(
SessionMessage(
types.JSONRPCMessage(
types.JSONRPCRequest(
jsonrpc="2.0",
id=1,
method="initialize",
params=types.InitializeRequestParams(
protocol_version=types.LATEST_PROTOCOL_VERSION,
capabilities=types.ClientCapabilities(),
client_info=types.Implementation(name="test-client", version="1.0.0"),
).model_dump(by_alias=True, mode="json", exclude_none=True),
)
types.JSONRPCRequest(
jsonrpc="2.0",
id=1,
method="initialize",
params=types.InitializeRequestParams(
protocol_version=types.LATEST_PROTOCOL_VERSION,
capabilities=types.ClientCapabilities(),
client_info=types.Implementation(name="test-client", version="1.0.0"),
).model_dump(by_alias=True, mode="json", exclude_none=True),
)
)
)
# Step 2: Wait for InitializeResult
init_msg = await server_to_client_receive.receive()
assert isinstance(init_msg.message.root, types.JSONRPCResponse)
assert isinstance(init_msg.message, types.JSONRPCResponse)
# Step 3: Immediately send tools/list BEFORE InitializedNotification
# This is the race condition scenario
await client_to_server_send.send(
SessionMessage(
types.JSONRPCMessage(
types.JSONRPCRequest(
jsonrpc="2.0",
id=2,
method="tools/list",
)
)
)
)
await client_to_server_send.send(SessionMessage(types.JSONRPCRequest(jsonrpc="2.0", id=2, method="tools/list")))
# Step 4: Check the response
tools_msg = await server_to_client_receive.receive()
if isinstance(tools_msg.message.root, types.JSONRPCError): # pragma: no cover
error_received = tools_msg.message.root.error.message
if isinstance(tools_msg.message, types.JSONRPCError): # pragma: no cover
error_received = tools_msg.message.error.message
# Step 5: Send InitializedNotification
await client_to_server_send.send(
SessionMessage(
types.JSONRPCMessage(
types.JSONRPCNotification(
jsonrpc="2.0",
method="notifications/initialized",
)
)
)
SessionMessage(types.JSONRPCNotification(jsonrpc="2.0", method="notifications/initialized"))
)
async with (

View file

@ -5,7 +5,7 @@ import pytest
from mcp.server.stdio import stdio_server
from mcp.shared.message import SessionMessage
from mcp.types import JSONRPCMessage, JSONRPCRequest, JSONRPCResponse
from mcp.types import JSONRPCMessage, JSONRPCRequest, JSONRPCResponse, jsonrpc_message_adapter
@pytest.mark.anyio
@ -14,8 +14,8 @@ async def test_stdio_server():
stdout = io.StringIO()
messages = [
JSONRPCMessage(root=JSONRPCRequest(jsonrpc="2.0", id=1, method="ping")),
JSONRPCMessage(root=JSONRPCResponse(jsonrpc="2.0", id=2, result={})),
JSONRPCRequest(jsonrpc="2.0", id=1, method="ping"),
JSONRPCResponse(jsonrpc="2.0", id=2, result={}),
]
for message in messages:
@ -37,13 +37,13 @@ async def test_stdio_server():
# Verify received messages
assert len(received_messages) == 2
assert received_messages[0] == JSONRPCMessage(root=JSONRPCRequest(jsonrpc="2.0", id=1, method="ping"))
assert received_messages[1] == JSONRPCMessage(root=JSONRPCResponse(jsonrpc="2.0", id=2, result={}))
assert received_messages[0] == JSONRPCRequest(jsonrpc="2.0", id=1, method="ping")
assert received_messages[1] == JSONRPCResponse(jsonrpc="2.0", id=2, result={})
# Test sending responses from the server
responses = [
JSONRPCMessage(root=JSONRPCRequest(jsonrpc="2.0", id=3, method="ping")),
JSONRPCMessage(root=JSONRPCResponse(jsonrpc="2.0", id=4, result={})),
JSONRPCRequest(jsonrpc="2.0", id=3, method="ping"),
JSONRPCResponse(jsonrpc="2.0", id=4, result={}),
]
async with write_stream:
@ -55,7 +55,7 @@ async def test_stdio_server():
output_lines = stdout.readlines()
assert len(output_lines) == 2
received_responses = [JSONRPCMessage.model_validate_json(line.strip()) for line in output_lines]
received_responses = [jsonrpc_message_adapter.validate_json(line.strip()) for line in output_lines]
assert len(received_responses) == 2
assert received_responses[0] == JSONRPCMessage(root=JSONRPCRequest(jsonrpc="2.0", id=3, method="ping"))
assert received_responses[1] == JSONRPCMessage(root=JSONRPCResponse(jsonrpc="2.0", id=4, result={}))
assert received_responses[0] == JSONRPCRequest(jsonrpc="2.0", id=3, method="ping")
assert received_responses[1] == JSONRPCResponse(jsonrpc="2.0", id=4, result={})

View file

@ -18,7 +18,6 @@ from mcp.types import (
EmptyResult,
ErrorData,
JSONRPCError,
JSONRPCMessage,
JSONRPCRequest,
JSONRPCResponse,
TextContent,
@ -130,7 +129,7 @@ async def test_response_id_type_mismatch_string_to_int():
"""Receive a request and respond with a string ID instead of integer."""
message = await server_read.receive()
assert isinstance(message, SessionMessage)
root = message.message.root
root = message.message
assert isinstance(root, JSONRPCRequest)
# Get the original request ID (which is an integer)
request_id = root.id
@ -142,7 +141,7 @@ async def test_response_id_type_mismatch_string_to_int():
id=str(request_id), # Convert to string to simulate mismatch
result={},
)
await server_write.send(SessionMessage(message=JSONRPCMessage(response)))
await server_write.send(SessionMessage(message=response))
async def make_request(client_session: ClientSession):
nonlocal result_holder
@ -185,7 +184,7 @@ async def test_error_response_id_type_mismatch_string_to_int():
"""Receive a request and respond with an error using a string ID."""
message = await server_read.receive()
assert isinstance(message, SessionMessage)
root = message.message.root
root = message.message
assert isinstance(root, JSONRPCRequest)
request_id = root.id
assert isinstance(request_id, int)
@ -196,7 +195,7 @@ async def test_error_response_id_type_mismatch_string_to_int():
id=str(request_id), # Convert to string to simulate mismatch
error=ErrorData(code=-32600, message="Test error"),
)
await server_write.send(SessionMessage(message=JSONRPCMessage(error_response)))
await server_write.send(SessionMessage(message=error_response))
async def make_request(client_session: ClientSession):
nonlocal error_holder
@ -247,7 +246,7 @@ async def test_response_id_non_numeric_string_no_match():
id="not_a_number", # Non-numeric string
result={},
)
await server_write.send(SessionMessage(message=JSONRPCMessage(response)))
await server_write.send(SessionMessage(message=response))
async def make_request(client_session: ClientSession):
try:

View file

@ -503,12 +503,12 @@ def test_sse_message_id_coercion():
See <https://www.jsonrpc.org/specification#response_object> for more details.
"""
json_message = '{"jsonrpc": "2.0", "id": "123", "method": "ping", "params": null}'
msg = types.JSONRPCMessage.model_validate_json(json_message)
assert msg == snapshot(types.JSONRPCMessage(root=types.JSONRPCRequest(method="ping", jsonrpc="2.0", id="123")))
msg = types.JSONRPCRequest.model_validate_json(json_message)
assert msg == snapshot(types.JSONRPCRequest(method="ping", jsonrpc="2.0", id="123"))
json_message = '{"jsonrpc": "2.0", "id": 123, "method": "ping", "params": null}'
msg = types.JSONRPCMessage.model_validate_json(json_message)
assert msg == snapshot(types.JSONRPCMessage(root=types.JSONRPCRequest(method="ping", jsonrpc="2.0", id=123)))
msg = types.JSONRPCRequest.model_validate_json(json_message)
assert msg == snapshot(types.JSONRPCRequest(method="ping", jsonrpc="2.0", id=123))
@pytest.mark.parametrize(
@ -601,5 +601,5 @@ async def test_sse_client_handles_empty_keepalive_pings() -> None:
msg = await read_stream.receive()
# If we get here without error, the empty message was skipped successfully
assert not isinstance(msg, Exception)
assert isinstance(msg.message.root, types.JSONRPCResponse)
assert msg.message.root.id == 1
assert isinstance(msg.message, types.JSONRPCResponse)
assert msg.message.id == 1

View file

@ -49,7 +49,6 @@ from mcp.shared.message import ClientMessageMetadata, ServerMessageMetadata, Ses
from mcp.shared.session import RequestResponder
from mcp.types import (
InitializeResult,
JSONRPCMessage,
JSONRPCRequest,
TextContent,
TextResourceContents,
@ -1859,7 +1858,7 @@ async def test_close_sse_stream_callback_not_provided_for_old_protocol_version()
)
# Create a mock message and request
mock_message = JSONRPCMessage(root=JSONRPCRequest(jsonrpc="2.0", id="test-1", method="tools/list"))
mock_message = JSONRPCRequest(jsonrpc="2.0", id="test-1", method="tools/list")
mock_request = MagicMock()
# Call _create_session_message with OLD protocol version

View file

@ -12,7 +12,6 @@ from mcp.types import (
Implementation,
InitializeRequest,
InitializeRequestParams,
JSONRPCMessage,
JSONRPCRequest,
ListToolsResult,
SamplingCapability,
@ -22,6 +21,7 @@ from mcp.types import (
ToolChoice,
ToolResultContent,
ToolUseContent,
jsonrpc_message_adapter,
)
@ -38,15 +38,15 @@ async def test_jsonrpc_request():
},
}
request = JSONRPCMessage.model_validate(json_data)
assert isinstance(request.root, JSONRPCRequest)
request = jsonrpc_message_adapter.validate_python(json_data)
assert isinstance(request, JSONRPCRequest)
ClientRequest.model_validate(request.model_dump(by_alias=True, exclude_none=True))
assert request.root.jsonrpc == "2.0"
assert request.root.id == 1
assert request.root.method == "initialize"
assert request.root.params is not None
assert request.root.params["protocolVersion"] == LATEST_PROTOCOL_VERSION
assert request.jsonrpc == "2.0"
assert request.id == 1
assert request.method == "initialize"
assert request.params is not None
assert request.params["protocolVersion"] == LATEST_PROTOCOL_VERSION
@pytest.mark.anyio