class RubyLLM::MCP::Tool
def create_content_for_message(content)
def create_content_for_message(content) case content["type"] when "text" MCP::Content.new(text: content["text"]) when "image", "audio" attachment = MCP::Attachment.new(content["data"], content["mimeType"]) MCP::Content.new(text: nil, attachments: [attachment]) when "resource" resource_data = { "name" => name, "description" => description, "uri" => content.dig("resource", "uri"), "mimeType" => content.dig("resource", "mimeType"), "content_response" => { "text" => content.dig("resource", "text"), "blob" => content.dig("resource", "blob") } } resource = Resource.new(coordinator, resource_data) resource.to_content when "resource_link" resource_data = { "name" => content["name"], "uri" => content["uri"], "description" => content["description"], "mimeType" => content["mimeType"] } resource = Resource.new(coordinator, resource_data) @coordinator.register_resource(resource) resource.to_content end end
def display_name
def display_name "#{@coordinator.name}: #{@name}" end
def ensure_object_properties(schema)
def ensure_object_properties(schema) if schema["type"] == "object" && !schema.key?("properties") schema["properties"] = {} end end
def execute(**params)
def execute(**params) result = @coordinator.execute_tool( name: @mcp_name, parameters: params ) if result.error? error = result.to_error return { error: error.to_s } end text_values = result.value["content"].map { |content| content["text"] }.compact.join("\n") if result.execution_error? return { error: "Tool execution error: #{text_values}" } end if result.value.key?("structuredContent") && !@output_schema.nil? is_valid = JSON::Validator.validate(@output_schema, result.value["structuredContent"]) unless is_valid return { error: "Structued outputs was not invalid: #{result.value['structuredContent']}" } end return text_values end if text_values.empty? create_content_for_message(result.value.dig("content", 0)) else create_content_for_message({ "type" => "text", "text" => text_values }) end end
def format_name(name)
def format_name(name) if @with_prefix "#{@coordinator.name}_#{name}" else name end end
def initialize(coordinator, tool_response, with_prefix: false)
def initialize(coordinator, tool_response, with_prefix: false) super() @coordinator = coordinator @with_prefix = with_prefix @name = format_name(tool_response["name"]) @mcp_name = tool_response["name"] @description = tool_response["description"].to_s @input_schema = tool_response["inputSchema"] @output_schema = tool_response["outputSchema"] @annotations = tool_response["annotations"] ? Annotation.new(tool_response["annotations"]) : nil @normalized_input_schema = normalize_if_invalid(@input_schema) end
def normalize_array_schema(schema)
def normalize_array_schema(schema) schema.map { |item| normalize_schema_value(item) } end
def normalize_hash_schema(schema)
def normalize_hash_schema(schema) normalized = schema.transform_values { |value| normalize_schema_value(value) } ensure_object_properties(normalized) normalized end
def normalize_if_invalid(schema)
def normalize_if_invalid(schema) return schema if schema.nil? if valid_schema?(schema) schema else normalize_schema(schema) end end
def normalize_schema(schema)
def normalize_schema(schema) return schema if schema.nil? case schema when Hash normalize_hash_schema(schema) when Array normalize_array_schema(schema) else schema end end
def normalize_schema_value(value)
def normalize_schema_value(value) case value when Hash normalize_schema(value) when Array normalize_array_schema(value) else value end end
def params_schema
def params_schema @normalized_input_schema end
def to_h
def to_h { name: @name, description: @description, params_schema: @@normalized_input_schema, annotations: @annotations&.to_h } end
def valid_hash_schema?(schema)
def valid_hash_schema?(schema) # Check if this level has missing properties for object type if schema["type"] == "object" && !schema.key?("properties") return false end # Recursively check nested schemas schema.each_value do |value| return false unless valid_schema?(value) end begin JSON::Validator.validate!(schema, {}) true rescue JSON::Schema::SchemaError false rescue JSON::Schema::ValidationError true end end
def valid_schema?(schema)
def valid_schema?(schema) return true if schema.nil? case schema when Hash valid_hash_schema?(schema) when Array schema.all? { |item| valid_schema?(item) } else true end end