|
17 | 17 | from mcp.client import ClientSession |
18 | 18 | from mcp.client.streamable_http import ( |
19 | 19 | MCP_PROTOCOL_VERSION, |
| 20 | + RequestContext, |
20 | 21 | StreamableHTTPTransport, |
21 | 22 | _encode_header_value, |
22 | 23 | streamable_http_client, |
23 | 24 | ) |
24 | | -from mcp.types import JSONRPCMessage, JSONRPCNotification, JSONRPCRequest, JSONRPCResponse |
| 25 | +from mcp.shared._context_streams import create_context_streams |
| 26 | +from mcp.shared.message import SessionMessage |
| 27 | +from mcp.types import ( |
| 28 | + CONNECTION_CLOSED, |
| 29 | + JSONRPCError, |
| 30 | + JSONRPCMessage, |
| 31 | + JSONRPCNotification, |
| 32 | + JSONRPCRequest, |
| 33 | + JSONRPCResponse, |
| 34 | +) |
25 | 35 |
|
26 | 36 |
|
27 | 37 | @pytest.mark.parametrize( |
@@ -98,6 +108,30 @@ def test_mcp_name_header_values_are_base64_wrapped_when_unsafe_for_an_http_field |
98 | 108 | assert encoded == raw |
99 | 109 |
|
100 | 110 |
|
| 111 | +@pytest.mark.anyio |
| 112 | +async def test_sse_response_disconnect_before_any_event_id_fails_request() -> None: |
| 113 | + transport = StreamableHTTPTransport("http://example.com/mcp") |
| 114 | + async with httpx.AsyncClient() as client: |
| 115 | + read_stream_writer, read_stream = create_context_streams[SessionMessage | Exception](1) |
| 116 | + request = JSONRPCRequest(jsonrpc="2.0", id=1, method="tools/call", params={"name": "noop", "arguments": {}}) |
| 117 | + ctx = RequestContext( |
| 118 | + client=client, |
| 119 | + session_id=None, |
| 120 | + session_message=SessionMessage(request), |
| 121 | + metadata=None, |
| 122 | + read_stream_writer=read_stream_writer, |
| 123 | + ) |
| 124 | + response = httpx.Response(200, headers={"content-type": "text/event-stream"}, content=b"") |
| 125 | + |
| 126 | + async with read_stream_writer, read_stream: |
| 127 | + await transport._handle_sse_response(response, ctx) |
| 128 | + message = await read_stream.receive() |
| 129 | + |
| 130 | + assert isinstance(message.message, JSONRPCError) |
| 131 | + assert message.message.id == 1 |
| 132 | + assert message.message.error.code == CONNECTION_CLOSED |
| 133 | + |
| 134 | + |
101 | 135 | @pytest.mark.anyio |
102 | 136 | async def test_pinned_transport_ignores_returned_session_id_and_never_opens_get_or_delete() -> None: |
103 | 137 | """A server-issued ``Mcp-Session-Id`` never reaches a pinned client's wire: only POSTs are sent. |
|
0 commit comments