Skip to content

Commit 7aa3465

Browse files
Fix JsonSchema null collection fields causing serialization failures
When an MCP server (e.g. Python/fastmcp) omits optional schema fields like required, $defs, and definitions, Jackson deserializes them as null. This causes failures when callers re-serialize the JsonSchema with a standard ObjectMapper or iterate over these collections. Add a compact constructor that defaults null collection fields to empty immutable collections (List.of(), Map.of()). Also add @JsonInclude(NON_EMPTY) on collection fields so that empty defaults are omitted during serialization, preserving wire format compatibility. Fixes #664 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 26304a7 commit 7aa3465

File tree

2 files changed

+63
-4
lines changed

2 files changed

+63
-4
lines changed

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

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1312,11 +1312,23 @@ public ListToolsResult(List<Tool> tools, String nextCursor) {
13121312
@JsonIgnoreProperties(ignoreUnknown = true)
13131313
public record JsonSchema( // @formatter:off
13141314
@JsonProperty("type") String type,
1315-
@JsonProperty("properties") Map<String, Object> properties,
1316-
@JsonProperty("required") List<String> required,
1315+
@JsonProperty("properties") @JsonInclude(JsonInclude.Include.NON_EMPTY) Map<String, Object> properties,
1316+
@JsonProperty("required") @JsonInclude(JsonInclude.Include.NON_EMPTY) List<String> required,
13171317
@JsonProperty("additionalProperties") Boolean additionalProperties,
1318-
@JsonProperty("$defs") Map<String, Object> defs,
1319-
@JsonProperty("definitions") Map<String, Object> definitions) { // @formatter:on
1318+
@JsonProperty("$defs") @JsonInclude(JsonInclude.Include.NON_EMPTY) Map<String, Object> defs,
1319+
@JsonProperty("definitions") @JsonInclude(JsonInclude.Include.NON_EMPTY) Map<String, Object> definitions) { // @formatter:on
1320+
1321+
/**
1322+
* Compact constructor that replaces null collection fields with empty defaults so
1323+
* that callers never encounter unexpected nulls when the server omits optional
1324+
* schema fields during deserialization.
1325+
*/
1326+
public JsonSchema {
1327+
required = required != null ? required : List.of();
1328+
properties = properties != null ? properties : Map.of();
1329+
defs = defs != null ? defs : Map.of();
1330+
definitions = definitions != null ? definitions : Map.of();
1331+
}
13201332
}
13211333

13221334
/**

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,53 @@ void testJsonSchemaWithDefinitions() throws Exception {
771771
assertThatJson(serializedAgain).when(Option.IGNORING_ARRAY_ORDER).isEqualTo(json(serialized));
772772
}
773773

774+
@Test
775+
void testJsonSchemaWithMissingOptionalFields() throws Exception {
776+
// Simulate a minimal schema from a Python MCP server that omits
777+
// required, additionalProperties, $defs, and definitions
778+
String schemaJson = """
779+
{
780+
"type": "object",
781+
"properties": {
782+
"query": {
783+
"type": "string"
784+
}
785+
}
786+
}
787+
""";
788+
789+
McpSchema.JsonSchema schema = JSON_MAPPER.readValue(schemaJson, McpSchema.JsonSchema.class);
790+
791+
// Verify null collection fields are replaced with empty defaults
792+
assertThat(schema.required()).isNotNull().isEmpty();
793+
assertThat(schema.properties()).isNotNull().containsKey("query");
794+
assertThat(schema.additionalProperties()).isNull();
795+
assertThat(schema.defs()).isNotNull().isEmpty();
796+
assertThat(schema.definitions()).isNotNull().isEmpty();
797+
798+
// Verify serialization round-trip succeeds without errors
799+
String serialized = JSON_MAPPER.writeValueAsString(schema);
800+
McpSchema.JsonSchema deserialized = JSON_MAPPER.readValue(serialized, McpSchema.JsonSchema.class);
801+
802+
assertThat(deserialized.type()).isEqualTo("object");
803+
assertThat(deserialized.required()).isNotNull().isEmpty();
804+
assertThat(deserialized.defs()).isNotNull().isEmpty();
805+
assertThat(deserialized.definitions()).isNotNull().isEmpty();
806+
}
807+
808+
@Test
809+
void testJsonSchemaConstructorDefaultsForNullCollections() {
810+
// Directly construct with null collection fields
811+
McpSchema.JsonSchema schema = new McpSchema.JsonSchema("object", null, null, null, null, null);
812+
813+
assertThat(schema.type()).isEqualTo("object");
814+
assertThat(schema.properties()).isNotNull().isEmpty();
815+
assertThat(schema.required()).isNotNull().isEmpty();
816+
assertThat(schema.additionalProperties()).isNull();
817+
assertThat(schema.defs()).isNotNull().isEmpty();
818+
assertThat(schema.definitions()).isNotNull().isEmpty();
819+
}
820+
774821
@Test
775822
void testTool() throws Exception {
776823
String schemaJson = """

0 commit comments

Comments
 (0)