Skip to content

Commit f70b98b

Browse files
arcaputo3Claude
and
Claude
authored
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 <[email protected]>
1 parent 86e3e90 commit f70b98b

File tree

2 files changed

+132
-1
lines changed

2 files changed

+132
-1
lines changed

mcp/src/main/java/io/modelcontextprotocol/spec/McpSchema.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -703,7 +703,9 @@ public record JsonSchema( // @formatter:off
703703
@JsonProperty("type") String type,
704704
@JsonProperty("properties") Map<String, Object> properties,
705705
@JsonProperty("required") List<String> required,
706-
@JsonProperty("additionalProperties") Boolean additionalProperties) {
706+
@JsonProperty("additionalProperties") Boolean additionalProperties,
707+
@JsonProperty("$defs") Map<String, Object> defs,
708+
@JsonProperty("definitions") Map<String, Object> definitions) {
707709
} // @formatter:on
708710

709711
/**

mcp/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java

+129
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.List;
1010
import java.util.Map;
1111

12+
import com.fasterxml.jackson.core.type.TypeReference;
1213
import com.fasterxml.jackson.databind.ObjectMapper;
1314
import com.fasterxml.jackson.databind.exc.InvalidTypeIdException;
1415
import io.modelcontextprotocol.spec.McpSchema.TextResourceContents;
@@ -449,6 +450,92 @@ void testGetPromptResult() throws Exception {
449450

450451
// Tool Tests
451452

453+
@Test
454+
void testJsonSchema() throws Exception {
455+
String schemaJson = """
456+
{
457+
"type": "object",
458+
"properties": {
459+
"name": {
460+
"type": "string"
461+
},
462+
"address": {
463+
"$ref": "#/$defs/Address"
464+
}
465+
},
466+
"required": ["name"],
467+
"$defs": {
468+
"Address": {
469+
"type": "object",
470+
"properties": {
471+
"street": {"type": "string"},
472+
"city": {"type": "string"}
473+
},
474+
"required": ["street", "city"]
475+
}
476+
}
477+
}
478+
""";
479+
480+
// Deserialize the original string to a JsonSchema object
481+
McpSchema.JsonSchema schema = mapper.readValue(schemaJson, McpSchema.JsonSchema.class);
482+
483+
// Serialize the object back to a string
484+
String serialized = mapper.writeValueAsString(schema);
485+
486+
// Deserialize again
487+
McpSchema.JsonSchema deserialized = mapper.readValue(serialized, McpSchema.JsonSchema.class);
488+
489+
// Serialize one more time and compare with the first serialization
490+
String serializedAgain = mapper.writeValueAsString(deserialized);
491+
492+
// The two serialized strings should be the same
493+
assertThatJson(serializedAgain).when(Option.IGNORING_ARRAY_ORDER).isEqualTo(json(serialized));
494+
}
495+
496+
@Test
497+
void testJsonSchemaWithDefinitions() throws Exception {
498+
String schemaJson = """
499+
{
500+
"type": "object",
501+
"properties": {
502+
"name": {
503+
"type": "string"
504+
},
505+
"address": {
506+
"$ref": "#/definitions/Address"
507+
}
508+
},
509+
"required": ["name"],
510+
"definitions": {
511+
"Address": {
512+
"type": "object",
513+
"properties": {
514+
"street": {"type": "string"},
515+
"city": {"type": "string"}
516+
},
517+
"required": ["street", "city"]
518+
}
519+
}
520+
}
521+
""";
522+
523+
// Deserialize the original string to a JsonSchema object
524+
McpSchema.JsonSchema schema = mapper.readValue(schemaJson, McpSchema.JsonSchema.class);
525+
526+
// Serialize the object back to a string
527+
String serialized = mapper.writeValueAsString(schema);
528+
529+
// Deserialize again
530+
McpSchema.JsonSchema deserialized = mapper.readValue(serialized, McpSchema.JsonSchema.class);
531+
532+
// Serialize one more time and compare with the first serialization
533+
String serializedAgain = mapper.writeValueAsString(deserialized);
534+
535+
// The two serialized strings should be the same
536+
assertThatJson(serializedAgain).when(Option.IGNORING_ARRAY_ORDER).isEqualTo(json(serialized));
537+
}
538+
452539
@Test
453540
void testTool() throws Exception {
454541
String schemaJson = """
@@ -477,6 +564,48 @@ void testTool() throws Exception {
477564
{"name":"test-tool","description":"A test tool","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"number"}},"required":["name"]}}"""));
478565
}
479566

567+
@Test
568+
void testToolWithComplexSchema() throws Exception {
569+
String complexSchemaJson = """
570+
{
571+
"type": "object",
572+
"$defs": {
573+
"Address": {
574+
"type": "object",
575+
"properties": {
576+
"street": {"type": "string"},
577+
"city": {"type": "string"}
578+
},
579+
"required": ["street", "city"]
580+
}
581+
},
582+
"properties": {
583+
"name": {"type": "string"},
584+
"shippingAddress": {"$ref": "#/$defs/Address"}
585+
},
586+
"required": ["name", "shippingAddress"]
587+
}
588+
""";
589+
590+
McpSchema.Tool tool = new McpSchema.Tool("addressTool", "Handles addresses", complexSchemaJson);
591+
592+
// Serialize the tool to a string
593+
String serialized = mapper.writeValueAsString(tool);
594+
595+
// Deserialize back to a Tool object
596+
McpSchema.Tool deserializedTool = mapper.readValue(serialized, McpSchema.Tool.class);
597+
598+
// Serialize again and compare with first serialization
599+
String serializedAgain = mapper.writeValueAsString(deserializedTool);
600+
601+
// The two serialized strings should be the same
602+
assertThatJson(serializedAgain).when(Option.IGNORING_ARRAY_ORDER).isEqualTo(json(serialized));
603+
604+
// Just verify the basic structure was preserved
605+
assertThat(deserializedTool.inputSchema().defs()).isNotNull();
606+
assertThat(deserializedTool.inputSchema().defs()).containsKey("Address");
607+
}
608+
480609
@Test
481610
void testCallToolRequest() throws Exception {
482611
Map<String, Object> arguments = new HashMap<>();

0 commit comments

Comments
 (0)