modelcontextprotocol / modelcontextprotocol/python-sdk

MCP SSE Server: Received request before initialization was complete

Offen
#423 25 Kommentare 29 Reaktionen 0 zugewiesene Personen Auf GitHub ansehen

Dieses Issue hat noch niemand übernommen.

bug P2 ready for work
Vorherrschende Sprache
Python
Sterne
24.3k
Forks
4k
Ø Merge
1 T. 1 Std.
Gemergte PRs (30 T.)
31

Beschreibung

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? 🤔 https://github.com/modelcontextprotocol/python-sdk/blob/c2ca8e03e046908935d089a2ceed4e80b0c29a24/src/mcp/server/sse.py#L153-L157

EDIT: I actually tested to comment this line and it works correctly now!

https://github.com/modelcontextprotocol/python-sdk/blob/c2ca8e03e046908935d089a2ceed4e80b0c29a24/src/mcp/server/session.py#L162-L165

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",

Beitragsleitfaden

Beitragsleitfaden öffnen

Erste Schritte

  1. Lies das ganze Issue und danach den Beitragsleitfaden des Projekts.
  2. Schreib ins Issue, dass du es übernimmst — das erspart doppelte Arbeit.
  3. Forke das Repository und arbeite in einem Branch.
  4. Öffne einen Pull Request, der die Issue-Nummer nennt.

Rechercherichtung

Stelle die Deployment-Sequenz mit der Beispiel-Datei main.py und uvicorn nach und beginne dann mit mcp/server/sse.py sowie dem Request-Flow durch fastmcp/server.py, lowlevel/server.py und server/session.py. Verfolge die Einrichtung und Initialisierung der Session über die protokollierte SSE-Verbindung hinweg; als abgeschlossen gilt die Aufgabe, wenn ein erneut deployter Server Tools auflisten oder ausführen kann, ohne dass der Initialisierungsfehler oder ein Timeout auftritt.

Vom Indexierungsmodell aus dem Issue-Text verfasst.

Bewertung

Tech-Stack
python
Bereich
api, backend
Issue-Typ
Bug
Schwierigkeit
4/5
Geschätzter Aufwand
3-5 Tage
Aktivitätsstatus
Ruhig
Klarheit
Größtenteils klar
Anfängerfreundlichkeit
45/100

Neue Issues direkt in Ihr Postfach

Eine kurze Übersicht über anfängerfreundliche GitHub-Issues.