Repository navigation
Duplicate initialize with changed parameters can overwrite ServerSession.client_params #2605
Description
Activity
- addedtriageQueued for automated analysis — bot will process and remove this labelQueued for automated analysis — bot will process and remove this label
on May 31, 2026 - ghost addedready for workEnough information for someone to start working onEnough information for someone to start working on
on Jun 1, 2026 - ghost addedP2Moderate issues affecting some users, edge cases, potentially valuable featureModerate issues affecting some users, edge cases, potentially valuable feature
on Jun 1, 2026 - ghost addedfix proposedBot has a verified fix diff in the commentBot has a verified fix diff in the comment
on Jun 1, 2026 - ghost removedtriageQueued for automated analysis — bot will process and remove this labelQueued for automated analysis — bot will process and remove this label
on Jun 1, 2026 - added 3 commits that reference this issue
on Jun 26, 2026 I’m going to work on a focused fix for this: reject a duplicate
initializeafter the session is initialized, and add a regression that the originalclient_paramsstay intact.Confirmed against current
main:_on_requestinsrc/mcp/server/runner.pycommitsself.connection.client_params, self.connection.protocol_version = self._negotiate_initialize(params)on every successfulinitializecall, with no check for whether the connection was already initialized._handle_initializedoesn't guard either, so a secondinitializewith differentclientInfo/capabilitiessilently overwrites the negotiated params for the life of the connection.On the two open PRs:
- fix(server): reject changed duplicate initialize #3078 modifies
ServerSession._received_requestinsrc/mcp/server/session.py(_client_params,_initialization_state). That internal state machine isn't where initialize is handled anymore —session.py'sclient_paramsis now a thin read-only proxy ontoConnection.client_params(src/mcp/server/connection.py), and the actual handling lives inrunner.py. The PR predates that move, so it doesn't apply to currentmain. - fix: reject changed duplicate initialize #2606 patches the right spot (
_handle_initializeinrunner.py), guarding onself.connection.client_params is not None and init != self.connection.client_params. That's correct for the common case and its test (test_runner_rejects_changed_duplicate_initialize) matches the architecture. One thing worth checking before merge: the guard runs inside_handle_initialize, which executes before the state commit at the end of_on_request(line ~239), so a rejected duplicate correctly leavesconnection.client_paramsuntouched — but it's worth an explicit test asserting the unchanged value post-rejection (matching the issue title), not just that an error is raised.
Proposed approach if this needs a fresh PR: same guard as #2606 (compare negotiated
InitializeRequestParamsagainstself.connection.client_paramswhen already set, reject withINVALID_REQUESTon mismatch, no-op-accept on an identical repeat), plus a regression test intests/server/test_runner.pythat explicitly assertsconnection.client_paramsis unchanged after the rejected duplicate.AI disclosure: this comment and the accompanying analysis were produced with AI assistance (Claude Code), reviewed by me before posting.
- fix(server): reject changed duplicate initialize #3078 modifies
Initial Checks
Description
After a normal MCP initialization flow, a Python SDK server accepts another
initializerequest on the same live connection/session and returns a normalinitializeresult.I agree that the spec does not explicitly say every repeated
initializemust be rejected. My concern is narrower: after the session has already moved into normal operation, a later same-sessioninitializewith 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.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.initializewas accepted afternotifications/initialized; a laterpingstill succeeded.initializewas accepted afternotifications/initialized; a laterpingstill succeeded.For stateful Streamable HTTP on
v1.27.1, I added a local diagnostic tool that readsctx.session.client_paramsbefore and after the duplicateinitializeon the same session:initializeparametersclientInfoandcapabilitiesclient_params.clientInfoandclient_params.capabilitieschangedprotocolVersion(2024-11-05)client_params.protocolVersionandclient_params.clientInfochangedOn the frozen
mainsnapshot (161834d4aee2633c42d3976c8f8751b6c4d947d5), the same duplicate-initialize acceptance still reproduced. My diagnostic snapshot directly observedclient_params.capabilitieschanging after a duplicateinitializewith changed capabilities on bothstdioand stateful Streamable HTTP.Expected behavior
If same-session duplicate initialization is not intended, I would expect the server to reject the later
initializeafter normal operation has begun, for example with a JSON-RPC-32600 Invalid Requesterror.If this tolerant behavior is intentional, I think it should be either:
ServerSession.client_params, client capabilities, client identity, and protocol version are allowed to change.Example Code
Python & MCP Python SDK