Skip to content

Duplicate initialize with changed parameters can overwrite stored session initialization parameters #961

@cclabadmin

Description

@cclabadmin

Describe the bug

After a normal initialization flow, the server accepts a second initialize request and returns a normal initialize result. The same session then continues to respond to later requests. Repeated initialize requests with changed client capabilities, changed client identity, and an older protocol version were also accepted.

I understand there is a spec-interpretation question here: initialize being the first interaction can be read as an ordering requirement rather than a cardinality requirement. This is framed as a changed-parameter session-state consistency issue rather than a strict spec-violation claim.

To Reproduce

Steps to reproduce the behavior:

  1. Start a Go SDK MCP server.
  2. Complete a normal initialize followed by notifications/initialized.
  3. Send a second initialize request on the same connection or HTTP session.
  4. Repeat with changed capabilities, changed clientInfo, or an older protocolVersion.
  5. Send ping after the duplicate initialization.

Expected behavior

If duplicate initialization is not intentional, the server should reject the second initialize with a JSON-RPC error such as -32600 Invalid Request. If it is intentional, the behavior should be documented, including whether the second request is allowed to replace ServerSession.InitializeParams() after the session is operational.

Logs

Observed with:

  • stable release v1.6.0 (f5f2015413f17f578fbacc839d2c05d0fdb3f1cc)
  • main snapshot da3535b6786149733cd8383f9ffb41191f45a9a6 from 2026-05-15

The same transport-level behavior was reproduced on stdio, stateful Streamable HTTP, and stateless Streamable HTTP:

  • Second initialize accepted
  • Duplicate initialize with an older protocolVersion accepted
  • Five repeated duplicate initialize requests accepted
  • Later ping succeeded

Code:

  • ServerSessionState.InitializeParams stores the parameters from initialize.
  • ServerSession.InitializeParams() exposes that stored value.
  • ServerSession.handle allows methodInitialize through even when state.InitializeParams != nil.
  • ServerSession.initialize assigns state.InitializeParams = params every time it handles an initialize request.
  • notifications/initialized does have duplicate protection, which makes the behavior asymmetric: duplicate initialized notifications are rejected, but duplicate initialize requests are not.

Session state after duplicate initialization

I compared ServerSession.InitializeParams() and ServerSession.ID() before and after a duplicate initialize on the same running session.

Description stdio stateful HTTP stateless HTTP
Same parameters No change No change No change
Changed clientInfo and capabilities Overwrites ClientInfo and Capabilities Overwrites ClientInfo and Capabilities No stored client state observed
Older protocolVersion (2024-11-05) Overwrites ProtocolVersion and ClientInfo Overwrites ProtocolVersion and ClientInfo Response reflects the older version, but stored client state was not observed

On stdio and stateful Streamable HTTP, the second initialize replaces the server's record of the client identity, client capabilities, and client protocol version. This means a session that was operating with 2025-11-25 can have ServerSession.InitializeParams().ProtocolVersion replaced with 2024-11-05, and capability-gated code that reads InitializeParams().Capabilities can observe the later request rather than the original negotiation.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions