99import threading
1010import coverage
1111from typing import AsyncGenerator , Generator
12+ from fastapi import FastAPI
1213from mcp .client .session import ClientSession
1314from mcp .client .sse import sse_client
1415from mcp import InitializeResult
1819import uvicorn
1920from fastapi_mcp import FastApiMCP
2021
21- from .fixtures .simple_app import make_simple_fastapi_app
22-
2322
2423HOST = "127.0.0.1"
2524SERVER_NAME = "Test MCP Server"
2625
2726
28- @pytest .fixture
29- def server_port () -> int :
30- with socket .socket () as s :
31- s .bind ((HOST , 0 ))
32- return s .getsockname ()[1 ]
33-
34-
35- @pytest .fixture
36- def server_url (server_port : int ) -> str :
37- return f"http://{ HOST } :{ server_port } "
38-
39-
40- def run_server (server_port : int ) -> None :
27+ def run_server (server_port : int , fastapi_app : FastAPI ) -> None :
4128 # Initialize coverage for subprocesses
4229 cov = None
4330 if "COVERAGE_PROCESS_START" in os .environ :
@@ -72,16 +59,15 @@ def periodic_save():
7259 save_thread .start ()
7360
7461 # Configure the server
75- fastapi = make_simple_fastapi_app ()
7662 mcp = FastApiMCP (
77- fastapi ,
63+ fastapi_app ,
7864 name = SERVER_NAME ,
7965 description = "Test description" ,
8066 )
8167 mcp .mount ()
8268
8369 # Start the server
84- server = uvicorn .Server (config = uvicorn .Config (app = fastapi , host = HOST , port = server_port , log_level = "error" ))
70+ server = uvicorn .Server (config = uvicorn .Config (app = fastapi_app , host = HOST , port = server_port , log_level = "error" ))
8571 server .run ()
8672
8773 # Give server time to start
@@ -94,13 +80,24 @@ def periodic_save():
9480 cov .save ()
9581
9682
97- @pytest .fixture ()
98- def server (server_port : int ) -> Generator [None , None , None ]:
83+ @pytest .fixture (params = [ "simple_fastapi_app" , "simple_fastapi_app_with_root_path" ] )
84+ def server (request : pytest . FixtureRequest ) -> Generator [str , None , None ]:
9985 # Ensure COVERAGE_PROCESS_START is set in the environment for subprocesses
10086 coverage_rc = os .path .abspath (".coveragerc" )
10187 os .environ ["COVERAGE_PROCESS_START" ] = coverage_rc
10288
103- proc = multiprocessing .Process (target = run_server , kwargs = {"server_port" : server_port }, daemon = True )
89+ # Get a free port
90+ with socket .socket () as s :
91+ s .bind ((HOST , 0 ))
92+ server_port = s .getsockname ()[1 ]
93+
94+ # Run the server in a subprocess
95+ fastapi_app = request .getfixturevalue (request .param )
96+ proc = multiprocessing .Process (
97+ target = run_server ,
98+ kwargs = {"server_port" : server_port , "fastapi_app" : fastapi_app },
99+ daemon = True ,
100+ )
104101 proc .start ()
105102
106103 # Wait for server to be running
@@ -117,7 +114,8 @@ def server(server_port: int) -> Generator[None, None, None]:
117114 else :
118115 raise RuntimeError (f"Server failed to start after { max_attempts } attempts" )
119116
120- yield
117+ # Return the server URL
118+ yield f"http://{ HOST } :{ server_port } { fastapi_app .root_path } "
121119
122120 # Signal the server to stop - added graceful shutdown before kill
123121 try :
@@ -134,8 +132,8 @@ def server(server_port: int) -> Generator[None, None, None]:
134132
135133
136134@pytest .fixture ()
137- async def http_client (server : None , server_url : str ) -> AsyncGenerator [httpx .AsyncClient , None ]:
138- async with httpx .AsyncClient (base_url = server_url ) as client :
135+ async def http_client (server : str ) -> AsyncGenerator [httpx .AsyncClient , None ]:
136+ async with httpx .AsyncClient (base_url = server ) as client :
139137 yield client
140138
141139
@@ -165,8 +163,8 @@ async def connection_test() -> None:
165163
166164
167165@pytest .mark .anyio
168- async def test_sse_basic_connection (server : None , server_url : str ) -> None :
169- async with sse_client (server_url + "/mcp" ) as streams :
166+ async def test_sse_basic_connection (server : str ) -> None :
167+ async with sse_client (server + "/mcp" ) as streams :
170168 async with ClientSession (* streams ) as session :
171169 # Test initialization
172170 result = await session .initialize ()
@@ -179,8 +177,8 @@ async def test_sse_basic_connection(server: None, server_url: str) -> None:
179177
180178
181179@pytest .mark .anyio
182- async def test_sse_tool_call (server : None , server_url : str ) -> None :
183- async with sse_client (server_url + "/mcp" ) as streams :
180+ async def test_sse_tool_call (server : str ) -> None :
181+ async with sse_client (server + "/mcp" ) as streams :
184182 async with ClientSession (* streams ) as session :
185183 await session .initialize ()
186184
0 commit comments