diff --git a/src/mcp/server/fastmcp/server.py b/src/mcp/server/fastmcp/server.py index b698b0497..2fe7c1224 100644 --- a/src/mcp/server/fastmcp/server.py +++ b/src/mcp/server/fastmcp/server.py @@ -828,7 +828,6 @@ async def sse_endpoint(request: Request) -> Response: def streamable_http_app(self) -> Starlette: """Return an instance of the StreamableHTTP server app.""" from starlette.middleware import Middleware - from starlette.routing import Mount # Create session manager on first call (lazy initialization) if self._session_manager is None: @@ -841,8 +840,7 @@ def streamable_http_app(self) -> Starlette: ) # Create the ASGI handler - async def handle_streamable_http(scope: Scope, receive: Receive, send: Send) -> None: - await self.session_manager.handle_request(scope, receive, send) + streamable_http_app = StreamableHTTPASGIApp(self._session_manager) # Create routes routes: list[Route | Mount] = [] @@ -889,17 +887,17 @@ async def handle_streamable_http(scope: Scope, receive: Receive, send: Send) -> ) routes.append( - Mount( + Route( self.settings.streamable_http_path, - app=RequireAuthMiddleware(handle_streamable_http, required_scopes, resource_metadata_url), + endpoint=RequireAuthMiddleware(streamable_http_app, required_scopes, resource_metadata_url), ) ) else: # Auth is disabled, no wrapper needed routes.append( - Mount( + Route( self.settings.streamable_http_path, - app=handle_streamable_http, + endpoint=streamable_http_app, ) ) @@ -972,6 +970,18 @@ async def get_prompt(self, name: str, arguments: dict[str, Any] | None = None) - raise ValueError(str(e)) +class StreamableHTTPASGIApp: + """ + ASGI application for Streamable HTTP server transport. + """ + + def __init__(self, session_manager: StreamableHTTPSessionManager): + self.session_manager = session_manager + + async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None: + await self.session_manager.handle_request(scope, receive, send) + + class Context(BaseModel, Generic[ServerSessionT, LifespanContextT, RequestT]): """Context object providing access to MCP capabilities. diff --git a/tests/server/fastmcp/test_server.py b/tests/server/fastmcp/test_server.py index 96cece9c3..a9e0d182a 100644 --- a/tests/server/fastmcp/test_server.py +++ b/tests/server/fastmcp/test_server.py @@ -1072,3 +1072,22 @@ def prompt_fn(name: str) -> str: async with client_session(mcp._mcp_server) as client: with pytest.raises(McpError, match="Missing required arguments"): await client.get_prompt("prompt_fn") + + +def test_streamable_http_no_redirect() -> None: + """Test that streamable HTTP routes are correctly configured.""" + mcp = FastMCP() + app = mcp.streamable_http_app() + + # Find routes by type - streamable_http_app creates Route objects, not Mount objects + streamable_routes = [ + r + for r in app.routes + if isinstance(r, Route) and hasattr(r, "path") and r.path == mcp.settings.streamable_http_path + ] + + # Verify routes exist + assert len(streamable_routes) == 1, "Should have one streamable route" + + # Verify path values + assert streamable_routes[0].path == "/mcp", "Streamable route path should be /mcp"