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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions