Update routing for streamable HTTP to avoid 307 redirect (#1115)

Co-authored-by: ihrpr <inna@anthropic.com>
This commit is contained in:
yurikunash
2025-07-11 18:49:24 +08:00
committed by GitHub
parent 0a4e8d4e17
commit fd11eca766
2 changed files with 36 additions and 7 deletions
+17 -7
View File
@@ -828,7 +828,6 @@ class FastMCP:
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 @@ class FastMCP:
)
# 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 @@ class FastMCP:
)
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 @@ class FastMCP:
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.
+19
View File
@@ -1072,3 +1072,22 @@ class TestServerPrompts:
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"