Skip to content

Tool Schema Builder — Python 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 Python 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). Python 3.9+, standard library only.
"""

from __future__ import annotations

import json
import re

SUPPORTED_TYPES = ("string", "number", "integer", "boolean", "object", "array")
NAME_RE = re.compile(r"^[a-z0-9_-]{1,64}$")


def check_object(path: str, obj: dict, issues: list) -> None:
    """The recursive strict-mode walk — every object nests the same rules."""
    if obj.get("additionalProperties") is not False:
        issues.append(("no-additional-properties", path))
    props = obj.get("properties")
    props = props if isinstance(props, dict) else {}
    keys = [k for k in props if isinstance(props[k], dict)]
    required = obj.get("required") if isinstance(obj.get("required"), list) else []
    missing = [k for k in keys if k not in required]
    if missing:
        issues.append(("all-required", f"{path}: required missing {', '.join(missing)}"))
    for key in keys:
        prop = props[key]
        p = f"{path}.properties.{key}"
        if "default" in prop:
            issues.append(("no-defaults", p))
        if not str(prop.get("description") or "").strip():
            issues.append(("description-present", p))
        if prop.get("type") not in SUPPORTED_TYPES:
            issues.append(("typed-properties", f"{p}: must be one of {' | '.join(SUPPORTED_TYPES)}"))
        enum = prop.get("enum")
        if isinstance(enum, list):
            kinds = {type(v).__name__ for v in enum}
            bad = len(kinds) > 1 or "dict" in kinds or "NoneType" in kinds
            if not enum or bad:
                issues.append(("enum-values", p))
        if prop.get("type") == "array" and not isinstance(prop.get("items"), dict):
            issues.append(("array-items", p))
        if prop.get("type") == "object" and isinstance(prop.get("properties"), dict):
            check_object(p, prop, issues)


def validate_tool_schema(text: str) -> list:
    """Validate a JSON tool definition; returns (rule, where) issue pairs."""
    try:
        root = json.loads(text)
    except json.JSONDecodeError as e:
        return [("json-parseable", f"$: {e.msg}")]
    if not isinstance(root, dict):
        return [("json-parseable", "$: input must be a JSON object")]
    issues: list = []
    if not NAME_RE.match(str(root.get("name") or "")):
        issues.append(("non-empty-name", "name: must be 1-64 chars of [a-z0-9_-]"))
    if not str(root.get("description") or "").strip():
        issues.append(("description-present", "description: the tool needs a description"))
    schema = root.get("input_schema")
    if isinstance(schema, dict) and schema.get("type") == "object":
        check_object("input_schema", schema, issues)
    else:
        issues.append(("json-parseable", 'input_schema: must be an object with type: "object"'))
    return issues


if __name__ == "__main__":
    broken = json.dumps({"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 rule, where in validate_tool_schema(broken):
        print(f"{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 →