Skip to content

Commit 7e2a7a5

Browse files
committed
fix: reject duplicate streamable HTTP initialize requests
1 parent c09ee67 commit 7e2a7a5

2 files changed

Lines changed: 96 additions & 0 deletions

File tree

mcp-core/src/main/java/io/modelcontextprotocol/server/transport/HttpServletStreamableServerTransportProvider.java

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -447,6 +447,15 @@ protected void doPost(HttpServletRequest request, HttpServletResponse response)
447447
return;
448448
}
449449

450+
String sessionId = request.getHeader(HttpHeaders.MCP_SESSION_ID);
451+
if (sessionId != null && this.sessions.containsKey(sessionId)) {
452+
this.responseError(response, HttpServletResponse.SC_BAD_REQUEST,
453+
McpError.builder(McpSchema.ErrorCodes.INVALID_REQUEST)
454+
.message("Duplicate initialize request for active session: " + sessionId)
455+
.build());
456+
return;
457+
}
458+
450459
McpSchema.InitializeRequest initializeRequest = jsonMapper.convertValue(jsonrpcRequest.params(),
451460
new TypeRef<McpSchema.InitializeRequest>() {
452461
});
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2026-2026 the original author or authors.
3+
*/
4+
5+
package io.modelcontextprotocol.server.transport;
6+
7+
import io.modelcontextprotocol.server.McpServer;
8+
import io.modelcontextprotocol.spec.HttpHeaders;
9+
import io.modelcontextprotocol.spec.McpSchema;
10+
import io.modelcontextprotocol.spec.ProtocolVersions;
11+
import org.junit.jupiter.api.Test;
12+
13+
import org.springframework.mock.web.MockHttpServletRequest;
14+
import org.springframework.mock.web.MockHttpServletResponse;
15+
16+
import static io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider.APPLICATION_JSON;
17+
import static io.modelcontextprotocol.server.transport.HttpServletStreamableServerTransportProvider.TEXT_EVENT_STREAM;
18+
import static io.modelcontextprotocol.util.McpJsonMapperUtils.JSON_MAPPER;
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
21+
class HttpServletStreamableServerTransportProviderTests {
22+
23+
private static final String MESSAGE_ENDPOINT = "/mcp/message";
24+
25+
@Test
26+
void shouldRejectDuplicateInitializeForActiveSession() throws Exception {
27+
var transportProvider = HttpServletStreamableServerTransportProvider.builder()
28+
.mcpEndpoint(MESSAGE_ENDPOINT)
29+
.build();
30+
var server = McpServer.sync(transportProvider).serverInfo("test-server", "1.0.0").build();
31+
32+
try {
33+
MockHttpServletResponse initialResponse = initialize(transportProvider, null, "init-1",
34+
ProtocolVersions.MCP_2025_11_25, "initial-client");
35+
assertThat(initialResponse.getStatus()).isEqualTo(200);
36+
37+
String sessionId = initialResponse.getHeader(HttpHeaders.MCP_SESSION_ID);
38+
assertThat(sessionId).isNotBlank();
39+
40+
MockHttpServletResponse initializedResponse = sendMessage(transportProvider, sessionId,
41+
new McpSchema.JSONRPCNotification(McpSchema.METHOD_NOTIFICATION_INITIALIZED),
42+
ProtocolVersions.MCP_2025_11_25);
43+
assertThat(initializedResponse.getStatus()).isEqualTo(202);
44+
45+
MockHttpServletResponse duplicateResponse = initialize(transportProvider, sessionId, "init-2",
46+
ProtocolVersions.MCP_2024_11_05, "duplicate-client");
47+
48+
assertThat(duplicateResponse.getStatus()).isEqualTo(400);
49+
assertThat(duplicateResponse.getHeader(HttpHeaders.MCP_SESSION_ID)).isNull();
50+
assertThat(duplicateResponse.getContentAsString()).contains("Duplicate initialize");
51+
}
52+
finally {
53+
server.closeGracefully();
54+
}
55+
}
56+
57+
private static MockHttpServletResponse initialize(HttpServletStreamableServerTransportProvider transportProvider,
58+
String sessionId, String requestId, String protocolVersion, String clientName) throws Exception {
59+
McpSchema.InitializeRequest initializeRequest = McpSchema.InitializeRequest
60+
.builder(protocolVersion, McpSchema.ClientCapabilities.builder().roots(true).build(),
61+
McpSchema.Implementation.builder(clientName, "1.0.0").build())
62+
.build();
63+
McpSchema.JSONRPCRequest jsonRpcRequest = new McpSchema.JSONRPCRequest(McpSchema.METHOD_INITIALIZE, requestId,
64+
initializeRequest);
65+
return sendMessage(transportProvider, sessionId, jsonRpcRequest, protocolVersion);
66+
}
67+
68+
private static MockHttpServletResponse sendMessage(HttpServletStreamableServerTransportProvider transportProvider,
69+
String sessionId, McpSchema.JSONRPCMessage message, String protocolVersion) throws Exception {
70+
byte[] content = JSON_MAPPER.writeValueAsBytes(message);
71+
72+
MockHttpServletRequest request = new MockHttpServletRequest("POST", MESSAGE_ENDPOINT);
73+
request.setContent(content);
74+
request.addHeader("Content-Type", APPLICATION_JSON);
75+
request.addHeader("Content-Length", Integer.toString(content.length));
76+
request.addHeader("Accept", APPLICATION_JSON + ", " + TEXT_EVENT_STREAM);
77+
request.addHeader(HttpHeaders.PROTOCOL_VERSION, protocolVersion);
78+
if (sessionId != null) {
79+
request.addHeader(HttpHeaders.MCP_SESSION_ID, sessionId);
80+
}
81+
82+
MockHttpServletResponse response = new MockHttpServletResponse();
83+
transportProvider.service(request, response);
84+
return response;
85+
}
86+
87+
}

0 commit comments

Comments
 (0)