|
18 | 18 | from mcp_types import ( |
19 | 19 | CLIENT_CAPABILITIES_META_KEY, |
20 | 20 | CLIENT_INFO_META_KEY, |
| 21 | + CONNECTION_CLOSED, |
21 | 22 | METHOD_NOT_FOUND, |
22 | 23 | PROTOCOL_VERSION_META_KEY, |
23 | 24 | JSONRPCError, |
|
28 | 29 | from mcp_types.version import LATEST_MODERN_VERSION |
29 | 30 | from starlette.types import Receive, Scope, Send |
30 | 31 |
|
31 | | -from mcp.client.streamable_http import streamable_http_client |
| 32 | +from mcp.client.streamable_http import RequestContext, StreamableHTTPTransport, streamable_http_client |
32 | 33 | from mcp.server import Server |
33 | 34 | from mcp.server._streamable_http_modern import handle_modern_request |
34 | 35 | from mcp.server.subscriptions import InMemorySubscriptionBus, ListenHandler, ServerEvent |
| 36 | +from mcp.shared._context_streams import create_context_streams |
35 | 37 | from mcp.shared.dispatcher import CallOptions, DispatchContext |
36 | 38 | from mcp.shared.inbound import MCP_METHOD_HEADER, MCP_PROTOCOL_VERSION_HEADER, encode_header_value |
37 | 39 | from mcp.shared.jsonrpc_dispatcher import JSONRPCDispatcher |
@@ -73,6 +75,30 @@ def test_mcp_name_header_values_are_base64_wrapped_when_unsafe_for_an_http_field |
73 | 75 | assert encoded == raw |
74 | 76 |
|
75 | 77 |
|
| 78 | +@pytest.mark.anyio |
| 79 | +async def test_sse_response_disconnect_before_any_event_id_fails_request() -> None: |
| 80 | + transport = StreamableHTTPTransport("http://example.com/mcp") |
| 81 | + async with httpx.AsyncClient() as client: |
| 82 | + read_stream_writer, read_stream = create_context_streams[SessionMessage | Exception](1) |
| 83 | + request = JSONRPCRequest(jsonrpc="2.0", id=1, method="tools/call", params={"name": "noop", "arguments": {}}) |
| 84 | + ctx = RequestContext( |
| 85 | + client=client, |
| 86 | + session_id=None, |
| 87 | + session_message=SessionMessage(request), |
| 88 | + metadata=None, |
| 89 | + read_stream_writer=read_stream_writer, |
| 90 | + ) |
| 91 | + response = httpx.Response(200, headers={"content-type": "text/event-stream"}, content=b"") |
| 92 | + |
| 93 | + async with read_stream_writer, read_stream: |
| 94 | + await transport._handle_sse_response(response, ctx) |
| 95 | + message = await read_stream.receive() |
| 96 | + |
| 97 | + assert isinstance(message.message, JSONRPCError) |
| 98 | + assert message.message.id == 1 |
| 99 | + assert message.message.error.code == CONNECTION_CLOSED |
| 100 | + |
| 101 | + |
76 | 102 | @pytest.mark.anyio |
77 | 103 | async def test_post_request_merges_per_message_metadata_headers() -> None: |
78 | 104 | """`ClientMessageMetadata.headers` on a `SessionMessage` are merged into the outgoing POST headers |
|
0 commit comments