Skip to content

Commit 10d1c1e

Browse files
arcaputo3claude
andcommitted
feat(schema): add support for JSON Schema $defs and definitions
Added support for $defs and definitions properties in JsonSchema record to handle JSON Schema references properly. Added tests to verify both formats work correctly. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent f348a83 commit 10d1c1e

2 files changed

Lines changed: 148 additions & 1 deletion

File tree

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -691,7 +691,9 @@ public record JsonSchema( // @formatter:off
691691
@JsonProperty("type") String type,
692692
@JsonProperty("properties") Map<String, Object> properties,
693693
@JsonProperty("required") List<String> required,
694-
@JsonProperty("additionalProperties") Boolean additionalProperties) {
694+
@JsonProperty("additionalProperties") Boolean additionalProperties,
695+
@JsonProperty("$defs") Map<String, Object> defs,
696+
@JsonProperty("definitions") Map<String, Object> definitions) {
695697
} // @formatter:on
696698

697699
/**

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

Lines changed: 145 additions & 0 deletions
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,106 @@ 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+
McpSchema.JsonSchema schema = mapper.readValue(schemaJson, McpSchema.JsonSchema.class);
481+
482+
assertThat(schema.type()).isEqualTo("object");
483+
assertThat(schema.properties()).containsKeys("name", "address");
484+
assertThat(schema.required()).containsExactly("name");
485+
assertThat(schema.defs()).isNotNull();
486+
assertThat(schema.defs()).containsKey("Address");
487+
488+
String value = mapper.writeValueAsString(schema);
489+
490+
// Convert to map for easier assertions
491+
Map<String, Object> jsonMap = mapper.readValue(value, new TypeReference<HashMap<String, Object>>() {
492+
});
493+
Map<String, Object> defs = (Map<String, Object>) jsonMap.get("$defs");
494+
Map<String, Object> address = (Map<String, Object>) defs.get("Address");
495+
496+
assertThat(address).containsEntry("type", "object");
497+
assertThat(((Map<String, Object>) ((Map<String, Object>) address.get("properties")).get("street")).get("type"))
498+
.isEqualTo("string");
499+
assertThat(((Map<String, Object>) ((Map<String, Object>) address.get("properties")).get("city")).get("type"))
500+
.isEqualTo("string");
501+
}
502+
503+
@Test
504+
void testJsonSchemaWithDefinitions() throws Exception {
505+
String schemaJson = """
506+
{
507+
"type": "object",
508+
"properties": {
509+
"name": {
510+
"type": "string"
511+
},
512+
"address": {
513+
"$ref": "#/definitions/Address"
514+
}
515+
},
516+
"required": ["name"],
517+
"definitions": {
518+
"Address": {
519+
"type": "object",
520+
"properties": {
521+
"street": {"type": "string"},
522+
"city": {"type": "string"}
523+
},
524+
"required": ["street", "city"]
525+
}
526+
}
527+
}
528+
""";
529+
530+
McpSchema.JsonSchema schema = mapper.readValue(schemaJson, McpSchema.JsonSchema.class);
531+
532+
assertThat(schema.type()).isEqualTo("object");
533+
assertThat(schema.properties()).containsKeys("name", "address");
534+
assertThat(schema.required()).containsExactly("name");
535+
assertThat(schema.definitions()).isNotNull();
536+
assertThat(schema.definitions()).containsKey("Address");
537+
538+
String value = mapper.writeValueAsString(schema);
539+
540+
// Convert to map for easier assertions
541+
Map<String, Object> jsonMap = mapper.readValue(value, new TypeReference<HashMap<String, Object>>() {
542+
});
543+
Map<String, Object> definitions = (Map<String, Object>) jsonMap.get("definitions");
544+
Map<String, Object> address = (Map<String, Object>) definitions.get("Address");
545+
546+
assertThat(address).containsEntry("type", "object");
547+
assertThat(((Map<String, Object>) ((Map<String, Object>) address.get("properties")).get("street")).get("type"))
548+
.isEqualTo("string");
549+
assertThat(((Map<String, Object>) ((Map<String, Object>) address.get("properties")).get("city")).get("type"))
550+
.isEqualTo("string");
551+
}
552+
452553
@Test
453554
void testTool() throws Exception {
454555
String schemaJson = """
@@ -477,6 +578,50 @@ void testTool() throws Exception {
477578
{"name":"test-tool","description":"A test tool","inputSchema":{"type":"object","properties":{"name":{"type":"string"},"value":{"type":"number"}},"required":["name"]}}"""));
478579
}
479580

581+
@Test
582+
void testToolWithComplexSchema() throws Exception {
583+
String complexSchemaJson = """
584+
{
585+
"type": "object",
586+
"$defs": {
587+
"Address": {
588+
"type": "object",
589+
"properties": {
590+
"street": {"type": "string"},
591+
"city": {"type": "string"}
592+
},
593+
"required": ["street", "city"]
594+
}
595+
},
596+
"properties": {
597+
"name": {"type": "string"},
598+
"shippingAddress": {"$ref": "#/$defs/Address"}
599+
},
600+
"required": ["name", "shippingAddress"]
601+
}
602+
""";
603+
604+
McpSchema.Tool tool = new McpSchema.Tool("addressTool", "Handles addresses", complexSchemaJson);
605+
606+
// Verify the schema was properly parsed and stored with $defs
607+
assertThat(tool.inputSchema().defs()).isNotNull();
608+
assertThat(tool.inputSchema().defs()).containsKey("Address");
609+
610+
String value = mapper.writeValueAsString(tool);
611+
612+
// Convert to map for easier assertions
613+
Map<String, Object> jsonMap = mapper.readValue(value, new TypeReference<HashMap<String, Object>>() {
614+
});
615+
Map<String, Object> inputSchema = (Map<String, Object>) jsonMap.get("inputSchema");
616+
Map<String, Object> defs = (Map<String, Object>) inputSchema.get("$defs");
617+
Map<String, Object> address = (Map<String, Object>) defs.get("Address");
618+
Map<String, Object> properties = (Map<String, Object>) inputSchema.get("properties");
619+
Map<String, Object> shippingAddress = (Map<String, Object>) properties.get("shippingAddress");
620+
621+
assertThat(address).containsEntry("type", "object");
622+
assertThat(shippingAddress).containsEntry("$ref", "#/$defs/Address");
623+
}
624+
480625
@Test
481626
void testCallToolRequest() throws Exception {
482627
Map<String, Object> arguments = new HashMap<>();

0 commit comments

Comments
 (0)