Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String, Object> 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==";
Expand Down Expand Up @@ -326,10 +325,10 @@ private static List<McpServerFeatures.SyncToolSpecification> 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");
Expand All @@ -355,10 +354,10 @@ private static List<McpServerFeatures.SyncToolSpecification> 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");
Expand Down
38 changes: 26 additions & 12 deletions mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -1307,7 +1307,9 @@ public ListToolsResult(List<Tool> tools, String nextCursor) {
* @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
Expand Down Expand Up @@ -1363,7 +1365,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<String, Object> inputSchema,
@JsonProperty("outputSchema") Map<String, Object> outputSchema,
@JsonProperty("annotations") ToolAnnotations annotations,
@JsonProperty("_meta") Map<String, Object> meta) { // @formatter:on
Expand All @@ -1380,7 +1382,7 @@ public static class Builder {

private String description;

private JsonSchema inputSchema;
private Map<String, Object> inputSchema;

private Map<String, Object> outputSchema;

Expand All @@ -1403,13 +1405,34 @@ public Builder description(String description) {
return this;
}

/**
* @deprecated use {@link #inputSchema(Map)} instead.
*/
@Deprecated
public Builder inputSchema(JsonSchema inputSchema) {
Map<String, Object> 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<String, Object> 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;
}

Expand Down Expand Up @@ -1450,15 +1473,6 @@ private static Map<String, Object> 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.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

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

Expand All @@ -22,7 +24,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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +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 McpSchema.JsonSchema EMPTY_JSON_SCHEMA = new McpSchema.JsonSchema("object",
Collections.emptyMap(), null, null, null, null);
public static final Map<String, Object> EMPTY_JSON_SCHEMA = Map.of("type", "object", "properties",
Collections.emptyMap());

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -56,7 +58,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -32,7 +34,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@

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;

import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
Expand All @@ -25,7 +28,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@

package io.modelcontextprotocol.server;

import static io.modelcontextprotocol.util.ToolsUtils.EMPTY_JSON_SCHEMA;

import java.util.List;
import java.util.Map;

import io.modelcontextprotocol.spec.McpSchema;
import io.modelcontextprotocol.spec.McpSchema.CallToolResult;
Expand All @@ -20,7 +23,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;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +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 McpSchema.JsonSchema EMPTY_JSON_SCHEMA = new McpSchema.JsonSchema("object",
Collections.emptyMap(), null, null, null, null);
public static final Map<String, Object> EMPTY_JSON_SCHEMA = Map.of("type", "object", "properties",
Collections.emptyMap());

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,10 @@ private McpClientTransport createMockTransportForToolValidation(boolean hasOutpu
Map<String, Object> 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<String, Object> outputSchema = Map.of("type", "object", "properties",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -48,7 +50,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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.Test;

import io.modelcontextprotocol.json.TypeRef;
import net.javacrumbs.jsonunit.core.Option;

/**
Expand Down Expand Up @@ -713,13 +714,15 @@ void testJsonSchema() throws Exception {
""";

// Deserialize the original string to a JsonSchema object
McpSchema.JsonSchema schema = JSON_MAPPER.readValue(schemaJson, McpSchema.JsonSchema.class);
Map<String, Object> schema = JSON_MAPPER.readValue(schemaJson, new TypeRef<HashMap<String, Object>>() {
});

// 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<String, Object> deserialized = JSON_MAPPER.readValue(serialized, new TypeRef<HashMap<String, Object>>() {
});

// Serialize one more time and compare with the first serialization
String serializedAgain = JSON_MAPPER.writeValueAsString(deserialized);
Expand Down Expand Up @@ -756,13 +759,15 @@ void testJsonSchemaWithDefinitions() throws Exception {
""";

// Deserialize the original string to a JsonSchema object
McpSchema.JsonSchema schema = JSON_MAPPER.readValue(schemaJson, McpSchema.JsonSchema.class);
Map<String, Object> schema = JSON_MAPPER.readValue(schemaJson, new TypeRef<HashMap<String, Object>>() {
});

// 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<String, Object> deserialized = JSON_MAPPER.readValue(serialized, new TypeRef<HashMap<String, Object>>() {
});

// Serialize one more time and compare with the first serialization
String serializedAgain = JSON_MAPPER.writeValueAsString(deserialized);
Expand Down Expand Up @@ -845,8 +850,11 @@ 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")
.extractingByKey("$defs")
.isNotNull()
.asInstanceOf(InstanceOfAssertFactories.MAP)
.containsKey("Address");
}

@Test
Expand All @@ -866,14 +874,14 @@ void testToolWithMeta() throws Exception {
}
""";

McpSchema.JsonSchema schema = JSON_MAPPER.readValue(schemaJson, McpSchema.JsonSchema.class);
Map<String, Object> inputSchema = Map.of("inputSchema", schemaJson);
Map<String, Object> 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();

Expand Down Expand Up @@ -1114,7 +1122,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");
Expand Down
Loading