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 →