Skip to content

Tool Schema Builder — Swift 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 Swift implementation — the same logic the interactive tool runs, in a shareable, citable form.

// 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). Swift 5.10, Foundation only.

import Foundation

let supportedTypes: Set<String> = ["string", "number", "integer", "boolean", "object", "array"]
let nameChars = CharacterSet(charactersIn: "abcdefghijklmnopqrstuvwxyz0123456789_-")

/// The recursive strict-mode walk — every object nests the same rules.
func checkObject(_ path: String, _ obj: [String: Any], _ issues: inout [(rule: String, path: String)]) {
    if (obj["additionalProperties"] as? Bool) != false {
        issues.append(("no-additional-properties", path))
    }
    guard let props = obj["properties"] as? [String: Any] else { return }
    let required = obj["required"] as? [String] ?? []
    let missing = props.keys.filter { !required.contains($0) }
    if !missing.isEmpty {
        issues.append(("all-required", "\(path): required missing \(missing.sorted().joined(separator: ", "))"))
    }
    for (key, raw) in props {
        guard let prop = raw as? [String: Any] else { continue }
        let p = "\(path).properties.\(key)"
        if prop["default"] != nil { issues.append(("no-defaults", p)) }
        let desc = prop["description"] as? String ?? ""
        if desc.trimmingCharacters(in: .whitespaces).isEmpty { issues.append(("description-present", p)) }
        let ty = prop["type"] as? String
        if ty == nil || !supportedTypes.contains(ty!) {
            issues.append(("typed-properties", "\(p): must be one of \(supportedTypes.sorted().joined(separator: " | "))"))
        }
        if let e = prop["enum"] as? [Any] {
            let kinds = Set(e.map { v -> String in
                if v is String { return "string" }
                if v is NSNumber { return "number" }
                return "other"
            })
            if e.isEmpty || kinds.count > 1 || kinds.contains("other") { issues.append(("enum-values", p)) }
        }
        if ty == "array" && (prop["items"] as? [String: Any]) == nil { issues.append(("array-items", p)) }
        if ty == "object" && prop["properties"] is [String: Any] { checkObject(p, prop, &issues) }
    }
}

/// Validate a JSON tool definition string; returns (rule, where) issue pairs.
func validateToolSchema(_ json: String) -> [(rule: String, path: String)] {
    guard let data = json.data(using: .utf8),
          let root = (try? JSONSerialization.jsonObject(with: data)) as? [String: Any] else {
        return [("json-parseable", "$: input is not a JSON object")]
    }
    var issues: [(rule: String, path: String)] = []
    let name = root["name"] as? String ?? ""
    let nameOk = (1...64).contains(name.count) && name.unicodeScalars.allSatisfy { nameChars.contains($0) }
    if !nameOk { issues.append(("non-empty-name", "name: must be 1-64 chars of [a-z0-9_-]")) }
    let desc = root["description"] as? String ?? ""
    if desc.trimmingCharacters(in: .whitespaces).isEmpty {
        issues.append(("description-present", "description: the tool needs a description"))
    }
    if let schema = root["input_schema"] as? [String: Any], schema["type"] as? String == "object" {
        checkObject("input_schema", schema, &issues)
    } else {
        issues.append(("json-parseable", "input_schema: must be an object with type: \"object\""))
    }
    return issues
}

// Demo: validate a small broken definition and print the issues.
let broken = """
{"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"]}}
"""
for i in validateToolSchema(broken) {
    print("\(i.rule)  \(i.path)")
}

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 →