feat(schema): add support for JSON Schema $defs and definitions (#146)
Added support for $defs and definitions properties in JsonSchema record to handle JSON Schema references properly. Added tests to verify both formats work correctly. The JsonSchema test approach uses serialization/deserialization round-trip validation instead of property-by-property assertions. This makes tests more maintainable and less likely to break when new properties are added. 🤖 Generated with [Claude Code](https://claude.ai/code) --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
86e3e9048f
commit
f70b98b4b4
2 changed files with 132 additions and 1 deletions
|
|
@ -703,7 +703,9 @@ public final class McpSchema {
|
||||||
@JsonProperty("type") String type,
|
@JsonProperty("type") String type,
|
||||||
@JsonProperty("properties") Map<String, Object> properties,
|
@JsonProperty("properties") Map<String, Object> properties,
|
||||||
@JsonProperty("required") List<String> required,
|
@JsonProperty("required") List<String> required,
|
||||||
@JsonProperty("additionalProperties") Boolean additionalProperties) {
|
@JsonProperty("additionalProperties") Boolean additionalProperties,
|
||||||
|
@JsonProperty("$defs") Map<String, Object> defs,
|
||||||
|
@JsonProperty("definitions") Map<String, Object> definitions) {
|
||||||
} // @formatter:on
|
} // @formatter:on
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
|
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
|
||||||
import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
|
import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
|
||||||
|
|
@ -449,6 +450,92 @@ public class McpSchemaTests {
|
||||||
|
|
||||||
// Tool Tests
|
// Tool Tests
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testJsonSchema() throws Exception {
|
||||||
|
String schemaJson = """
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"$ref": "#/$defs/Address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"],
|
||||||
|
"$defs": {
|
||||||
|
"Address": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"street": {"type": "string"},
|
||||||
|
"city": {"type": "string"}
|
||||||
|
},
|
||||||
|
"required": ["street", "city"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Deserialize the original string to a JsonSchema object
|
||||||
|
McpSchema.JsonSchema schema = mapper.readValue(schemaJson, McpSchema.JsonSchema.class);
|
||||||
|
|
||||||
|
// Serialize the object back to a string
|
||||||
|
String serialized = mapper.writeValueAsString(schema);
|
||||||
|
|
||||||
|
// Deserialize again
|
||||||
|
McpSchema.JsonSchema deserialized = mapper.readValue(serialized, McpSchema.JsonSchema.class);
|
||||||
|
|
||||||
|
// Serialize one more time and compare with the first serialization
|
||||||
|
String serializedAgain = mapper.writeValueAsString(deserialized);
|
||||||
|
|
||||||
|
// The two serialized strings should be the same
|
||||||
|
assertThatJson(serializedAgain).when(Option.IGNORING_ARRAY_ORDER).isEqualTo(json(serialized));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testJsonSchemaWithDefinitions() throws Exception {
|
||||||
|
String schemaJson = """
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"address": {
|
||||||
|
"$ref": "#/definitions/Address"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["name"],
|
||||||
|
"definitions": {
|
||||||
|
"Address": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"street": {"type": "string"},
|
||||||
|
"city": {"type": "string"}
|
||||||
|
},
|
||||||
|
"required": ["street", "city"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// Deserialize the original string to a JsonSchema object
|
||||||
|
McpSchema.JsonSchema schema = mapper.readValue(schemaJson, McpSchema.JsonSchema.class);
|
||||||
|
|
||||||
|
// Serialize the object back to a string
|
||||||
|
String serialized = mapper.writeValueAsString(schema);
|
||||||
|
|
||||||
|
// Deserialize again
|
||||||
|
McpSchema.JsonSchema deserialized = mapper.readValue(serialized, McpSchema.JsonSchema.class);
|
||||||
|
|
||||||
|
// Serialize one more time and compare with the first serialization
|
||||||
|
String serializedAgain = mapper.writeValueAsString(deserialized);
|
||||||
|
|
||||||
|
// The two serialized strings should be the same
|
||||||
|
assertThatJson(serializedAgain).when(Option.IGNORING_ARRAY_ORDER).isEqualTo(json(serialized));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testTool() throws Exception {
|
void testTool() throws Exception {
|
||||||
String schemaJson = """
|
String schemaJson = """
|
||||||
|
|
@ -477,6 +564,48 @@ public class McpSchemaTests {
|
||||||
{"name":"test-tool","description":"A test tool","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"number"}},"required":["name"]}}"""));
|
{"name":"test-tool","description":"A test tool","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"number"}},"required":["name"]}}"""));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void testToolWithComplexSchema() throws Exception {
|
||||||
|
String complexSchemaJson = """
|
||||||
|
{
|
||||||
|
"type": "object",
|
||||||
|
"$defs": {
|
||||||
|
"Address": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"street": {"type": "string"},
|
||||||
|
"city": {"type": "string"}
|
||||||
|
},
|
||||||
|
"required": ["street", "city"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"name": {"type": "string"},
|
||||||
|
"shippingAddress": {"$ref": "#/$defs/Address"}
|
||||||
|
},
|
||||||
|
"required": ["name", "shippingAddress"]
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
McpSchema.Tool tool = new McpSchema.Tool("addressTool", "Handles addresses", complexSchemaJson);
|
||||||
|
|
||||||
|
// Serialize the tool to a string
|
||||||
|
String serialized = mapper.writeValueAsString(tool);
|
||||||
|
|
||||||
|
// Deserialize back to a Tool object
|
||||||
|
McpSchema.Tool deserializedTool = mapper.readValue(serialized, McpSchema.Tool.class);
|
||||||
|
|
||||||
|
// Serialize again and compare with first serialization
|
||||||
|
String serializedAgain = mapper.writeValueAsString(deserializedTool);
|
||||||
|
|
||||||
|
// The two serialized strings should be the same
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void testCallToolRequest() throws Exception {
|
void testCallToolRequest() throws Exception {
|
||||||
Map<String, Object> arguments = new HashMap<>();
|
Map<String, Object> arguments = new HashMap<>();
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue