Skip to content

Commit d83f811

Browse files
committed
refactor: update test files to use ParsedMessage
Updates test files to work with the ParsedMessage stream type aliases and fixes a line length issue in test_201_client_hangs_on_logging.py. Github-Issue:#201
1 parent 8c9285e commit d83f811

File tree

4 files changed

+117
-29
lines changed

4 files changed

+117
-29
lines changed

tests/client/test_session.py

+16-13
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import pytest
33

44
from mcp.client.session import ClientSession
5+
from mcp.shared.session import ParsedMessage
56
from mcp.types import (
67
LATEST_PROTOCOL_VERSION,
78
ClientNotification,
@@ -11,7 +12,6 @@
1112
InitializeRequest,
1213
InitializeResult,
1314
JSONRPCMessage,
14-
JSONRPCNotification,
1515
JSONRPCRequest,
1616
JSONRPCResponse,
1717
ServerCapabilities,
@@ -22,10 +22,10 @@
2222
@pytest.mark.anyio
2323
async def test_client_session_initialize():
2424
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[
25-
JSONRPCMessage
25+
ParsedMessage[None]
2626
](1)
2727
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[
28-
JSONRPCMessage
28+
ParsedMessage[None]
2929
](1)
3030

3131
initialized_notification = None
@@ -57,20 +57,23 @@ async def mock_server():
5757

5858
async with server_to_client_send:
5959
await server_to_client_send.send(
60-
JSONRPCMessage(
61-
JSONRPCResponse(
62-
jsonrpc="2.0",
63-
id=jsonrpc_request.root.id,
64-
result=result.model_dump(
65-
by_alias=True, mode="json", exclude_none=True
66-
),
67-
)
60+
ParsedMessage(
61+
root=JSONRPCMessage(
62+
JSONRPCResponse(
63+
jsonrpc="2.0",
64+
id=jsonrpc_request.root.id,
65+
result=result.model_dump(
66+
by_alias=True, mode="json", exclude_none=True
67+
),
68+
)
69+
),
70+
raw=None,
6871
)
6972
)
7073
jsonrpc_notification = await client_to_server_receive.receive()
71-
assert isinstance(jsonrpc_notification.root, JSONRPCNotification)
74+
assert isinstance(jsonrpc_notification.root, ParsedMessage)
7275
initialized_notification = ClientNotification.model_validate(
73-
jsonrpc_notification.model_dump(
76+
jsonrpc_notification.root.model_dump(
7477
by_alias=True, mode="json", exclude_none=True
7578
)
7679
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import logging
2+
3+
import anyio
4+
import pytest
5+
6+
from mcp.server.fastmcp import Context, FastMCP
7+
from mcp.shared.memory import create_connected_server_and_client_session
8+
from mcp.types import TextContent
9+
10+
logger = logging.getLogger(__name__)
11+
12+
13+
@pytest.mark.anyio
14+
async def test_client_hangs_on_logging():
15+
"""
16+
Test case for issue #201: Client hangs when ctx.info() logging with pure Python
17+
client.
18+
19+
This test creates a FastMCP server with a tool that uses ctx.info() for logging.
20+
The issue is that when this tool is called, the client hangs and doesn't complete.
21+
"""
22+
# Create a FastMCP server with a tool that uses ctx.info() for logging
23+
server = FastMCP("test-server")
24+
25+
@server.tool()
26+
async def simple_tool(x: float, y: float) -> str:
27+
"""A simple tool without logging"""
28+
return str(x * y)
29+
30+
@server.tool()
31+
async def tool_with_logging(x: float, y: float, ctx: Context) -> str:
32+
"""A tool that uses ctx.info() for logging - this causes the client to hang"""
33+
await ctx.info(f"Processing tool with x={x} and y={y}")
34+
logger.debug("Inside tool_with_logging")
35+
await ctx.report_progress(1, 2)
36+
return str(x * y)
37+
38+
# Create a client session connected to the server
39+
async with create_connected_server_and_client_session(
40+
server._mcp_server
41+
) as client_session:
42+
# First test that a simple tool works correctly
43+
result = await client_session.call_tool("simple_tool", {"x": 5, "y": 7})
44+
assert isinstance(result.content[0], TextContent)
45+
assert result.content[0].text == "35.0"
46+
47+
# Create an event to signal when the task is done
48+
done_event = anyio.Event()
49+
50+
# Use a separate task for calling the tool that might hang
51+
async def call_logging_tool():
52+
try:
53+
with anyio.fail_after(5): # Add a timeout to prevent test from hanging
54+
result = await client_session.call_tool(
55+
"tool_with_logging", {"x": 3, "y": 4}
56+
)
57+
assert isinstance(result.content[0], TextContent)
58+
assert result.content[0].text == "12.0"
59+
done_event.set()
60+
except Exception as e:
61+
logger.error(f"Error calling tool_with_logging: {e}")
62+
raise
63+
64+
# Start the task
65+
async with anyio.create_task_group() as tg:
66+
tg.start_soon(call_logging_tool)
67+
68+
# Wait for the task to complete or timeout
69+
with anyio.fail_after(10):
70+
await done_event.wait()
71+
72+
# If we get here without hanging or timeout exceptions, the test passes

tests/server/test_session.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
from mcp.server.lowlevel import NotificationOptions
77
from mcp.server.models import InitializationOptions
88
from mcp.server.session import ServerSession
9+
from mcp.shared.session import ParsedMessage
910
from mcp.types import (
1011
ClientNotification,
1112
InitializedNotification,
12-
JSONRPCMessage,
1313
PromptsCapability,
1414
ResourcesCapability,
1515
ServerCapabilities,
@@ -19,10 +19,10 @@
1919
@pytest.mark.anyio
2020
async def test_server_session_initialize():
2121
server_to_client_send, server_to_client_receive = anyio.create_memory_object_stream[
22-
JSONRPCMessage
22+
ParsedMessage[None]
2323
](1)
2424
client_to_server_send, client_to_server_receive = anyio.create_memory_object_stream[
25-
JSONRPCMessage
25+
ParsedMessage[None]
2626
](1)
2727

2828
async def run_client(client: ClientSession):

tests/server/test_stdio.py

+26-13
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import pytest
55

66
from mcp.server.stdio import stdio_server
7+
from mcp.shared.session import ParsedMessage
78
from mcp.types import JSONRPCMessage, JSONRPCRequest, JSONRPCResponse
89

910

@@ -13,8 +14,12 @@ async def test_stdio_server():
1314
stdout = io.StringIO()
1415

1516
messages = [
16-
JSONRPCMessage(root=JSONRPCRequest(jsonrpc="2.0", id=1, method="ping")),
17-
JSONRPCMessage(root=JSONRPCResponse(jsonrpc="2.0", id=2, result={})),
17+
ParsedMessage(
18+
root=JSONRPCMessage(root=JSONRPCRequest(jsonrpc="2.0", id=1, method="ping"))
19+
),
20+
ParsedMessage(
21+
root=JSONRPCMessage(root=JSONRPCResponse(jsonrpc="2.0", id=2, result={}))
22+
),
1823
]
1924

2025
for message in messages:
@@ -35,17 +40,25 @@ async def test_stdio_server():
3540

3641
# Verify received messages
3742
assert len(received_messages) == 2
38-
assert received_messages[0] == JSONRPCMessage(
39-
root=JSONRPCRequest(jsonrpc="2.0", id=1, method="ping")
43+
assert received_messages[0] == ParsedMessage(
44+
root=JSONRPCMessage(root=JSONRPCRequest(jsonrpc="2.0", id=1, method="ping"))
4045
)
41-
assert received_messages[1] == JSONRPCMessage(
42-
root=JSONRPCResponse(jsonrpc="2.0", id=2, result={})
46+
assert received_messages[1] == ParsedMessage(
47+
root=JSONRPCMessage(root=JSONRPCResponse(jsonrpc="2.0", id=2, result={}))
4348
)
4449

4550
# Test sending responses from the server
4651
responses = [
47-
JSONRPCMessage(root=JSONRPCRequest(jsonrpc="2.0", id=3, method="ping")),
48-
JSONRPCMessage(root=JSONRPCResponse(jsonrpc="2.0", id=4, result={})),
52+
ParsedMessage(
53+
root=JSONRPCMessage(
54+
root=JSONRPCRequest(jsonrpc="2.0", id=3, method="ping")
55+
)
56+
),
57+
ParsedMessage(
58+
root=JSONRPCMessage(
59+
root=JSONRPCResponse(jsonrpc="2.0", id=4, result={})
60+
)
61+
),
4962
]
5063

5164
async with write_stream:
@@ -57,12 +70,12 @@ async def test_stdio_server():
5770
assert len(output_lines) == 2
5871

5972
received_responses = [
60-
JSONRPCMessage.model_validate_json(line.strip()) for line in output_lines
73+
ParsedMessage.model_validate_json(line.strip()) for line in output_lines
6174
]
6275
assert len(received_responses) == 2
63-
assert received_responses[0] == JSONRPCMessage(
64-
root=JSONRPCRequest(jsonrpc="2.0", id=3, method="ping")
76+
assert received_responses[0] == ParsedMessage(
77+
root=JSONRPCMessage(root=JSONRPCRequest(jsonrpc="2.0", id=3, method="ping"))
6578
)
66-
assert received_responses[1] == JSONRPCMessage(
67-
root=JSONRPCResponse(jsonrpc="2.0", id=4, result={})
79+
assert received_responses[1] == ParsedMessage(
80+
root=JSONRPCMessage(root=JSONRPCResponse(jsonrpc="2.0", id=4, result={}))
6881
)

0 commit comments

Comments
 (0)