|
| 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