Module: MCPClient::ElicitationValidator
- Defined in:
- lib/mcp_client/elicitation_validator.rb
Overview
Validates elicitation schemas and content per MCP 2025-11-25 spec. Schemas are restricted to flat objects with primitive property types:
string (with optional enum, pattern, format, minLength, maxLength)
number / integer (with optional minimum, maximum)
boolean
array (multi-select enum only, with items containing enum or anyOf)
Constant Summary collapse
- PRIMITIVE_TYPES =
Allowed primitive types for schema properties
%w[string number integer boolean].freeze
- STRING_FORMATS =
Allowed string formats per MCP spec
%w[email uri date date-time].freeze
Class Method Summary collapse
-
.validate_array_property(name, prop) ⇒ Array<String>
Validate an array property (multi-select enum only).
-
.validate_array_value(field, value, prop) ⇒ Array<String>
Validate an array value against its property schema (multi-select enum).
-
.validate_content(content, schema) ⇒ Array<String>
Validate content against a requestedSchema.
-
.validate_number_value(field, value, prop) ⇒ Array<String>
Validate a number value against its property schema.
-
.validate_primitive_property(name, prop) ⇒ Array<String>
Validate a primitive property (string, number, integer, boolean).
-
.validate_property(name, prop) ⇒ Array<String>
Validate a single property definition.
-
.validate_schema(schema) ⇒ Array<String>
Validate that a requestedSchema conforms to MCP elicitation constraints.
-
.validate_string_value(field, value, prop) ⇒ Array<String>
Validate a string value against its property schema.
-
.validate_value(field, value, prop) ⇒ Array<String>
Validate a single value against its property schema.
Class Method Details
.validate_array_property(name, prop) ⇒ Array<String>
Validate an array property (multi-select enum only).
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/mcp_client/elicitation_validator.rb', line 91 def self.validate_array_property(name, prop) errors = [] items = prop['items'] unless items.is_a?(Hash) errors << "Property '#{name}' array type requires 'items' definition" return errors end has_enum = items['enum'].is_a?(Array) has_any_of = items['anyOf'].is_a?(Array) errors << "Property '#{name}' array items must have 'enum' or 'anyOf'" unless has_enum || has_any_of errors end |
.validate_array_value(field, value, prop) ⇒ Array<String>
Validate an array value against its property schema (multi-select enum).
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/mcp_client/elicitation_validator.rb', line 230 def self.validate_array_value(field, value, prop) errors = [] unless value.is_a?(Array) errors << "Field '#{field}' must be an array" return errors end items = prop['items'] || {} allowed = if items['enum'].is_a?(Array) items['enum'] elsif items['anyOf'].is_a?(Array) items['anyOf'].map { |o| o['const'] } end if allowed value.each do |v| errors << "Field '#{field}' contains invalid value '#{v}'" unless allowed.include?(v) end end if prop['minItems'] && value.length < prop['minItems'] errors << "Field '#{field}' must have at least #{prop['minItems']} items" end if prop['maxItems'] && value.length > prop['maxItems'] errors << "Field '#{field}' must have at most #{prop['maxItems']} items" end errors end |
.validate_content(content, schema) ⇒ Array<String>
Validate content against a requestedSchema. Returns an array of error messages (empty if valid).
113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/mcp_client/elicitation_validator.rb', line 113 def self.validate_content(content, schema) errors = [] return errors unless content.is_a?(Hash) && schema.is_a?(Hash) properties = schema['properties'] || {} required = Array(schema['required']) # Check required fields required.each do |field| field_s = field.to_s errors << "Missing required field '#{field_s}'" unless content.key?(field_s) || content.key?(field_s.to_sym) end # Validate each provided field content.each do |field, value| prop = properties[field.to_s] next unless prop.is_a?(Hash) errors.concat(validate_value(field.to_s, value, prop)) end errors end |
.validate_number_value(field, value, prop) ⇒ Array<String>
Validate a number value against its property schema.
208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 |
# File 'lib/mcp_client/elicitation_validator.rb', line 208 def self.validate_number_value(field, value, prop) errors = [] unless value.is_a?(Numeric) errors << "Field '#{field}' must be a number" return errors end errors << "Field '#{field}' must be an integer" if prop['type'] == 'integer' && !value.is_a?(Integer) errors << "Field '#{field}' must be >= #{prop['minimum']}" if prop['minimum'] && value < prop['minimum'] errors << "Field '#{field}' must be <= #{prop['maximum']}" if prop['maximum'] && value > prop['maximum'] errors end |
.validate_primitive_property(name, prop) ⇒ Array<String>
Validate a primitive property (string, number, integer, boolean).
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
# File 'lib/mcp_client/elicitation_validator.rb', line 65 def self.validate_primitive_property(name, prop) errors = [] type = prop['type'] case type when 'string' if prop['format'] && !STRING_FORMATS.include?(prop['format']) errors << "Property '#{name}' has unsupported format '#{prop['format']}'" end errors << "Property '#{name}' enum must be an array" if prop['enum'] && !prop['enum'].is_a?(Array) when 'number', 'integer' if prop.key?('minimum') && !prop['minimum'].is_a?(Numeric) errors << "Property '#{name}' minimum must be numeric" end if prop.key?('maximum') && !prop['maximum'].is_a?(Numeric) errors << "Property '#{name}' maximum must be numeric" end end errors end |
.validate_property(name, prop) ⇒ Array<String>
Validate a single property definition.
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/mcp_client/elicitation_validator.rb', line 44 def self.validate_property(name, prop) errors = [] return errors unless prop.is_a?(Hash) type = prop['type'] if type == 'array' errors.concat(validate_array_property(name, prop)) elsif PRIMITIVE_TYPES.include?(type) errors.concat(validate_primitive_property(name, prop)) else errors << "Property '#{name}' has unsupported type '#{type}'" end errors end |
.validate_schema(schema) ⇒ Array<String>
Validate that a requestedSchema conforms to MCP elicitation constraints. Returns an array of error messages (empty if valid).
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
# File 'lib/mcp_client/elicitation_validator.rb', line 21 def self.validate_schema(schema) errors = [] return errors unless schema.is_a?(Hash) unless schema['type'] == 'object' errors << "Schema type must be 'object', got '#{schema['type']}'" return errors end properties = schema['properties'] return errors unless properties.is_a?(Hash) properties.each do |name, prop| errors.concat(validate_property(name, prop)) end errors end |
.validate_string_value(field, value, prop) ⇒ Array<String>
Validate a string value against its property schema.
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/mcp_client/elicitation_validator.rb', line 165 def self.validate_string_value(field, value, prop) errors = [] unless value.is_a?(String) errors << "Field '#{field}' must be a string" return errors end if prop['enum'].is_a?(Array) && !prop['enum'].include?(value) errors << "Field '#{field}' must be one of: #{prop['enum'].join(', ')}" end if prop['oneOf'].is_a?(Array) allowed = prop['oneOf'].map { |o| o['const'] } errors << "Field '#{field}' must be one of: #{allowed.join(', ')}" unless allowed.include?(value) end if prop['pattern'] begin unless value.match?(Regexp.new(prop['pattern'])) errors << "Field '#{field}' must match pattern '#{prop['pattern']}'" end rescue RegexpError # Skip pattern validation if the pattern is invalid end end if prop['minLength'] && value.length < prop['minLength'] errors << "Field '#{field}' must be at least #{prop['minLength']} characters" end if prop['maxLength'] && value.length > prop['maxLength'] errors << "Field '#{field}' must be at most #{prop['maxLength']} characters" end errors end |
.validate_value(field, value, prop) ⇒ Array<String>
Validate a single value against its property schema.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 |
# File 'lib/mcp_client/elicitation_validator.rb', line 142 def self.validate_value(field, value, prop) errors = [] type = prop['type'] case type when 'string' errors.concat(validate_string_value(field, value, prop)) when 'number', 'integer' errors.concat(validate_number_value(field, value, prop)) when 'boolean' errors << "Field '#{field}' must be a boolean" unless [true, false].include?(value) when 'array' errors.concat(validate_array_value(field, value, prop)) end errors end |