Drop RootModel from JSONRPCMessage (#1908)
This commit is contained in:
parent
dcc9b4f8e0
commit
f4672c5084
16 changed files with 314 additions and 476 deletions
|
|
@ -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)
|
||||
]
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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={})
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue