@@ -85,25 +85,10 @@ async def handle_call_tool(name: str, args: dict) -> list[TextContent]:
8585 return [TextContent (type = "text" , text = f"Called { name } " )]
8686
8787
88- @pytest .fixture
89- def server_port () -> int :
90- """Find an available port for the test server."""
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 server_url (server_port : int ) -> str :
98- """Get the URL for the test server."""
99- return f"http://127.0.0.1:{ server_port } "
100-
101-
102- def create_app (session_id = None , is_json_response_enabled = False ) -> Starlette :
88+ def create_app (is_json_response_enabled = False ) -> Starlette :
10389 """Create a Starlette application for testing that matches the example server.
10490
10591 Args:
106- session_id: Optional session ID to use for the server.
10792 is_json_response_enabled: If True, use JSON responses instead of SSE streams.
10893 """
10994 # Create server instance
@@ -197,20 +182,19 @@ async def run_server():
197182 return app
198183
199184
200- def run_server (port : int , session_id = None , is_json_response_enabled = False ) -> None :
185+ def run_server (port : int , is_json_response_enabled = False ) -> None :
201186 """Run the test server.
202187
203188 Args:
204189 port: Port to listen on.
205- session_id: Optional session ID to use for the server.
206190 is_json_response_enabled: If True, use JSON responses instead of SSE streams.
207191 """
208192 print (
209193 f"Starting test server on port { port } with "
210- f"session_id= { session_id } , json_enabled={ is_json_response_enabled } "
194+ f"json_enabled={ is_json_response_enabled } "
211195 )
212196
213- app = create_app (session_id , is_json_response_enabled )
197+ app = create_app (is_json_response_enabled )
214198 # Configure server
215199 config = uvicorn .Config (
216200 app = app ,
@@ -238,22 +222,38 @@ def run_server(port: int, session_id=None, is_json_response_enabled=False) -> No
238222 print ("Server shutdown" )
239223
240224
225+ # Test fixtures - using same approach as SSE tests
241226@pytest .fixture
242- def basic_server (server_port : int ) -> Generator [None , None , None ]:
243- """Start a basic server without session ID."""
244- # Start server process
245- process = multiprocessing .Process (
246- target = run_server , kwargs = {"port" : server_port }, daemon = True
227+ def basic_server_port () -> int :
228+ """Find an available port for the basic server."""
229+ with socket .socket () as s :
230+ s .bind (("127.0.0.1" , 0 ))
231+ return s .getsockname ()[1 ]
232+
233+
234+ @pytest .fixture
235+ def json_server_port () -> int :
236+ """Find an available port for the JSON response server."""
237+ with socket .socket () as s :
238+ s .bind (("127.0.0.1" , 0 ))
239+ return s .getsockname ()[1 ]
240+
241+
242+ @pytest .fixture
243+ def basic_server (basic_server_port : int ) -> Generator [None , None , None ]:
244+ """Start a basic server."""
245+ proc = multiprocessing .Process (
246+ target = run_server , kwargs = {"port" : basic_server_port }, daemon = True
247247 )
248- process .start ()
248+ proc .start ()
249249
250- # Wait for server to start
250+ # Wait for server to be running
251251 max_attempts = 20
252252 attempt = 0
253253 while attempt < max_attempts :
254254 try :
255255 with socket .socket (socket .AF_INET , socket .SOCK_STREAM ) as s :
256- s .connect (("127.0.0.1" , server_port ))
256+ s .connect (("127.0.0.1" , basic_server_port ))
257257 break
258258 except ConnectionRefusedError :
259259 time .sleep (0.1 )
@@ -264,30 +264,29 @@ def basic_server(server_port: int) -> Generator[None, None, None]:
264264 yield
265265
266266 # Clean up
267- process . terminate ()
268- process .join (timeout = 1 )
269- if process .is_alive ():
270- process . kill ( )
267+ proc . kill ()
268+ proc .join (timeout = 2 )
269+ if proc .is_alive ():
270+ print ( "server process failed to terminate" )
271271
272272
273273@pytest .fixture
274- def json_response_server (server_port : int ) -> Generator [None , None , None ]:
274+ def json_response_server (json_server_port : int ) -> Generator [None , None , None ]:
275275 """Start a server with JSON response enabled."""
276- # Start server process with is_json_response_enabled=True
277- process = multiprocessing .Process (
276+ proc = multiprocessing .Process (
278277 target = run_server ,
279- kwargs = {"port" : server_port , "is_json_response_enabled" : True },
278+ kwargs = {"port" : json_server_port , "is_json_response_enabled" : True },
280279 daemon = True ,
281280 )
282- process .start ()
281+ proc .start ()
283282
284- # Wait for server to start
283+ # Wait for server to be running
285284 max_attempts = 20
286285 attempt = 0
287286 while attempt < max_attempts :
288287 try :
289288 with socket .socket (socket .AF_INET , socket .SOCK_STREAM ) as s :
290- s .connect (("127.0.0.1" , server_port ))
289+ s .connect (("127.0.0.1" , json_server_port ))
291290 break
292291 except ConnectionRefusedError :
293292 time .sleep (0.1 )
@@ -298,30 +297,42 @@ def json_response_server(server_port: int) -> Generator[None, None, None]:
298297 yield
299298
300299 # Clean up
301- process .terminate ()
302- process .join (timeout = 1 )
303- if process .is_alive ():
304- process .kill ()
300+ proc .kill ()
301+ proc .join (timeout = 2 )
302+ if proc .is_alive ():
303+ print ("server process failed to terminate" )
304+
305+
306+ @pytest .fixture
307+ def basic_server_url (basic_server_port : int ) -> str :
308+ """Get the URL for the basic test server."""
309+ return f"http://127.0.0.1:{ basic_server_port } "
310+
311+
312+ @pytest .fixture
313+ def json_server_url (json_server_port : int ) -> str :
314+ """Get the URL for the JSON response test server."""
315+ return f"http://127.0.0.1:{ json_server_port } "
305316
306317
307318# Basic request validation tests
308- def test_accept_header_validation (basic_server , server_url ):
319+ def test_accept_header_validation (basic_server , basic_server_url ):
309320 """Test that Accept header is properly validated."""
310321 # Test without Accept header
311322 response = requests .post (
312- f"{ server_url } /mcp" ,
323+ f"{ basic_server_url } /mcp" ,
313324 headers = {"Content-Type" : "application/json" },
314325 json = {"jsonrpc" : "2.0" , "method" : "initialize" , "id" : 1 },
315326 )
316327 assert response .status_code == 406
317328 assert "Not Acceptable" in response .text
318329
319330
320- def test_content_type_validation (basic_server , server_url ):
331+ def test_content_type_validation (basic_server , basic_server_url ):
321332 """Test that Content-Type header is properly validated."""
322333 # Test with incorrect Content-Type
323334 response = requests .post (
324- f"{ server_url } /mcp" ,
335+ f"{ basic_server_url } /mcp" ,
325336 headers = {
326337 "Accept" : "application/json, text/event-stream" ,
327338 "Content-Type" : "text/plain" ,
@@ -332,11 +343,11 @@ def test_content_type_validation(basic_server, server_url):
332343 assert "Unsupported Media Type" in response .text
333344
334345
335- def test_json_validation (basic_server , server_url ):
346+ def test_json_validation (basic_server , basic_server_url ):
336347 """Test that JSON content is properly validated."""
337348 # Test with invalid JSON
338349 response = requests .post (
339- f"{ server_url } /mcp" ,
350+ f"{ basic_server_url } /mcp" ,
340351 headers = {
341352 "Accept" : "application/json, text/event-stream" ,
342353 "Content-Type" : "application/json" ,
@@ -347,11 +358,11 @@ def test_json_validation(basic_server, server_url):
347358 assert "Parse error" in response .text
348359
349360
350- def test_json_parsing (basic_server , server_url ):
361+ def test_json_parsing (basic_server , basic_server_url ):
351362 """Test that JSON content is properly parse."""
352363 # Test with valid JSON but invalid JSON-RPC
353364 response = requests .post (
354- f"{ server_url } /mcp" ,
365+ f"{ basic_server_url } /mcp" ,
355366 headers = {
356367 "Accept" : "application/json, text/event-stream" ,
357368 "Content-Type" : "application/json" ,
@@ -362,11 +373,11 @@ def test_json_parsing(basic_server, server_url):
362373 assert "Validation error" in response .text
363374
364375
365- def test_method_not_allowed (basic_server , server_url ):
376+ def test_method_not_allowed (basic_server , basic_server_url ):
366377 """Test that unsupported HTTP methods are rejected."""
367378 # Test with unsupported method (PUT)
368379 response = requests .put (
369- f"{ server_url } /mcp" ,
380+ f"{ basic_server_url } /mcp" ,
370381 headers = {
371382 "Accept" : "application/json, text/event-stream" ,
372383 "Content-Type" : "application/json" ,
@@ -377,13 +388,13 @@ def test_method_not_allowed(basic_server, server_url):
377388 assert "Method Not Allowed" in response .text
378389
379390
380- def test_session_validation (basic_server , server_url ):
391+ def test_session_validation (basic_server , basic_server_url ):
381392 """Test session ID validation."""
382393 # session_id not used directly in this test
383394
384395 # Test without session ID
385396 response = requests .post (
386- f"{ server_url } /mcp" ,
397+ f"{ basic_server_url } /mcp" ,
387398 headers = {
388399 "Accept" : "application/json, text/event-stream" ,
389400 "Content-Type" : "application/json" ,
@@ -452,10 +463,10 @@ def test_streamable_http_transport_init_validation():
452463 StreamableHTTPServerTransport (mcp_session_id = "test\n " )
453464
454465
455- def test_session_termination (basic_server , server_url ):
466+ def test_session_termination (basic_server , basic_server_url ):
456467 """Test session termination via DELETE and subsequent request handling."""
457468 response = requests .post (
458- f"{ server_url } /mcp" ,
469+ f"{ basic_server_url } /mcp" ,
459470 headers = {
460471 "Accept" : "application/json, text/event-stream" ,
461472 "Content-Type" : "application/json" ,
@@ -467,15 +478,15 @@ def test_session_termination(basic_server, server_url):
467478 # Now terminate the session
468479 session_id = response .headers .get (MCP_SESSION_ID_HEADER )
469480 response = requests .delete (
470- f"{ server_url } /mcp" ,
481+ f"{ basic_server_url } /mcp" ,
471482 headers = {MCP_SESSION_ID_HEADER : session_id },
472483 )
473484 assert response .status_code == 200
474485 assert "Session terminated" in response .text
475486
476487 # Try to use the terminated session
477488 response = requests .post (
478- f"{ server_url } /mcp" ,
489+ f"{ basic_server_url } /mcp" ,
479490 headers = {
480491 "Accept" : "application/json, text/event-stream" ,
481492 "Content-Type" : "application/json" ,
@@ -487,9 +498,9 @@ def test_session_termination(basic_server, server_url):
487498 assert "Session has been terminated" in response .text
488499
489500
490- def test_response (basic_server , server_url ):
501+ def test_response (basic_server , basic_server_url ):
491502 """Test response handling for a valid request."""
492- mcp_url = f"{ server_url } /mcp"
503+ mcp_url = f"{ basic_server_url } /mcp"
493504 response = requests .post (
494505 mcp_url ,
495506 headers = {
@@ -518,9 +529,9 @@ def test_response(basic_server, server_url):
518529 assert tools_response .headers .get ("Content-Type" ) == "text/event-stream"
519530
520531
521- def test_json_response (json_response_server , server_url ):
532+ def test_json_response (json_response_server , json_server_url ):
522533 """Test response handling when is_json_response_enabled is True."""
523- mcp_url = f"{ server_url } /mcp"
534+ mcp_url = f"{ json_server_url } /mcp"
524535 response = requests .post (
525536 mcp_url ,
526537 headers = {
@@ -530,4 +541,4 @@ def test_json_response(json_response_server, server_url):
530541 json = INIT_REQUEST ,
531542 )
532543 assert response .status_code == 200
533- assert response .headers .get ("Content-Type" ) == "application/json"
544+ assert response .headers .get ("Content-Type" ) == "application/json"
0 commit comments