modelcontextprotocol / modelcontextprotocol/python-sdk
Duplicate `initialize` with changed parameters can overwrite `ServerSession.client_params`
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ả
Initial Checks
- I confirm that I'm using the latest version of MCP Python SDK
- I confirm that I searched for my issue in https://github.com/modelcontextprotocol/python-sdk/issues before opening this issue
Description
After a normal MCP initialization flow, a Python SDK server accepts another initialize request on the same live connection/session and returns a normal initialize result.
I agree that the spec does not explicitly say every repeated initialize must be rejected. My concern is narrower: after the session has already moved into normal operation, a later same-session initialize with different parameters can update the server's stored client initialization parameters.
The MCP lifecycle documentation describes initialization as the first client/server interaction and says the operation phase uses the negotiated protocol version and capabilities. In that model, silently changing the stored client identity/capabilities/protocol version after operation has begun seems surprising unless the SDK intentionally supports same-session renegotiation.
src/mcp/server/session.pystores the client initialization request inServerSession._client_params.ServerSession.client_paramsexposes that value.- In both the stable release and the
mainsnapshot I tested, theInitializeRequesthandler assignsself._client_params = params. ServerSession.check_client_capability()later readsself._client_params.capabilities, so a duplicateinitializewith differentcapabilitiescan affect capability-gated server logic.
What I observed
Reproduced on stable v1.27.1 (77431ebe7dda9ed0c61451b22d3e7f8d981bc092):
stdio: a secondinitializewas accepted afternotifications/initialized; a laterpingstill succeeded.- stateful Streamable HTTP: a second
initializewas accepted afternotifications/initialized; a laterpingstill succeeded. - stateless Streamable HTTP: a second
initializewas accepted afternotifications/initialized; a laterpingstill succeeded.
For stateful Streamable HTTP on v1.27.1, I added a local diagnostic tool that reads ctx.session.client_params before and after the duplicate initialize on the same session:
Duplicate initialize parameters |
Observed result |
|---|---|
| Same parameters | No state change |
Changed clientInfo and capabilities |
client_params.clientInfo and client_params.capabilities changed |
Older protocolVersion (2024-11-05) |
client_params.protocolVersion and client_params.clientInfo changed |
On the frozen main snapshot (161834d4aee2633c42d3976c8f8751b6c4d947d5), the same duplicate-initialize acceptance still reproduced. My diagnostic snapshot directly observed client_params.capabilities changing after a duplicate initialize with changed capabilities on both stdio and stateful Streamable HTTP.
Expected behavior
If same-session duplicate initialization is not intended, I would expect the server to reject the later initialize after normal operation has begun, for example with a JSON-RPC -32600 Invalid Request error.
If this tolerant behavior is intentional, I think it should be either:
- a no-op once the session has already initialized, especially for changed parameters, or
- documented as same-session renegotiation, including whether
ServerSession.client_params, client capabilities, client identity, and protocol version are allowed to change.
Example Code
import json
try:
# Current main branch naming.
from mcp.server.mcpserver import Context, MCPServer
except ImportError:
# Latest stable release naming.
from mcp.server.fastmcp import Context, FastMCP as MCPServer
mcp = MCPServer("dup-init-repro")
def field(obj, *names):
if obj is None:
return None
for name in names:
if hasattr(obj, name):
return getattr(obj, name)
return None
def dump(value):
if value is None:
return None
if hasattr(value, "model_dump"):
return value.model_dump(mode="json", by_alias=True)
return value
@mcp.tool()
async def session_snapshot(ctx: Context) -> str:
params = ctx.session.client_params
return json.dumps(
{
"protocolVersion": field(params, "protocolVersion", "protocol_version"),
"clientInfo": dump(field(params, "clientInfo", "client_info")),
"clientCapabilities": dump(field(params, "capabilities")),
},
separators=(",", ":"),
default=str,
)
if __name__ == "__main__":
mcp.run(transport="streamable-http")
'''
Request sequence:
1. Start the server.
2. Send a normal `initialize`.
3. Send `notifications/initialized`.
4. Call the `session_snapshot` tool and record the returned JSON.
5. Send another `initialize` on the same live session, but change `clientInfo` and/or `capabilities`.
6. Call `session_snapshot` again on the same session.
On `v1.27.1`, the second snapshot reflects the changed `clientInfo` / `capabilities`. On the `main` snapshot I tested, the second snapshot still reflects changed `capabilities`.
'''
Python & MCP Python SDK
- Python: `3.12.3`
- MCP Python SDK: `mcp==1.27.1` (`v1.27.1`, `77431ebe7dda9ed0c61451b22d3e7f8d981bc092`)
- Also reproduced on `main` snapshot `161834d4aee2633c42d3976c8f8751b6c4d947d5` (`v1.25.0-159-g161834d4`, pinned 2026-05-11)
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
Bắt đầu với src/mcp/server/session.py, đặc biệt là handler của InitializeRequest và ServerSession.client_params. Tái hiện chuỗi khởi tạo trùng lặp được mô tả trong issue trên cả stdio và Streamable HTTP stateful hoặc stateless, sau đó kiểm tra cách các tham số của client được đọc. Được xem là hoàn tất khi hành vi khởi tạo trùng lặp đã chọn nhất quán và không có tham số nào của client bị ghi đè ngoài ý muốn.
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
- Sôi nổi
- Độ rõ ràng
- Khá rõ ràng
- Mức phù hợp với người mới
- 48/100