Repository navigation
MCP SSE Server: Received request before initialization was complete #423
Description
Describe the bug
The MCP server receives Received request before initialization was complete after a second deployment when a client is trying to list the different tools or even when it tries to run a tool on the server.
To Reproduce
SImple main.py which is one of your examples
import httpx
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("My App")
@mcp.tool()
def calculate_bmi(weight_kg: float, height_m: float) -> float:
"""Calculate BMI given weight in kg and height in meters"""
return weight_kg / (height_m**2)
@mcp.tool()
async def fetch_weather(city: str) -> str:
"""Fetch current weather for a city"""
async with httpx.AsyncClient() as client:
response = await client.get(f"https://api.weather.com/{city}")
return response.text
app = mcp.sse_app()-
Start using uvicorn
FASTMCP_LOG_LEVEL=DEBUG uv run uvicorn main:app --port=8001 --host=0.0.0.0 --timeout-graceful-shutdown 5 -
Run the inspector in SSE mode and listen on
http://localhost:8001/see. You will be able to list tools. -
But now, reload you server to simulate a new deployment using the same command:
FASTMCP_LOG_LEVEL=DEBUG uv run uvicorn main:app --port=8001 --host=0.0.0.0 --timeout-graceful-shutdown 5 -
Go to the inspector and try to list the tools, it will timeout and you'll get the following server logs
FASTMCP_LOG_LEVEL=DEBUG uv run uvicorn main:app --port=8001 --host=0.0.0.0 --timeout-graceful-shutdown 5
[04/03/25 23:48:40] DEBUG SseServerTransport initialized with endpoint: /messages/ sse.py:79
INFO: Started server process [89042]
INFO: Waiting for application startup.
INFO: Application startup complete.
INFO: Uvicorn running on http://0.0.0.0:8001 (Press CTRL+C to quit)
[04/03/25 23:48:41] DEBUG Setting up SSE connection sse.py:87
DEBUG Created new session with ID: f55c6f64-be08-464c-bbab-203223adb44c sse.py:100
DEBUG Starting SSE response task sse.py:127
DEBUG Yielding read and write streams sse.py:130
INFO: 127.0.0.1:62930 - "GET /sse HTTP/1.1" 200 OK
DEBUG Starting SSE writer sse.py:107
DEBUG Sent endpoint event: /messages/?session_id=f55c6f64be08464cbbab203223adb44c sse.py:110
DEBUG chunk: b'event: endpoint\r\ndata: /messages/?session_id=f55c6f64be08464cbbab203223adb44c\r\n\r\n' sse.py:156
[04/03/25 23:48:45] DEBUG Handling POST message sse.py:136
DEBUG Parsed session ID: f55c6f64-be08-464c-bbab-203223adb44c sse.py:147
writer: MemoryObjectSendStream(_state=MemoryObjectStreamState(max_buffer_size=0, buffer=deque([]), open_send_channels=1, open_receive_channels=1, waiting_receivers=OrderedDict({<anyio._backends._asyncio.Event object at 0x103ff6f00>: MemoryObjectItemReceiver(task_info=AsyncIOTaskInfo(id=4386289792, name='mcp.shared.session.BaseSession._receive_loop'), item=None)}), waiting_senders=OrderedDict()), _closed=False)
DEBUG Received JSON: b'{"jsonrpc":"2.0","id":3,"method":"tools/list","params":{}}' sse.py:161
DEBUG Validated client message: root=JSONRPCRequest(method='tools/list', params={}, jsonrpc='2.0', id=3) sse.py:165
DEBUG Sending message to writer: root=JSONRPCRequest(method='tools/list', params={}, jsonrpc='2.0', id=3) sse.py:173
INFO: 127.0.0.1:62936 - "POST /messages/?session_id=f55c6f64be08464cbbab203223adb44c HTTP/1.1" 202 Accepted
DEBUG Got event: http.disconnect. Stop streaming. sse.py:177
ERROR: Exception in ASGI application
+ Exception Group Traceback (most recent call last):
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 403, in run_asgi
| result = await app( # type: ignore[func-returns-value]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in __call__
| return await self.app(scope, receive, send)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/applications.py", line 112, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 187, in __call__
| raise exc
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/middleware/errors.py", line 165, in __call__
| await self.app(scope, receive, _send)
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in __call__
| await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
| raise exc
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
| await app(scope, receive, sender)
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/routing.py", line 714, in __call__
| await self.middleware_stack(scope, receive, send)
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/routing.py", line 734, in app
| await route.handle(scope, receive, send)
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/routing.py", line 288, in handle
| await self.app(scope, receive, send)
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/routing.py", line 76, in app
| await wrap_app_handling_exceptions(app, request)(scope, receive, send)
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
| raise exc
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
| await app(scope, receive, sender)
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/starlette/routing.py", line 73, in app
| response = await f(request)
| ^^^^^^^^^^^^^^^^
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/mcp/server/fastmcp/server.py", line 485, in handle_sse
| async with sse.connect_sse(
| File "/Users/xxxxx/.pyenv/versions/3.12.2/lib/python3.12/contextlib.py", line 231, in __aexit__
| await self.gen.athrow(value)
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/mcp/server/sse.py", line 123, in connect_sse
| async with anyio.create_task_group() as tg:
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 772, in __aexit__
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Exception Group Traceback (most recent call last):
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/mcp/server/sse.py", line 131, in connect_sse
| yield (read_stream, write_stream)
| File "/Users/xxxxx.venv/lib/python3.12/site-packages/mcp/server/fastmcp/server.py", line 490, in handle_sse
| await self._mcp_server.run(
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/mcp/server/lowlevel/server.py", line 483, in run
| async with AsyncExitStack() as stack:
| File "/Users/xxxxx/.pyenv/versions/3.12.2/lib/python3.12/contextlib.py", line 754, in __aexit__
| raise exc_details[1]
| File "/Users/xxxxx/.pyenv/versions/3.12.2/lib/python3.12/contextlib.py", line 737, in __aexit__
| cb_suppress = await cb(*exc_details)
| ^^^^^^^^^^^^^^^^^^^^^^
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/mcp/shared/session.py", line 210, in __aexit__
| return await self._task_group.__aexit__(exc_type, exc_val, exc_tb)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 772, in __aexit__
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/mcp/shared/session.py", line 324, in _receive_loop
| await self._received_request(responder)
| File "/Users/xxxxx/.venv/lib/python3.12/site-packages/mcp/server/session.py", line 163, in _received_request
| raise RuntimeError(
| RuntimeError: Received request before initialization was complete
+------------------------------------
[04/03/25 23:48:48] DEBUG Setting up SSE connection sse.py:87
DEBUG Created new session with ID: 1541ccf4-7bf9-407b-b2a2-94552715a527 sse.py:100
DEBUG Starting SSE response task sse.py:127
DEBUG Yielding read and write streams sse.py:130
INFO: 127.0.0.1:62938 - "GET /sse HTTP/1.1" 200 OK
DEBUG Starting SSE writer sse.py:107
DEBUG Sent endpoint event: /messages/?session_id=1541ccf47bf9407bb2a294552715a527 sse.py:110
DEBUG chunk: b'event: endpoint\r\ndata: /messages/?session_id=1541ccf47bf9407bb2a294552715a527\r\n\r\n' sse.py:156
[04/03/25 23:48:55] DEBUG Handling POST message sse.py:136
DEBUG Parsed session ID: 1541ccf4-7bf9-407b-b2a2-94552715a527 sse.py:147
writer: MemoryObjectSendStream(_state=MemoryObjectStreamState(max_buffer_size=0, buffer=deque([]), open_send_channels=1, open_receive_channels=1, waiting_receivers=OrderedDict({<anyio._backends._asyncio.Event object at 0x105916510>: MemoryObjectItemReceiver(task_info=AsyncIOTaskInfo(id=4386290368, name='mcp.shared.session.BaseSession._receive_loop'), item=None)}), waiting_senders=OrderedDict()), _closed=False)
DEBUG Received JSON: b'{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":3,"reason":"Request timed out"}}' sse.py:161
DEBUG Validated client message: root=JSONRPCNotification(method='notifications/cancelled', params={'requestId': 3, 'reason': 'Request timed out'}, jsonrpc='2.0') sse.py:165
DEBUG Sending message to writer: root=JSONRPCNotification(method='notifications/cancelled', params={'requestId': 3, 'reason': 'Request timed out'}, sse.py:173
jsonrpc='2.0')
INFO: 127.0.0.1:62945 - "POST /messages/?session_id=1541ccf47bf9407bb2a294552715a527 HTTP/1.1" 202 Accepted
[04/03/25 23:49:03] DEBUG ping: b': ping - 2025-04-03 21:49:03.579244+00:00\r\n\r\n'
Expected behavior
Somehow, a POST message is accepted while it should not. Is it correct to accept such request?
Shouldn't we have a 404 here instead of a 200 after the second reload? 🤔
python-sdk/src/mcp/server/sse.py
Lines 153 to 157 in c2ca8e0
| writer = self._read_stream_writers.get(session_id) | |
| if not writer: | |
| logger.warning(f"Could not find session for ID: {session_id}") | |
| response = Response("Could not find session", status_code=404) | |
| return await response(scope, receive, send) |
EDIT: I actually tested to comment this line and it works correctly now!
python-sdk/src/mcp/server/session.py
Lines 162 to 165 in c2ca8e0
| if self._initialization_state != InitializationState.Initialized: | |
| raise RuntimeError( | |
| "Received request before initialization was complete" | |
| ) |
Tested with fast-agent, LibreChat and Inspector. Even after a server reload, call tools are still working.
If I understand correctly the global mechanism It should not be possible to have, at this point (after a server reload), a session-id corresponding to a SSE transport that should not have been initialized. So this comment actually is a patch on something that should not happen 😬
Desktop:
- OS: MacOS 15.3
- Browser chrome latest
- Python 3.12
"mcp[cli]>=1.6.0",
"starlette>=0.46.1",
"uvicorn>=0.34.0",
Activity
mheubi commentedon Apr 8, 2025
It's a bug in supergateway, which sends it's own initialize message upon launch - against protocol spec - then forwards the (actual) intitialization message from the client, once it is received.
So the server receives initialize twice.
It's not a bug in the example, it's using the example outside the spec.
...although the example could handle this type of protocol violation more gracefully.
PROOF: If you use mcp-proxy rather than supergateway, the example will work flawlessly
yubigfish commentedon Apr 8, 2025
一个mysql查询功能的mcp,打包到docker中提供sse服务,有如下报错:
ERROR: Exception in ASGI application
| File "/usr/local/lib/python3.12/site-packages/uvicorn/protocols/http/h11_impl.py", line 403, in run_asgi
| result = await app( # type: ignore[func-returns-value]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.12/site-packages/uvicorn/middleware/proxy_headers.py", line 60, in call
| return await self.app(scope, receive, send)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.12/site-packages/starlette/applications.py", line 112, in call
| await self.middleware_stack(scope, receive, send)
| File "/usr/local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 187, in call
| raise exc
| File "/usr/local/lib/python3.12/site-packages/starlette/middleware/errors.py", line 165, in call
| await self.app(scope, receive, _send)
| File "/usr/local/lib/python3.12/site-packages/starlette/middleware/exceptions.py", line 62, in call
| await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
| File "/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
| raise exc
| File "/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
| await app(scope, receive, sender)
| File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 714, in call
| await self.middleware_stack(scope, receive, send)
| File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 734, in app
| await route.handle(scope, receive, send)
| File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 288, in handle
| await self.app(scope, receive, send)
| File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 76, in app
| await wrap_app_handling_exceptions(app, request)(scope, receive, send)
| File "/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 53, in wrapped_app
| raise exc
| File "/usr/local/lib/python3.12/site-packages/starlette/_exception_handler.py", line 42, in wrapped_app
| await app(scope, receive, sender)
| File "/usr/local/lib/python3.12/site-packages/starlette/routing.py", line 73, in app
| response = await f(request)
| ^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.12/site-packages/mcp/server/fastmcp/server.py", line 485, in handle_sse
| async with sse.connect_sse(
| File "/usr/local/lib/python3.12/contextlib.py", line 231, in aexit
| await self.gen.athrow(value)
| File "/usr/local/lib/python3.12/site-packages/mcp/server/sse.py", line 123, in connect_sse
| async with anyio.create_task_group() as tg:
| File "/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 772, in aexit
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Exception Group Traceback (most recent call last):
| File "/usr/local/lib/python3.12/site-packages/mcp/server/sse.py", line 131, in connect_sse
| yield (read_stream, write_stream)
| File "/usr/local/lib/python3.12/site-packages/mcp/server/fastmcp/server.py", line 490, in handle_sse
| await self._mcp_server.run(
| File "/usr/local/lib/python3.12/site-packages/mcp/server/lowlevel/server.py", line 483, in run
| async with AsyncExitStack() as stack:
| File "/usr/local/lib/python3.12/contextlib.py", line 754, in aexit
| raise exc_details[1]
| File "/usr/local/lib/python3.12/contextlib.py", line 737, in aexit
| cb_suppress = await cb(*exc_details)
| ^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.12/site-packages/mcp/shared/session.py", line 210, in aexit
| return await self._task_group.aexit(exc_type, exc_val, exc_tb)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| File "/usr/local/lib/python3.12/site-packages/anyio/_backends/_asyncio.py", line 772, in aexit
| raise BaseExceptionGroup(
| ExceptionGroup: unhandled errors in a TaskGroup (1 sub-exception)
+-+---------------- 1 ----------------
| Traceback (most recent call last):
| File "/usr/local/lib/python3.12/site-packages/mcp/shared/session.py", line 324, in _receive_loop
| await self._received_request(responder)
| File "/usr/local/lib/python3.12/site-packages/mcp/server/session.py", line 163, in _received_request
| raise RuntimeError(
| RuntimeError: Received request before initialization was complete
altmind commentedon Apr 9, 2025
I'm having the same issue with Cursor AI SSE MCP. Getting "RuntimeError: Received request before initialization was complete" upon calling a tool from cursor. Not using FastMCP - plain Starlette app with
log
Works fine with a custom python mcp client:
It seems that cursor does not send initialize on each sse connection - only once when starting the IDE/editing MCP server. And it becomes a problem if the sse connection got dropped or MCP server is restarted. Seems like a cursor error, nvm :)
hungchuntse commentedon Apr 12, 2025
Hi, I’m currently facing an issue and was wondering if you might have any suggestions on how to solve it?
owenliang commentedon Apr 13, 2025
same error
folkvir commentedon Apr 13, 2025
You can use this patch, but this is not a long-term solution as it will obfuscate any other RuntimeError(s) and maybe create other errors that should not happen.
@mheubi Suggested the root cause was the different clients that are not using the specification as they should. Hence 2 initialization messages are sent, or a wrong re-connection workflow.
hungchuntse commentedon Apr 15, 2025
@folkvir Thank you very much for your reply. The error no longer appears.
wilson-urdaneta commentedon Apr 15, 2025
Just wanted to confirm that I was also encountering the exact same RuntimeError:
Received request before initialization was completeerror on the server side when using a Python client based on mcp.client.sse.sse_client and mcp.client.session.ClientSession to connect to a FastMCP server over SSE.Similar to others, the error occurred when the client attempted its first session.call_tool() request after establishing the connection.
Debugging attempts like adding asyncio.sleep() delays (even up to 10 seconds) after creating the ClientSession did not resolve the issue. For reasons that now are obvious (we need to smash the supergateway bug)
Following the suggestion from @altmind 's experience, adding an explicit await session.initialize() call immediately after the ClientSession is created solved the problem reliably.
Here's the structure that worked for my client script:
As of today, it seems explicit initialization via await session.initialize() is crucial for proper synchronization when using the Python client SDK over SSE.
Hope this confirmation helps others debugging this issue!
Nedomas commentedon Apr 16, 2025
@mheubi supergateway bug with double initialization was patched 2 days ago in 2.7.0 if still relevant (refs)
rohitjangid commentedon Apr 22, 2025
I am too facing this error when using cursor as mcp client
folkvir commentedon Apr 23, 2025
@Nedomas Without my patch and with the supergateway 2.7.0 I still get the error when I reload my server.
At this point I think supergateway is working correctly and it is only the different MCP clients that do not regenerate the session correctly when the server connection has been lost that are the cause of the error. In my case this is the MCP clients (anything-llm and fastagent) that are sending a post message for an invalid session. anything-llm is built with the typescript-sdk client so I guess the workflow is correct between the 2 SDKs. As @altmind said, the main problem is for the initialization workflow. The different clients are intializing the session only when starting the server and there is no reconnection workflow. This should be part of the MCP client implementation to handle such disconnection. But it may be possible that a client is not aware of the server disconnection/reload and so if the client sends its POST message the server will crash with our famous
Received request before initialization was complete. Instead we should receive a kind ofreconnection notificationthat is acancel notification(for stopping the current action) + aInitialization Phaserequest to regenerate the current session.I don't know how it can be done easily with the current implementation and specification but what I know is that we should never do
await session.initialize()followed byawait session.<action>everytime. In the worst case we will reinitialize the session everytime we want to call the MCP server. A simple REST HTTP POST with the MCP server answer would have been better at this point.CaoChensy commentedon May 7, 2025
Mark same error
goshut commentedon May 16, 2025
When the server restarts quickly, clients maintaining SSE connections from previous sessions receive RuntimeError: RuntimeError: Received request before initialization was complete.
The root cause is:
Session State Persistence
Clients retain old session IDs after server restart and continue using them for non-initialization requests.
Missing Session Validation
The current _received_request handler in server/session.py doesn't detect stale sessions when processing non-initialization requests (the case _ branch).
Affected Component:
mcp/server/session.py in _received_request method
Server should detect stale sessions after restart
Clients should be notified to reinitialize when using obsolete sessions
Or for performance, provide quick reconnection function
folkvir commentedon May 18, 2025
Unrelated to the issue but, for those interested to completely fix your problem, then use the recommended streamable http protocol implementation in stateless mode with mcp-remote/supergateway as proxy. It should solve your problem.
nafan2008 commentedon May 19, 2025
@folkvir could you please give more specific advice how to implement this?
Can it be resolved on server side only - as in my case I try to connect vscode to my mcp?
11 remaining items
Pin mcp to 1.8.1 to fix SSE initialization bug
Pin mcp to 1.8.1 to fix SSE initialization bug
Add workaround for SSE initialization race condition
fix: clean up SSE session on client disconnect
Add regression test for SSE session cleanup on disconnect
Update test docstring to reference correct issue (modelcontextprotoco…
fix(session): return INVALID_REQUEST error instead of crashing on pre…
MukundaKatta commentedon May 10, 2026
Saw this when our MCP server pod restarted while clients still held the previous SSE connection. The server lost the in-memory session record but the client kept sending against the old session-id and hit "before initialization was complete" on every request.
Two practical fixes: client should re-handshake on any 4xx/5xx instead of retrying the same session-id, and server should emit a clear close frame on shutdown so clients drop the session promptly.
Pluggable session storage upstream (#880) would also fix the root cause but the client-side resilience pattern works today.
laozhangzzz commentedon Jun 25, 2026
fix: switch MCP transport from legacy SSE to Streamable HTTP