11"""Regression tests for auth context in StreamableHTTP servers."""
22
3+ import multiprocessing
4+ import socket
35import time
46from collections .abc import Generator
57
68import httpx
79import pytest
10+ import uvicorn
811from starlette .applications import Starlette
912from starlette .middleware import Middleware
1013from starlette .middleware .authentication import AuthenticationMiddleware
2528 TextContent ,
2629 Tool ,
2730)
28- from tests .test_helpers import run_uvicorn_in_thread
31+ from tests .test_helpers import wait_for_server
2932
3033
3134class _EchoTokenVerifier :
@@ -55,15 +58,14 @@ def auth_flow(self, request: httpx.Request):
5558 yield request
5659
5760
58- @pytest .fixture
59- def stateful_auth_server () -> Generator [str , None , None ]:
61+ def _create_stateful_auth_app () -> Starlette :
6062 server = Server (
6163 "auth-test-server" ,
6264 on_call_tool = _handle_whoami ,
6365 on_list_tools = _handle_list_tools ,
6466 )
6567 session_manager = StreamableHTTPSessionManager (app = server , stateless = False )
66- app = Starlette (
68+ return Starlette (
6769 routes = [Mount ("/mcp" , app = session_manager .handle_request )],
6870 middleware = [
6971 Middleware (AuthenticationMiddleware , backend = BearerAuthBackend (_EchoTokenVerifier ())),
@@ -72,8 +74,43 @@ def stateful_auth_server() -> Generator[str, None, None]:
7274 lifespan = lambda app : session_manager .run (),
7375 )
7476
75- with run_uvicorn_in_thread (app , host = "127.0.0.1" , log_level = "error" ) as base_url :
76- yield f"{ base_url } /mcp"
77+
78+ def run_stateful_auth_server (port : int ) -> None : # pragma: no cover
79+ config = uvicorn .Config (
80+ app = _create_stateful_auth_app (),
81+ host = "127.0.0.1" ,
82+ port = port ,
83+ log_level = "error" ,
84+ access_log = False ,
85+ )
86+ uvicorn .Server (config ).run ()
87+
88+
89+ @pytest .fixture
90+ def stateful_auth_server_port () -> int :
91+ with socket .socket () as s :
92+ s .bind (("127.0.0.1" , 0 ))
93+ return s .getsockname ()[1 ]
94+
95+
96+ @pytest .fixture
97+ def stateful_auth_server (stateful_auth_server_port : int ) -> Generator [str , None , None ]:
98+ proc = multiprocessing .Process (
99+ target = run_stateful_auth_server ,
100+ kwargs = {"port" : stateful_auth_server_port },
101+ daemon = True ,
102+ )
103+ proc .start ()
104+ wait_for_server (stateful_auth_server_port )
105+
106+ try :
107+ yield f"http://127.0.0.1:{ stateful_auth_server_port } /mcp"
108+ finally :
109+ proc .terminate ()
110+ proc .join (timeout = 2 )
111+ if proc .is_alive (): # pragma: no cover
112+ proc .kill ()
113+ proc .join (timeout = 1 )
77114
78115
79116@pytest .mark .anyio
0 commit comments