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
- Start a Java SDK Streamable HTTP server.
- Send an initial
initialize request and save the returned Mcp-Session-Id.
- Send
notifications/initialized with that session ID.
- Send a normal request such as
ping with that session ID and observe a successful response.
- 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.
- Repeat with changed
clientInfo, changed capabilities, or an older protocolVersion such as 2024-11-05.
- 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.
Bug description
After a normal Streamable HTTP initialization flow, the Java SDK server accepts later
initializerequests instead of rejecting them as duplicate initialization for an already-initialized session. The duplicate request returns HTTP 200 and a normalInitializeResult; 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
initializecreates a newMcpStreamableServerSessionand returns a newMcp-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
initializeshould be handled, so I am filing this as a lifecycle/session-boundary ambiguity rather than claiming that the spec explicitly requires rejecting duplicateinitialize.I checked the Java SDK implementation path directly:
HttpServletStreamableServerTransportProviderhandles any JSON-RPC request whose method isinitializeby callingsessionFactory.startSession(initializeRequest)and then storing the returned session underinit.session().getId().DefaultMcpStreamableServerSessionFactory#startSessioncreates a new UUID and constructs a newMcpStreamableServerSessionfrom thatInitializeRequest.McpStreamableServerSessionstoresclientCapabilitiesandclientInfofrom that request, and later createsMcpAsyncServerExchangeobjects using those values.So the observed new session IDs are consistent with the current implementation path.
Environment
v1.1.2(e9e1a2f34dedb72008d90e9919052d46eb2b701c)mainsnapshot: from 2026-05-15 (c09ee67f60260bd258b1a1aab9315a647a239d86)21.0.10java.version,maven.compiler.source, andmaven.compiler.targetare set to17in the SDKpom.xml)Steps to reproduce
initializerequest and save the returnedMcp-Session-Id.notifications/initializedwith that session ID.pingwith that session ID and observe a successful response.initializerequest after the server is already operational. Include the existingMcp-Session-Idheader to make clear that this is not a client intentionally starting a fresh session without a session ID.clientInfo, changedcapabilities, or an olderprotocolVersionsuch as2024-11-05.initializereturns HTTP 200, a normalInitializeResult, and a newMcp-Session-Id.In my logs, both the stable release and the 2026-05-15
mainsnapshot showed:Expected behavior
If a client already has an active Streamable HTTP session, I would expect a later
initializesent with that activeMcp-Session-Idto be rejected, for example with a JSON-RPCInvalid Requesterror 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-Idon aninitializerequest and issue a new session ID.Minimal reproducible example
Set
ENDPOINTto a Java SDK Streamable HTTP endpoint:Initialize and copy the returned
Mcp-Session-Idheader intoSID:Send the initialized notification:
Confirm that the session is operational:
Now send a second
initializewith changed parameters on the already-operational session:Observed result:
I observed that the new Streamable HTTP session's
clientInfoandclientCapabilitiesreflected 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 duplicateinitializeafter the client already had an active session.