Skip to content

Commit be87b12

Browse files
committed
fix: restore test_883_middleware and exclude server helpers from coverage
- Resurrects tests/issues/test_883_middleware.py with AsyncClient fixes - Marks server helper functions in test_streamable_http.py as no cover because subprocess coverage is not reliable in CI
1 parent adace0b commit be87b12

File tree

2 files changed

+64
-9
lines changed

2 files changed

+64
-9
lines changed
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import pytest
2+
from starlette.applications import Starlette
3+
from starlette.middleware import Middleware
4+
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint
5+
from starlette.requests import Request
6+
from starlette.responses import Response
7+
from httpx import AsyncClient, ASGITransport
8+
9+
from mcp.server.mcpserver import MCPServer
10+
from mcp.server.transport_security import TransportSecuritySettings
11+
12+
13+
class MockMiddleware(BaseHTTPMiddleware):
14+
async def dispatch(self, request: Request, call_next: RequestResponseEndpoint) -> Response:
15+
return await call_next(request)
16+
17+
18+
@pytest.mark.anyio
19+
async def test_883_middleware_sse_no_assertion_error():
20+
"""Test that using MCP SSE with Starlette middleware doesn't cause double-response error."""
21+
mcp_server = MCPServer("test-server")
22+
transport_security = TransportSecuritySettings(enable_dns_rebinding_protection=False)
23+
# Using host="0.0.0.0" avoids auto-protection triggering logic for localhost
24+
sse_app = mcp_server.sse_app(transport_security=transport_security, host="0.0.0.0")
25+
26+
app = Starlette(middleware=[Middleware(MockMiddleware)])
27+
# Mount at root to simplify test paths
28+
app.mount("/", sse_app)
29+
30+
# Use ASGITransport to properly test the ASGI app stack
31+
transport = ASGITransport(app=app)
32+
async with AsyncClient(transport=transport, base_url="http://testserver") as client:
33+
async with client.stream("GET", "/sse") as response:
34+
assert response.status_code == 200
35+
assert "text/event-stream" in response.headers["content-type"]
36+
# Consume stream a bit or close immediately
37+
pass
38+
39+
40+
@pytest.mark.anyio
41+
async def test_883_middleware_post_accepted():
42+
"""Test that POST messages work with middleware."""
43+
mcp_server = MCPServer("test-server")
44+
transport_security = TransportSecuritySettings(enable_dns_rebinding_protection=False)
45+
sse_app = mcp_server.sse_app(transport_security=transport_security, host="0.0.0.0")
46+
47+
app = Starlette(middleware=[Middleware(MockMiddleware)])
48+
app.mount("/", sse_app)
49+
50+
transport = ASGITransport(app=app)
51+
async with AsyncClient(transport=transport, base_url="http://testserver") as client:
52+
response = await client.post(
53+
"/messages/?session_id=00000000000000000000000000000000",
54+
json={"jsonrpc": "2.0", "method": "notifications/initialized", "params": {}},
55+
)
56+
# 404 is expected here as we didn't establish a real session
57+
assert response.status_code == 404

tests/shared/test_streamable_http.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ async def replay_events_after( # pragma: no cover
125125

126126

127127
# Test server implementation that follows MCP protocol
128-
class ServerTest(Server):
128+
class ServerTest(Server): # pragma: no cover
129129
def __init__(self):
130130
super().__init__(SERVER_NAME)
131131
self._lock = None # Will be initialized in async context
@@ -383,11 +383,11 @@ async def handle_call_tool(name: str, args: dict[str, Any]) -> list[TextContent]
383383
return [TextContent(type="text", text=f"Called {name}")]
384384

385385

386-
def create_app(
386+
def create_app( # pragma: no cover
387387
is_json_response_enabled: bool = False,
388388
event_store: EventStore | None = None,
389389
retry_interval: int | None = None,
390-
) -> Starlette: # pragma: no cover
390+
) -> Starlette:
391391
"""Create a Starlette application for testing using the session manager.
392392
393393
Args:
@@ -422,12 +422,12 @@ def create_app(
422422
return app
423423

424424

425-
def run_server(
425+
def run_server( # pragma: no cover
426426
port: int,
427427
is_json_response_enabled: bool = False,
428428
event_store: EventStore | None = None,
429429
retry_interval: int | None = None,
430-
) -> None: # pragma: no cover
430+
) -> None:
431431
"""Run the test server.
432432
433433
Args:
@@ -957,8 +957,6 @@ def test_get_validation(basic_server: None, basic_server_url: str):
957957
assert "Not Acceptable" in response.text
958958

959959

960-
961-
962960
@pytest.fixture
963961
async def initialized_client_session(basic_server: None, basic_server_url: str):
964962
"""Create initialized StreamableHTTP client session."""
@@ -1379,7 +1377,7 @@ async def sampling_callback(
13791377

13801378

13811379
# Context-aware server implementation for testing request context propagation
1382-
class ContextAwareServerTest(Server):
1380+
class ContextAwareServerTest(Server): # pragma: no cover
13831381
def __init__(self):
13841382
super().__init__("ContextAwareServer")
13851383

@@ -1439,7 +1437,7 @@ async def handle_call_tool(name: str, args: dict[str, Any]) -> list[TextContent]
14391437

14401438

14411439
# Server runner for context-aware testing
1442-
def run_context_aware_server(port: int):
1440+
def run_context_aware_server(port: int): # pragma: no cover
14431441
"""Run the context-aware test server."""
14441442
server = ContextAwareServerTest()
14451443

0 commit comments

Comments
 (0)