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 bb9cead7e..bed27aef8 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -1298,27 +1298,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. * @@ -1363,7 +1342,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 @@ -1380,7 +1359,7 @@ public static class Builder { private String description; - private JsonSchema inputSchema; + private Map inputSchema; private Map outputSchema; @@ -1403,13 +1382,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; } @@ -1450,15 +1429,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/server/AsyncToolSpecificationBuilderTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java index 897ae2ccc..ec2335547 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/AsyncToolSpecificationBuilderTest.java @@ -25,7 +25,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; @@ -41,11 +40,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) @@ -68,11 +63,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) @@ -81,11 +72,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 @@ -100,7 +87,7 @@ void builtSpecificationShouldExecuteCallToolCorrectly() { Tool tool = McpSchema.Tool.builder() .name("calculator") .title("Simple calculator") - .inputSchema(EMPTY_JSON_SCHEMA) + .inputSchema(Map.of()) .build(); String expectedResult = "42"; @@ -124,11 +111,7 @@ void builtSpecificationShouldExecuteCallToolCorrectly() { @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 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 54c45e561..e1a730541 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/server/SyncToolSpecificationBuilderTest.java @@ -22,7 +22,6 @@ import org.junit.jupiter.api.Test; import org.slf4j.LoggerFactory; -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; @@ -38,7 +37,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) @@ -62,7 +61,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) @@ -71,7 +70,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 @@ -83,11 +82,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/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 e5d55c39d..008790548 100644 --- a/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java +++ b/mcp-test/src/main/java/io/modelcontextprotocol/AbstractMcpClientServerIntegrationTests.java @@ -54,7 +54,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; @@ -106,7 +105,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))); @@ -156,7 +155,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() @@ -235,7 +234,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() @@ -310,7 +309,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() @@ -360,7 +359,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(); @@ -406,7 +405,7 @@ void testCreateElicitationSuccess(String clientType) { AtomicReference elicitResultRef = 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() @@ -465,7 +464,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() @@ -536,7 +535,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() @@ -634,7 +633,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 @@ -776,7 +775,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 { @@ -827,11 +826,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)); @@ -869,7 +864,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(); @@ -925,7 +920,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 { @@ -991,11 +986,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(); @@ -1046,7 +1037,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) -> { @@ -1163,7 +1154,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) -> { @@ -1321,7 +1312,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 7755ce456..af139dc03 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; @@ -88,7 +87,7 @@ void testToolCallSuccess(String clientType) { .build(); 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 { @@ -138,11 +137,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)); @@ -179,7 +174,7 @@ void testToolListChangeHandlingSuccess(String clientType) { .build(); 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 { @@ -237,11 +232,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 9cd1191d1..87d413f8a 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; @@ -97,11 +97,7 @@ void testImmediateClose() { // --------------------------------------- @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()) @@ -121,7 +117,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") @@ -145,7 +141,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") @@ -165,7 +161,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( @@ -193,7 +189,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") @@ -215,11 +211,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()) @@ -246,11 +238,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 eee5f1a4d..dc7e6b189 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; @@ -105,11 +105,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) @@ -124,7 +120,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") @@ -146,7 +142,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") @@ -164,7 +160,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() @@ -191,7 +187,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") @@ -212,11 +208,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); - } diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/client/McpAsyncClientTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/client/McpAsyncClientTests.java index 48bf1da5b..a6738d4f8 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/client/McpAsyncClientTests.java +++ b/mcp-test/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-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java index 491c2d4ed..ebae4d8d5 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/server/HttpServletStatelessIntegrationTests.java +++ b/mcp-test/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-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java index 942e0a6e2..c0d2becf9 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java @@ -21,6 +21,8 @@ import org.assertj.core.api.InstanceOfAssertFactories; import org.junit.jupiter.api.Test; + +import io.modelcontextprotocol.json.TypeRef; import net.javacrumbs.jsonunit.core.Option; /** @@ -713,13 +715,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); @@ -756,13 +760,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); @@ -845,8 +851,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().defs()).isNotNull(); - 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 @@ -866,14 +924,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(); @@ -1114,7 +1172,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");