Skip to content

MCP SSE Server: Received request before initialization was complete #423

Open

Description

@folkvir

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? 🤔

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!

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

  1. folkvir commented on May 20, 2025

    @folkvir
    Author

    @nafan2008 Just follow their examples: https://github.com/modelcontextprotocol/python-sdk/blob/main/examples/servers/simple-streamablehttp/mcp_simple_streamablehttp/server.py
    And use mcp-remote like that (adapt to your needs):

    {
      "mcpServers": {
        "myserver": {
          "command": "npx",
          "args": [
            "-y",
            "mcp-remote",
            "http://localhost:8000/mcp",
            "--header",
            "Authorization: Bearer <my-access-token-if-needed>"
          ]
        }
      }
    }

    Notes:

  2. LTaooo commented on May 27, 2025

    @LTaooo

    mark

  3. NAVNAV221 commented on May 29, 2025

    @NAVNAV221

    @folkvir It happens with mcp-remote also.
    Even if I remove ~/.mcp-auth folder content, it still get RuntimeError.

  4. bruno-oliveira commented on Jun 2, 2025

    @bruno-oliveira

    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.

    Hey! So I’m using mcp-remote just as you have in the example above with streamable http but I still get the same error

  5. Nedomas commented on Jun 2, 2025

    @Nedomas

    @bruno-oliveira We added Streamable HTTP to Stdio to Supergateway v3.0.1 and previously solved the Received request before initialization was complete bug, so you can test it out. Let me know if it solves it for you

    https://github.com/supercorp-ai/supergateway

  6. added a commit that references this issue on Jun 6, 2025
  7. BobDickinson commented on Jul 31, 2025

    @BobDickinson

    It seems like there are several things going in how people are experiencing this error. Certainly the SuperGateway bug was a factor in some, and the bugs in SSE reconnection are another set. I have a specific instance that is different from these.

    In my case, I can cause this to happen (infrequently), by just sending notifications/initialized and tools/list in quick succession (which is allowed by the protocol and a pretty normal thing to do).

    My assertion is that the server session code in the Python SDK is not properly sequencing the notifications/initialized message that concludes initialization and any following messages. I'm not really sure how the Python code handles this case (in terms of async and message overlap), but it looks like while it's handling the notifications/initialized message the tools/list messages comes it, gets processed, and generates the error (because it hasn't completed initialization yet). I think this might work correctly almost all of the time because the session initialization is fast and usually completes in time.

    I think the correct logic is that if initialization is in process, but not complete, it should wait for completion before processing any other pending messages.

  8. bruno-oliveira commented on Sep 1, 2025

    @bruno-oliveira

    Any update?

  9. warjiang commented on Sep 12, 2025

    @warjiang

    meet the same problem, any updates for official sdk?

  10. added
    bugSomething isn't working
    ready for workEnough information for someone to start working on
    P1Significant bug affecting many users, highly requested feature
    P2Moderate issues affecting some users, edge cases, potentially valuable feature
    and removed
    P1Significant bug affecting many users, highly requested feature
    on Oct 3, 2025
  11. added 2 commits that reference this issue on Mar 2, 2026
    1fce2fd
    c152ca5
  12. MukundaKatta commented on May 10, 2026

    @MukundaKatta

    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.

  13. laozhangzzz commented on Jun 25, 2026

    @laozhangzzz
  14. mohammad-abbasi-sh commented on Nov 8, 2026

    @mohammad-abbasi-sh

    I reproduced this against current main (77001c9): a request sent before initialize is answered with -32602 "Invalid request parameters" and empty data (src/mcp/server/runner.py:211-213). The crash from the original report is gone, but the misleading error shape is still there — exactly what @Luxian flagged.

    I have a fix ready that follows closed PR #2411's approach, re-implemented for the v2 runner: in ServerRunner._on_request, when the method is known but initialization has not been accepted, raise MCPError(code=INVALID_REQUEST, message="Received request before initialization was complete", data="The client must send an 'initialize' request before any other request.") instead of the generic INVALID_PARAMS error. ping stays exempt, unknown methods still get -32601, and genuine parameter-validation failures still get -32602. Tests updated accordingly (3 gate assertions + 1 snapshot); 233 tests pass across the five related files, ruff and pyright clean.

    Opened as PR #3839: #3839

    Per CONTRIBUTING.md, could a maintainer assign #423 to me so the PR can be reviewed? Happy to adjust the approach if the error shape needs a different decision.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Moderate issues affecting some users, edge cases, potentially valuable featurebugSomething isn't workingready for workEnough information for someone to start working on

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions