From 6b0a8eea061cd19b829eb104122c8451cc5fd120 Mon Sep 17 00:00:00 2001 From: Bilal Oumehdi Date: Tue, 13 Jan 2026 02:51:33 +0100 Subject: [PATCH 1/4] feat: remove JsonSchema an use a Map for inputSchema --- .../modelcontextprotocol/spec/McpSchema.java | 38 ++-------------- .../client/McpAsyncClientTests.java | 3 +- .../server/AbstractMcpAsyncServerTests.java | 36 +++++----------- ...stractMcpClientServerIntegrationTests.java | 43 ++++++++----------- .../server/AbstractMcpSyncServerTests.java | 30 ++++--------- .../AsyncToolSpecificationBuilderTest.java | 31 +++---------- .../HttpServletStatelessIntegrationTests.java | 3 +- .../SyncToolSpecificationBuilderTest.java | 13 ++---- .../spec/McpSchemaTests.java | 23 ++++++---- .../modelcontextprotocol/util/ToolsUtils.java | 3 -- ...stractMcpClientServerIntegrationTests.java | 43 ++++++++----------- .../AbstractStatelessIntegrationTests.java | 17 ++------ .../server/AbstractMcpAsyncServerTests.java | 36 +++++----------- .../server/AbstractMcpSyncServerTests.java | 30 ++++--------- .../modelcontextprotocol/util/ToolsUtils.java | 3 -- 15 files changed, 107 insertions(+), 245 deletions(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index b58f1c552..c271f734f 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -1330,27 +1330,6 @@ public ListToolsResult(List tools, String nextCursor) { } } - /** - * A JSON Schema object that describes the expected structure of arguments or output. - * - * @param type The type of the schema (e.g., "object") - * @param properties The properties of the schema object - * @param required List of required property names - * @param additionalProperties Whether additional properties are allowed - * @param defs Schema definitions using the newer $defs keyword - * @param definitions Schema definitions using the legacy definitions keyword - */ - @JsonInclude(JsonInclude.Include.NON_ABSENT) - @JsonIgnoreProperties(ignoreUnknown = true) - public record JsonSchema( // @formatter:off - @JsonProperty("type") String type, - @JsonProperty("properties") Map properties, - @JsonProperty("required") List required, - @JsonProperty("additionalProperties") Boolean additionalProperties, - @JsonProperty("$defs") Map defs, - @JsonProperty("definitions") Map definitions) { // @formatter:on - } - /** * Additional properties describing a Tool to clients. * @@ -1395,7 +1374,7 @@ public record Tool( // @formatter:off @JsonProperty("name") String name, @JsonProperty("title") String title, @JsonProperty("description") String description, - @JsonProperty("inputSchema") JsonSchema inputSchema, + @JsonProperty("inputSchema") Map inputSchema, @JsonProperty("outputSchema") Map outputSchema, @JsonProperty("annotations") ToolAnnotations annotations, @JsonProperty("_meta") Map meta) { // @formatter:on @@ -1412,7 +1391,7 @@ public static class Builder { private String description; - private JsonSchema inputSchema; + private Map inputSchema; private Map outputSchema; @@ -1435,13 +1414,13 @@ public Builder description(String description) { return this; } - public Builder inputSchema(JsonSchema inputSchema) { + public Builder inputSchema(Map inputSchema) { this.inputSchema = inputSchema; return this; } public Builder inputSchema(McpJsonMapper jsonMapper, String inputSchema) { - this.inputSchema = parseSchema(jsonMapper, inputSchema); + this.inputSchema = schemaToMap(jsonMapper, inputSchema); return this; } @@ -1482,15 +1461,6 @@ private static Map schemaToMap(McpJsonMapper jsonMapper, String } } - private static JsonSchema parseSchema(McpJsonMapper jsonMapper, String schema) { - try { - return jsonMapper.readValue(schema, JsonSchema.class); - } - catch (IOException e) { - throw new IllegalArgumentException("Invalid schema: " + schema, e); - } - } - /** * Used by the client to call a tool provided by the server. * diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/client/McpAsyncClientTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/client/McpAsyncClientTests.java index 48bf1da5b..a6738d4f8 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/client/McpAsyncClientTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/client/McpAsyncClientTests.java @@ -44,11 +44,10 @@ private McpClientTransport createMockTransportForToolValidation(boolean hasOutpu Map inputSchemaMap = Map.of("type", "object", "properties", Map.of("expression", Map.of("type", "string")), "required", List.of("expression")); - McpSchema.JsonSchema inputSchema = new McpSchema.JsonSchema("object", inputSchemaMap, null, null, null, null); McpSchema.Tool.Builder toolBuilder = McpSchema.Tool.builder() .name("calculator") .description("Performs mathematical calculations") - .inputSchema(inputSchema); + .inputSchema(inputSchemaMap); if (hasOutputSchema) { Map outputSchema = Map.of("type", "object", "properties", diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java index 090710248..a425d68fa 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java @@ -6,6 +6,7 @@ import java.time.Duration; import java.util.List; +import java.util.Map; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.CallToolResult; @@ -23,7 +24,6 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -94,11 +94,7 @@ void testImmediateClose() { @Test @Deprecated void testAddTool() { - Tool newTool = McpSchema.Tool.builder() - .name("new-tool") - .title("New test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool newTool = McpSchema.Tool.builder().name("new-tool").title("New test tool").inputSchema(Map.of()).build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) .build(); @@ -113,11 +109,7 @@ void testAddTool() { @Test void testAddToolCall() { - Tool newTool = McpSchema.Tool.builder() - .name("new-tool") - .title("New test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool newTool = McpSchema.Tool.builder().name("new-tool").title("New test tool").inputSchema(Map.of()).build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) @@ -138,7 +130,7 @@ void testAddDuplicateTool() { Tool duplicateTool = McpSchema.Tool.builder() .name(TEST_TOOL_NAME) .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -160,7 +152,7 @@ void testAddDuplicateToolCall() { Tool duplicateTool = McpSchema.Tool.builder() .name(TEST_TOOL_NAME) .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -184,7 +176,7 @@ void testDuplicateToolCallDuringBuilding() { Tool duplicateTool = McpSchema.Tool.builder() .name("duplicate-build-toolcall") .title("Duplicate toolcall during building") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); assertThatThrownBy(() -> prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -204,7 +196,7 @@ void testDuplicateToolsInBatchListRegistration() { Tool duplicateTool = McpSchema.Tool.builder() .name("batch-list-tool") .title("Duplicate tool in batch list") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); List specs = List.of( @@ -232,7 +224,7 @@ void testDuplicateToolsInBatchVarargsRegistration() { Tool duplicateTool = McpSchema.Tool.builder() .name("batch-varargs-tool") .title("Duplicate tool in batch varargs") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); assertThatThrownBy(() -> prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -254,11 +246,7 @@ void testDuplicateToolsInBatchVarargsRegistration() { @Test void testRemoveTool() { - Tool too = McpSchema.Tool.builder() - .name(TEST_TOOL_NAME) - .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool too = McpSchema.Tool.builder().name(TEST_TOOL_NAME).title("Duplicate tool").inputSchema(Map.of()).build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) @@ -285,11 +273,7 @@ void testRemoveNonexistentTool() { @Test void testNotifyToolsListChanged() { - Tool too = McpSchema.Tool.builder() - .name(TEST_TOOL_NAME) - .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool too = McpSchema.Tool.builder().name(TEST_TOOL_NAME).title("Duplicate tool").inputSchema(Map.of()).build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/AbstractMcpClientServerIntegrationTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/AbstractMcpClientServerIntegrationTests.java index 1f5387f37..7996a476f 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/AbstractMcpClientServerIntegrationTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/AbstractMcpClientServerIntegrationTests.java @@ -51,7 +51,6 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json; import static org.assertj.core.api.Assertions.assertThat; @@ -103,7 +102,7 @@ void testCreateMessageWithoutSamplingCapabilities(String clientType) { var clientBuilder = clientBuilders.get(clientType); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { return exchange.createMessage(mock(McpSchema.CreateMessageRequest.class)) .then(Mono.just(mock(CallToolResult.class))); @@ -153,7 +152,7 @@ void testCreateMessageSuccess(String clientType) { AtomicReference samplingResult = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { var createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -232,7 +231,7 @@ void testCreateMessageWithRequestTimeoutSuccess(String clientType) throws Interr AtomicReference samplingResult = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { var createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -307,7 +306,7 @@ void testCreateMessageWithRequestTimeoutFail(String clientType) throws Interrupt .build(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { var createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -357,7 +356,7 @@ void testCreateElicitationWithoutElicitationCapabilities(String clientType) { var clientBuilder = clientBuilders.get(clientType); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> exchange.createElicitation(mock(ElicitRequest.class)) .then(Mono.just(mock(CallToolResult.class)))) .build(); @@ -401,7 +400,7 @@ void testCreateElicitationSuccess(String clientType) { .build(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { var elicitationRequest = McpSchema.ElicitRequest.builder() @@ -459,7 +458,7 @@ void testCreateElicitationWithRequestTimeoutSuccess(String clientType) { AtomicReference resultRef = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { var elicitationRequest = McpSchema.ElicitRequest.builder() @@ -530,7 +529,7 @@ void testCreateElicitationWithRequestTimeoutFail(String clientType) { AtomicReference resultRef = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { var elicitationRequest = ElicitRequest.builder() @@ -628,7 +627,7 @@ void testRootsWithoutCapability(String clientType) { var clientBuilder = clientBuilders.get(clientType); McpServerFeatures.SyncToolSpecification tool = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { exchange.listRoots(); // try to list roots @@ -770,7 +769,7 @@ void testToolCallSuccess(String clientType) { .addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=importantValue")) .build(); McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { try { @@ -821,11 +820,7 @@ void testThrowingToolCallIsCaughtBeforeTimeout(String clientType) { McpSyncServer mcpServer = prepareSyncServerBuilder() .capabilities(ServerCapabilities.builder().tools(true).build()) .tools(McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder() - .name("tool1") - .description("tool1 description") - .inputSchema(EMPTY_JSON_SCHEMA) - .build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { // We trigger a timeout on blocking read, raising an exception Mono.never().block(Duration.ofSeconds(1)); @@ -863,7 +858,7 @@ void testToolCallSuccessWithTransportContextExtraction(String clientType) { .addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=value")) .build(); McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { McpTransportContext transportContext = exchange.transportContext(); @@ -919,7 +914,7 @@ void testToolListChangeHandlingSuccess(String clientType) { .build(); McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { // perform a blocking call to a remote service try { @@ -985,11 +980,7 @@ void testToolListChangeHandlingSuccess(String clientType) { // Add a new tool McpServerFeatures.SyncToolSpecification tool2 = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder() - .name("tool2") - .description("tool2 description") - .inputSchema(EMPTY_JSON_SCHEMA) - .build()) + .tool(Tool.builder().name("tool2").description("tool2 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> callResponse) .build(); @@ -1040,7 +1031,7 @@ void testLoggingNotification(String clientType) throws InterruptedException { .tool(Tool.builder() .name("logging-test") .description("Test logging notifications") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build()) .callHandler((exchange, request) -> { @@ -1157,7 +1148,7 @@ void testProgressNotification(String clientType) throws InterruptedException { .tool(McpSchema.Tool.builder() .name("progress-test") .description("Test progress notifications") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build()) .callHandler((exchange, request) -> { @@ -1315,7 +1306,7 @@ void testPingSuccess(String clientType) { .tool(Tool.builder() .name("ping-async-test") .description("Test ping async behavior") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build()) .callHandler((exchange, request) -> { diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java index 915c658e3..4dd1788d2 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java @@ -5,6 +5,7 @@ package io.modelcontextprotocol.server; import java.util.List; +import java.util.Map; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.CallToolResult; @@ -20,7 +21,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -107,11 +107,7 @@ void testAddTool() { .capabilities(ServerCapabilities.builder().tools(true).build()) .build(); - Tool newTool = McpSchema.Tool.builder() - .name("new-tool") - .title("New test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool newTool = McpSchema.Tool.builder().name("new-tool").title("New test tool").inputSchema(Map.of()).build(); assertThatCode(() -> mcpSyncServer.addTool(new McpServerFeatures.SyncToolSpecification(newTool, (exchange, args) -> CallToolResult.builder().content(List.of()).isError(false).build()))) .doesNotThrowAnyException(); @@ -125,11 +121,7 @@ void testAddToolCall() { .capabilities(ServerCapabilities.builder().tools(true).build()) .build(); - Tool newTool = McpSchema.Tool.builder() - .name("new-tool") - .title("New test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool newTool = McpSchema.Tool.builder().name("new-tool").title("New test tool").inputSchema(Map.of()).build(); assertThatCode(() -> mcpSyncServer.addTool(McpServerFeatures.SyncToolSpecification.builder() .tool(newTool) @@ -145,7 +137,7 @@ void testAddDuplicateTool() { Tool duplicateTool = McpSchema.Tool.builder() .name(TEST_TOOL_NAME) .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -165,7 +157,7 @@ void testAddDuplicateToolCall() { Tool duplicateTool = McpSchema.Tool.builder() .name(TEST_TOOL_NAME) .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -187,7 +179,7 @@ void testDuplicateToolCallDuringBuilding() { Tool duplicateTool = McpSchema.Tool.builder() .name("duplicate-build-toolcall") .title("Duplicate toolcall during building") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); assertThatThrownBy(() -> prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -205,7 +197,7 @@ void testDuplicateToolsInBatchListRegistration() { Tool duplicateTool = McpSchema.Tool.builder() .name("batch-list-tool") .title("Duplicate tool in batch list") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); List specs = List.of( McpServerFeatures.SyncToolSpecification.builder() @@ -232,7 +224,7 @@ void testDuplicateToolsInBatchVarargsRegistration() { Tool duplicateTool = McpSchema.Tool.builder() .name("batch-varargs-tool") .title("Duplicate tool in batch varargs") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); assertThatThrownBy(() -> prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -253,11 +245,7 @@ void testDuplicateToolsInBatchVarargsRegistration() { @Test void testRemoveTool() { - Tool tool = McpSchema.Tool.builder() - .name(TEST_TOOL_NAME) - .title("Test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool tool = McpSchema.Tool.builder().name(TEST_TOOL_NAME).title("Test tool").inputSchema(Map.of()).build(); var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java index 62332fcdb..55466241c 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java @@ -4,7 +4,6 @@ package io.modelcontextprotocol.server; -import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -31,11 +30,7 @@ class AsyncToolSpecificationBuilderTest { @Test void builderShouldCreateValidAsyncToolSpecification() { - Tool tool = McpSchema.Tool.builder() - .name("test-tool") - .title("A test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool tool = McpSchema.Tool.builder().name("test-tool").title("A test tool").inputSchema(Map.of()).build(); McpServerFeatures.AsyncToolSpecification specification = McpServerFeatures.AsyncToolSpecification.builder() .tool(tool) @@ -59,11 +54,7 @@ void builderShouldThrowExceptionWhenToolIsNull() { @Test void builderShouldThrowExceptionWhenCallToolIsNull() { - Tool tool = McpSchema.Tool.builder() - .name("test-tool") - .title("A test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool tool = McpSchema.Tool.builder().name("test-tool").title("A test tool").inputSchema(Map.of()).build(); assertThatThrownBy(() -> McpServerFeatures.AsyncToolSpecification.builder().tool(tool).build()) .isInstanceOf(IllegalArgumentException.class) @@ -72,11 +63,7 @@ void builderShouldThrowExceptionWhenCallToolIsNull() { @Test void builderShouldAllowMethodChaining() { - Tool tool = McpSchema.Tool.builder() - .name("test-tool") - .title("A test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool tool = McpSchema.Tool.builder().name("test-tool").title("A test tool").inputSchema(Map.of()).build(); McpServerFeatures.AsyncToolSpecification.Builder builder = McpServerFeatures.AsyncToolSpecification.builder(); // Then - verify method chaining returns the same builder instance @@ -91,7 +78,7 @@ void builtSpecificationShouldExecuteCallToolCorrectly() { Tool tool = McpSchema.Tool.builder() .name("calculator") .title("Simple calculator") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); String expectedResult = "42"; @@ -123,7 +110,7 @@ void deprecatedConstructorShouldWorkCorrectly() { Tool tool = McpSchema.Tool.builder() .name("deprecated-tool") .title("A deprecated tool") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); String expectedResult = "deprecated result"; @@ -167,11 +154,7 @@ void deprecatedConstructorShouldWorkCorrectly() { @Test void fromSyncShouldConvertSyncToolSpecificationCorrectly() { - Tool tool = McpSchema.Tool.builder() - .name("sync-tool") - .title("A sync tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool tool = McpSchema.Tool.builder().name("sync-tool").title("A sync tool").inputSchema(Map.of()).build(); String expectedResult = "sync result"; // Create a sync tool specification @@ -212,7 +195,7 @@ void fromSyncShouldConvertSyncToolSpecificationWithDeprecatedCallCorrectly() { Tool tool = McpSchema.Tool.builder() .name("sync-deprecated-tool") .title("A sync tool with deprecated call") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); String expectedResult = "sync deprecated result"; McpAsyncServerExchange nullExchange = null; // Mock or create a suitable exchange diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java index 491c2d4ed..ebae4d8d5 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java @@ -48,7 +48,6 @@ import static io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport.APPLICATION_JSON; import static io.modelcontextprotocol.server.transport.HttpServletStatelessServerTransport.TEXT_EVENT_STREAM; import static io.modelcontextprotocol.util.McpJsonMapperUtils.JSON_MAPPER; -import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json; import static org.assertj.core.api.Assertions.assertThat; @@ -119,7 +118,7 @@ void testToolCallSuccess(String clientType) { .isError(false) .build(); McpStatelessServerFeatures.SyncToolSpecification tool1 = new McpStatelessServerFeatures.SyncToolSpecification( - Tool.builder().name("tool1").title("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build(), + Tool.builder().name("tool1").title("tool1 description").inputSchema(Map.of()).build(), (transportContext, request) -> { // perform a blocking call to a remote service String response = RestClient.create() diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java index 9bcd2bc84..d34f212c0 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java @@ -4,7 +4,6 @@ package io.modelcontextprotocol.server; -import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -28,7 +27,7 @@ class SyncToolSpecificationBuilderTest { @Test void builderShouldCreateValidSyncToolSpecification() { - Tool tool = Tool.builder().name("test-tool").title("A test tool").inputSchema(EMPTY_JSON_SCHEMA).build(); + Tool tool = Tool.builder().name("test-tool").title("A test tool").inputSchema(Map.of()).build(); McpServerFeatures.SyncToolSpecification specification = McpServerFeatures.SyncToolSpecification.builder() .tool(tool) @@ -53,7 +52,7 @@ void builderShouldThrowExceptionWhenToolIsNull() { @Test void builderShouldThrowExceptionWhenCallToolIsNull() { - Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(EMPTY_JSON_SCHEMA).build(); + Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(Map.of()).build(); assertThatThrownBy(() -> McpServerFeatures.SyncToolSpecification.builder().tool(tool).build()) .isInstanceOf(IllegalArgumentException.class) @@ -62,7 +61,7 @@ void builderShouldThrowExceptionWhenCallToolIsNull() { @Test void builderShouldAllowMethodChaining() { - Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(EMPTY_JSON_SCHEMA).build(); + Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(Map.of()).build(); McpServerFeatures.SyncToolSpecification.Builder builder = McpServerFeatures.SyncToolSpecification.builder(); // Then - verify method chaining returns the same builder instance @@ -74,11 +73,7 @@ void builderShouldAllowMethodChaining() { @Test void builtSpecificationShouldExecuteCallToolCorrectly() { - Tool tool = Tool.builder() - .name("calculator") - .description("Simple calculator") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool tool = Tool.builder().name("calculator").description("Simple calculator").inputSchema(Map.of()).build(); String expectedResult = "42"; McpServerFeatures.SyncToolSpecification specification = McpServerFeatures.SyncToolSpecification.builder() diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java index 6b0004cb9..5beb03b11 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.exc.InvalidTypeIdException; import io.modelcontextprotocol.spec.McpSchema.TextResourceContents; +import io.modelcontextprotocol.json.TypeRef; import net.javacrumbs.jsonunit.core.Option; /** @@ -694,13 +695,15 @@ void testJsonSchema() throws Exception { """; // Deserialize the original string to a JsonSchema object - McpSchema.JsonSchema schema = JSON_MAPPER.readValue(schemaJson, McpSchema.JsonSchema.class); + Map schema = JSON_MAPPER.readValue(schemaJson, new TypeRef>() { + }); // Serialize the object back to a string String serialized = JSON_MAPPER.writeValueAsString(schema); // Deserialize again - McpSchema.JsonSchema deserialized = JSON_MAPPER.readValue(serialized, McpSchema.JsonSchema.class); + Map deserialized = JSON_MAPPER.readValue(serialized, new TypeRef>() { + }); // Serialize one more time and compare with the first serialization String serializedAgain = JSON_MAPPER.writeValueAsString(deserialized); @@ -737,13 +740,15 @@ void testJsonSchemaWithDefinitions() throws Exception { """; // Deserialize the original string to a JsonSchema object - McpSchema.JsonSchema schema = JSON_MAPPER.readValue(schemaJson, McpSchema.JsonSchema.class); + Map schema = JSON_MAPPER.readValue(schemaJson, new TypeRef>() { + }); // Serialize the object back to a string String serialized = JSON_MAPPER.writeValueAsString(schema); // Deserialize again - McpSchema.JsonSchema deserialized = JSON_MAPPER.readValue(serialized, McpSchema.JsonSchema.class); + Map deserialized = JSON_MAPPER.readValue(serialized, new TypeRef>() { + }); // Serialize one more time and compare with the first serialization String serializedAgain = JSON_MAPPER.writeValueAsString(deserialized); @@ -826,8 +831,8 @@ void testToolWithComplexSchema() throws Exception { assertThatJson(serializedAgain).when(Option.IGNORING_ARRAY_ORDER).isEqualTo(json(serialized)); // Just verify the basic structure was preserved - assertThat(deserializedTool.inputSchema().defs()).isNotNull(); - assertThat(deserializedTool.inputSchema().defs()).containsKey("Address"); + assertThat(deserializedTool.inputSchema().containsKey("defs")); + // assertThat(deserializedTool.inputSchema().defs()).containsKey("Address"); } @Test @@ -847,14 +852,14 @@ void testToolWithMeta() throws Exception { } """; - McpSchema.JsonSchema schema = JSON_MAPPER.readValue(schemaJson, McpSchema.JsonSchema.class); + Map inputSchema = Map.of("inputSchema", schemaJson); Map meta = Map.of("metaKey", "metaValue"); McpSchema.Tool tool = McpSchema.Tool.builder() .name("addressTool") .title("addressTool") .description("Handles addresses") - .inputSchema(schema) + .inputSchema(inputSchema) .meta(meta) .build(); @@ -1095,7 +1100,7 @@ void testToolDeserialization() throws Exception { assertThat(tool.name()).isEqualTo("test-tool"); assertThat(tool.description()).isEqualTo("A test tool"); assertThat(tool.inputSchema()).isNotNull(); - assertThat(tool.inputSchema().type()).isEqualTo("object"); + assertThat(tool.inputSchema().get("type")).isEqualTo("object"); assertThat(tool.outputSchema()).isNotNull(); assertThat(tool.outputSchema()).containsKey("type"); assertThat(tool.outputSchema().get("type")).isEqualTo("object"); diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/util/ToolsUtils.java b/mcp-core/src/test/java/io/modelcontextprotocol/util/ToolsUtils.java index ce8755223..e6085a5ce 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/util/ToolsUtils.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/util/ToolsUtils.java @@ -9,7 +9,4 @@ public final class ToolsUtils { private ToolsUtils() { } - public static final McpSchema.JsonSchema EMPTY_JSON_SCHEMA = new McpSchema.JsonSchema("object", - Collections.emptyMap(), null, null, null, null); - } diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java index 270bc4308..c1eae18bc 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java @@ -55,7 +55,6 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json; import static org.assertj.core.api.Assertions.assertThat; @@ -107,7 +106,7 @@ void testCreateMessageWithoutSamplingCapabilities(String clientType) { var clientBuilder = clientBuilders.get(clientType); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { return exchange.createMessage(mock(McpSchema.CreateMessageRequest.class)) .then(Mono.just(mock(CallToolResult.class))); @@ -157,7 +156,7 @@ void testCreateMessageSuccess(String clientType) { AtomicReference samplingResult = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { var createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -236,7 +235,7 @@ void testCreateMessageWithRequestTimeoutSuccess(String clientType) throws Interr AtomicReference samplingResult = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { var createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -311,7 +310,7 @@ void testCreateMessageWithRequestTimeoutFail(String clientType) throws Interrupt .build(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { var createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -361,7 +360,7 @@ void testCreateElicitationWithoutElicitationCapabilities(String clientType) { var clientBuilder = clientBuilders.get(clientType); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> exchange.createElicitation(mock(ElicitRequest.class)) .then(Mono.just(mock(CallToolResult.class)))) .build(); @@ -405,7 +404,7 @@ void testCreateElicitationSuccess(String clientType) { .build(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { var elicitationRequest = McpSchema.ElicitRequest.builder() @@ -463,7 +462,7 @@ void testCreateElicitationWithRequestTimeoutSuccess(String clientType) { AtomicReference resultRef = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { var elicitationRequest = McpSchema.ElicitRequest.builder() @@ -534,7 +533,7 @@ void testCreateElicitationWithRequestTimeoutFail(String clientType) { AtomicReference resultRef = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { var elicitationRequest = ElicitRequest.builder() @@ -632,7 +631,7 @@ void testRootsWithoutCapability(String clientType) { var clientBuilder = clientBuilders.get(clientType); McpServerFeatures.SyncToolSpecification tool = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { exchange.listRoots(); // try to list roots @@ -774,7 +773,7 @@ void testToolCallSuccess(String clientType) { .addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=importantValue")) .build(); McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { try { @@ -825,11 +824,7 @@ void testThrowingToolCallIsCaughtBeforeTimeout(String clientType) { McpSyncServer mcpServer = prepareSyncServerBuilder() .capabilities(ServerCapabilities.builder().tools(true).build()) .tools(McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder() - .name("tool1") - .description("tool1 description") - .inputSchema(EMPTY_JSON_SCHEMA) - .build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { // We trigger a timeout on blocking read, raising an exception Mono.never().block(Duration.ofSeconds(1)); @@ -867,7 +862,7 @@ void testToolCallSuccessWithTranportContextExtraction(String clientType) { .addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=value")) .build(); McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { McpTransportContext transportContext = exchange.transportContext(); @@ -923,7 +918,7 @@ void testToolListChangeHandlingSuccess(String clientType) { .build(); McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> { // perform a blocking call to a remote service try { @@ -989,11 +984,7 @@ void testToolListChangeHandlingSuccess(String clientType) { // Add a new tool McpServerFeatures.SyncToolSpecification tool2 = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder() - .name("tool2") - .description("tool2 description") - .inputSchema(EMPTY_JSON_SCHEMA) - .build()) + .tool(Tool.builder().name("tool2").description("tool2 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> callResponse) .build(); @@ -1044,7 +1035,7 @@ void testLoggingNotification(String clientType) throws InterruptedException { .tool(Tool.builder() .name("logging-test") .description("Test logging notifications") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build()) .callHandler((exchange, request) -> { @@ -1161,7 +1152,7 @@ void testProgressNotification(String clientType) throws InterruptedException { .tool(McpSchema.Tool.builder() .name("progress-test") .description("Test progress notifications") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build()) .callHandler((exchange, request) -> { @@ -1319,7 +1310,7 @@ void testPingSuccess(String clientType) { .tool(Tool.builder() .name("ping-async-test") .description("Test ping async behavior") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build()) .callHandler((exchange, request) -> { diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java index 240732ebe..f7bbffa3b 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java @@ -32,7 +32,6 @@ import org.junit.jupiter.params.provider.ValueSource; import reactor.core.publisher.Mono; -import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson; import static net.javacrumbs.jsonunit.assertj.JsonAssertions.json; import static org.assertj.core.api.Assertions.assertThat; @@ -85,7 +84,7 @@ void testToolCallSuccess(String clientType) { var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null); McpStatelessServerFeatures.SyncToolSpecification tool1 = McpStatelessServerFeatures.SyncToolSpecification .builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((ctx, request) -> { try { @@ -135,11 +134,7 @@ void testThrowingToolCallIsCaughtBeforeTimeout(String clientType) { McpStatelessSyncServer mcpServer = prepareSyncServerBuilder() .capabilities(ServerCapabilities.builder().tools(true).build()) .tools(McpStatelessServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder() - .name("tool1") - .description("tool1 description") - .inputSchema(EMPTY_JSON_SCHEMA) - .build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((context, request) -> { // We trigger a timeout on blocking read, raising an exception Mono.never().block(Duration.ofSeconds(1)); @@ -173,7 +168,7 @@ void testToolListChangeHandlingSuccess(String clientType) { var callResponse = new McpSchema.CallToolResult(List.of(new McpSchema.TextContent("CALL RESPONSE")), null); McpStatelessServerFeatures.SyncToolSpecification tool1 = McpStatelessServerFeatures.SyncToolSpecification .builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) .callHandler((ctx, request) -> { // perform a blocking call to a remote service try { @@ -231,11 +226,7 @@ void testToolListChangeHandlingSuccess(String clientType) { // Add a new tool McpStatelessServerFeatures.SyncToolSpecification tool2 = McpStatelessServerFeatures.SyncToolSpecification .builder() - .tool(Tool.builder() - .name("tool2") - .description("tool2 description") - .inputSchema(EMPTY_JSON_SCHEMA) - .build()) + .tool(Tool.builder().name("tool2").description("tool2 description").inputSchema(Map.of()).build()) .callHandler((exchange, request) -> callResponse) .build(); diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java index d6677ec9a..5e01a62f9 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java @@ -6,6 +6,7 @@ import java.time.Duration; import java.util.List; +import java.util.Map; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.CallToolResult; @@ -25,7 +26,6 @@ import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -98,11 +98,7 @@ void testImmediateClose() { @Test @Deprecated void testAddTool() { - Tool newTool = McpSchema.Tool.builder() - .name("new-tool") - .title("New test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool newTool = McpSchema.Tool.builder().name("new-tool").title("New test tool").inputSchema(Map.of()).build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) .build(); @@ -117,11 +113,7 @@ void testAddTool() { @Test void testAddToolCall() { - Tool newTool = McpSchema.Tool.builder() - .name("new-tool") - .title("New test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool newTool = McpSchema.Tool.builder().name("new-tool").title("New test tool").inputSchema(Map.of()).build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) @@ -142,7 +134,7 @@ void testAddDuplicateTool() { Tool duplicateTool = McpSchema.Tool.builder() .name(TEST_TOOL_NAME) .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -164,7 +156,7 @@ void testAddDuplicateToolCall() { Tool duplicateTool = McpSchema.Tool.builder() .name(TEST_TOOL_NAME) .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -188,7 +180,7 @@ void testDuplicateToolCallDuringBuilding() { Tool duplicateTool = McpSchema.Tool.builder() .name("duplicate-build-toolcall") .title("Duplicate toolcall during building") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); assertThatThrownBy(() -> prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -208,7 +200,7 @@ void testDuplicateToolsInBatchListRegistration() { Tool duplicateTool = McpSchema.Tool.builder() .name("batch-list-tool") .title("Duplicate tool in batch list") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); List specs = List.of( @@ -236,7 +228,7 @@ void testDuplicateToolsInBatchVarargsRegistration() { Tool duplicateTool = McpSchema.Tool.builder() .name("batch-varargs-tool") .title("Duplicate tool in batch varargs") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); assertThatThrownBy(() -> prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -258,11 +250,7 @@ void testDuplicateToolsInBatchVarargsRegistration() { @Test void testRemoveTool() { - Tool too = McpSchema.Tool.builder() - .name(TEST_TOOL_NAME) - .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool too = McpSchema.Tool.builder().name(TEST_TOOL_NAME).title("Duplicate tool").inputSchema(Map.of()).build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) @@ -289,11 +277,7 @@ void testRemoveNonexistentTool() { @Test void testNotifyToolsListChanged() { - Tool too = McpSchema.Tool.builder() - .name(TEST_TOOL_NAME) - .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool too = McpSchema.Tool.builder().name(TEST_TOOL_NAME).title("Duplicate tool").inputSchema(Map.of()).build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java index 0a59d0aae..e4b9afd81 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java @@ -5,6 +5,7 @@ package io.modelcontextprotocol.server; import java.util.List; +import java.util.Map; import io.modelcontextprotocol.spec.McpSchema; import io.modelcontextprotocol.spec.McpSchema.CallToolResult; @@ -20,7 +21,6 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatCode; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -106,11 +106,7 @@ void testAddTool() { .capabilities(ServerCapabilities.builder().tools(true).build()) .build(); - Tool newTool = McpSchema.Tool.builder() - .name("new-tool") - .title("New test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool newTool = McpSchema.Tool.builder().name("new-tool").title("New test tool").inputSchema(Map.of()).build(); assertThatCode(() -> mcpSyncServer.addTool(new McpServerFeatures.SyncToolSpecification(newTool, (exchange, args) -> CallToolResult.builder().content(List.of()).isError(false).build()))) .doesNotThrowAnyException(); @@ -124,11 +120,7 @@ void testAddToolCall() { .capabilities(ServerCapabilities.builder().tools(true).build()) .build(); - Tool newTool = McpSchema.Tool.builder() - .name("new-tool") - .title("New test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool newTool = McpSchema.Tool.builder().name("new-tool").title("New test tool").inputSchema(Map.of()).build(); assertThatCode(() -> mcpSyncServer.addTool(McpServerFeatures.SyncToolSpecification.builder() .tool(newTool) @@ -144,7 +136,7 @@ void testAddDuplicateTool() { Tool duplicateTool = McpSchema.Tool.builder() .name(TEST_TOOL_NAME) .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -164,7 +156,7 @@ void testAddDuplicateToolCall() { Tool duplicateTool = McpSchema.Tool.builder() .name(TEST_TOOL_NAME) .title("Duplicate tool") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -186,7 +178,7 @@ void testDuplicateToolCallDuringBuilding() { Tool duplicateTool = McpSchema.Tool.builder() .name("duplicate-build-toolcall") .title("Duplicate toolcall during building") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); assertThatThrownBy(() -> prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -204,7 +196,7 @@ void testDuplicateToolsInBatchListRegistration() { Tool duplicateTool = McpSchema.Tool.builder() .name("batch-list-tool") .title("Duplicate tool in batch list") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); List specs = List.of( McpServerFeatures.SyncToolSpecification.builder() @@ -231,7 +223,7 @@ void testDuplicateToolsInBatchVarargsRegistration() { Tool duplicateTool = McpSchema.Tool.builder() .name("batch-varargs-tool") .title("Duplicate tool in batch varargs") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); assertThatThrownBy(() -> prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -252,11 +244,7 @@ void testDuplicateToolsInBatchVarargsRegistration() { @Test void testRemoveTool() { - Tool tool = McpSchema.Tool.builder() - .name(TEST_TOOL_NAME) - .title("Test tool") - .inputSchema(EMPTY_JSON_SCHEMA) - .build(); + Tool tool = McpSchema.Tool.builder().name(TEST_TOOL_NAME).title("Test tool").inputSchema(Map.of()).build(); var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/util/ToolsUtils.java b/mcp-test/src/main/java/io/modelcontextprotocol/util/ToolsUtils.java index ce8755223..e6085a5ce 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/util/ToolsUtils.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/util/ToolsUtils.java @@ -9,7 +9,4 @@ public final class ToolsUtils { private ToolsUtils() { } - public static final McpSchema.JsonSchema EMPTY_JSON_SCHEMA = new McpSchema.JsonSchema("object", - Collections.emptyMap(), null, null, null, null); - } From 26ecf80a478eef02e1e74c1f17b473ce737fa84c Mon Sep 17 00:00:00 2001 From: bilaloum Date: Wed, 14 Jan 2026 01:01:19 +0100 Subject: [PATCH 2/4] test: add test for a schema contains oneof --- .../spec/McpSchemaTests.java | 56 ++++++++++++++++++- 1 file changed, 54 insertions(+), 2 deletions(-) diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java index 5beb03b11..ce30e4ba5 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java @@ -831,8 +831,60 @@ void testToolWithComplexSchema() throws Exception { assertThatJson(serializedAgain).when(Option.IGNORING_ARRAY_ORDER).isEqualTo(json(serialized)); // Just verify the basic structure was preserved - assertThat(deserializedTool.inputSchema().containsKey("defs")); - // assertThat(deserializedTool.inputSchema().defs()).containsKey("Address"); + Map defs = ((Map) deserializedTool.inputSchema().get("$defs")); + Map address = ((Map) defs.get("Address")); + + assertThat(deserializedTool.inputSchema().containsKey("$defs")).isTrue(); + assertThat(address.get("type")).isEqualTo("object"); + } + + @Test + void testToolWithSchemaThatDoesNotHavePropertiesAtTopLevel() throws Exception { + String schemaJson = """ + { + "type": "object", + "oneOf": [ + { + "properties": { + "name": {"type": "string"} + }, + "required":["name"] + }, + { + "properties": { + "id": {"type": "integer"} + }, + "required":["id"] + } + ] + } + """; + + McpSchema.Tool tool = McpSchema.Tool.builder() + .name("addressTool") + .title("Handles addresses") + .inputSchema(JSON_MAPPER, schemaJson) + .build(); + + // Serialize the tool to a string + String serialized = JSON_MAPPER.writeValueAsString(tool); + + // Deserialize back to a Tool object + McpSchema.Tool deserializedTool = JSON_MAPPER.readValue(serialized, McpSchema.Tool.class); + + // Serialize again and compare with first serialization + String serializedAgain = JSON_MAPPER.writeValueAsString(deserializedTool); + + // The two serialized strings should be the same + assertThatJson(serializedAgain).when(Option.IGNORING_ARRAY_ORDER).isEqualTo(json(serialized)); + + Map nameInput = Map.of("properties", Map.of("name", Map.of("type", "string")), "required", + List.of("name")); + Map idInput = Map.of("properties", Map.of("id", Map.of("type", "integer")), "required", + List.of("id")); + + List oneOf = (List) deserializedTool.inputSchema().get("oneOf"); + assertThat(oneOf).isEqualTo(List.of(nameInput, idInput)); } @Test From fc76143f7898b9e30d401ebcfb79e70efb4cbd17 Mon Sep 17 00:00:00 2001 From: bilal_oumehdi Date: Sun, 8 Mar 2026 16:54:55 +0000 Subject: [PATCH 3/4] test: fix conformance-tests --- .../conformance/server/ConformanceServlet.java | 13 ++++++------- .../modelcontextprotocol/spec/McpSchemaTests.java | 1 - 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/conformance-tests/server-servlet/src/main/java/io/modelcontextprotocol/conformance/server/ConformanceServlet.java b/conformance-tests/server-servlet/src/main/java/io/modelcontextprotocol/conformance/server/ConformanceServlet.java index 3d162a5de..25ec2c106 100644 --- a/conformance-tests/server-servlet/src/main/java/io/modelcontextprotocol/conformance/server/ConformanceServlet.java +++ b/conformance-tests/server-servlet/src/main/java/io/modelcontextprotocol/conformance/server/ConformanceServlet.java @@ -20,7 +20,6 @@ import io.modelcontextprotocol.spec.McpSchema.EmbeddedResource; import io.modelcontextprotocol.spec.McpSchema.GetPromptResult; import io.modelcontextprotocol.spec.McpSchema.ImageContent; -import io.modelcontextprotocol.spec.McpSchema.JsonSchema; import io.modelcontextprotocol.spec.McpSchema.LoggingLevel; import io.modelcontextprotocol.spec.McpSchema.LoggingMessageNotification; import io.modelcontextprotocol.spec.McpSchema.ProgressNotification; @@ -51,8 +50,8 @@ public class ConformanceServlet { private static final String MCP_ENDPOINT = "/mcp"; - private static final JsonSchema EMPTY_JSON_SCHEMA = new JsonSchema("object", Collections.emptyMap(), null, null, - null, null); + private static final Map EMPTY_JSON_SCHEMA = Map.of("type", "object", "properties", + Collections.emptyMap()); // Minimal 1x1 red pixel PNG (base64 encoded) private static final String RED_PIXEL_PNG = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8DwHwAFBQIAX8jx0gAAAABJRU5ErkJggg=="; @@ -326,10 +325,10 @@ private static List createToolSpecs() { .tool(Tool.builder() .name("test_sampling") .description("Tool that requests LLM sampling from client") - .inputSchema(new JsonSchema("object", + .inputSchema(Map.of("type", "object", "properties", Map.of("prompt", Map.of("type", "string", "description", "The prompt to send to the LLM")), - List.of("prompt"), null, null, null)) + "required", List.of("prompt"))) .build()) .callHandler((exchange, request) -> { logger.info("Tool 'test_sampling' called"); @@ -355,10 +354,10 @@ private static List createToolSpecs() { .tool(Tool.builder() .name("test_elicitation") .description("Tool that requests user input from client") - .inputSchema(new JsonSchema("object", + .inputSchema(Map.of("type", "object", "properties", Map.of("message", Map.of("type", "string", "description", "The message to show the user")), - List.of("message"), null, null, null)) + "required", List.of("message"))) .build()) .callHandler((exchange, request) -> { logger.info("Tool 'test_elicitation' called"); diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java index c0d2becf9..63eb93138 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java @@ -21,7 +21,6 @@ import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; - import io.modelcontextprotocol.json.TypeRef; import net.javacrumbs.jsonunit.core.Option; From 52b564283ade8ec411abdb6c0ac8e889355af6e7 Mon Sep 17 00:00:00 2001 From: Daniel Garnier-Moiroux Date: Fri, 10 Apr 2026 10:54:08 +0200 Subject: [PATCH 4/4] polish gh-749 Signed-off-by: Daniel Garnier-Moiroux --- .../modelcontextprotocol/spec/McpSchema.java | 44 ++++++++++++++ .../AsyncToolSpecificationBuilderTest.java | 28 +++++++-- .../SyncToolSpecificationBuilderTest.java | 14 +++-- .../modelcontextprotocol/util/ToolsUtils.java | 6 +- ...stractMcpClientServerIntegrationTests.java | 44 ++++++++------ .../AbstractStatelessIntegrationTests.java | 18 ++++-- .../server/AbstractMcpAsyncServerTests.java | 28 ++++++--- .../server/AbstractMcpSyncServerTests.java | 22 +++++-- .../modelcontextprotocol/util/ToolsUtils.java | 6 +- .../HttpServletStatelessIntegrationTests.java | 4 +- .../spec/McpSchemaTests.java | 59 ++----------------- 11 files changed, 171 insertions(+), 102 deletions(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index bed27aef8..2e7f73b72 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -1298,6 +1298,29 @@ public ListToolsResult(List tools, String nextCursor) { } } + /** + * A JSON Schema object that describes the expected structure of arguments or output. + * + * @param type The type of the schema (e.g., "object") + * @param properties The properties of the schema object + * @param required List of required property names + * @param additionalProperties Whether additional properties are allowed + * @param defs Schema definitions using the newer $defs keyword + * @param definitions Schema definitions using the legacy definitions keyword + * @deprecated use {@link Map} instead. + */ + @Deprecated + @JsonInclude(JsonInclude.Include.NON_ABSENT) + @JsonIgnoreProperties(ignoreUnknown = true) + public record JsonSchema( // @formatter:off + @JsonProperty("type") String type, + @JsonProperty("properties") Map properties, + @JsonProperty("required") List required, + @JsonProperty("additionalProperties") Boolean additionalProperties, + @JsonProperty("$defs") Map defs, + @JsonProperty("definitions") Map definitions) { // @formatter:on + } + /** * Additional properties describing a Tool to clients. * @@ -1382,6 +1405,27 @@ public Builder description(String description) { return this; } + /** + * @deprecated use {@link #inputSchema(Map)} instead. + */ + @Deprecated + public Builder inputSchema(JsonSchema inputSchema) { + Map schema = new HashMap<>(); + if (inputSchema.type() != null) + schema.put("type", inputSchema.type()); + if (inputSchema.properties() != null) + schema.put("properties", inputSchema.properties()); + if (inputSchema.required() != null) + schema.put("required", inputSchema.required()); + if (inputSchema.additionalProperties() != null) + schema.put("additionalProperties", inputSchema.additionalProperties()); + if (inputSchema.defs() != null) + schema.put("$defs", inputSchema.defs()); + if (inputSchema.definitions() != null) + schema.put("definitions", inputSchema.definitions()); + return inputSchema(schema); + } + public Builder inputSchema(Map inputSchema) { this.inputSchema = inputSchema; return this; diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java index ec2335547..ee8c70ffe 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java @@ -4,6 +4,8 @@ package io.modelcontextprotocol.server; +import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; + import java.util.List; import java.util.Map; @@ -40,7 +42,11 @@ class AsyncToolSpecificationBuilderTest { @Test void builderShouldCreateValidAsyncToolSpecification() { - Tool tool = McpSchema.Tool.builder().name("test-tool").title("A test tool").inputSchema(Map.of()).build(); + Tool tool = McpSchema.Tool.builder() + .name("test-tool") + .title("A test tool") + .inputSchema(EMPTY_JSON_SCHEMA) + .build(); McpServerFeatures.AsyncToolSpecification specification = McpServerFeatures.AsyncToolSpecification.builder() .tool(tool) @@ -63,7 +69,11 @@ void builderShouldThrowExceptionWhenToolIsNull() { @Test void builderShouldThrowExceptionWhenCallToolIsNull() { - Tool tool = McpSchema.Tool.builder().name("test-tool").title("A test tool").inputSchema(Map.of()).build(); + Tool tool = McpSchema.Tool.builder() + .name("test-tool") + .title("A test tool") + .inputSchema(EMPTY_JSON_SCHEMA) + .build(); assertThatThrownBy(() -> McpServerFeatures.AsyncToolSpecification.builder().tool(tool).build()) .isInstanceOf(IllegalArgumentException.class) @@ -72,7 +82,11 @@ void builderShouldThrowExceptionWhenCallToolIsNull() { @Test void builderShouldAllowMethodChaining() { - Tool tool = McpSchema.Tool.builder().name("test-tool").title("A test tool").inputSchema(Map.of()).build(); + Tool tool = McpSchema.Tool.builder() + .name("test-tool") + .title("A test tool") + .inputSchema(EMPTY_JSON_SCHEMA) + .build(); McpServerFeatures.AsyncToolSpecification.Builder builder = McpServerFeatures.AsyncToolSpecification.builder(); // Then - verify method chaining returns the same builder instance @@ -87,7 +101,7 @@ void builtSpecificationShouldExecuteCallToolCorrectly() { Tool tool = McpSchema.Tool.builder() .name("calculator") .title("Simple calculator") - .inputSchema(Map.of()) + .inputSchema(EMPTY_JSON_SCHEMA) .build(); String expectedResult = "42"; @@ -111,7 +125,11 @@ void builtSpecificationShouldExecuteCallToolCorrectly() { @Test void fromSyncShouldConvertSyncToolSpecificationCorrectly() { - Tool tool = McpSchema.Tool.builder().name("sync-tool").title("A sync tool").inputSchema(Map.of()).build(); + Tool tool = McpSchema.Tool.builder() + .name("sync-tool") + .title("A sync tool") + .inputSchema(EMPTY_JSON_SCHEMA) + .build(); String expectedResult = "sync result"; // Create a sync tool specification diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java index e1a730541..f7364be2d 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java @@ -4,6 +4,8 @@ package io.modelcontextprotocol.server; +import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; + import java.util.List; import java.util.Map; @@ -37,7 +39,7 @@ class SyncToolSpecificationBuilderTest { @Test void builderShouldCreateValidSyncToolSpecification() { - Tool tool = Tool.builder().name("test-tool").title("A test tool").inputSchema(Map.of()).build(); + Tool tool = Tool.builder().name("test-tool").title("A test tool").inputSchema(EMPTY_JSON_SCHEMA).build(); McpServerFeatures.SyncToolSpecification specification = McpServerFeatures.SyncToolSpecification.builder() .tool(tool) @@ -61,7 +63,7 @@ void builderShouldThrowExceptionWhenToolIsNull() { @Test void builderShouldThrowExceptionWhenCallToolIsNull() { - Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(Map.of()).build(); + Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(EMPTY_JSON_SCHEMA).build(); assertThatThrownBy(() -> McpServerFeatures.SyncToolSpecification.builder().tool(tool).build()) .isInstanceOf(IllegalArgumentException.class) @@ -70,7 +72,7 @@ void builderShouldThrowExceptionWhenCallToolIsNull() { @Test void builderShouldAllowMethodChaining() { - Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(Map.of()).build(); + Tool tool = Tool.builder().name("test-tool").description("A test tool").inputSchema(EMPTY_JSON_SCHEMA).build(); McpServerFeatures.SyncToolSpecification.Builder builder = McpServerFeatures.SyncToolSpecification.builder(); // Then - verify method chaining returns the same builder instance @@ -82,7 +84,11 @@ void builderShouldAllowMethodChaining() { @Test void builtSpecificationShouldExecuteCallToolCorrectly() { - Tool tool = Tool.builder().name("calculator").description("Simple calculator").inputSchema(Map.of()).build(); + Tool tool = Tool.builder() + .name("calculator") + .description("Simple calculator") + .inputSchema(EMPTY_JSON_SCHEMA) + .build(); String expectedResult = "42"; McpServerFeatures.SyncToolSpecification specification = McpServerFeatures.SyncToolSpecification.builder() diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/util/ToolsUtils.java b/mcp-core/src/test/java/io/modelcontextprotocol/util/ToolsUtils.java index e6085a5ce..a1cafa2e1 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/util/ToolsUtils.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/util/ToolsUtils.java @@ -1,12 +1,14 @@ package io.modelcontextprotocol.util; -import io.modelcontextprotocol.spec.McpSchema; - import java.util.Collections; +import java.util.Map; public final class ToolsUtils { private ToolsUtils() { } + public static final Map EMPTY_JSON_SCHEMA = Map.of("type", "object", "properties", + Collections.emptyMap()); + } diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java index 008790548..880800969 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java @@ -4,6 +4,8 @@ package io.modelcontextprotocol; +import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; + import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -105,7 +107,7 @@ void testCreateMessageWithoutSamplingCapabilities(String clientType) { var clientBuilder = clientBuilders.get(clientType); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((exchange, request) -> { return exchange.createMessage(mock(McpSchema.CreateMessageRequest.class)) .then(Mono.just(mock(CallToolResult.class))); @@ -155,7 +157,7 @@ void testCreateMessageSuccess(String clientType) { AtomicReference samplingResult = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((exchange, request) -> { var createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -234,7 +236,7 @@ void testCreateMessageWithRequestTimeoutSuccess(String clientType) throws Interr AtomicReference samplingResult = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((exchange, request) -> { var createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -309,7 +311,7 @@ void testCreateMessageWithRequestTimeoutFail(String clientType) throws Interrupt .build(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((exchange, request) -> { var createMessageRequest = McpSchema.CreateMessageRequest.builder() @@ -359,7 +361,7 @@ void testCreateElicitationWithoutElicitationCapabilities(String clientType) { var clientBuilder = clientBuilders.get(clientType); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((exchange, request) -> exchange.createElicitation(mock(ElicitRequest.class)) .then(Mono.just(mock(CallToolResult.class)))) .build(); @@ -405,7 +407,7 @@ void testCreateElicitationSuccess(String clientType) { AtomicReference elicitResultRef = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((exchange, request) -> { var elicitationRequest = McpSchema.ElicitRequest.builder() @@ -464,7 +466,7 @@ void testCreateElicitationWithRequestTimeoutSuccess(String clientType) { AtomicReference resultRef = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((exchange, request) -> { var elicitationRequest = McpSchema.ElicitRequest.builder() @@ -535,7 +537,7 @@ void testCreateElicitationWithRequestTimeoutFail(String clientType) { AtomicReference resultRef = new AtomicReference<>(); McpServerFeatures.AsyncToolSpecification tool = McpServerFeatures.AsyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((exchange, request) -> { var elicitationRequest = ElicitRequest.builder() @@ -633,7 +635,7 @@ void testRootsWithoutCapability(String clientType) { var clientBuilder = clientBuilders.get(clientType); McpServerFeatures.SyncToolSpecification tool = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((exchange, request) -> { exchange.listRoots(); // try to list roots @@ -775,7 +777,7 @@ void testToolCallSuccess(String clientType) { .addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=importantValue")) .build(); McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((exchange, request) -> { try { @@ -826,7 +828,11 @@ void testThrowingToolCallIsCaughtBeforeTimeout(String clientType) { McpSyncServer mcpServer = prepareSyncServerBuilder() .capabilities(ServerCapabilities.builder().tools(true).build()) .tools(McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder() + .name("tool1") + .description("tool1 description") + .inputSchema(EMPTY_JSON_SCHEMA) + .build()) .callHandler((exchange, request) -> { // We trigger a timeout on blocking read, raising an exception Mono.never().block(Duration.ofSeconds(1)); @@ -864,7 +870,7 @@ void testToolCallSuccessWithTranportContextExtraction(String clientType) { .addContent(new McpSchema.TextContent("CALL RESPONSE; ctx=value")) .build(); McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((exchange, request) -> { McpTransportContext transportContext = exchange.transportContext(); @@ -920,7 +926,7 @@ void testToolListChangeHandlingSuccess(String clientType) { .build(); McpServerFeatures.SyncToolSpecification tool1 = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((exchange, request) -> { // perform a blocking call to a remote service try { @@ -986,7 +992,11 @@ void testToolListChangeHandlingSuccess(String clientType) { // Add a new tool McpServerFeatures.SyncToolSpecification tool2 = McpServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool2").description("tool2 description").inputSchema(Map.of()).build()) + .tool(Tool.builder() + .name("tool2") + .description("tool2 description") + .inputSchema(EMPTY_JSON_SCHEMA) + .build()) .callHandler((exchange, request) -> callResponse) .build(); @@ -1037,7 +1047,7 @@ void testLoggingNotification(String clientType) throws InterruptedException { .tool(Tool.builder() .name("logging-test") .description("Test logging notifications") - .inputSchema(Map.of()) + .inputSchema(EMPTY_JSON_SCHEMA) .build()) .callHandler((exchange, request) -> { @@ -1154,7 +1164,7 @@ void testProgressNotification(String clientType) throws InterruptedException { .tool(McpSchema.Tool.builder() .name("progress-test") .description("Test progress notifications") - .inputSchema(Map.of()) + .inputSchema(EMPTY_JSON_SCHEMA) .build()) .callHandler((exchange, request) -> { @@ -1312,7 +1322,7 @@ void testPingSuccess(String clientType) { .tool(Tool.builder() .name("ping-async-test") .description("Test ping async behavior") - .inputSchema(Map.of()) + .inputSchema(EMPTY_JSON_SCHEMA) .build()) .callHandler((exchange, request) -> { diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java index af139dc03..24cc9c3d0 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractStatelessIntegrationTests.java @@ -4,6 +4,8 @@ package io.modelcontextprotocol; +import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; + import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -87,7 +89,7 @@ void testToolCallSuccess(String clientType) { .build(); McpStatelessServerFeatures.SyncToolSpecification tool1 = McpStatelessServerFeatures.SyncToolSpecification .builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((ctx, request) -> { try { @@ -137,7 +139,11 @@ void testThrowingToolCallIsCaughtBeforeTimeout(String clientType) { McpStatelessSyncServer mcpServer = prepareSyncServerBuilder() .capabilities(ServerCapabilities.builder().tools(true).build()) .tools(McpStatelessServerFeatures.SyncToolSpecification.builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder() + .name("tool1") + .description("tool1 description") + .inputSchema(EMPTY_JSON_SCHEMA) + .build()) .callHandler((context, request) -> { // We trigger a timeout on blocking read, raising an exception Mono.never().block(Duration.ofSeconds(1)); @@ -174,7 +180,7 @@ void testToolListChangeHandlingSuccess(String clientType) { .build(); McpStatelessServerFeatures.SyncToolSpecification tool1 = McpStatelessServerFeatures.SyncToolSpecification .builder() - .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(Map.of()).build()) + .tool(Tool.builder().name("tool1").description("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build()) .callHandler((ctx, request) -> { // perform a blocking call to a remote service try { @@ -232,7 +238,11 @@ void testToolListChangeHandlingSuccess(String clientType) { // Add a new tool McpStatelessServerFeatures.SyncToolSpecification tool2 = McpStatelessServerFeatures.SyncToolSpecification .builder() - .tool(Tool.builder().name("tool2").description("tool2 description").inputSchema(Map.of()).build()) + .tool(Tool.builder() + .name("tool2") + .description("tool2 description") + .inputSchema(EMPTY_JSON_SCHEMA) + .build()) .callHandler((exchange, request) -> callResponse) .build(); diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java index 87d413f8a..731f763a3 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpAsyncServerTests.java @@ -4,6 +4,8 @@ package io.modelcontextprotocol.server; +import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; + import java.time.Duration; import java.util.List; import java.util.Map; @@ -97,7 +99,11 @@ void testImmediateClose() { // --------------------------------------- @Test void testAddToolCall() { - Tool newTool = McpSchema.Tool.builder().name("new-tool").title("New test tool").inputSchema(Map.of()).build(); + Tool newTool = McpSchema.Tool.builder() + .name("new-tool") + .title("New test tool") + .inputSchema(EMPTY_JSON_SCHEMA) + .build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) @@ -117,7 +123,7 @@ void testAddDuplicateToolCall() { Tool duplicateTool = McpSchema.Tool.builder() .name(TEST_TOOL_NAME) .title("Duplicate tool") - .inputSchema(Map.of()) + .inputSchema(EMPTY_JSON_SCHEMA) .build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -141,7 +147,7 @@ void testDuplicateToolCallDuringBuilding() { Tool duplicateTool = McpSchema.Tool.builder() .name("duplicate-build-toolcall") .title("Duplicate toolcall during building") - .inputSchema(Map.of()) + .inputSchema(EMPTY_JSON_SCHEMA) .build(); assertThatThrownBy(() -> prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -161,7 +167,7 @@ void testDuplicateToolsInBatchListRegistration() { Tool duplicateTool = McpSchema.Tool.builder() .name("batch-list-tool") .title("Duplicate tool in batch list") - .inputSchema(Map.of()) + .inputSchema(EMPTY_JSON_SCHEMA) .build(); List specs = List.of( @@ -189,7 +195,7 @@ void testDuplicateToolsInBatchVarargsRegistration() { Tool duplicateTool = McpSchema.Tool.builder() .name("batch-varargs-tool") .title("Duplicate tool in batch varargs") - .inputSchema(Map.of()) + .inputSchema(EMPTY_JSON_SCHEMA) .build(); assertThatThrownBy(() -> prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -211,7 +217,11 @@ void testDuplicateToolsInBatchVarargsRegistration() { @Test void testRemoveTool() { - Tool too = McpSchema.Tool.builder().name(TEST_TOOL_NAME).title("Duplicate tool").inputSchema(Map.of()).build(); + Tool too = McpSchema.Tool.builder() + .name(TEST_TOOL_NAME) + .title("Duplicate tool") + .inputSchema(EMPTY_JSON_SCHEMA) + .build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) @@ -238,7 +248,11 @@ void testRemoveNonexistentTool() { @Test void testNotifyToolsListChanged() { - Tool too = McpSchema.Tool.builder().name(TEST_TOOL_NAME).title("Duplicate tool").inputSchema(Map.of()).build(); + Tool too = McpSchema.Tool.builder() + .name(TEST_TOOL_NAME) + .title("Duplicate tool") + .inputSchema(EMPTY_JSON_SCHEMA) + .build(); var mcpAsyncServer = prepareAsyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java index dc7e6b189..d8d036dc0 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/server/AbstractMcpSyncServerTests.java @@ -4,6 +4,8 @@ package io.modelcontextprotocol.server; +import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; + import java.util.List; import java.util.Map; @@ -105,7 +107,11 @@ void testAddToolCall() { .capabilities(ServerCapabilities.builder().tools(true).build()) .build(); - Tool newTool = McpSchema.Tool.builder().name("new-tool").title("New test tool").inputSchema(Map.of()).build(); + Tool newTool = McpSchema.Tool.builder() + .name("new-tool") + .title("New test tool") + .inputSchema(EMPTY_JSON_SCHEMA) + .build(); assertThatCode(() -> mcpSyncServer.addTool(McpServerFeatures.SyncToolSpecification.builder() .tool(newTool) @@ -120,7 +126,7 @@ void testAddDuplicateToolCall() { Tool duplicateTool = McpSchema.Tool.builder() .name(TEST_TOOL_NAME) .title("Duplicate tool") - .inputSchema(Map.of()) + .inputSchema(EMPTY_JSON_SCHEMA) .build(); var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -142,7 +148,7 @@ void testDuplicateToolCallDuringBuilding() { Tool duplicateTool = McpSchema.Tool.builder() .name("duplicate-build-toolcall") .title("Duplicate toolcall during building") - .inputSchema(Map.of()) + .inputSchema(EMPTY_JSON_SCHEMA) .build(); assertThatThrownBy(() -> prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -160,7 +166,7 @@ void testDuplicateToolsInBatchListRegistration() { Tool duplicateTool = McpSchema.Tool.builder() .name("batch-list-tool") .title("Duplicate tool in batch list") - .inputSchema(Map.of()) + .inputSchema(EMPTY_JSON_SCHEMA) .build(); List specs = List.of( McpServerFeatures.SyncToolSpecification.builder() @@ -187,7 +193,7 @@ void testDuplicateToolsInBatchVarargsRegistration() { Tool duplicateTool = McpSchema.Tool.builder() .name("batch-varargs-tool") .title("Duplicate tool in batch varargs") - .inputSchema(Map.of()) + .inputSchema(EMPTY_JSON_SCHEMA) .build(); assertThatThrownBy(() -> prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") @@ -208,7 +214,11 @@ void testDuplicateToolsInBatchVarargsRegistration() { @Test void testRemoveTool() { - Tool tool = McpSchema.Tool.builder().name(TEST_TOOL_NAME).title("Test tool").inputSchema(Map.of()).build(); + Tool tool = McpSchema.Tool.builder() + .name(TEST_TOOL_NAME) + .title("Test tool") + .inputSchema(EMPTY_JSON_SCHEMA) + .build(); var mcpSyncServer = prepareSyncServerBuilder().serverInfo("test-server", "1.0.0") .capabilities(ServerCapabilities.builder().tools(true).build()) diff --git a/mcp-test/src/main/java/io/modelcontextprotocol/util/ToolsUtils.java b/mcp-test/src/main/java/io/modelcontextprotocol/util/ToolsUtils.java index e6085a5ce..a1cafa2e1 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/util/ToolsUtils.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/util/ToolsUtils.java @@ -1,12 +1,14 @@ package io.modelcontextprotocol.util; -import io.modelcontextprotocol.spec.McpSchema; - import java.util.Collections; +import java.util.Map; public final class ToolsUtils { private ToolsUtils() { } + public static final Map EMPTY_JSON_SCHEMA = Map.of("type", "object", "properties", + Collections.emptyMap()); + } diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java index ebae4d8d5..3d40453a3 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java @@ -4,6 +4,8 @@ package io.modelcontextprotocol.server; +import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA; + import java.time.Duration; import java.util.List; import java.util.Map; @@ -118,7 +120,7 @@ void testToolCallSuccess(String clientType) { .isError(false) .build(); McpStatelessServerFeatures.SyncToolSpecification tool1 = new McpStatelessServerFeatures.SyncToolSpecification( - Tool.builder().name("tool1").title("tool1 description").inputSchema(Map.of()).build(), + Tool.builder().name("tool1").title("tool1 description").inputSchema(EMPTY_JSON_SCHEMA).build(), (transportContext, request) -> { // perform a blocking call to a remote service String response = RestClient.create() diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java index 63eb93138..09529f2e0 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java @@ -850,60 +850,11 @@ void testToolWithComplexSchema() throws Exception { assertThatJson(serializedAgain).when(Option.IGNORING_ARRAY_ORDER).isEqualTo(json(serialized)); // Just verify the basic structure was preserved - Map defs = ((Map) deserializedTool.inputSchema().get("$defs")); - Map address = ((Map) defs.get("Address")); - - assertThat(deserializedTool.inputSchema().containsKey("$defs")).isTrue(); - assertThat(address.get("type")).isEqualTo("object"); - } - - @Test - void testToolWithSchemaThatDoesNotHavePropertiesAtTopLevel() throws Exception { - String schemaJson = """ - { - "type": "object", - "oneOf": [ - { - "properties": { - "name": {"type": "string"} - }, - "required":["name"] - }, - { - "properties": { - "id": {"type": "integer"} - }, - "required":["id"] - } - ] - } - """; - - McpSchema.Tool tool = McpSchema.Tool.builder() - .name("addressTool") - .title("Handles addresses") - .inputSchema(JSON_MAPPER, schemaJson) - .build(); - - // Serialize the tool to a string - String serialized = JSON_MAPPER.writeValueAsString(tool); - - // Deserialize back to a Tool object - McpSchema.Tool deserializedTool = JSON_MAPPER.readValue(serialized, McpSchema.Tool.class); - - // Serialize again and compare with first serialization - String serializedAgain = JSON_MAPPER.writeValueAsString(deserializedTool); - - // The two serialized strings should be the same - assertThatJson(serializedAgain).when(Option.IGNORING_ARRAY_ORDER).isEqualTo(json(serialized)); - - Map nameInput = Map.of("properties", Map.of("name", Map.of("type", "string")), "required", - List.of("name")); - Map idInput = Map.of("properties", Map.of("id", Map.of("type", "integer")), "required", - List.of("id")); - - List oneOf = (List) deserializedTool.inputSchema().get("oneOf"); - assertThat(oneOf).isEqualTo(List.of(nameInput, idInput)); + assertThat(deserializedTool.inputSchema()).containsKey("$defs") + .extractingByKey("$defs") + .isNotNull() + .asInstanceOf(InstanceOfAssertFactories.MAP) + .containsKey("Address"); } @Test