-
Notifications
You must be signed in to change notification settings - Fork 2.7k
Open
Labels
P1Significant bug affecting many users, highly requested featureSignificant bug affecting many users, highly requested featurebugSomething isn't workingSomething isn't workingready for workEnough information for someone to start working onEnough information for someone to start working on
Milestone
Description
Bug Report: FastMCP RuntimeError: Received request before initialization was complete Leading to Empty SSE Responses When Embedded in FastAPI
**Library Version:** `mcp[cli]==1.8.0` (Python SDK)
**Python Version:** 3.11.x
**Web Framework:** FastAPI (e.g., 0.100+)
**ASGI Server:** Uvicorn
**Problem Description:**
When embedding `FastMCP.streamable_http_app()` as a sub-application within a FastAPI application and managing its lifecycle via FastAPI's `lifespan` context manager (using `asyncio.TaskGroup` to run `FastMCP` components), we encounter a persistent `RuntimeError: Received request before initialization was complete`.
This error occurs specifically when an MCP tool is called by a client and is expected to return data via Server-Sent Events (SSE).
The sequence of events observed is:
1. Client establishes a session successfully (receives `mcp-session-id` header, often with an initial 400 "Missing session ID" response which is then handled).
2. Client makes a `tools/call` request with the valid `mcp-session-id`.
3. The MCP server (FastAPI with FastMCP mounted) sends HTTP 200 OK headers with `Content-Type: text/event-stream`.
4. **Immediately after sending the 200 OK headers, before any `event: message` or `data:` lines are sent in the SSE stream, the `mcp.server.streamable_http_manager.StreamableHTTPSessionManager` logs that it is "shutting down."**
5. The server then logs a `RuntimeError: Received request before initialization was complete` (traceback points to `mcp/server/session.py`).
6. The client receives the 200 OK but with an empty response body, as the SSE stream was closed before any data events were sent.
This issue occurs even if the tool is designed to return a very simple JSON payload (e.g., `{"result_id": "some_id"}`). The tool's Python code that prepares the `List[ContentPart]` often appears not to be fully executed, or at least its return value is not successfully processed for SSE streaming due to this premature shutdown.
**Steps to Reproduce (Conceptual - based on our integration pattern):**
1. **`app.py` (FastAPI main application):**
* A global `FastMCP` instance (`mcp_server`) is created. To avoid Uvicorn port conflicts when FastMCP's internal server mechanisms are invoked, we've tried initializing `FastMCP` with `host="127.0.0.1", port=0`.
* Tool modules (e.g., `core_tools.py` with a simple `log_decision` tool returning `[ContentPart(text="...")]`) are imported *after* the global `mcp_server` is created, so decorators use this instance.
* The FastAPI `lifespan` context manager is used:
* It obtains `mcp_server.streamable_http_app()` and mounts it at a sub-path (e.g., `/mcp`).
* It attempts to manage the lifecycle of FastMCP's services. Our current best approach (which starts the session manager but still leads to the runtime error) involves:
```python
# (Inside lifespan)
# Ensure session manager is created by accessing streamable_http_app first
starlette_app = mcp_server.streamable_http_app()
if mcp_server._session_manager is None:
raise RuntimeError("Session manager not initialized by streamable_http_app")
async with mcp_server._session_manager.run(): # Manages StreamableHTTPSessionManager
# We previously also tried running mcp_server.run_streamable_http_async()
# in an asyncio.TaskGroup here, but that method unconditionally
# tries to start its own Uvicorn server, causing port conflicts
# unless host/port in FastMCP settings are None/0.
# However, even then, the "initialization not complete" error persists
# for actual data return.
logger.info("FastAPI Lifespan: FastMCP components (attempted) started.")
yield
```
2. **Client Script:**
* Establishes a session with the `/mcp/mcp/` endpoint (receives `mcp-session-id` header).
* Makes a `tools/call` POST request to `/mcp/mcp/` with the `mcp-session-id` header, targeting a simple tool that should return `List[ContentPart]`.
* Client expects an SSE stream.
**Expected Behavior:**
The tool executes, and its `List[ContentPart]` result is serialized and streamed back to the client as SSE `data:` payloads after the initial 200 OK and SSE headers. The `StreamableHTTPSessionManager` should remain active throughout this process for the given session.
**Actual Behavior:**
* HTTP 200 OK is received by the client.
* `Content-Type: text/event-stream` header is present.
* The response body is empty.
* Server logs show `StreamableHTTPSessionManager` shutting down.
* Server logs show `RuntimeError: Received request before initialization was complete` originating from `mcp/server/session.py`.
* The actual tool code that generates the `List[ContentPart]` might not be reached or its result is not processed.
**Analysis & Hypothesis:**
It appears there's a race condition or an incomplete initialization of a required component (possibly the `RequestProcessor` or its internal `anyio.TaskGroup`) when FastMCP is embedded and its lifecycle is managed externally by FastAPI's lifespan. The `StreamableHTTPSessionManager` (started via `async with _session_manager.run()`) might be ready to accept HTTP connections and send initial headers, but the underlying machinery to fully process the tool call and stream results is not yet (or no longer) in a ready state when the request is internally dispatched after the initial HTTP handshake.
The method `FastMCP.run_streamable_http_async()` is documented to "initialize all necessary components, including the RequestProcessor and its task group." However, this method also unconditionally starts its own Uvicorn server. We have not found parameters for `run_streamable_http_async()` or the `FastMCP` constructor that would allow it to perform *only* the service/task-group initializations without starting a new HTTP server instance, which is necessary for embedding within an existing FastAPI application managed by its own Uvicorn process.
The example `mcp/server/streamable_http_stateless_demo/server.py` in the SDK shows direct use of `StreamableHTTPSessionManager` with `mcp.server.lowlevel.Server`, and its lifespan correctly manages `session_manager.run()`. It seems the `FastMCP` wrapper around these components might have an issue when its `run_streamable_http_async` is not the primary server entry point.
**Question to SDK Authors:**
1. What is the recommended pattern to fully initialize all FastMCP services (including `RequestProcessor` and its task group) when `FastMCP.streamable_http_app()` is mounted as a sub-application in a parent ASGI framework like FastAPI, ensuring that FastMCP does not attempt to start its own HTTP server?
2. Is the `RuntimeError: Received request before initialization was complete` indicative of a specific component's lifecycle not being correctly managed in such an embedded scenario?
3. Are there specific parameters for `FastMCP.__init__` or an alternative to `run_streamable_http_async` for performing a "service-only" initialization suitable for embedding?
Any guidance or clarification on the intended lifecycle management for embedded `FastMCP` instances would be greatly appreciated. We believe resolving this will significantly improve its usability with frameworks like FastAPI.
Thank you for your work on this SDK.
ch-xhr, thomaswetzler, salvatore-081, Nx5, jryan-coatue and 4 more
Metadata
Metadata
Assignees
Labels
P1Significant bug affecting many users, highly requested featureSignificant bug affecting many users, highly requested featurebugSomething isn't workingSomething isn't workingready for workEnough information for someone to start working onEnough information for someone to start working on