|
4 | 4 |
|
5 | 5 | package io.modelcontextprotocol; |
6 | 6 |
|
7 | | -import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; |
8 | | -import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json; |
9 | | -import static org.assertj.core.api.Assertions.assertThat; |
10 | | -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
11 | | -import static org.assertj.core.api.Assertions.assertWith; |
12 | | -import static org.awaitility.Awaitility.await; |
13 | | -import static org.mockito.Mockito.mock; |
14 | | - |
15 | 7 | import java.net.URI; |
16 | 8 | import java.net.http.HttpClient; |
17 | 9 | import java.net.http.HttpRequest; |
|
29 | 21 | import java.util.function.Function; |
30 | 22 | import java.util.stream.Collectors; |
31 | 23 |
|
32 | | -import org.junit.jupiter.params.ParameterizedTest; |
33 | | -import org.junit.jupiter.params.provider.ValueSource; |
34 | | - |
35 | 24 | import io.modelcontextprotocol.client.McpClient; |
36 | 25 | import io.modelcontextprotocol.common.McpTransportContext; |
37 | 26 | import io.modelcontextprotocol.server.McpServer; |
|
56 | 45 | import io.modelcontextprotocol.spec.McpSchema.Role; |
57 | 46 | import io.modelcontextprotocol.spec.McpSchema.Root; |
58 | 47 | import io.modelcontextprotocol.spec.McpSchema.ServerCapabilities; |
| 48 | +import io.modelcontextprotocol.spec.McpSchema.TextContent; |
59 | 49 | import io.modelcontextprotocol.spec.McpSchema.Tool; |
60 | 50 | import io.modelcontextprotocol.util.Utils; |
61 | 51 | import net.javacrumbs.jsonunit.core.Option; |
| 52 | +import org.junit.jupiter.params.ParameterizedTest; |
| 53 | +import org.junit.jupiter.params.provider.ValueSource; |
62 | 54 | import reactor.core.publisher.Mono; |
63 | 55 | import reactor.test.StepVerifier; |
64 | 56 |
|
| 57 | +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; |
| 58 | +import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json; |
| 59 | +import static org.assertj.core.api.Assertions.assertThat; |
| 60 | +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
| 61 | +import static org.assertj.core.api.Assertions.assertWith; |
| 62 | +import static org.awaitility.Awaitility.await; |
| 63 | +import static org.mockito.Mockito.mock; |
| 64 | + |
65 | 65 | public abstract class AbstractMcpClientServerIntegrationTests { |
66 | 66 |
|
67 | 67 | protected ConcurrentHashMap<String, McpClient.SyncSpec> clientBuilders = new ConcurrentHashMap<>(); |
@@ -108,8 +108,8 @@ void testCreateMessageWithoutSamplingCapabilities(String clientType) { |
108 | 108 | McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() |
109 | 109 | .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(emptyJsonSchema).build()) |
110 | 110 | .callHandler((exchange, request) -> { |
111 | | - exchange.createMessage(mock(McpSchema.CreateMessageRequest.class)).block(); |
112 | | - return Mono.just(mock(CallToolResult.class)); |
| 111 | + return exchange.createMessage(mock(McpSchema.CreateMessageRequest.class)) |
| 112 | + .then(Mono.just(mock(CallToolResult.class))); |
113 | 113 | }) |
114 | 114 | .build(); |
115 | 115 |
|
@@ -1434,6 +1434,66 @@ void testStructuredOutputValidationSuccess(String clientType) { |
1434 | 1434 |
|
1435 | 1435 | @ParameterizedTest(name = "{0} : {displayName} ") |
1436 | 1436 | @ValueSource(strings = { "httpclient", "webflux" }) |
| 1437 | + void testStructuredOutputWithInHandlerError(String clientType) { |
| 1438 | + var clientBuilder = clientBuilders.get(clientType); |
| 1439 | + |
| 1440 | + // Create a tool with output schema |
| 1441 | + Map<String, Object> outputSchema = Map.of( |
| 1442 | + "type", "object", "properties", Map.of("result", Map.of("type", "number"), "operation", |
| 1443 | + Map.of("type", "string"), "timestamp", Map.of("type", "string")), |
| 1444 | + "required", List.of("result", "operation")); |
| 1445 | + |
| 1446 | + Tool calculatorTool = Tool.builder() |
| 1447 | + .name("calculator") |
| 1448 | + .description("Performs mathematical calculations") |
| 1449 | + .outputSchema(outputSchema) |
| 1450 | + .build(); |
| 1451 | + |
| 1452 | + // Handler that throws an exception to simulate an error |
| 1453 | + McpServerFeatures.SyncToolSpecification tool = McpServerFeatures.SyncToolSpecification.builder() |
| 1454 | + .tool(calculatorTool) |
| 1455 | + .callHandler((exchange, request) -> { |
| 1456 | + |
| 1457 | + return CallToolResult.builder() |
| 1458 | + .isError(true) |
| 1459 | + .content(List.of(new TextContent("Error calling tool: Simulated in-handler error"))) |
| 1460 | + .build(); |
| 1461 | + }) |
| 1462 | + .build(); |
| 1463 | + |
| 1464 | + var mcpServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") |
| 1465 | + .capabilities(ServerCapabilities.builder().tools(true).build()) |
| 1466 | + .tools(tool) |
| 1467 | + .build(); |
| 1468 | + |
| 1469 | + try (var mcpClient = clientBuilder.build()) { |
| 1470 | + InitializeResult initResult = mcpClient.initialize(); |
| 1471 | + assertThat(initResult).isNotNull(); |
| 1472 | + |
| 1473 | + // Verify tool is listed with output schema |
| 1474 | + var toolsList = mcpClient.listTools(); |
| 1475 | + assertThat(toolsList.tools()).hasSize(1); |
| 1476 | + assertThat(toolsList.tools().get(0).name()).isEqualTo("calculator"); |
| 1477 | + // Note: outputSchema might be null in sync server, but validation still works |
| 1478 | + |
| 1479 | + // Call tool with valid structured output |
| 1480 | + CallToolResult response = mcpClient |
| 1481 | + .callTool(new McpSchema.CallToolRequest("calculator", Map.of("expression", "2 + 3"))); |
| 1482 | + |
| 1483 | + assertThat(response).isNotNull(); |
| 1484 | + assertThat(response.isError()).isTrue(); |
| 1485 | + assertThat(response.content()).isNotEmpty(); |
| 1486 | + assertThat(response.content()) |
| 1487 | + .containsExactly(new McpSchema.TextContent("Error calling tool: Simulated in-handler error")); |
| 1488 | + assertThat(response.structuredContent()).isNull(); |
| 1489 | + } |
| 1490 | + finally { |
| 1491 | + mcpServer.closeGracefully(); |
| 1492 | + } |
| 1493 | + } |
| 1494 | + |
| 1495 | + @ParameterizedTest(name = "{0} : {displayName} ") |
| 1496 | + @ValueSource(strings = { "httpclient" }) |
1437 | 1497 | void testStructuredOutputValidationFailure(String clientType) { |
1438 | 1498 |
|
1439 | 1499 | var clientBuilder = clientBuilders.get(clientType); |
|
0 commit comments