Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down
26 changes: 26 additions & 0 deletions mcp-json-jackson2/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,5 +49,31 @@
<artifactId>json-schema-validator</artifactId>
<version>${json-schema-validator.version}</version>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<version>${assert4j.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>${mockito.version}</version>
<scope>test</scope>
</dependency>

</dependencies>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -60,7 +58,9 @@ public ValidationResponse validate(Map<String, Object> 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<ValidationMessage> validationResult = this.getOrCreateJsonSchema(schema).validate(jsonStructuredOutput);

Expand Down Expand Up @@ -125,17 +125,6 @@ private JsonSchema createJsonSchema(Map<String, Object> 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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -64,6 +65,16 @@ private Map<String, Object> toMap(String json) {
}
}

private List<Map<String, Object>> toListMap(String json) {
try {
return objectMapper.readValue(json, new TypeReference<List<Map<String, Object>>>() {
});
}
catch (Exception e) {
throw new RuntimeException("Failed to parse JSON: " + json, e);
}
}

@Test
void testDefaultConstructor() {
DefaultJsonSchemaValidator defaultValidator = new DefaultJsonSchemaValidator();
Expand Down Expand Up @@ -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<String, Object> schema = toMap(schemaJson);

// Validate as JSON string
ValidationResponse response = validator.validate(schema, contentJson);

assertTrue(response.valid());
assertNull(response.errorMessage());

List<Map<String, Object>> structuredContent = toListMap(contentJson);

// Validate as List<Map<String, Object>>
response = validator.validate(schema, structuredContent);

assertTrue(response.valid());
assertNull(response.errorMessage());
}

@Test
void testValidateWithInvalidTypeSchema() {
String schemaJson = """
Expand Down Expand Up @@ -266,7 +345,8 @@ void testValidateWithAdditionalPropertiesNotAllowed() {
"properties": {
"name": {"type": "string"}
},
"required": ["name"]
"required": ["name"],
"additionalProperties": false
}
""";

Expand Down Expand Up @@ -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<String, Object> schema = toMap(schemaJson);
Map<String, Object> structuredContent = toMap(contentJson);

ValidationResponse response = validator.validate(schema, structuredContent);

assertTrue(response.valid());
assertNull(response.errorMessage());
}

@Test
void testValidateWithAdditionalPropertiesExplicitlyDisallowed() {
String schemaJson = """
Expand Down