diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/TestUtil.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/TestUtil.java new file mode 100644 index 00000000..7ce98056 --- /dev/null +++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/TestUtil.java @@ -0,0 +1,31 @@ +/* +* Copyright 2025 - 2025 the original author or authors. +*/ +package io.modelcontextprotocol; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; + +public class TestUtil { + + TestUtil() { + // Prevent instantiation + } + + /** + * Finds an available port on the local machine. + * @return an available port number + * @throws IllegalStateException if no available port can be found + */ + public static int findAvailablePort() { + try (final ServerSocket socket = new ServerSocket()) { + socket.bind(new InetSocketAddress(0)); + return socket.getLocalPort(); + } + catch (final IOException e) { + throw new IllegalStateException("Cannot bind to an available port!", e); + } + } + +} diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java index 76f908b8..dc61f1c6 100644 --- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java +++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/WebFluxSseIntegrationTests.java @@ -50,9 +50,9 @@ import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.mock; -public class WebFluxSseIntegrationTests { +class WebFluxSseIntegrationTests { - private static final int PORT = 8182; + private static final int PORT = TestUtil.findAvailablePort(); private static final String CUSTOM_SSE_ENDPOINT = "/somePath/sse"; @@ -133,7 +133,7 @@ void testCreateMessageWithoutSamplingCapabilities(String clientType) { @ParameterizedTest(name = "{0} : {displayName} ") @ValueSource(strings = { "httpclient", "webflux" }) - void testCreateMessageSuccess(String clientType) throws InterruptedException { + void testCreateMessageSuccess(String clientType) { var clientBuilder = clientBuilders.get(clientType); @@ -189,8 +189,7 @@ void testCreateMessageSuccess(String clientType) throws InterruptedException { CallToolResult response = mcpClient.callTool(new McpSchema.CallToolRequest("tool1", Map.of())); - assertThat(response).isNotNull(); - assertThat(response).isEqualTo(callResponse); + assertThat(response).isNotNull().isEqualTo(callResponse); } mcpServer.close(); } @@ -417,8 +416,7 @@ void testToolCallSuccess(String clientType) { CallToolResult response = mcpClient.callTool(new McpSchema.CallToolRequest("tool1", Map.of())); - assertThat(response).isNotNull(); - assertThat(response).isEqualTo(callResponse); + assertThat(response).isNotNull().isEqualTo(callResponse); } mcpServer.close(); diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpAsyncServerTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpAsyncServerTests.java index 98844c74..fdeb0fd7 100644 --- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpAsyncServerTests.java +++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpAsyncServerTests.java @@ -5,6 +5,8 @@ package io.modelcontextprotocol.server; import com.fasterxml.jackson.databind.ObjectMapper; + +import io.modelcontextprotocol.TestUtil; import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider; import io.modelcontextprotocol.spec.McpServerTransportProvider; import org.junit.jupiter.api.Timeout; @@ -23,7 +25,7 @@ @Timeout(15) // Giving extra time beyond the client timeout class WebFluxSseMcpAsyncServerTests extends AbstractMcpAsyncServerTests { - private static final int PORT = 8181; + private static final int PORT = TestUtil.findAvailablePort(); private static final String MESSAGE_ENDPOINT = "/mcp/message"; diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpSyncServerTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpSyncServerTests.java index 71072855..a3937bad 100644 --- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpSyncServerTests.java +++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpSyncServerTests.java @@ -5,6 +5,8 @@ package io.modelcontextprotocol.server; import com.fasterxml.jackson.databind.ObjectMapper; + +import io.modelcontextprotocol.TestUtil; import io.modelcontextprotocol.server.transport.WebFluxSseServerTransportProvider; import io.modelcontextprotocol.spec.McpServerTransportProvider; import org.junit.jupiter.api.Timeout; @@ -23,7 +25,7 @@ @Timeout(15) // Giving extra time beyond the client timeout class WebFluxSseMcpSyncServerTests extends AbstractMcpSyncServerTests { - private static final int PORT = 8182; + private static final int PORT = TestUtil.findAvailablePort(); private static final String MESSAGE_ENDPOINT = "/mcp/message"; diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/TomcatTestUtil.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/TomcatTestUtil.java index fcd7fb4d..233646e6 100644 --- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/TomcatTestUtil.java +++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/TomcatTestUtil.java @@ -3,6 +3,10 @@ */ package io.modelcontextprotocol.server; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; + import org.apache.catalina.Context; import org.apache.catalina.startup.Tomcat; @@ -14,10 +18,14 @@ */ public class TomcatTestUtil { + TomcatTestUtil() { + // Prevent instantiation + } + public record TomcatServer(Tomcat tomcat, AnnotationConfigWebApplicationContext appContext) { } - public TomcatServer createTomcatServer(String contextPath, int port, Class componentClass) { + public static TomcatServer createTomcatServer(String contextPath, int port, Class componentClass) { // Set up Tomcat first var tomcat = new Tomcat(); @@ -57,4 +65,19 @@ public TomcatServer createTomcatServer(String contextPath, int port, Class co return new TomcatServer(tomcat, appContext); } + /** + * Finds an available port on the local machine. + * @return an available port number + * @throws IllegalStateException if no available port can be found + */ + public static int findAvailablePort() { + try (final ServerSocket socket = new ServerSocket()) { + socket.bind(new InetSocketAddress(0)); + return socket.getLocalPort(); + } + catch (final IOException e) { + throw new IllegalStateException("Cannot bind to an available port!", e); + } + } + } diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseAsyncServerTransportTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseAsyncServerTransportTests.java index 08d5de67..4d322bc1 100644 --- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseAsyncServerTransportTests.java +++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseAsyncServerTransportTests.java @@ -25,7 +25,7 @@ class WebMvcSseAsyncServerTransportTests extends AbstractMcpAsyncServerTests { private static final String MESSAGE_ENDPOINT = "/mcp/message"; - private static final int PORT = 8181; + private static final int PORT = TomcatTestUtil.findAvailablePort(); private Tomcat tomcat; @@ -73,7 +73,6 @@ protected McpServerTransportProvider createMcpTransportProvider() { // Create DispatcherServlet with our Spring context DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext); - // dispatcherServlet.setThrowExceptionIfNoHandlerFound(true); // Add servlet to Tomcat and get the wrapper var wrapper = Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet); diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseCustomContextPathTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseCustomContextPathTests.java index 0e81104b..0b46874b 100644 --- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseCustomContextPathTests.java +++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseCustomContextPathTests.java @@ -22,11 +22,11 @@ import static org.assertj.core.api.Assertions.assertThat; -public class WebMvcSseCustomContextPathTests { +class WebMvcSseCustomContextPathTests { private static final String CUSTOM_CONTEXT_PATH = "/app/1"; - private static final int PORT = 8183; + private static final int PORT = TomcatTestUtil.findAvailablePort(); private static final String MESSAGE_ENDPOINT = "/mcp/message"; @@ -39,11 +39,11 @@ public class WebMvcSseCustomContextPathTests { @BeforeEach public void before() { - tomcatServer = new TomcatTestUtil().createTomcatServer(CUSTOM_CONTEXT_PATH, PORT, TestConfig.class); + tomcatServer = TomcatTestUtil.createTomcatServer(CUSTOM_CONTEXT_PATH, PORT, TestConfig.class); try { tomcatServer.tomcat().start(); - assertThat(tomcatServer.tomcat().getServer().getState() == LifecycleState.STARTED); + assertThat(tomcatServer.tomcat().getServer().getState()).isEqualTo(LifecycleState.STARTED); } catch (Exception e) { throw new RuntimeException("Failed to start Tomcat", e); diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java index be01365a..2d18cd23 100644 --- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java +++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseIntegrationTests.java @@ -46,7 +46,7 @@ class WebMvcSseIntegrationTests { - private static final int PORT = 8183; + private static final int PORT = TomcatTestUtil.findAvailablePort(); private static final String MESSAGE_ENDPOINT = "/mcp/message"; @@ -75,7 +75,7 @@ public RouterFunction routerFunction(WebMvcSseServerTransportPro @BeforeEach public void before() { - tomcatServer = new TomcatTestUtil().createTomcatServer("", PORT, TestConfig.class); + tomcatServer = TomcatTestUtil.createTomcatServer("", PORT, TestConfig.class); try { tomcatServer.tomcat().start(); @@ -151,7 +151,7 @@ void testCreateMessageWithoutSamplingCapabilities() { } @Test - void testCreateMessageSuccess() throws InterruptedException { + void testCreateMessageSuccess() { Function samplingHandler = request -> { assertThat(request.messages()).hasSize(1); diff --git a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseSyncServerTransportTests.java b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseSyncServerTransportTests.java index b85bed37..196c12dd 100644 --- a/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseSyncServerTransportTests.java +++ b/mcp-spring/mcp-spring-webmvc/src/test/java/io/modelcontextprotocol/server/WebMvcSseSyncServerTransportTests.java @@ -24,7 +24,7 @@ class WebMvcSseSyncServerTransportTests extends AbstractMcpSyncServerTests { private static final String MESSAGE_ENDPOINT = "/mcp/message"; - private static final int PORT = 8181; + private static final int PORT = TomcatTestUtil.findAvailablePort(); private Tomcat tomcat; @@ -72,7 +72,6 @@ protected WebMvcSseServerTransportProvider createMcpTransportProvider() { // Create DispatcherServlet with our Spring context DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext); - // dispatcherServlet.setThrowExceptionIfNoHandlerFound(true); // Add servlet to Tomcat and get the wrapper var wrapper = Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet); diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerCustomContextPathTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerCustomContextPathTests.java index 212a3c95..2cd62889 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerCustomContextPathTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerCustomContextPathTests.java @@ -4,6 +4,7 @@ package io.modelcontextprotocol.server.transport; import com.fasterxml.jackson.databind.ObjectMapper; + import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; import io.modelcontextprotocol.server.McpServer; @@ -17,9 +18,9 @@ import static org.assertj.core.api.Assertions.assertThat; -public class HttpServletSseServerCustomContextPathTests { +class HttpServletSseServerCustomContextPathTests { - private static final int PORT = 8195; + private static final int PORT = TomcatTestUtil.findAvailablePort(); private static final String CUSTOM_CONTEXT_PATH = "/api/v1"; @@ -48,7 +49,7 @@ public void before() { try { tomcat.start(); - assertThat(tomcat.getServer().getState() == LifecycleState.STARTED); + assertThat(tomcat.getServer().getState()).isEqualTo(LifecycleState.STARTED); } catch (Exception e) { throw new RuntimeException("Failed to start Tomcat", e); diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProviderIntegrationTests.java b/mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProviderIntegrationTests.java index a7b63482..93b4aff6 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProviderIntegrationTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProviderIntegrationTests.java @@ -11,6 +11,7 @@ import java.util.function.Function; import com.fasterxml.jackson.databind.ObjectMapper; + import io.modelcontextprotocol.client.McpClient; import io.modelcontextprotocol.client.transport.HttpClientSseClientTransport; import io.modelcontextprotocol.server.McpServer; @@ -43,9 +44,9 @@ import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.mock; -public class HttpServletSseServerTransportProviderIntegrationTests { +class HttpServletSseServerTransportProviderIntegrationTests { - private static final int PORT = 8189; + private static final int PORT = TomcatTestUtil.findAvailablePort(); private static final String CUSTOM_SSE_ENDPOINT = "/somePath/sse"; @@ -69,7 +70,7 @@ public void before() { tomcat = TomcatTestUtil.createTomcatServer("", PORT, mcpServerTransportProvider); try { tomcat.start(); - assertThat(tomcat.getServer().getState() == LifecycleState.STARTED); + assertThat(tomcat.getServer().getState()).isEqualTo(LifecycleState.STARTED); } catch (Exception e) { throw new RuntimeException("Failed to start Tomcat", e); @@ -132,7 +133,7 @@ void testCreateMessageWithoutSamplingCapabilities() { } @Test - void testCreateMessageSuccess() throws InterruptedException { + void testCreateMessageSuccess() { Function samplingHandler = request -> { assertThat(request.messages()).hasSize(1); @@ -186,8 +187,7 @@ void testCreateMessageSuccess() throws InterruptedException { CallToolResult response = mcpClient.callTool(new McpSchema.CallToolRequest("tool1", Map.of())); - assertThat(response).isNotNull(); - assertThat(response).isEqualTo(callResponse); + assertThat(response).isNotNull().isEqualTo(callResponse); } mcpServer.close(); } @@ -391,8 +391,7 @@ void testToolCallSuccess() { CallToolResult response = mcpClient.callTool(new McpSchema.CallToolRequest("tool1", Map.of())); - assertThat(response).isNotNull(); - assertThat(response).isEqualTo(callResponse); + assertThat(response).isNotNull().isEqualTo(callResponse); } mcpServer.close(); diff --git a/mcp/src/test/java/io/modelcontextprotocol/server/transport/TomcatTestUtil.java b/mcp/src/test/java/io/modelcontextprotocol/server/transport/TomcatTestUtil.java index 6f922dfa..f61cdc41 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/transport/TomcatTestUtil.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/transport/TomcatTestUtil.java @@ -3,19 +3,23 @@ */ package io.modelcontextprotocol.server.transport; -import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; + import jakarta.servlet.Servlet; import org.apache.catalina.Context; -import org.apache.catalina.LifecycleState; import org.apache.catalina.startup.Tomcat; -import static org.junit.Assert.assertThat; - /** * @author Christian Tzolov */ public class TomcatTestUtil { + TomcatTestUtil() { + // Prevent instantiation + } + public static Tomcat createTomcatServer(String contextPath, int port, Servlet servlet) { var tomcat = new Tomcat(); @@ -24,7 +28,6 @@ public static Tomcat createTomcatServer(String contextPath, int port, Servlet se String baseDir = System.getProperty("java.io.tmpdir"); tomcat.setBaseDir(baseDir); - // Context context = tomcat.addContext("", baseDir); Context context = tomcat.addContext(contextPath, baseDir); // Add transport servlet to Tomcat @@ -42,4 +45,19 @@ public static Tomcat createTomcatServer(String contextPath, int port, Servlet se return tomcat; } + /** + * Finds an available port on the local machine. + * @return an available port number + * @throws IllegalStateException if no available port can be found + */ + public static int findAvailablePort() { + try (final ServerSocket socket = new ServerSocket()) { + socket.bind(new InetSocketAddress(0)); + return socket.getLocalPort(); + } + catch (final IOException e) { + throw new IllegalStateException("Cannot bind to an available port!", e); + } + } + }