`Tool::Schema` rejected `$ref` with an `ArgumentError`. Earlier MCP spec versions (2024-11-05 through 2025-06-18) did not define a JSON Schema dialect or reference the `$ref` keyword. `inputSchema` was described only as "JSON Schema defining expected parameters" with no further constraints: - https://modelcontextprotocol.io/specification/2024-11-05/server/tools - https://modelcontextprotocol.io/specification/2025-06-18/server/tools The MCP spec 2025-11-25 introduced a "JSON Schema Usage" section that adopts JSON Schema 2020-12 as the default dialect. Since `$ref` is a core keyword in 2020-12, it is now allowed for protocol version 2025-11-25 and later. - https://modelcontextprotocol.io/specification/2025-11-25/basic#json-schema-usage - https://modelcontextprotocol.io/specification/2025-11-25/server/tools - https://json-schema.org/draft/2020-12/release-notes For backward compatibility with older protocol versions (2025-06-18 and earlier), `Server#validate!` continues to raise `ArgumentError` when a tool input schema contains `$ref`. This preserves the existing behavior for servers targeting those spec versions.
61 lines
1.7 KiB
Ruby
61 lines
1.7 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require "json-schema"
|
|
|
|
module MCP
|
|
class Tool
|
|
class Schema
|
|
attr_reader :schema
|
|
|
|
def initialize(schema = {})
|
|
@schema = deep_transform_keys(JSON.parse(JSON.dump(schema)), &:to_sym)
|
|
@schema[:type] ||= "object"
|
|
validate_schema!
|
|
end
|
|
|
|
def ==(other)
|
|
other.is_a?(self.class) && schema == other.schema
|
|
end
|
|
|
|
def to_h
|
|
@schema
|
|
end
|
|
|
|
private
|
|
|
|
def fully_validate(data)
|
|
JSON::Validator.fully_validate(to_h, data)
|
|
end
|
|
|
|
def deep_transform_keys(schema, &block)
|
|
case schema
|
|
when Hash
|
|
schema.each_with_object({}) do |(key, value), result|
|
|
result[yield(key)] = deep_transform_keys(value, &block)
|
|
end
|
|
when Array
|
|
schema.map { |e| deep_transform_keys(e, &block) }
|
|
else
|
|
schema
|
|
end
|
|
end
|
|
|
|
def validate_schema!
|
|
schema = to_h
|
|
gem_path = File.realpath(Gem.loaded_specs["json-schema"].full_gem_path)
|
|
schema_reader = JSON::Schema::Reader.new(
|
|
accept_uri: false,
|
|
accept_file: ->(path) { File.realpath(path.to_s).start_with?(gem_path) },
|
|
)
|
|
metaschema_path = Pathname.new(JSON::Validator.validator_for_name("draft4").metaschema)
|
|
# Converts metaschema to a file URI for cross-platform compatibility
|
|
metaschema_uri = JSON::Util::URI.file_uri(metaschema_path.expand_path.cleanpath.to_s.tr("\\", "/"))
|
|
metaschema = metaschema_uri.to_s
|
|
errors = JSON::Validator.fully_validate(metaschema, schema, schema_reader: schema_reader)
|
|
if errors.any?
|
|
raise ArgumentError, "Invalid JSON Schema: #{errors.join(", ")}"
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|