Skip to content

Commit de72011

Browse files
committed
Run StreamableHTTP transport tests in process instead of over sockets
Final installment of the in-process test migration: this was the last file spawning uvicorn subprocesses on bind-then-close ports with readiness polling, which races under pytest-xdist when two workers pick the same ephemeral port. Two tests in this file have flaked exactly that way under parallel load. All four subprocess servers (basic, JSON-response, event-store, context-aware) become in-process apps served through the interaction suite's StreamingASGITransport, held open by the session manager's run() context. Raw `requests` calls become httpx calls against the bridge client; the sync request-validation tests become anyio tests. The second-GET-409 test now holds the first stream open by construction, where the subprocess version noted it "might fail if the first stream fully closed before this runs". Assertions are unchanged, with documented exceptions now that the server handlers run as traced in-process code: - The long_running_with_checkpoints tool and the slow:// resource branch had no callers and are removed, so the expected tools/list count drops from 10 to 9 in five tests. - Dead defensive arms become asserts (sampling non-text fallback, close_sse_stream truthiness checks, the context server's unknown-tool fallthrough and request checks), and the event store's replay-from-unknown-event arm becomes a lookup that requires a stored event, since unreachable branches now fail branch coverage instead of hiding in an untraced subprocess. - test_client_crash_handled no longer sleeps between crashing clients; the bridge drains each client's teardown before the next connects. Three pragmas in src/mcp/server/streamable_http.py covered only by the formerly untraced subprocess (close_standalone_sse_stream, its session message callback, and the JSON-mode Accept rejection) are now executed by traced tests and removed. With the last wait_for_server user migrated, the helper is deleted from tests/test_helpers.py; run_uvicorn_in_thread stays for the websocket smoke test.
1 parent ed39e73 commit de72011

3 files changed

Lines changed: 937 additions & 1143 deletions

File tree

src/mcp/server/streamable_http.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@ def close_sse_stream(self, request_id: RequestId) -> None:
207207
send_stream.close()
208208
receive_stream.close()
209209

210-
def close_standalone_sse_stream(self) -> None: # pragma: no cover
210+
def close_standalone_sse_stream(self) -> None:
211211
"""Close the standalone GET SSE stream, triggering client reconnection.
212212
213213
This method closes the HTTP connection for the standalone GET stream used
@@ -221,8 +221,6 @@ def close_standalone_sse_stream(self) -> None: # pragma: no cover
221221
This is a no-op if there is no active standalone SSE stream.
222222
Requires event_store to be configured for events to be stored during
223223
the disconnect.
224-
Currently, client reconnection for standalone GET streams is NOT
225-
implemented - this is a known gap (see test_standalone_get_stream_reconnection).
226224
"""
227225
self.close_sse_stream(GET_STREAM_KEY)
228226

@@ -245,7 +243,7 @@ def _create_session_message(
245243
async def close_stream_callback() -> None:
246244
self.close_sse_stream(request_id)
247245

248-
async def close_standalone_stream_callback() -> None: # pragma: no cover
246+
async def close_standalone_stream_callback() -> None:
249247
self.close_standalone_sse_stream()
250248

251249
metadata = ServerMessageMetadata(
@@ -421,7 +419,7 @@ async def _validate_accept_header(self, request: Request, scope: Scope, send: Se
421419
has_json, has_sse = self._check_accept_headers(request)
422420
if self.is_json_response_enabled:
423421
# For JSON-only responses, only require application/json
424-
if not has_json: # pragma: no cover
422+
if not has_json:
425423
response = self._create_error_response(
426424
"Not Acceptable: Client must accept application/json",
427425
HTTPStatus.NOT_ACCEPTABLE,

0 commit comments

Comments
 (0)