Module: ActionMCP::Content::Validation
- Defined in:
- lib/action_mcp/content/validation.rb
Overview
Stable MCP wire-shape validation shared by content and response value objects.
Constant Summary collapse
- META_PROPERTY =
{ "_meta" => { "type" => "object" } }.freeze
- ANNOTATIONS =
{ "type" => "object", "properties" => { "audience" => { "type" => "array", "items" => { "type" => "string", "enum" => %w[assistant user] } }, "lastModified" => { "type" => "string" }, "priority" => { "type" => "number", "minimum" => 0, "maximum" => 1 } } }.freeze
- ICON =
{ "type" => "object", "required" => [ "src" ], "properties" => { "src" => { "type" => "string", "format" => "uri" }, "mimeType" => { "type" => "string" }, "sizes" => { "type" => "array", "items" => { "type" => "string" } }, "theme" => { "type" => "string", "enum" => %w[dark light] } } }.freeze
- DEFINITIONS =
{ "Annotations" => ANNOTATIONS, "Icon" => ICON, "TextContent" => { "type" => "object", "required" => %w[text type], "properties" => META_PROPERTY.merge( "annotations" => { "$ref" => "#/$defs/Annotations" }, "text" => { "type" => "string" }, "type" => { "type" => "string", "const" => "text" } ) }, "ImageContent" => { "type" => "object", "required" => %w[data mimeType type], "properties" => META_PROPERTY.merge( "annotations" => { "$ref" => "#/$defs/Annotations" }, "data" => { "type" => "string", "format" => "byte" }, "mimeType" => { "type" => "string" }, "type" => { "type" => "string", "const" => "image" } ) }, "AudioContent" => { "type" => "object", "required" => %w[data mimeType type], "properties" => META_PROPERTY.merge( "annotations" => { "$ref" => "#/$defs/Annotations" }, "data" => { "type" => "string", "format" => "byte" }, "mimeType" => { "type" => "string" }, "type" => { "type" => "string", "const" => "audio" } ) }, "TextResourceContents" => { "type" => "object", "required" => %w[text uri], "properties" => META_PROPERTY.merge( "mimeType" => { "type" => "string" }, "text" => { "type" => "string" }, "uri" => { "type" => "string", "format" => "uri" } ) }, "BlobResourceContents" => { "type" => "object", "required" => %w[blob uri], "properties" => META_PROPERTY.merge( "blob" => { "type" => "string", "format" => "byte" }, "mimeType" => { "type" => "string" }, "uri" => { "type" => "string", "format" => "uri" } ) }, "EmbeddedResource" => { "type" => "object", "required" => %w[resource type], "properties" => META_PROPERTY.merge( "annotations" => { "$ref" => "#/$defs/Annotations" }, "resource" => { "anyOf" => [ { "$ref" => "#/$defs/TextResourceContents" }, { "$ref" => "#/$defs/BlobResourceContents" } ] }, "type" => { "type" => "string", "const" => "resource" } ) }, "ResourceLink" => { "type" => "object", "required" => %w[name type uri], "properties" => META_PROPERTY.merge( "annotations" => { "$ref" => "#/$defs/Annotations" }, "description" => { "type" => "string" }, "icons" => { "type" => "array", "items" => { "$ref" => "#/$defs/Icon" } }, "mimeType" => { "type" => "string" }, "name" => { "type" => "string" }, "size" => { "type" => "integer" }, "title" => { "type" => "string" }, "type" => { "type" => "string", "const" => "resource_link" }, "uri" => { "type" => "string", "format" => "uri" } ) }, "ContentBlock" => { "anyOf" => %w[TextContent ImageContent AudioContent ResourceLink EmbeddedResource].map do |name| { "$ref" => "#/$defs/#{name}" } end }, "Resource" => { "type" => "object", "required" => %w[name uri], "properties" => META_PROPERTY.merge( "annotations" => { "$ref" => "#/$defs/Annotations" }, "description" => { "type" => "string" }, "icons" => { "type" => "array", "items" => { "$ref" => "#/$defs/Icon" } }, "mimeType" => { "type" => "string" }, "name" => { "type" => "string" }, "size" => { "type" => "integer" }, "title" => { "type" => "string" }, "uri" => { "type" => "string", "format" => "uri" } ) }, "PromptMessage" => { "type" => "object", "required" => %w[content role], "properties" => { "content" => { "$ref" => "#/$defs/ContentBlock" }, "role" => { "type" => "string", "enum" => %w[assistant user] } } }, "GetPromptResult" => { "type" => "object", "required" => [ "messages" ], "properties" => META_PROPERTY.merge( "description" => { "type" => "string" }, "messages" => { "type" => "array", "items" => { "$ref" => "#/$defs/PromptMessage" } } ) }, "CallToolResult" => { "type" => "object", "required" => [ "content" ], "properties" => META_PROPERTY.merge( "content" => { "type" => "array", "items" => { "$ref" => "#/$defs/ContentBlock" } }, "isError" => { "type" => "boolean" }, "structuredContent" => { "type" => "object" } ) } }.freeze
- FORMAT_VALIDATORS =
{ "byte" => lambda do |value, _format| Base64.strict_decode64(value) true rescue ArgumentError false end, "uri" => lambda do |value, _format| Addressable::URI.parse(value).absolute? rescue Addressable::URI::InvalidURIError, TypeError false end }.freeze
- SCHEMERS =
%w[Annotations ContentBlock Resource GetPromptResult CallToolResult].to_h do |name| schema = { "$schema" => "https://json-schema.org/draft/2020-12/schema", "$ref" => "#/$defs/#{name}", "$defs" => DEFINITIONS } [ name, JSONSchemer.schema(schema, formats: FORMAT_VALIDATORS) ] end.freeze
Class Method Summary collapse
- .copy_content_block!(content) ⇒ Object
- .copy_object!(value, label) ⇒ Object
- .ensure_json!(value, label) ⇒ Object
- .validate_annotations!(annotations) ⇒ Object
- .validate_content_block!(content) ⇒ Object
- .validate_prompt_result!(result) ⇒ Object
- .validate_resource!(resource) ⇒ Object
- .validate_tool_result!(result) ⇒ Object
Class Method Details
.copy_content_block!(content) ⇒ Object
229 230 231 232 233 |
# File 'lib/action_mcp/content/validation.rb', line 229 def copy_content_block!(content) copy = copy_object!(content, "content") validate_content_block!(copy) copy end |
.copy_object!(value, label) ⇒ Object
212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 |
# File 'lib/action_mcp/content/validation.rb', line 212 def copy_object!(value, label) source = if value.is_a?(Hash) value elsif value.is_a?(Array) nil elsif value.respond_to?(:to_hash) value.to_hash elsif value.respond_to?(:to_h) value.to_h end raise ArgumentError, "#{label} must be a JSON object" unless source.is_a?(Hash) source.deep_dup.tap { |copy| ensure_json!(copy, label) } end |
.ensure_json!(value, label) ⇒ Object
235 236 237 238 239 |
# File 'lib/action_mcp/content/validation.rb', line 235 def ensure_json!(value, label) JSON.generate(value) rescue JSON::GeneratorError, JSON::NestingError => e raise ArgumentError, "#{label} must be JSON-serializable: #{e.}" end |
.validate_annotations!(annotations) ⇒ Object
190 191 192 193 194 |
# File 'lib/action_mcp/content/validation.rb', line 190 def validate_annotations!(annotations) return if annotations.nil? validate!(SCHEMERS.fetch("Annotations"), annotations, "annotations") end |
.validate_content_block!(content) ⇒ Object
196 197 198 |
# File 'lib/action_mcp/content/validation.rb', line 196 def validate_content_block!(content) validate!(SCHEMERS.fetch("ContentBlock"), content, "content") end |
.validate_prompt_result!(result) ⇒ Object
204 205 206 |
# File 'lib/action_mcp/content/validation.rb', line 204 def validate_prompt_result!(result) validate!(SCHEMERS.fetch("GetPromptResult"), result, "prompt result") end |
.validate_resource!(resource) ⇒ Object
200 201 202 |
# File 'lib/action_mcp/content/validation.rb', line 200 def validate_resource!(resource) validate!(SCHEMERS.fetch("Resource"), resource, "resource") end |
.validate_tool_result!(result) ⇒ Object
208 209 210 |
# File 'lib/action_mcp/content/validation.rb', line 208 def validate_tool_result!(result) validate!(SCHEMERS.fetch("CallToolResult"), result, "tool result") end |