The Mandrel MCP Test Harness needs a comprehensive validation engine to verify that MCP server responses conform to:
- Expected JSON schemas
- JSONPath expression constraints
- MCP protocol-specific field requirements
- Custom validation rules defined in test specifications
Currently, our existing ValidationEngine only handles configuration file validation. We need a dedicated engine for validating actual MCP protocol responses during test execution.
-
JSONPath Field Validation
- Evaluate JSONPath expressions against response data
- Support field existence, type, value, and pattern validation
- Handle nested object and array validation
- Provide detailed path-specific error reporting
-
JSON Schema Validation
- Validate entire response structures against JSON schemas
- Support MCP-specific schema definitions
- Handle schema composition and references
- Generate comprehensive schema violation reports
-
MCP Protocol Validation
- Validate JSON-RPC 2.0 message structure
- Verify MCP-specific method responses
- Check required vs optional fields per MCP specification
- Validate capability announcements and tool/resource definitions
-
Custom Validation Rules
- Support user-defined validation logic
- Enable test-specific validation scenarios
- Provide extension points for domain-specific validation
- Support conditional validation based on context
// Main validation engine
pub struct McpValidationEngine {
schema_validator: SchemaValidator,
jsonpath_evaluator: JsonPathEvaluator,
protocol_validator: ProtocolValidator,
custom_validators: Vec<Box<dyn CustomValidator>>,
}
// Validation result with detailed diagnostics
#[derive(Debug, Clone)]
pub struct McpValidationResult {
pub is_valid: bool,
pub errors: Vec<ValidationError>,
pub warnings: Vec<ValidationWarning>,
pub field_results: HashMap<String, FieldValidationResult>,
pub schema_violations: Vec<SchemaViolation>,
pub protocol_issues: Vec<ProtocolIssue>,
}
// Individual field validation
#[derive(Debug, Clone)]
pub struct FieldValidationResult {
pub field_path: String,
pub expected_type: Option<String>,
pub actual_type: String,
pub expected_value: Option<serde_json::Value>,
pub actual_value: serde_json::Value,
pub validation_rules: Vec<ValidationRule>,
pub is_valid: bool,
pub errors: Vec<String>,
}
// Validation configuration
#[derive(Debug, Clone)]
pub struct ValidationSpec {
pub schema: Option<serde_json::Value>,
pub jsonpath_rules: Vec<JsonPathRule>,
pub protocol_requirements: ProtocolRequirements,
pub custom_rules: Vec<CustomRule>,
pub strict_mode: bool,
}pub struct JsonPathEvaluator {
engine: jsonpath_lib::Selector,
}
#[derive(Debug, Clone)]
pub struct JsonPathRule {
pub path: String,
pub constraint: PathConstraint,
pub description: String,
pub severity: ValidationSeverity,
}
#[derive(Debug, Clone)]
pub enum PathConstraint {
Exists,
NotExists,
Equals(serde_json::Value),
NotEquals(serde_json::Value),
Contains(String),
MatchesPattern(String),
HasType(JsonType),
InRange(f64, f64),
ArrayLength(usize, Option<usize>),
Custom(Box<dyn Fn(&serde_json::Value) -> bool>),
}
impl JsonPathEvaluator {
pub fn evaluate_rule(
&self,
data: &serde_json::Value,
rule: &JsonPathRule,
) -> FieldValidationResult;
pub fn extract_values(
&self,
data: &serde_json::Value,
path: &str,
) -> Result<Vec<serde_json::Value>, JsonPathError>;
}use jsonschema::{Draft, JSONSchema};
pub struct SchemaValidator {
compiled_schemas: HashMap<String, JSONSchema>,
draft_version: Draft,
}
impl SchemaValidator {
pub fn new() -> Self;
pub fn add_schema(
&mut self,
name: String,
schema: serde_json::Value,
) -> Result<(), SchemaError>;
pub fn validate_against_schema(
&self,
data: &serde_json::Value,
schema_name: &str,
) -> Result<SchemaValidationResult, SchemaError>;
pub fn validate_with_inline_schema(
&self,
data: &serde_json::Value,
schema: &serde_json::Value,
) -> SchemaValidationResult;
}
#[derive(Debug, Clone)]
pub struct SchemaValidationResult {
pub is_valid: bool,
pub violations: Vec<SchemaViolation>,
}
#[derive(Debug, Clone)]
pub struct SchemaViolation {
pub instance_path: String,
pub schema_path: String,
pub message: String,
pub violation_type: ViolationType,
}pub struct ProtocolValidator {
mcp_version: String,
capabilities: McpCapabilities,
}
#[derive(Debug, Clone)]
pub struct ProtocolRequirements {
pub method: String,
pub required_fields: Vec<String>,
pub optional_fields: Vec<String>,
pub expected_error_codes: Vec<i32>,
pub capability_requirements: Vec<String>,
}
impl ProtocolValidator {
pub fn validate_jsonrpc_structure(
&self,
response: &serde_json::Value,
) -> Vec<ProtocolIssue>;
pub fn validate_mcp_method_response(
&self,
method: &str,
response: &serde_json::Value,
requirements: &ProtocolRequirements,
) -> Vec<ProtocolIssue>;
pub fn validate_capability_announcement(
&self,
capabilities: &serde_json::Value,
) -> Vec<ProtocolIssue>;
pub fn validate_tool_definition(
&self,
tool: &serde_json::Value,
) -> Vec<ProtocolIssue>;
}
#[derive(Debug, Clone)]
pub struct ProtocolIssue {
pub category: ProtocolCategory,
pub severity: ValidationSeverity,
pub message: String,
pub field_path: Option<String>,
pub expected: Option<String>,
pub actual: Option<String>,
}
#[derive(Debug, Clone)]
pub enum ProtocolCategory {
JsonRpcStructure,
McpMethodCompliance,
CapabilityMismatch,
FieldMissing,
FieldType,
FieldValue,
}RED Phase: Write failing tests for basic validation structures
#[test]
fn test_mcp_validation_engine_creation() {
let engine = McpValidationEngine::new();
assert!(engine.is_ok());
}
#[test]
fn test_validation_spec_deserialization() {
let spec_json = r#"
{
"jsonpath_rules": [
{
"path": "$.result.content[0].text",
"constraint": {"Exists": null},
"description": "Response must contain text content"
}
],
"protocol_requirements": {
"method": "tools/call",
"required_fields": ["result"]
}
}"#;
let spec: ValidationSpec = serde_json::from_str(spec_json).unwrap();
assert_eq!(spec.jsonpath_rules.len(), 1);
}GREEN Phase: Implement minimal structures to pass tests
REFACTOR Phase: Optimize and enhance structure design
RED Phase: Write failing tests for JSONPath evaluation
#[test]
fn test_jsonpath_field_exists_validation() {
let evaluator = JsonPathEvaluator::new();
let data = json!({
"result": {
"content": [{"text": "Hello"}]
}
});
let rule = JsonPathRule {
path: "$.result.content[0].text".to_string(),
constraint: PathConstraint::Exists,
description: "Text field must exist".to_string(),
severity: ValidationSeverity::Error,
};
let result = evaluator.evaluate_rule(&data, &rule);
assert!(result.is_valid);
}
#[test]
fn test_jsonpath_value_equals_validation() {
let evaluator = JsonPathEvaluator::new();
let data = json!({"status": "success"});
let rule = JsonPathRule {
path: "$.status".to_string(),
constraint: PathConstraint::Equals(json!("success")),
description: "Status must be success".to_string(),
severity: ValidationSeverity::Error,
};
let result = evaluator.evaluate_rule(&data, &rule);
assert!(result.is_valid);
}GREEN Phase: Implement JSONPath evaluation using jsonpath-lib
REFACTOR Phase: Optimize performance and error handling
RED Phase: Write failing tests for schema validation
#[test]
fn test_schema_validation_success() {
let mut validator = SchemaValidator::new();
let schema = json!({
"type": "object",
"properties": {
"result": {
"type": "object",
"properties": {
"content": {
"type": "array",
"items": {"type": "object"}
}
},
"required": ["content"]
}
},
"required": ["result"]
});
validator.add_schema("tool_response".to_string(), schema).unwrap();
let data = json!({
"result": {
"content": [{"text": "Hello"}]
}
});
let result = validator.validate_against_schema(&data, "tool_response").unwrap();
assert!(result.is_valid);
}
#[test]
fn test_schema_validation_failure() {
let mut validator = SchemaValidator::new();
let schema = json!({
"type": "object",
"properties": {"required_field": {"type": "string"}},
"required": ["required_field"]
});
validator.add_schema("test_schema".to_string(), schema).unwrap();
let invalid_data = json!({"wrong_field": "value"});
let result = validator.validate_against_schema(&invalid_data, "test_schema").unwrap();
assert!(!result.is_valid);
assert!(!result.violations.is_empty());
}GREEN Phase: Implement schema validation using jsonschema crate
REFACTOR Phase: Add schema caching and performance optimization
RED Phase: Write failing tests for MCP protocol compliance
#[test]
fn test_jsonrpc_structure_validation() {
let validator = ProtocolValidator::new("1.0".to_string());
let valid_response = json!({
"jsonrpc": "2.0",
"id": 1,
"result": {"status": "success"}
});
let issues = validator.validate_jsonrpc_structure(&valid_response);
assert!(issues.is_empty());
let invalid_response = json!({
"id": 1,
"result": {"status": "success"}
// Missing "jsonrpc" field
});
let issues = validator.validate_jsonrpc_structure(&invalid_response);
assert!(!issues.is_empty());
assert!(issues.iter().any(|issue| issue.category == ProtocolCategory::JsonRpcStructure));
}
#[test]
fn test_mcp_tool_call_validation() {
let validator = ProtocolValidator::new("1.0".to_string());
let requirements = ProtocolRequirements {
method: "tools/call".to_string(),
required_fields: vec!["result".to_string()],
optional_fields: vec!["meta".to_string()],
expected_error_codes: vec![],
capability_requirements: vec!["tools".to_string()],
};
let response = json!({
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{"type": "text", "text": "Analysis complete"}],
"isError": false
}
});
let issues = validator.validate_mcp_method_response("tools/call", &response, &requirements);
assert!(issues.is_empty());
}GREEN Phase: Implement MCP protocol-specific validation rules REFACTOR Phase: Add comprehensive MCP specification compliance
RED Phase: Write integration tests with CLI and execution framework
#[tokio::test]
async fn test_validation_engine_integration() {
let engine = McpValidationEngine::new();
let spec = ValidationSpec {
schema: Some(json!({
"type": "object",
"required": ["result"]
})),
jsonpath_rules: vec![
JsonPathRule {
path: "$.result.content".to_string(),
constraint: PathConstraint::Exists,
description: "Content must exist".to_string(),
severity: ValidationSeverity::Error,
}
],
protocol_requirements: ProtocolRequirements {
method: "tools/call".to_string(),
required_fields: vec!["result".to_string()],
optional_fields: vec![],
expected_error_codes: vec![],
capability_requirements: vec![],
},
custom_rules: vec![],
strict_mode: true,
};
let response = json!({
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{"type": "text", "text": "Hello"}],
"isError": false
}
});
let result = engine.validate_response(&response, &spec).await.unwrap();
assert!(result.is_valid);
assert!(result.errors.is_empty());
}GREEN Phase: Implement full integration with existing test execution framework REFACTOR Phase: Optimize performance and add comprehensive error handling
impl McpValidationEngine {
pub fn new() -> Result<Self, ValidationEngineError>;
pub async fn validate_response(
&self,
response: &serde_json::Value,
spec: &ValidationSpec,
) -> Result<McpValidationResult, ValidationEngineError>;
pub fn add_custom_validator(
&mut self,
validator: Box<dyn CustomValidator>,
) -> Result<(), ValidationEngineError>;
pub fn load_validation_spec_from_file(
&self,
path: &Path,
) -> Result<ValidationSpec, ValidationEngineError>;
pub fn precompile_schemas(
&mut self,
schemas: HashMap<String, serde_json::Value>,
) -> Result<(), ValidationEngineError>;
}pub trait CustomValidator: Send + Sync {
fn name(&self) -> &str;
fn validate(
&self,
data: &serde_json::Value,
context: &ValidationContext,
) -> Result<Vec<ValidationError>, Box<dyn std::error::Error>>;
}
pub struct ValidationContext {
pub method: String,
pub request_id: Option<serde_json::Value>,
pub server_capabilities: Option<serde_json::Value>,
pub test_metadata: HashMap<String, String>,
}#[derive(Debug, thiserror::Error)]
pub enum ValidationEngineError {
#[error("JSONPath evaluation failed: {0}")]
JsonPathError(String),
#[error("Schema validation failed: {0}")]
SchemaError(String),
#[error("Protocol validation failed: {0}")]
ProtocolError(String),
#[error("Custom validator error: {0}")]
CustomValidatorError(String),
#[error("Validation specification error: {0}")]
SpecificationError(String),
#[error("IO error: {0}")]
IoError(#[from] std::io::Error),
#[error("JSON parsing error: {0}")]
JsonError(#[from] serde_json::Error),
}
#[derive(Debug, Clone)]
pub enum ValidationSeverity {
Error,
Warning,
Info,
}Add to Cargo.toml:
[dependencies]
# JSON Path evaluation
jsonpath-lib = "0.3"
# JSON Schema validation
jsonschema = "0.18"
# Regular expressions for pattern matching
regex = { workspace = true }
# Async support (already included)
tokio = { workspace = true }
futures = { workspace = true }
# Error handling (already included)
thiserror = { workspace = true }
anyhow = { workspace = true }
# Serialization (already included)
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }- Schema Compilation Caching - Pre-compile and cache JSON schemas for reuse
- JSONPath Expression Caching - Cache compiled JSONPath selectors
- Parallel Validation - Run independent validation rules in parallel
- Memory-Efficient Processing - Stream large responses for validation
- Incremental Validation - Support partial validation for large datasets
- Input Sanitization - Validate all JSONPath expressions and schemas
- Resource Limits - Prevent DoS through complex validation expressions
- Safe Evaluation - Sandbox custom validation logic execution
- Schema Injection Prevention - Validate schema definitions before compilation
- Support JSONPath expression evaluation with all major constraint types
- Implement comprehensive JSON Schema validation with detailed error reporting
- Validate MCP protocol compliance including JSON-RPC 2.0 structure
- Support custom validation rules with extension points
- Generate detailed, actionable validation reports
- Handle complex nested structures and arrays efficiently
- 95%+ test coverage with comprehensive unit and integration tests
- Sub-100ms validation time for typical MCP responses
- Memory usage <10MB for large response validation
- Support for concurrent validation of multiple responses
- Comprehensive error handling with detailed diagnostics
- Seamless integration with existing test execution framework
- CLI integration for standalone validation operations
- Support for test specification file formats (JSON, YAML)
- Compatibility with existing reporting system design
- Extension points for custom domain-specific validation
- MCP Specification - Protocol requirements
- JSONPath Specification - Expression syntax
- JSON Schema Specification - Schema validation
- Issue #194: Reporting System - Integration requirements
- Current ValidationEngine - Configuration validation patterns