modelcontextprotocol / modelcontextprotocol/python-sdk

MCP SSE Server: Received request before initialization was complete

Aberta
#423 25 comentários 29 reações 0 responsáveis Ver no GitHub

Ninguém assumiu esta issue ainda.

bug P2 ready for work
Linguagem predominante
Python
Estrelas
24.3k
Forks
4k
Merge médio
1d 1h
PRs com merge (30d)
31

Descrição

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

Guia de contribuição

Abrir o guia de contribuição

Primeiros passos

  1. Leia a issue inteira e depois o guia de contribuição do projeto.
  2. Comente na issue dizendo que vai assumir — evita que duas pessoas façam o mesmo trabalho.
  3. Faça um fork do repositório e trabalhe em uma branch.
  4. Abra um pull request que referencie o número da issue.

Direção de pesquisa

Reproduza a sequência de deployment com o arquivo de exemplo main.py e uvicorn e, em seguida, comece por mcp/server/sse.py e acompanhe o fluxo da requisição por fastmcp/server.py, lowlevel/server.py e server/session.py. Rastreie a configuração e a inicialização da sessão ao longo da conexão SSE registrada; o trabalho estará concluído quando um servidor reimplantado puder listar ou executar ferramentas sem o erro de inicialização ou um timeout.

Escrita pelo modelo de indexação a partir do texto da issue.

Avaliação

Stack de tecnologia
python
Domínio
api, backend
Tipo de issue
Bug
Dificuldade
4/5
Tempo estimado
3-5 dias
Status de atividade
Pouca atividade
Clareza
Razoavelmente clara
Facilidade para iniciantes
45/100

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.