From 2846e53445828cd6f14ff30644a6a5c37c407e15 Mon Sep 17 00:00:00 2001 From: Ramesh Reddy Adutla Date: Mon, 28 Sep 2026 13:37:34 +0100 Subject: [PATCH] Default JsonSchema collection fields to empty immutable values Servers that omit required, properties, $defs or definitions produce null collections, which break re-serialisation and cause NPEs when iterated. Default them to empty immutable collections and omit empty values on output. Fixes #664. --- .../modelcontextprotocol/spec/McpSchema.java | 15 ++++-- .../spec/McpSchemaTests.java | 47 +++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java index 648be8b4b..d011e94ae 100644 --- a/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java +++ b/mcp-core/src/main/java/io/modelcontextprotocol/spec/McpSchema.java @@ -2697,11 +2697,18 @@ public ListToolsResult build() { @JsonIgnoreProperties(ignoreUnknown = true) public record JsonSchema( // @formatter:off @JsonProperty("type") String type, - @JsonProperty("properties") Map properties, - @JsonProperty("required") List required, + @JsonProperty("properties") @JsonInclude(JsonInclude.Include.NON_EMPTY) Map properties, + @JsonProperty("required") @JsonInclude(JsonInclude.Include.NON_EMPTY) List required, @JsonProperty("additionalProperties") Boolean additionalProperties, - @JsonProperty("$defs") Map defs, - @JsonProperty("definitions") Map definitions) { // @formatter:on + @JsonProperty("$defs") @JsonInclude(JsonInclude.Include.NON_EMPTY) Map defs, + @JsonProperty("definitions") @JsonInclude(JsonInclude.Include.NON_EMPTY) Map definitions) { // @formatter:on + + public JsonSchema { + required = required != null ? required : List.of(); + properties = properties != null ? properties : Map.of(); + defs = defs != null ? defs : Map.of(); + definitions = definitions != null ? definitions : Map.of(); + } public static Builder builder() { return new Builder(); diff --git a/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java b/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java index ab9bc8643..16df105ce 100644 --- a/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java +++ b/mcp-test/src/test/java/io/modelcontextprotocol/spec/McpSchemaTests.java @@ -828,6 +828,53 @@ void testJsonSchemaWithDefinitions() throws Exception { assertThatJson(serializedAgain).when(Option.IGNORING_ARRAY_ORDER).isEqualTo(json(serialized)); } + @Test + void testJsonSchemaWithMissingOptionalFields() throws Exception { + // Simulate a minimal schema from a Python MCP server that omits + // required, additionalProperties, $defs, and definitions + String schemaJson = """ + { + "type": "object", + "properties": { + "query": { + "type": "string" + } + } + } + """; + + McpSchema.JsonSchema schema = JSON_MAPPER.readValue(schemaJson, McpSchema.JsonSchema.class); + + // Verify null collection fields are replaced with empty defaults + assertThat(schema.required()).isNotNull().isEmpty(); + assertThat(schema.properties()).isNotNull().containsKey("query"); + assertThat(schema.additionalProperties()).isNull(); + assertThat(schema.defs()).isNotNull().isEmpty(); + assertThat(schema.definitions()).isNotNull().isEmpty(); + + // Verify serialization round-trip succeeds without errors + String serialized = JSON_MAPPER.writeValueAsString(schema); + McpSchema.JsonSchema deserialized = JSON_MAPPER.readValue(serialized, McpSchema.JsonSchema.class); + + assertThat(deserialized.type()).isEqualTo("object"); + assertThat(deserialized.required()).isNotNull().isEmpty(); + assertThat(deserialized.defs()).isNotNull().isEmpty(); + assertThat(deserialized.definitions()).isNotNull().isEmpty(); + } + + @Test + void testJsonSchemaConstructorDefaultsForNullCollections() { + // Directly construct with null collection fields + McpSchema.JsonSchema schema = new McpSchema.JsonSchema("object", null, null, null, null, null); + + assertThat(schema.type()).isEqualTo("object"); + assertThat(schema.properties()).isNotNull().isEmpty(); + assertThat(schema.required()).isNotNull().isEmpty(); + assertThat(schema.additionalProperties()).isNull(); + assertThat(schema.defs()).isNotNull().isEmpty(); + assertThat(schema.definitions()).isNotNull().isEmpty(); + } + @Test void testTool() throws Exception { String schemaJson = """