diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/spec/JSONRPCRequestMcpValidationTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/spec/JSONRPCRequestMcpValidationTest.java index d03a6926d..fbe17d464 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/spec/JSONRPCRequestMcpValidationTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/spec/JSONRPCRequestMcpValidationTest.java @@ -5,7 +5,10 @@ package io.modelcontextprotocol.spec; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * Tests for MCP-specific validation of JSONRPCRequest ID requirements. diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpErrorTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpErrorTest.java index 84d650ab3..0978ffe0b 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpErrorTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/spec/McpErrorTest.java @@ -4,7 +4,8 @@ import java.util.Map; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertEquals; class McpErrorTest { diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/spec/PromptReferenceEqualsTest.java b/mcp-core/src/test/java/io/modelcontextprotocol/spec/PromptReferenceEqualsTest.java index 382cda1ce..1d7be0b51 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/spec/PromptReferenceEqualsTest.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/spec/PromptReferenceEqualsTest.java @@ -7,7 +7,9 @@ import io.modelcontextprotocol.spec.McpSchema.PromptReference; import org.junit.jupiter.api.Test; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertEquals; /** * Test class to verify the equals method implementation for PromptReference. diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/spec/json/gson/GsonMcpJsonMapperTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/spec/json/gson/GsonMcpJsonMapperTests.java index 4f1dffe1d..498194d17 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/spec/json/gson/GsonMcpJsonMapperTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/spec/json/gson/GsonMcpJsonMapperTests.java @@ -8,7 +8,10 @@ import java.util.List; import java.util.Map; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; class GsonMcpJsonMapperTests { diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/util/AssertTests.java b/mcp-core/src/test/java/io/modelcontextprotocol/util/AssertTests.java index 08555fef5..0038d4e1b 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/util/AssertTests.java +++ b/mcp-core/src/test/java/io/modelcontextprotocol/util/AssertTests.java @@ -8,7 +8,9 @@ import java.util.List; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; class AssertTests { diff --git a/mcp-json-jackson2/pom.xml b/mcp-json-jackson2/pom.xml index 75569c78e..e53d5e57b 100644 --- a/mcp-json-jackson2/pom.xml +++ b/mcp-json-jackson2/pom.xml @@ -49,5 +49,31 @@ json-schema-validator ${json-schema-validator.version} + + + org.assertj + assertj-core + ${assert4j.version} + test + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-params + ${junit.version} + test + + + org.mockito + mockito-core + ${mockito.version} + test + + diff --git a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/DefaultJsonSchemaValidator.java b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/DefaultJsonSchemaValidator.java index 7ec0419c8..15511c9c2 100644 --- a/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/DefaultJsonSchemaValidator.java +++ b/mcp-json-jackson2/src/main/java/io/modelcontextprotocol/json/schema/jackson/DefaultJsonSchemaValidator.java @@ -7,18 +7,16 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; -import io.modelcontextprotocol.json.schema.JsonSchemaValidator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.SpecVersion; import com.networknt.schema.ValidationMessage; +import io.modelcontextprotocol.json.schema.JsonSchemaValidator; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Default implementation of the {@link JsonSchemaValidator} interface. This class @@ -60,7 +58,9 @@ public ValidationResponse validate(Map schema, Object structured try { - JsonNode jsonStructuredOutput = this.objectMapper.valueToTree(structuredContent); + JsonNode jsonStructuredOutput = (structuredContent instanceof String) + ? this.objectMapper.readTree((String) structuredContent) + : this.objectMapper.valueToTree(structuredContent); Set validationResult = this.getOrCreateJsonSchema(schema).validate(jsonStructuredOutput); @@ -125,17 +125,6 @@ private JsonSchema createJsonSchema(Map schema) throws JsonProce }; } - // Handle additionalProperties setting - if (schemaNode.isObject()) { - ObjectNode objectSchemaNode = (ObjectNode) schemaNode; - if (!objectSchemaNode.has("additionalProperties")) { - // Clone the node before modification to avoid mutating the original - objectSchemaNode = objectSchemaNode.deepCopy(); - objectSchemaNode.put("additionalProperties", false); - schemaNode = objectSchemaNode; - } - } - return this.schemaFactory.getSchema(schemaNode); } diff --git a/mcp-core/src/test/java/io/modelcontextprotocol/spec/DefaultJsonSchemaValidatorTests.java b/mcp-json-jackson2/src/test/java/io/modelcontextprotocol/json/DefaultJsonSchemaValidatorTests.java similarity index 84% rename from mcp-core/src/test/java/io/modelcontextprotocol/spec/DefaultJsonSchemaValidatorTests.java rename to mcp-json-jackson2/src/test/java/io/modelcontextprotocol/json/DefaultJsonSchemaValidatorTests.java index 76ca29684..7642f0480 100644 --- a/mcp-core/src/test/java/io/modelcontextprotocol/spec/DefaultJsonSchemaValidatorTests.java +++ b/mcp-json-jackson2/src/test/java/io/modelcontextprotocol/json/DefaultJsonSchemaValidatorTests.java @@ -2,7 +2,7 @@ * Copyright 2024-2024 the original author or authors. */ -package io.modelcontextprotocol.spec; +package io.modelcontextprotocol.json; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; @@ -13,6 +13,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.when; +import java.util.List; import java.util.Map; import java.util.stream.Stream; @@ -64,6 +65,16 @@ private Map toMap(String json) { } } + private List> toListMap(String json) { + try { + return objectMapper.readValue(json, new TypeReference>>() { + }); + } + catch (Exception e) { + throw new RuntimeException("Failed to parse JSON: " + json, e); + } + } + @Test void testDefaultConstructor() { DefaultJsonSchemaValidator defaultValidator = new DefaultJsonSchemaValidator(); @@ -198,6 +209,74 @@ void testValidateWithValidArraySchema() { assertNull(response.errorMessage()); } + @Test + void testValidateWithValidArraySchemaTopLevelArray() { + String schemaJson = """ + { + "$schema" : "https://json-schema.org/draft/2020-12/schema", + "type" : "array", + "items" : { + "type" : "object", + "properties" : { + "city" : { + "type" : "string" + }, + "summary" : { + "type" : "string" + }, + "temperatureC" : { + "type" : "number", + "format" : "float" + } + }, + "required" : [ "city", "summary", "temperatureC" ] + }, + "additionalProperties" : false + } + """; + + String contentJson = """ + [ + { + "city": "London", + "summary": "Generally mild with frequent rainfall. Winters are cool and damp, summers are warm but rarely hot. Cloudy conditions are common throughout the year.", + "temperatureC": 11.3 + }, + { + "city": "New York", + "summary": "Four distinct seasons with hot and humid summers, cold winters with snow, and mild springs and autumns. Precipitation is fairly evenly distributed throughout the year.", + "temperatureC": 12.8 + }, + { + "city": "San Francisco", + "summary": "Mild year-round with a distinctive Mediterranean climate. Famous for summer fog, mild winters, and little temperature variation throughout the year. Very little rainfall in summer months.", + "temperatureC": 14.6 + }, + { + "city": "Tokyo", + "summary": "Humid subtropical climate with hot, wet summers and mild winters. Experiences a rainy season in early summer and occasional typhoons in late summer to early autumn.", + "temperatureC": 15.4 + } + ] + """; + + Map schema = toMap(schemaJson); + + // Validate as JSON string + ValidationResponse response = validator.validate(schema, contentJson); + + assertTrue(response.valid()); + assertNull(response.errorMessage()); + + List> structuredContent = toListMap(contentJson); + + // Validate as List> + response = validator.validate(schema, structuredContent); + + assertTrue(response.valid()); + assertNull(response.errorMessage()); + } + @Test void testValidateWithInvalidTypeSchema() { String schemaJson = """ @@ -266,7 +345,8 @@ void testValidateWithAdditionalPropertiesNotAllowed() { "properties": { "name": {"type": "string"} }, - "required": ["name"] + "required": ["name"], + "additionalProperties": false } """; @@ -316,6 +396,35 @@ void testValidateWithAdditionalPropertiesExplicitlyAllowed() { assertNull(response.errorMessage()); } + @Test + void testValidateWithDefaultAdditionalProperties() { + String schemaJson = """ + { + "type": "object", + "properties": { + "name": {"type": "string"} + }, + "required": ["name"], + "additionalProperties": true + } + """; + + String contentJson = """ + { + "name": "John Doe", + "extraField": "should be allowed" + } + """; + + Map schema = toMap(schemaJson); + Map structuredContent = toMap(contentJson); + + ValidationResponse response = validator.validate(schema, structuredContent); + + assertTrue(response.valid()); + assertNull(response.errorMessage()); + } + @Test void testValidateWithAdditionalPropertiesExplicitlyDisallowed() { String schemaJson = """