11"""Regression tests for auth context in StreamableHTTP servers."""
22
3+ from __future__ import annotations
4+
35import multiprocessing
6+ import queue
47import socket
58import time
69from collections .abc import Generator
10+ from multiprocessing .queues import Queue
711
12+ import anyio
813import httpx
914import pytest
1015import uvicorn
@@ -58,11 +63,19 @@ def auth_flow(self, request: httpx.Request):
5863 yield request
5964
6065
61- def _create_stateful_auth_app () -> Starlette :
66+ def _create_stateful_auth_app (progress_tokens : Queue [str ] | None = None ) -> Starlette :
67+ async def _handle_progress (ctx : ServerRequestContext , params : object ) -> None :
68+ if progress_tokens is None :
69+ return
70+
71+ access = get_access_token ()
72+ progress_tokens .put (access .token if access else "<none>" )
73+
6274 server = Server (
6375 "auth-test-server" ,
6476 on_call_tool = _handle_whoami ,
6577 on_list_tools = _handle_list_tools ,
78+ on_progress = _handle_progress ,
6679 )
6780 session_manager = StreamableHTTPSessionManager (app = server , stateless = False )
6881 return Starlette (
@@ -75,9 +88,12 @@ def _create_stateful_auth_app() -> Starlette:
7588 )
7689
7790
78- def run_stateful_auth_server (port : int ) -> None : # pragma: no cover
91+ def run_stateful_auth_server (
92+ port : int ,
93+ progress_tokens : Queue [str ] | None = None ,
94+ ) -> None : # pragma: no cover
7995 config = uvicorn .Config (
80- app = _create_stateful_auth_app (),
96+ app = _create_stateful_auth_app (progress_tokens ),
8197 host = "127.0.0.1" ,
8298 port = port ,
8399 log_level = "error" ,
@@ -94,34 +110,45 @@ def stateful_auth_server_port() -> int:
94110
95111
96112@pytest .fixture
97- def stateful_auth_server (stateful_auth_server_port : int ) -> Generator [str , None , None ]:
113+ def stateful_auth_server (
114+ stateful_auth_server_port : int ,
115+ ) -> Generator [tuple [str , Queue [str ]], None , None ]:
116+ progress_tokens : Queue [str ] = multiprocessing .Queue ()
98117 proc = multiprocessing .Process (
99118 target = run_stateful_auth_server ,
100- kwargs = {"port" : stateful_auth_server_port },
119+ kwargs = {
120+ "port" : stateful_auth_server_port ,
121+ "progress_tokens" : progress_tokens ,
122+ },
101123 daemon = True ,
102124 )
103125 proc .start ()
104126 wait_for_server (stateful_auth_server_port )
105127
106128 try :
107- yield f"http://127.0.0.1:{ stateful_auth_server_port } /mcp"
129+ yield f"http://127.0.0.1:{ stateful_auth_server_port } /mcp" , progress_tokens
108130 finally :
109131 proc .terminate ()
110132 proc .join (timeout = 2 )
111133 if proc .is_alive (): # pragma: no cover
112134 proc .kill ()
113135 proc .join (timeout = 1 )
136+ progress_tokens .close ()
137+ progress_tokens .join_thread ()
114138
115139
116140@pytest .mark .anyio
117- async def test_get_access_token_reflects_current_request_in_stateful_session (stateful_auth_server : str ) -> None :
141+ async def test_get_access_token_reflects_current_request_in_stateful_session (
142+ stateful_auth_server : tuple [str , Queue [str ]],
143+ ) -> None :
144+ server_url , _ = stateful_auth_server
118145 auth = _MutableBearerAuth ("token-A" )
119146 async with httpx .AsyncClient (
120147 auth = auth ,
121148 timeout = httpx .Timeout (30 , read = 30 ),
122149 follow_redirects = True ,
123150 ) as http_client :
124- async with streamable_http_client (stateful_auth_server , http_client = http_client ) as (
151+ async with streamable_http_client (server_url , http_client = http_client ) as (
125152 read_stream ,
126153 write_stream ,
127154 ):
@@ -139,3 +166,33 @@ async def test_get_access_token_reflects_current_request_in_stateful_session(sta
139166 assert len (second_response .content ) == 1
140167 assert isinstance (second_response .content [0 ], TextContent )
141168 assert second_response .content [0 ].text == "token-B"
169+
170+
171+ @pytest .mark .anyio
172+ async def test_get_access_token_reflects_current_notification_in_stateful_session (
173+ stateful_auth_server : tuple [str , Queue [str ]],
174+ ) -> None :
175+ server_url , progress_tokens = stateful_auth_server
176+ auth = _MutableBearerAuth ("token-A" )
177+ async with httpx .AsyncClient (
178+ auth = auth ,
179+ timeout = httpx .Timeout (30 , read = 30 ),
180+ follow_redirects = True ,
181+ ) as http_client :
182+ async with streamable_http_client (server_url , http_client = http_client ) as (
183+ read_stream ,
184+ write_stream ,
185+ ):
186+ async with ClientSession (read_stream , write_stream ) as session :
187+ await session .initialize ()
188+
189+ auth .token = "token-B"
190+ await session .send_progress_notification (progress_token = "progress-1" , progress = 1 )
191+
192+ with anyio .fail_after (5 ):
193+ while True :
194+ try :
195+ assert progress_tokens .get_nowait () == "token-B"
196+ break
197+ except queue .Empty :
198+ await anyio .sleep (0.01 )
0 commit comments