Title: Run StreamableHTTP transport tests in process instead of over sockets by maxisbey · Pull Request #2767 · modelcontextprotocol/python-sdk · GitHub
Open Graph Title: Run StreamableHTTP transport tests in process instead of over sockets by maxisbey · Pull Request #2767 · modelcontextprotocol/python-sdk
X Title: Run StreamableHTTP transport tests in process instead of over sockets by maxisbey · Pull Request #2767 · modelcontextprotocol/python-sdk
Description: Final installment of the in-process test migration (#2764, #2765): tests/shared/test_streamable_http.py was the last file spawning uvicorn subprocesses on bind-then-close ports, the pattern that races under pytest-xdist when two workers pick the same ephemeral port. Two of its tests have flaked exactly this way under parallel load (test_server_validates_protocol_version_header, test_streamable_http_client_mcp_headers_override_defaults). Closes #2704. Motivation and Context Same mechanism and fix as the previous two PRs. All four subprocess servers (basic, JSON-response, event-store, context) become in-process apps driven through StreamingASGITransport; with this, no test in the repo binds a port outside the one pre-bound websocket smoke test. What changed beyond the plumbing swap: The 19 raw-HTTP validation tests were synchronous requests-library tests; they're now anyio + httpx over the bridge with the same methods, headers, bodies, and assertions (including suppressing the library-default Accept header where the old tests did). wait_for_server is deleted from tests/test_helpers.py — zero users remain. run_uvicorn_in_thread stays for tests/shared/test_ws.py. Three # pragma: no cover in src/mcp/server/streamable_http.py are removed (close_standalone_sse_stream, its session-message callback, the JSON-mode Accept rejection): those lines were only ever executed inside the untraced subprocess; the migrated tests now cover them, and keeping the pragmas would fail strict-no-cover. One stale src docstring note is dropped: close_standalone_sse_stream claimed client reconnection for standalone GET streams "is NOT implemented — this is a known gap", but the note was self-contradictory from the day it landed — the same commit (#1654) implemented the auto-reconnect and the test that proves it. The long_running_with_checkpoints tool and the slow:// resource branch had no callers anywhere; they're deleted, and tool_with_multiple_stream_closes moves out of the shared server into the reworked reconnection test's inline server, which together move the tools/list count assertions from 10 to 8 in five tests. Unreachable handler branches elsewhere become dispatch asserts, since they now fail branch coverage instead of hiding in an untraced subprocess. test_streamable_http_client_resumption's flag-polling loop (while not flag: sleep(0.1), unbounded) is now two anyio.Events awaited under fail_after(5). test_get_sse_stream's 409 assertion is deterministic by construction (the first GET is held open across the second), where the old version's comment admitted it could race. One test was further reworked rather than carried over: test_streamable_http_multiple_reconnections previously paced its server tool with fixed sleeps against the client's retry interval — a timing margin that could silently merge two close/reconnect cycles on a starved runner and break its exact token count. The tool is now gated on the client-side resumption-token callback observing each cycle's reconnect (the priming event is only sent after the server has re-registered the resumed stream, so the count becomes a consequence of causality rather than margins). No sleep remains in it, its retry interval drops to 50ms, and a genuinely failed reconnect now fails loudly at a fail_after(5) instead of silently merging cycles. Deliberately retained elsewhere: the remaining genuinely time-based tests (the elapsed >= 0.4 retry-interval assertion, tool sleeps that create disconnect windows for single-cycle reconnect tests) keep their real-clock behaviour and byte-identical assertions — in-process execution strictly tightens their margins versus the subprocess version. A follow-up may apply the same gating pattern to those remaining sleeps; that's kept out of this PR so the harness swap stays auditable. How Has This Been Tested? ./scripts/test green: 1526 passed, 100% line+branch coverage including tests/, strict-no-cover clean The file 3× solo and together with the four previously-migrated files under pytest-xdist -n 4, 3× — stable; the nine resumption/reconnection tests 5× solo Targeted coverage runs confirm the three de-pragma'd src lines (and both arcs of the branchy one) are executed by this file alone Breaking Changes None — test-only, plus pragma/docstring removals in src. Types of changes Bug fix (non-breaking change which fixes an issue) Checklist I have read the MCP Documentation My code follows the repository's style guidelines New and existing tests pass locally I have added appropriate error handling I have added or updated documentation as needed Additional context Several tests here overlap with tests/interaction/transports/ coverage (resumption-token replay, stream-close auto-reconnect, priming events, standalone GET delivery); deduplication is inventoried for a follow-up ticket, kept out of scope so this diff remains a pure harness swap. AI Disclaimer
Open Graph Description: Final installment of the in-process test migration (#2764, #2765): tests/shared/test_streamable_http.py was the last file spawning uvicorn subprocesses on bind-then-close ports, the pattern that ra...
X Description: Final installment of the in-process test migration (#2764, #2765): tests/shared/test_streamable_http.py was the last file spawning uvicorn subprocesses on bind-then-close ports, the pattern that ra...
Opengraph URL: https://github.com/modelcontextprotocol/python-sdk/pull/2767
X: @github
Domain: github.com
| route-pattern | /:user_id/:repository/pull/:id/files(.:format) |
| route-controller | pull_requests |
| route-action | files |
| fetch-nonce | v2:66586bfd-3ee5-c65a-8370-2b6597e0712e |
| current-catalog-service-hash | ae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b |
| request-id | 96C4:256587:C5AAB4:11C8177:6A5A01EB |
| html-safe-nonce | 1b15ddce5759fdbee233bf7a7e175e75e69c003770ff131385b917e7fe58ac75 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5NkM0OjI1NjU4NzpDNUFBQjQ6MTFDODE3Nzo2QTVBMDFFQiIsInZpc2l0b3JfaWQiOiI4ODAyMTE4MTE1Nzg0MjYyMTIzIiwicmVnaW9uX2VkZ2UiOiJpYWQiLCJyZWdpb25fcmVuZGVyIjoiaWFkIn0= |
| visitor-hmac | 9d5254f700465009b97a69767e86add8fe0484865e4844822dab47fd453306b1 |
| hovercard-subject-tag | pull_request:3791210264 |
| github-keyboard-shortcuts | repository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/modelcontextprotocol/python-sdk/pull/2767/files |
| twitter:image | https://avatars.githubusercontent.com/u/224885523?s=400&v=4 |
| twitter:card | summary_large_image |
| og:image | https://avatars.githubusercontent.com/u/224885523?s=400&v=4 |
| og:image:alt | Final installment of the in-process test migration (#2764, #2765): tests/shared/test_streamable_http.py was the last file spawning uvicorn subprocesses on bind-then-close ports, the pattern that ra... |
| og:site_name | GitHub |
| og:type | object |
| hostname | github.com |
| expected-hostname | github.com |
| None | 4d3f961cfc83a4348ee5be7691cf4ae3515aab3ef5200c13a7e41b048125fb61 |
| turbo-cache-control | no-preview |
| diff-view | unified |
| go-import | github.com/modelcontextprotocol/python-sdk git https://github.com/modelcontextprotocol/python-sdk.git |
| octolytics-dimension-user_id | 182288589 |
| octolytics-dimension-user_login | modelcontextprotocol |
| octolytics-dimension-repository_id | 862584018 |
| octolytics-dimension-repository_nwo | modelcontextprotocol/python-sdk |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 862584018 |
| octolytics-dimension-repository_network_root_nwo | modelcontextprotocol/python-sdk |
| turbo-body-classes | logged-out env-production page-responsive full-width |
| disable-turbo | true |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | ac4d28603af300ac851b62521abb878ab5d3319a |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width