Skip to content

feat(test): adds findAvailablePort for test server #133

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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();
Expand Down Expand Up @@ -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);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -75,7 +75,7 @@ public RouterFunction<ServerResponse> routerFunction(WebMvcSseServerTransportPro
@BeforeEach
public void before() {

tomcatServer = new TomcatTestUtil().createTomcatServer("", PORT, TestConfig.class);
tomcatServer = TomcatTestUtil.createTomcatServer("", PORT, TestConfig.class);

try {
tomcatServer.tomcat().start();
Expand Down Expand Up @@ -151,7 +151,7 @@ void testCreateMessageWithoutSamplingCapabilities() {
}

@Test
void testCreateMessageSuccess() throws InterruptedException {
void testCreateMessageSuccess() {

Function<CreateMessageRequest, CreateMessageResult> samplingHandler = request -> {
assertThat(request.messages()).hasSize(1);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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";

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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";

Expand All @@ -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);
Expand Down Expand Up @@ -132,7 +133,7 @@ void testCreateMessageWithoutSamplingCapabilities() {
}

@Test
void testCreateMessageSuccess() throws InterruptedException {
void testCreateMessageSuccess() {

Function<CreateMessageRequest, CreateMessageResult> samplingHandler = request -> {
assertThat(request.messages()).hasSize(1);
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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
Expand All @@ -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);
}
}

}