From f7899b2dc47e1ab6abaccb6f56ce5161c5134d52 Mon Sep 17 00:00:00 2001 From: "a.darafeyeu" Date: Wed, 9 Apr 2025 19:43:29 +0200 Subject: [PATCH 1/3] feat(test): adds findAvailablePort for test server --- ...ervletSseServerCustomContextPathTests.java | 8 +++--- ...rverTransportProviderIntegrationTests.java | 15 +++++----- .../server/transport/TomcatTestUtil.java | 28 +++++++++++++++---- 3 files changed, 34 insertions(+), 17 deletions(-) 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 1254e2ad..4fd383f9 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerCustomContextPathTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerCustomContextPathTests.java @@ -4,11 +4,11 @@ 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; import io.modelcontextprotocol.spec.McpSchema; -import org.apache.catalina.Context; import org.apache.catalina.LifecycleException; import org.apache.catalina.LifecycleState; import org.apache.catalina.startup.Tomcat; @@ -18,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"; @@ -49,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 e34baf9d..1e0f395a 100644 --- a/mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProviderIntegrationTests.java +++ b/mcp/src/test/java/io/modelcontextprotocol/server/transport/HttpServletSseServerTransportProviderIntegrationTests.java @@ -10,6 +10,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; @@ -42,9 +43,9 @@ import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.mock; -public class HttpServletSseServerTransportProviderIntegrationTests { +class HttpServletSseServerTransportProviderIntegrationTests { - private static final int PORT = 8185; + private static final int PORT = TomcatTestUtil.findAvailablePort(); private static final String CUSTOM_SSE_ENDPOINT = "/somePath/sse"; @@ -68,7 +69,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); @@ -127,7 +128,7 @@ void testCreateMessageWithoutSamplingCapabilities() { } @Test - void testCreateMessageSuccess() throws InterruptedException { + void testCreateMessageSuccess() { // Client @@ -185,8 +186,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); mcpClient.close(); mcpServer.close(); @@ -395,8 +395,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); mcpClient.close(); 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); + } + } + } From f9807930f6cdda4a7aa396858f9a48f9293674fd Mon Sep 17 00:00:00 2001 From: "a.darafeyeu" Date: Wed, 9 Apr 2025 20:05:49 +0200 Subject: [PATCH 2/3] feat(test): adds findAvailablePort for test server... --- .../modelcontextprotocol/server/TestUtil.java | 31 +++++++++++++++++++ .../server/WebFluxSseMcpAsyncServerTests.java | 2 +- .../server/WebFluxSseMcpSyncServerTests.java | 2 +- .../server/TomcatTestUtil.java | 25 ++++++++++++++- .../WebMvcSseAsyncServerTransportTests.java | 3 +- .../WebMvcSseCustomContextPathTests.java | 8 ++--- .../server/WebMvcSseIntegrationTests.java | 6 ++-- .../WebMvcSseSyncServerTransportTests.java | 3 +- 8 files changed, 66 insertions(+), 14 deletions(-) create mode 100644 mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/TestUtil.java diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/TestUtil.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/TestUtil.java new file mode 100644 index 00000000..0085f31e --- /dev/null +++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/TestUtil.java @@ -0,0 +1,31 @@ +/* +* Copyright 2025 - 2025 the original author or authors. +*/ +package io.modelcontextprotocol.server; + +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/server/WebFluxSseMcpAsyncServerTests.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/WebFluxSseMcpAsyncServerTests.java index 98844c74..cc33e7b9 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 @@ -23,7 +23,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..2fc10453 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 @@ -23,7 +23,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 d5c9f90f..1fcecd8a 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(); @@ -142,7 +142,7 @@ void testCreateMessageWithoutSamplingCapabilities() { } @Test - void testCreateMessageSuccess() throws InterruptedException { + void testCreateMessageSuccess() { // Client 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); From 1c7225e1a6c0253f14db48f91d733c63aa181c1c Mon Sep 17 00:00:00 2001 From: "a.darafeyeu" Date: Wed, 9 Apr 2025 20:08:42 +0200 Subject: [PATCH 3/3] feat(test): adds findAvailablePort for test server... --- .../modelcontextprotocol/{server => }/TestUtil.java | 2 +- .../WebFluxSseIntegrationTests.java | 12 +++++------- .../server/WebFluxSseMcpAsyncServerTests.java | 2 ++ .../server/WebFluxSseMcpSyncServerTests.java | 2 ++ 4 files changed, 10 insertions(+), 8 deletions(-) rename mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/{server => }/TestUtil.java (94%) diff --git a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/TestUtil.java b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/TestUtil.java similarity index 94% rename from mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/TestUtil.java rename to mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/TestUtil.java index 0085f31e..7ce98056 100644 --- a/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/server/TestUtil.java +++ b/mcp-spring/mcp-spring-webflux/src/test/java/io/modelcontextprotocol/TestUtil.java @@ -1,7 +1,7 @@ /* * Copyright 2025 - 2025 the original author or authors. */ -package io.modelcontextprotocol.server; +package io.modelcontextprotocol; import java.io.IOException; import java.net.InetSocketAddress; 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 ac487b6f..cfc10ae7 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 @@ -48,9 +48,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"; @@ -129,7 +129,7 @@ void testCreateMessageWithoutSamplingCapabilities(String clientType) { @ParameterizedTest(name = "{0} : {displayName} ") @ValueSource(strings = { "httpclient", "webflux" }) - void testCreateMessageSuccess(String clientType) throws InterruptedException { + void testCreateMessageSuccess(String clientType) { // Client var clientBuilder = clientBuilders.get(clientType); @@ -188,8 +188,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); mcpClient.close(); 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); mcpClient.close(); 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 cc33e7b9..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; 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 2fc10453..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;