modelcontextprotocol / modelcontextprotocol/python-sdk
MCP SSE Server: Received request before initialization was complete
Chưa có ai nhận issue này.
- Ngôn ngữ chính
- Python
- Star
- 24.3k
- Fork
- 4k
- Merge trung bình
- 1 ngày 1 giờ
- Pull request đã merge (30 ngày)
- 31
Mô tả
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!
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",
Hướng dẫn đóng góp
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Hướng nghiên cứu
Tái hiện chuỗi triển khai bằng tệp ví dụ main.py và uvicorn, sau đó bắt đầu với mcp/server/sse.py và lần theo luồng request qua fastmcp/server.py, lowlevel/server.py và server/session.py. Theo dõi quá trình thiết lập và khởi tạo session trên kết nối SSE được ghi log; được xem là hoàn tất khi một server được triển khai lại có thể liệt kê hoặc chạy các tool mà không gặp lỗi khởi tạo hoặc timeout.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Đánh giá
- Công nghệ
- python
- Lĩnh vực
- api, backend
- Loại issue
- Lỗi
- Độ khó
- 4/5
- Thời gian dự kiến
- 3-5 ngày
- Mức độ hoạt động
- Ít trao đổi
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 45/100