Skip to content

Duplicate initialize is accepted after initialization and returns a new session ID #962

@cclabadmin

Description

@cclabadmin

Bug description

After a normal Streamable HTTP initialization flow, the Java SDK server accepts later initialize requests instead of rejecting them as duplicate initialization for an already-initialized session. The duplicate request returns HTTP 200 and a normal InitializeResult; when the duplicate request uses an older protocol version, that older version is echoed in the result.

The behavior I observed is not a same-session overwrite in the Streamable HTTP transport. Instead, it behaves like fresh session creation: each duplicate initialize creates a new McpStreamableServerSession and returns a new Mcp-Session-Id, even though the client already has an active session.

From a spec perspective, the lifecycle text says initialization is the first interaction and that the operation phase should use the negotiated protocol version and capabilities. The Streamable HTTP transport also defines a session as logically related interactions beginning with initialization, and says a client should start a new session after receiving HTTP 404 for an existing session ID. I do not see text that explicitly defines how a same-endpoint duplicate initialize should be handled, so I am filing this as a lifecycle/session-boundary ambiguity rather than claiming that the spec explicitly requires rejecting duplicate initialize.

I checked the Java SDK implementation path directly:

  • HttpServletStreamableServerTransportProvider handles any JSON-RPC request whose method is initialize by calling sessionFactory.startSession(initializeRequest) and then storing the returned session under init.session().getId().
  • DefaultMcpStreamableServerSessionFactory#startSession creates a new UUID and constructs a new McpStreamableServerSession from that InitializeRequest.
  • McpStreamableServerSession stores clientCapabilities and clientInfo from that request, and later creates McpAsyncServerExchange objects using those values.

So the observed new session IDs are consistent with the current implementation path.

Environment

  • Stable release: v1.1.2 (e9e1a2f34dedb72008d90e9919052d46eb2b701c)
  • main snapshot: from 2026-05-15 (c09ee67f60260bd258b1a1aab9315a647a239d86)
  • Transport: Streamable HTTP server, stateful mode
  • Java runtime used for repro: OpenJDK 21.0.10
  • SDK build target: Java 17 (java.version, maven.compiler.source, and maven.compiler.target are set to 17 in the SDK pom.xml)

Steps to reproduce

  1. Start a Java SDK Streamable HTTP server.
  2. Send an initial initialize request and save the returned Mcp-Session-Id.
  3. Send notifications/initialized with that session ID.
  4. Send a normal request such as ping with that session ID and observe a successful response.
  5. Send a second initialize request after the server is already operational. Include the existing Mcp-Session-Id header to make clear that this is not a client intentionally starting a fresh session without a session ID.
  6. Repeat with changed clientInfo, changed capabilities, or an older protocolVersion such as 2024-11-05.
  7. Observe that each later initialize returns HTTP 200, a normal InitializeResult, and a new Mcp-Session-Id.

In my logs, both the stable release and the 2026-05-15 main snapshot showed:

Second initialize after normal initialization: HTTP 200, InitializeResult returned
Duplicate initialize with changed capabilities/clientInfo: HTTP 200, InitializeResult returned
Duplicate initialize with protocolVersion 2024-11-05: HTTP 200, InitializeResult returned with protocolVersion 2024-11-05
Five repeated later initialize requests: all returned HTTP 200
A later ping still succeeded

Expected behavior

If a client already has an active Streamable HTTP session, I would expect a later initialize sent with that active Mcp-Session-Id to be rejected, for example with a JSON-RPC Invalid Request error or another clear 4xx/JSON-RPC error.

If this behavior is intentional, I would expect it to be documented explicitly, including whether servers should ignore an existing Mcp-Session-Id on an initialize request and issue a new session ID.

Minimal reproducible example

Set ENDPOINT to a Java SDK Streamable HTTP endpoint:

ENDPOINT=http://127.0.0.1:8080/mcp

Initialize and copy the returned Mcp-Session-Id header into SID:

curl -i -sS --http1.1 -X POST "$ENDPOINT" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2025-11-25' \
  --data '{"jsonrpc":"2.0","id":"init-1","method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{"roots":{"listChanged":true},"sampling":{}},"clientInfo":{"name":"repro-initial","version":"1.0.0"}}}'

Send the initialized notification:

curl -i -sS --http1.1 -X POST "$ENDPOINT" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2025-11-25' \
  -H "Mcp-Session-Id: $SID" \
  --data '{"jsonrpc":"2.0","method":"notifications/initialized","params":{}}'

Confirm that the session is operational:

curl -i -sS --http1.1 -X POST "$ENDPOINT" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2025-11-25' \
  -H "Mcp-Session-Id: $SID" \
  --data '{"jsonrpc":"2.0","id":"ping-1","method":"ping","params":{}}'

Now send a second initialize with changed parameters on the already-operational session:

curl -i -sS --http1.1 -X POST "$ENDPOINT" \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -H 'MCP-Protocol-Version: 2025-11-25' \
  -H "Mcp-Session-Id: $SID" \
  --data '{"jsonrpc":"2.0","id":"init-2","method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{"roots":{"listChanged":false}},"clientInfo":{"name":"repro-duplicate","version":"2.0.0"}}}'

Observed result:

HTTP/1.1 200 OK
Mcp-Session-Id: <new-session-id>

{"jsonrpc":"2.0","id":"init-2","result":{"protocolVersion":"2024-11-05", ...}}

I observed that the new Streamable HTTP session's clientInfo and clientCapabilities reflected the duplicate request. I did not observe a stored negotiated-protocol-version field in the Streamable HTTP session object. My concern here is the new session boundary created by a duplicate initialize after the client already had an active session.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions