Skip to content

Tool Schema Builder — C# 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 C# 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. C# 12, System.Text.Json only.

using System.Text.Json;
using System.Text.Json.Nodes;

static class ToolSchema
{
    static readonly string[] Supported = ["string", "number", "integer", "boolean", "object", "array"];

    static string? Str(JsonNode? n) => n is JsonValue v && v.TryGetValue<string>(out var s) ? s : null;

    static bool? Bool(JsonNode? n) => n is JsonValue v && v.TryGetValue<bool>(out var b) ? b : null;
    static string Kind(JsonNode? v) => v is JsonValue j ? (j.TryGetValue<string>(out _) ? "string"
        : j.TryGetValue<decimal>(out _) ? "number"
        : j.TryGetValue<bool>(out _) ? "bool" : "other") : "other";

    // The recursive strict-mode walk — every object nests the same rules.
    static void CheckObject(string path, JsonObject obj, List<(string Rule, string Path)> issues)
    {
        if (Bool(obj["additionalProperties"]) != false) issues.Add(("no-additional-properties", path));
        if (obj["properties"] is not JsonObject props) return;
        var required = obj["required"] as JsonArray;
        var missing = props.Where(kv => kv.Value is JsonObject
                             && (required?.All(r => Str(r) != kv.Key) ?? true))
                           .Select(kv => kv.Key).ToList();
        if (missing.Count > 0)
            issues.Add(("all-required", $"{path}: required missing {string.Join(", ", missing)}"));
        foreach (var kv in props)
        {
            if (kv.Value is not JsonObject prop) continue;
            var p = $"{path}.properties.{kv.Key}";
            if (prop.ContainsKey("default")) issues.Add(("no-defaults", p));
            if (string.IsNullOrWhiteSpace(Str(prop["description"])))
                issues.Add(("description-present", p));
            var ty = Str(prop["type"]);
            if (ty == null || !Supported.Contains(ty))
                issues.Add(("typed-properties", $"{p}: must be one of {string.Join(" | ", Supported)}"));
            if (prop["enum"] is JsonArray en)
            {
                var kinds = en.Select(v => Kind(v)).ToHashSet();
                if (en.Count == 0 || kinds.Count > 1 || kinds.Contains("other")) issues.Add(("enum-values", p));
            }
            if (ty == "array" && prop["items"] is not JsonObject) issues.Add(("array-items", p));
            if (ty == "object" && prop["properties"] is JsonObject) CheckObject(p, prop, issues);
        }
    }

    // Validate a JSON tool definition string.
    static List<(string Rule, string Path)> ValidateToolSchema(string json)
    {
        JsonNode? root;
        try { root = JsonNode.Parse(json); }
        catch (JsonException e) { return [("json-parseable", $"$: {e.Message}")]; }
        if (root is not JsonObject o) return [("json-parseable", "$: input must be a JSON object")];
        var issues = new List<(string, string)>();
        var name = Str(o["name"]) ?? "";
        var nameOk = name.Length is >= 1 and <= 64
            && name.All(c => c is ((>= 'a' and <= 'z') or (>= '0' and <= '9') or '_' or '-'));
        if (!nameOk) issues.Add(("non-empty-name", "name: must be 1-64 chars of [a-z0-9_-]"));
        if (string.IsNullOrWhiteSpace(Str(o["description"])))
            issues.Add(("description-present", "description: the tool needs a description"));
        if (o["input_schema"] is JsonObject schema && Str(schema["type"]) == "object")
            CheckObject("input_schema", schema, issues);
        else
            issues.Add(("json-parseable", "input_schema: must be an object with type: \"object\""));
        return issues;
    }

    static void Main()
    {
        // Demo: validate a small broken definition and print the issues.
        var 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"]}}
        """;
        foreach (var (rule, where) in ValidateToolSchema(broken)) Console.WriteLine($"{rule}  {where}");
    }
}

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 →