Jeremiah Lowin commited on
Commit
58bd353
·
1 Parent(s): f827f24

test nested servers

Browse files
pyproject.toml CHANGED
@@ -83,7 +83,7 @@ asyncio_mode = "auto"
83
  asyncio_default_fixture_loop_scope = "session"
84
  asyncio_default_test_loop_scope = "session"
85
  filterwarnings = []
86
- timeout = 5
87
 
88
  [tool.pyright]
89
  include = ["src", "tests"]
 
83
  asyncio_default_fixture_loop_scope = "session"
84
  asyncio_default_test_loop_scope = "session"
85
  filterwarnings = []
86
+ timeout = 3
87
 
88
  [tool.pyright]
89
  include = ["src", "tests"]
src/fastmcp/server/http.py CHANGED
@@ -110,7 +110,7 @@ def setup_auth_middleware_and_routes(
110
  def create_base_app(
111
  routes: list[Route | Mount],
112
  middleware: list[Middleware],
113
- debug: bool,
114
  lifespan: Callable | None = None,
115
  ) -> Starlette:
116
  """Create a base Starlette app with common middleware and routes.
@@ -127,17 +127,12 @@ def create_base_app(
127
  # Always add RequestContextMiddleware as the outermost middleware
128
  middleware.append(Middleware(RequestContextMiddleware))
129
 
130
- # Create the app
131
- app_kwargs = {
132
- "debug": debug,
133
- "routes": routes,
134
- "middleware": middleware,
135
- }
136
-
137
- if lifespan:
138
- app_kwargs["lifespan"] = lifespan
139
-
140
- return Starlette(**app_kwargs)
141
 
142
 
143
  def create_sse_app(
@@ -224,7 +219,11 @@ def create_sse_app(
224
  routes.extend(cast(list[Route | Mount], additional_routes))
225
 
226
  # Create and return the app
227
- return create_base_app(routes, middleware, debug)
 
 
 
 
228
 
229
 
230
  def create_streamable_http_app(
@@ -305,4 +304,9 @@ def create_streamable_http_app(
305
  yield
306
 
307
  # Create and return the app with lifespan
308
- return create_base_app(routes, middleware, debug, lifespan)
 
 
 
 
 
 
110
  def create_base_app(
111
  routes: list[Route | Mount],
112
  middleware: list[Middleware],
113
+ debug: bool = False,
114
  lifespan: Callable | None = None,
115
  ) -> Starlette:
116
  """Create a base Starlette app with common middleware and routes.
 
127
  # Always add RequestContextMiddleware as the outermost middleware
128
  middleware.append(Middleware(RequestContextMiddleware))
129
 
130
+ return Starlette(
131
+ routes=routes,
132
+ middleware=middleware,
133
+ debug=debug,
134
+ lifespan=lifespan,
135
+ )
 
 
 
 
 
136
 
137
 
138
  def create_sse_app(
 
219
  routes.extend(cast(list[Route | Mount], additional_routes))
220
 
221
  # Create and return the app
222
+ return create_base_app(
223
+ routes=routes,
224
+ middleware=middleware,
225
+ debug=debug,
226
+ )
227
 
228
 
229
  def create_streamable_http_app(
 
304
  yield
305
 
306
  # Create and return the app with lifespan
307
+ return create_base_app(
308
+ routes=routes,
309
+ middleware=middleware,
310
+ debug=debug,
311
+ lifespan=lifespan,
312
+ )
tests/client/test_streamable_http.py CHANGED
@@ -5,6 +5,8 @@ from collections.abc import Generator
5
  import pytest
6
  import uvicorn
7
  from mcp.types import TextResourceContents
 
 
8
 
9
  from fastmcp.client import Client
10
  from fastmcp.client.transports import StreamableHttpTransport
@@ -100,3 +102,40 @@ async def test_http_headers(streamable_http_server: str):
100
  json_result = json.loads(raw_result[0].text)
101
  assert "x-demo-header" in json_result
102
  assert json_result["x-demo-header"] == "ABC"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  import pytest
6
  import uvicorn
7
  from mcp.types import TextResourceContents
8
+ from starlette.applications import Starlette
9
+ from starlette.routing import Mount
10
 
11
  from fastmcp.client import Client
12
  from fastmcp.client.transports import StreamableHttpTransport
 
102
  json_result = json.loads(raw_result[0].text)
103
  assert "x-demo-header" in json_result
104
  assert json_result["x-demo-header"] == "ABC"
105
+
106
+
107
+ def run_nested_server(host: str, port: int) -> None:
108
+ try:
109
+ mcp_app = fastmcp_server().streamable_http_app()
110
+
111
+ mount = Starlette(routes=[Mount("/nest-inner", app=mcp_app)])
112
+ mount2 = Starlette(
113
+ routes=[Mount("/nest-outer", app=mount)],
114
+ lifespan=mcp_app.router.lifespan_context,
115
+ )
116
+ server = uvicorn.Server(
117
+ config=uvicorn.Config(
118
+ app=mount2,
119
+ host=host,
120
+ port=port,
121
+ log_level="error",
122
+ lifespan="on",
123
+ )
124
+ )
125
+ server.run()
126
+ except Exception as e:
127
+ print(f"Server error: {e}")
128
+ sys.exit(1)
129
+ sys.exit(0)
130
+
131
+
132
+ async def test_nested_streamable_http_server_resolves_correctly():
133
+ # tests patch for
134
+ # https://github.com/modelcontextprotocol/python-sdk/pull/659
135
+
136
+ with run_server_in_process(run_nested_server) as url:
137
+ async with Client(
138
+ transport=StreamableHttpTransport(f"{url}/nest-outer/nest-inner/mcp")
139
+ ) as client:
140
+ result = await client.ping()
141
+ assert result is True