diff --git a/mcp-bom/pom.xml b/mcp-bom/pom.xml
index 83d8bc5..388c06d 100644
--- a/mcp-bom/pom.xml
+++ b/mcp-bom/pom.xml
@@ -7,7 +7,7 @@
io.modelcontextprotocol.sdkmcp-parent
- 0.12.0-SNAPSHOT
+ 0.12.1mcp-bom
diff --git a/mcp-spring/mcp-spring-webflux/pom.xml b/mcp-spring/mcp-spring-webflux/pom.xml
index 300d518..b83ecc9 100644
--- a/mcp-spring/mcp-spring-webflux/pom.xml
+++ b/mcp-spring/mcp-spring-webflux/pom.xml
@@ -6,7 +6,7 @@
io.modelcontextprotocol.sdkmcp-parent
- 0.12.0-SNAPSHOT
+ 0.12.1../../pom.xmlmcp-spring-webflux
@@ -25,13 +25,13 @@
io.modelcontextprotocol.sdkmcp
- 0.12.0-SNAPSHOT
+ 0.12.1io.modelcontextprotocol.sdkmcp-test
- 0.12.0-SNAPSHOT
+ 0.12.1test
diff --git a/mcp-spring/mcp-spring-webmvc/pom.xml b/mcp-spring/mcp-spring-webmvc/pom.xml
index 1703092..528123f 100644
--- a/mcp-spring/mcp-spring-webmvc/pom.xml
+++ b/mcp-spring/mcp-spring-webmvc/pom.xml
@@ -6,7 +6,7 @@
io.modelcontextprotocol.sdkmcp-parent
- 0.12.0-SNAPSHOT
+ 0.12.1../../pom.xmlmcp-spring-webmvc
@@ -25,7 +25,7 @@
io.modelcontextprotocol.sdkmcp
- 0.12.0-SNAPSHOT
+ 0.12.1
@@ -37,14 +37,14 @@
io.modelcontextprotocol.sdkmcp-test
- 0.12.0-SNAPSHOT
+ 0.12.1testio.modelcontextprotocol.sdkmcp-spring-webflux
- 0.12.0-SNAPSHOT
+ 0.12.1test
diff --git a/mcp-test/pom.xml b/mcp-test/pom.xml
index 563f60d..c56d139 100644
--- a/mcp-test/pom.xml
+++ b/mcp-test/pom.xml
@@ -6,7 +6,7 @@
io.modelcontextprotocol.sdkmcp-parent
- 0.12.0-SNAPSHOT
+ 0.12.1mcp-testjar
@@ -24,7 +24,7 @@
io.modelcontextprotocol.sdkmcp
- 0.12.0-SNAPSHOT
+ 0.12.1
diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java
index c0e2509..dd3bc59 100644
--- a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java
+++ b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java
@@ -4,14 +4,6 @@
package io.modelcontextprotocol;
-import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
-import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static org.assertj.core.api.Assertions.assertWith;
-import static org.awaitility.Awaitility.await;
-import static org.mockito.Mockito.mock;
-
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
@@ -29,9 +21,6 @@ import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.server.McpServer;
@@ -56,12 +45,23 @@ import io.modelcontextprotocol.spec.McpSchema.PromptReference;
import io.modelcontextprotocol.spec.McpSchema.Role;
import io.modelcontextprotocol.spec.McpSchema.Root;
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
+import io.modelcontextprotocol.spec.McpSchema.TextContent;
import io.modelcontextprotocol.spec.McpSchema.Tool;
import io.modelcontextprotocol.util.Utils;
import net.javacrumbs.jsonunit.core.Option;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertWith;
+import static org.awaitility.Awaitility.await;
+import static org.mockito.Mockito.mock;
+
public abstract class AbstractMcpClientServerIntegrationTests {
protected ConcurrentHashMap clientBuilders = new ConcurrentHashMap<>();
@@ -108,8 +108,8 @@ public abstract class AbstractMcpClientServerIntegrationTests {
McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder()
.tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build())
.callHandler((exchange, request) -> {
- exchange.createMessage(mock(McpSchema.CreateMessageRequest.class)).block();
- return Mono.just(mock(CallToolResult.class));
+ return exchange.createMessage(mock(McpSchema.CreateMessageRequest.class))
+ .then(Mono.just(mock(CallToolResult.class)));
})
.build();
@@ -1434,6 +1434,66 @@ public abstract class AbstractMcpClientServerIntegrationTests {
@ParameterizedTest(name = "{0} : {displayName} ")
@ValueSource(strings = { "httpclient", "webflux" })
+ void testStructuredOutputWithInHandlerError(String clientType) {
+ var clientBuilder = clientBuilders.get(clientType);
+
+ // Create a tool with output schema
+ Map outputSchema = Map.of(
+ "type", "object", "properties", Map.of("result", Map.of("type", "number"), "operation",
+ Map.of("type", "string"), "timestamp", Map.of("type", "string")),
+ "required", List.of("result", "operation"));
+
+ Tool calculatorTool = Tool.builder()
+ .name("calculator")
+ .description("Performs mathematical calculations")
+ .outputSchema(outputSchema)
+ .build();
+
+ // Handler that throws an exception to simulate an error
+ McpServerFeatures.SyncToolSpecification tool = McpServerFeatures.SyncToolSpecification.builder()
+ .tool(calculatorTool)
+ .callHandler((exchange, request) -> {
+
+ return CallToolResult.builder()
+ .isError(true)
+ .content(List.of(new TextContent("Error calling tool: Simulated in-handler error")))
+ .build();
+ })
+ .build();
+
+ var mcpServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
+ .capabilities(ServerCapabilities.builder().tools(true).build())
+ .tools(tool)
+ .build();
+
+ try (var mcpClient = clientBuilder.build()) {
+ InitializeResult initResult = mcpClient.initialize();
+ assertThat(initResult).isNotNull();
+
+ // Verify tool is listed with output schema
+ var toolsList = mcpClient.listTools();
+ assertThat(toolsList.tools()).hasSize(1);
+ assertThat(toolsList.tools().get(0).name()).isEqualTo("calculator");
+ // Note: outputSchema might be null in sync server, but validation still works
+
+ // Call tool with valid structured output
+ CallToolResult response = mcpClient
+ .callTool(new McpSchema.CallToolRequest("calculator", Map.of("expression", "2 + 3")));
+
+ assertThat(response).isNotNull();
+ assertThat(response.isError()).isTrue();
+ assertThat(response.content()).isNotEmpty();
+ assertThat(response.content())
+ .containsExactly(new McpSchema.TextContent("Error calling tool: Simulated in-handler error"));
+ assertThat(response.structuredContent()).isNull();
+ }
+ finally {
+ mcpServer.closeGracefully();
+ }
+ }
+
+ @ParameterizedTest(name = "{0} : {displayName} ")
+ @ValueSource(strings = { "httpclient" })
void testStructuredOutputValidationFailure(String clientType) {
var clientBuilder = clientBuilders.get(clientType);
diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java
index 777e12a..c96f10e 100644
--- a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java
+++ b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java
@@ -4,12 +4,6 @@
package io.modelcontextprotocol;
-import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
-import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static org.awaitility.Awaitility.await;
-
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
@@ -20,9 +14,6 @@ import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.ValueSource;
-
import io.modelcontextprotocol.client.McpClient;
import io.modelcontextprotocol.server.McpServer.StatelessAsyncSpecification;
import io.modelcontextprotocol.server.McpServer.StatelessSyncSpecification;
@@ -33,10 +24,19 @@ import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
import io.modelcontextprotocol.spec.McpSchema.InitializeResult;
import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities;
+import io.modelcontextprotocol.spec.McpSchema.TextContent;
import io.modelcontextprotocol.spec.McpSchema.Tool;
import net.javacrumbs.jsonunit.core.Option;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import reactor.core.publisher.Mono;
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
+import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.awaitility.Awaitility.await;
+
public abstract class AbstractStatelessIntegrationTests {
protected ConcurrentHashMap clientBuilders = new ConcurrentHashMap<>();
@@ -350,6 +350,64 @@ public abstract class AbstractStatelessIntegrationTests {
}
}
+ @ParameterizedTest(name = "{0} : {displayName} ")
+ @ValueSource(strings = { "httpclient", "webflux" })
+ void testStructuredOutputWithInHandlerError(String clientType) {
+ var clientBuilder = clientBuilders.get(clientType);
+
+ // Create a tool with output schema
+ Map outputSchema = Map.of(
+ "type", "object", "properties", Map.of("result", Map.of("type", "number"), "operation",
+ Map.of("type", "string"), "timestamp", Map.of("type", "string")),
+ "required", List.of("result", "operation"));
+
+ Tool calculatorTool = Tool.builder()
+ .name("calculator")
+ .description("Performs mathematical calculations")
+ .outputSchema(outputSchema)
+ .build();
+
+ // Handler that throws an exception to simulate an error
+ McpStatelessServerFeatures.SyncToolSpecification tool = McpStatelessServerFeatures.SyncToolSpecification
+ .builder()
+ .tool(calculatorTool)
+ .callHandler((exchange, request) -> CallToolResult.builder()
+ .isError(true)
+ .content(List.of(new TextContent("Error calling tool: Simulated in-handler error")))
+ .build())
+ .build();
+
+ var mcpServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0")
+ .capabilities(ServerCapabilities.builder().tools(true).build())
+ .tools(tool)
+ .build();
+
+ try (var mcpClient = clientBuilder.build()) {
+ InitializeResult initResult = mcpClient.initialize();
+ assertThat(initResult).isNotNull();
+
+ // Verify tool is listed with output schema
+ var toolsList = mcpClient.listTools();
+ assertThat(toolsList.tools()).hasSize(1);
+ assertThat(toolsList.tools().get(0).name()).isEqualTo("calculator");
+ // Note: outputSchema might be null in sync server, but validation still works
+
+ // Call tool with valid structured output
+ CallToolResult response = mcpClient
+ .callTool(new McpSchema.CallToolRequest("calculator", Map.of("expression", "2 + 3")));
+
+ assertThat(response).isNotNull();
+ assertThat(response.isError()).isTrue();
+ assertThat(response.content()).isNotEmpty();
+ assertThat(response.content())
+ .containsExactly(new McpSchema.TextContent("Error calling tool: Simulated in-handler error"));
+ assertThat(response.structuredContent()).isNull();
+ }
+ finally {
+ mcpServer.closeGracefully();
+ }
+ }
+
@ParameterizedTest(name = "{0} : {displayName} ")
@ValueSource(strings = { "httpclient", "webflux" })
void testStructuredOutputValidationFailure(String clientType) {
diff --git a/mcp/pom.xml b/mcp/pom.xml
index 1cf61c4..b410605 100644
--- a/mcp/pom.xml
+++ b/mcp/pom.xml
@@ -6,7 +6,7 @@
io.modelcontextprotocol.sdkmcp-parent
- 0.12.0-SNAPSHOT
+ 0.12.1mcpjar
diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java
index dc81e65..3c8057a 100644
--- a/mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java
+++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpAsyncServer.java
@@ -29,10 +29,12 @@ import io.modelcontextprotocol.spec.McpClientSession;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
+import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse;
import io.modelcontextprotocol.spec.McpSchema.LoggingLevel;
import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification;
import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
import io.modelcontextprotocol.spec.McpSchema.SetLevelRequest;
+import io.modelcontextprotocol.spec.McpSchema.TextContent;
import io.modelcontextprotocol.spec.McpSchema.Tool;
import io.modelcontextprotocol.spec.McpServerSession;
import io.modelcontextprotocol.spec.McpServerTransportProvider;
@@ -376,6 +378,11 @@ public class McpAsyncServer {
return this.delegateCallToolResult.apply(exchange, request).map(result -> {
+ if (Boolean.TRUE.equals(result.isError())) {
+ // If the tool call resulted in an error, skip further validation
+ return result;
+ }
+
if (outputSchema == null) {
if (result.structuredContent() != null) {
logger.warn(
@@ -507,11 +514,11 @@ public class McpAsyncServer {
.findAny();
if (toolSpecification.isEmpty()) {
- return Mono.error(new McpError("Tool not found: " + callToolRequest.name()));
+ return Mono.error(new McpError(new JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.INVALID_PARAMS,
+ "Unknown tool: invalid_tool_name", "Tool not found: " + callToolRequest.name())));
}
- return toolSpecification.map(tool -> Mono.defer(() -> tool.callHandler().apply(exchange, callToolRequest)))
- .orElse(Mono.error(new McpError("Tool not found: " + callToolRequest.name())));
+ return toolSpecification.get().callHandler().apply(exchange, callToolRequest);
};
}
diff --git a/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java b/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java
index 4517712..50d45b1 100644
--- a/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java
+++ b/mcp/src/main/java/io/modelcontextprotocol/server/McpStatelessAsyncServer.java
@@ -11,7 +11,9 @@ import io.modelcontextprotocol.spec.JsonSchemaValidator;
import io.modelcontextprotocol.spec.McpError;
import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
+import io.modelcontextprotocol.spec.McpSchema.JSONRPCResponse;
import io.modelcontextprotocol.spec.McpSchema.ResourceTemplate;
+import io.modelcontextprotocol.spec.McpSchema.TextContent;
import io.modelcontextprotocol.spec.McpSchema.Tool;
import io.modelcontextprotocol.spec.McpStatelessServerTransport;
import io.modelcontextprotocol.util.Assert;
@@ -249,6 +251,11 @@ public class McpStatelessAsyncServer {
return this.delegateHandler.apply(transportContext, request).map(result -> {
+ if (Boolean.TRUE.equals(result.isError())) {
+ // If the tool call resulted in an error, skip further validation
+ return result;
+ }
+
if (outputSchema == null) {
if (result.structuredContent() != null) {
logger.warn(
@@ -375,11 +382,11 @@ public class McpStatelessAsyncServer {
.findAny();
if (toolSpecification.isEmpty()) {
- return Mono.error(new McpError("Tool not found: " + callToolRequest.name()));
+ return Mono.error(new McpError(new JSONRPCResponse.JSONRPCError(McpSchema.ErrorCodes.INVALID_PARAMS,
+ "Unknown tool: invalid_tool_name", "Tool not found: " + callToolRequest.name())));
}
- return toolSpecification.map(tool -> tool.callHandler().apply(ctx, callToolRequest))
- .orElse(Mono.error(new McpError("Tool not found: " + callToolRequest.name())));
+ return toolSpecification.get().callHandler().apply(ctx, callToolRequest);
};
}
diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java
index f7db3d7..6ac8def 100644
--- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java
+++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpClientSession.java
@@ -35,6 +35,7 @@ import java.util.function.Function;
*
* @author Christian Tzolov
* @author Dariusz Jędrzejczyk
+ * @author Yanming Zhou
*/
public class McpClientSession implements McpSession {
@@ -146,13 +147,20 @@ public class McpClientSession implements McpSession {
private void handle(McpSchema.JSONRPCMessage message) {
if (message instanceof McpSchema.JSONRPCResponse response) {
- logger.debug("Received Response: {}", response);
- var sink = pendingResponses.remove(response.id());
- if (sink == null) {
- logger.warn("Unexpected response for unknown id {}", response.id());
+ logger.debug("Received response: {}", response);
+ if (response.id() != null) {
+ var sink = pendingResponses.remove(response.id());
+ if (sink == null) {
+ logger.warn("Unexpected response for unknown id {}", response.id());
+ }
+ else {
+ sink.success(response);
+ }
}
else {
- sink.success(response);
+ logger.error("Discarded MCP request response without session id. "
+ + "This is an indication of a bug in the request sender code that can lead to memory "
+ + "leaks as pending requests will never be completed.");
}
}
else if (message instanceof McpSchema.JSONRPCRequest request) {
diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java
index e6a0c8b..747b454 100644
--- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java
+++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpServerSession.java
@@ -204,13 +204,20 @@ public class McpServerSession implements McpLoggableSession {
// TODO handle errors for communication to without initialization happening
// first
if (message instanceof McpSchema.JSONRPCResponse response) {
- logger.debug("Received Response: {}", response);
- var sink = pendingResponses.remove(response.id());
- if (sink == null) {
- logger.warn("Unexpected response for unknown id {}", response.id());
+ logger.debug("Received response: {}", response);
+ if (response.id() != null) {
+ var sink = pendingResponses.remove(response.id());
+ if (sink == null) {
+ logger.warn("Unexpected response for unknown id {}", response.id());
+ }
+ else {
+ sink.success(response);
+ }
}
else {
- sink.success(response);
+ logger.error("Discarded MCP request response without session id. "
+ + "This is an indication of a bug in the request sender code that can lead to memory "
+ + "leaks as pending requests will never be completed.");
}
return Mono.empty();
}
diff --git a/mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java b/mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java
index af29ce0..53b56c7 100644
--- a/mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java
+++ b/mcp/src/main/java/io/modelcontextprotocol/spec/McpStreamableServerSession.java
@@ -21,6 +21,7 @@ import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.server.McpAsyncServerExchange;
import io.modelcontextprotocol.server.McpNotificationHandler;
import io.modelcontextprotocol.server.McpRequestHandler;
+import io.modelcontextprotocol.spec.McpSchema.ErrorCodes;
import io.modelcontextprotocol.util.Assert;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@@ -33,6 +34,7 @@ import reactor.core.publisher.MonoSink;
* capability without the insight into the transport-specific details of HTTP handling.
*
* @author Dariusz Jędrzejczyk
+ * @author Yanming Zhou
*/
public class McpStreamableServerSession implements McpLoggableSession {
@@ -214,19 +216,30 @@ public class McpStreamableServerSession implements McpLoggableSession {
*/
public Mono accept(McpSchema.JSONRPCResponse response) {
return Mono.defer(() -> {
- var stream = this.requestIdToStream.get(response.id());
- if (stream == null) {
- return Mono.error(new McpError("Unexpected response for unknown id " + response.id())); // TODO
- // JSONize
- }
- // TODO: encapsulate this inside the stream itself
- var sink = stream.pendingResponses.remove(response.id());
- if (sink == null) {
- return Mono.error(new McpError("Unexpected response for unknown id " + response.id())); // TODO
- // JSONize
+ logger.debug("Received response: {}", response);
+
+ if (response.id() != null) {
+ var stream = this.requestIdToStream.get(response.id());
+ if (stream == null) {
+ return Mono.error(McpError.builder(ErrorCodes.INTERNAL_ERROR)
+ .message("Unexpected response for unknown id " + response.id())
+ .build());
+ }
+ // TODO: encapsulate this inside the stream itself
+ var sink = stream.pendingResponses.remove(response.id());
+ if (sink == null) {
+ return Mono.error(McpError.builder(ErrorCodes.INTERNAL_ERROR)
+ .message("Unexpected response for unknown id " + response.id())
+ .build());
+ }
+ else {
+ sink.success(response);
+ }
}
else {
- sink.success(response);
+ logger.error("Discarded MCP request response without session id. "
+ + "This is an indication of a bug in the request sender code that can lead to memory "
+ + "leaks as pending requests will never be completed.");
}
return Mono.empty();
});
diff --git a/mcp/src/main/java/io/modelcontextprotocol/util/Utils.java b/mcp/src/main/java/io/modelcontextprotocol/util/Utils.java
index 039b0d6..cd42010 100644
--- a/mcp/src/main/java/io/modelcontextprotocol/util/Utils.java
+++ b/mcp/src/main/java/io/modelcontextprotocol/util/Utils.java
@@ -4,12 +4,12 @@
package io.modelcontextprotocol.util;
-import reactor.util.annotation.Nullable;
-
import java.net.URI;
import java.util.Collection;
import java.util.Map;
+import reactor.util.annotation.Nullable;
+
/**
* Miscellaneous utility methods.
*
diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/StdioMcpAsyncClientTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/StdioMcpAsyncClientTests.java
index e9356d0..ef404c9 100644
--- a/mcp/src/test/java/io/modelcontextprotocol/client/StdioMcpAsyncClientTests.java
+++ b/mcp/src/test/java/io/modelcontextprotocol/client/StdioMcpAsyncClientTests.java
@@ -14,10 +14,17 @@ import org.junit.jupiter.api.Timeout;
/**
* Tests for the {@link McpAsyncClient} with {@link StdioClientTransport}.
*
+ *
+ * These tests use npx to download and run the MCP "everything" server locally. The first
+ * test execution will download the everything server scripts and cache them locally,
+ * which can take more than 15 seconds. Subsequent test runs will use the cached version
+ * and execute faster.
+ *
* @author Christian Tzolov
* @author Dariusz Jędrzejczyk
*/
-@Timeout(15) // Giving extra time beyond the client timeout
+@Timeout(25) // Giving extra time beyond the client timeout to account for initial server
+ // download
class StdioMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
@Override
@@ -40,4 +47,9 @@ class StdioMcpAsyncClientTests extends AbstractMcpAsyncClientTests {
return Duration.ofSeconds(20);
}
+ @Override
+ protected Duration getRequestTimeout() {
+ return Duration.ofSeconds(25);
+ }
+
}
diff --git a/mcp/src/test/java/io/modelcontextprotocol/client/StdioMcpSyncClientTests.java b/mcp/src/test/java/io/modelcontextprotocol/client/StdioMcpSyncClientTests.java
index 4b5f4f9..95c1e29 100644
--- a/mcp/src/test/java/io/modelcontextprotocol/client/StdioMcpSyncClientTests.java
+++ b/mcp/src/test/java/io/modelcontextprotocol/client/StdioMcpSyncClientTests.java
@@ -22,10 +22,17 @@ import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for the {@link McpSyncClient} with {@link StdioClientTransport}.
*
+ *