Tool Schema Builder — Ruby source
Build function-calling and MCP tool schemas that pass strict mode on the first try, and lint pasted ones against the strict-mode contract — additionalProperties, required-sync, defaults, enums — with one-click autofix for every mechanical violation. Runs entirely in your browser.
This is the Ruby implementation — the same logic the interactive tool runs, in a shareable, citable form.
# frozen_string_literal: true
# Tool Schema Builder — strict-mode validation of function-calling / MCP
# tool definitions (OpenAI strict mode / MCP inputSchema contract).
# CosmoDev polyglot showcase port, from src/lib/tool-schema.ts
# (the canonical TypeScript implementation). Ruby 3.2, stdlib only.
require 'json'
module ToolSchema
SUPPORTED_TYPES = %w[string number integer boolean object array].freeze
NAME_RE = /\A[a-z0-9_-]{1,64}\z/
module_function
# The recursive strict-mode walk — every object nests the same rules.
def check_object(path, obj, issues)
issues << ['no-additional-properties', path] if obj['additionalProperties'] != false
props = obj['properties'].is_a?(Hash) ? obj['properties'] : {}
keys = props.keys.select { |k| props[k].is_a?(Hash) }
required = obj['required'].is_a?(Array) ? obj['required'] : []
missing = keys - required
issues << ['all-required', "#{path}: required missing #{missing.join(', ')}"] unless missing.empty?
keys.each do |key|
prop = props[key]
p = "#{path}.properties.#{key}"
issues << ['no-defaults', p] if prop.key?('default')
issues << ['description-present', p] if prop['description'].to_s.strip.empty?
issues << ['typed-properties', p] unless SUPPORTED_TYPES.include?(prop['type'])
enum = prop['enum']
if enum.is_a?(Array)
kinds = enum.map { |v| v.class.name }.uniq
bad = kinds.size > 1 || kinds.include?('Hash') || kinds.include?('NilClass')
issues << ['enum-values', p] if enum.empty? || bad
end
issues << ['array-items', p] if prop['type'] == 'array' && !prop['items'].is_a?(Hash)
if prop['type'] == 'object' && prop['properties'].is_a?(Hash)
check_object(p, prop, issues)
end
end
end
# Validate a JSON tool definition; returns [rule, where] issue pairs.
def validate_tool_schema(text)
root = JSON.parse(text)
return [['json-parseable', '$: input must be a JSON object']] unless root.is_a?(Hash)
issues = []
unless root['name'].to_s.match?(NAME_RE)
issues << ['non-empty-name', 'name: must be 1-64 chars of [a-z0-9_-]']
end
if root['description'].to_s.strip.empty?
issues << ['description-present', 'description: the tool needs a description']
end
schema = root['input_schema']
if schema.is_a?(Hash) && schema['type'] == 'object'
check_object('input_schema', schema, issues)
else
issues << ['json-parseable', 'input_schema: must be an object with type: "object"']
end
issues
rescue JSON::ParserError => e
[["json-parseable", "$: #{e.message}"]]
end
end
if $PROGRAM_NAME == __FILE__
broken = JSON.generate('name' => 'Get_Weather', 'input_schema' => {
'type' => 'object',
'properties' => {
'city' => { 'type' => 'string', 'default' => 'Paris' },
'unit' => { 'type' => 'string', 'description' => 'celsius or fahrenheit', 'enum' => ['c', 2] },
'tags' => { 'type' => 'array' } },
'required' => ['city'] })
ToolSchema.validate_tool_schema(broken).each { |rule, where| puts "#{rule} #{where}" }
end
Also available in 13 other languages
Every CosmoDev tool ships its pure logic in TypeScript (web) and Go (CLI), with authored implementations in a dozen-plus languages — the same contract, ported. Compare all languages side by side →