Tool Schema Builder — Rust 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 Rust 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. Rust 2021 + serde_json.
use serde_json::{json, Map, Value};
const SUPPORTED_TYPES: [&str; 6] = ["string", "number", "integer", "boolean", "object", "array"];
struct Issue { rule: &'static str, path: String }
macro_rules! issue {
($issues:expr, $rule:expr, $path:expr) => { $issues.push(Issue { rule: $rule, path: $path.into() }) };
}
fn as_obj(v: Option<&Value>) -> Option<&Map<String, Value>> { match v { Some(Value::Object(m)) => Some(m), _ => None } }
fn str_field<'a>(m: &'a Map<String, Value>, k: &str) -> Option<&'a str> { m.get(k).and_then(Value::as_str) }
// The recursive strict-mode walk — every object nests the same rules.
fn check_object(path: &str, obj: &Map<String, Value>, issues: &mut Vec<Issue>) {
if obj.get("additionalProperties").and_then(Value::as_bool) != Some(false) {
issue!(issues, "no-additional-properties", path);
}
if let Some(props) = as_obj(obj.get("properties")) {
let required: Vec<&str> = obj.get("required").and_then(Value::as_array)
.map(|a| a.iter().filter_map(Value::as_str).collect()).unwrap_or_default();
let missing: Vec<&str> = props.keys()
.filter(|k| !required.contains(&k.as_str())).map(String::as_str).collect();
if !missing.is_empty() {
issue!(issues, "all-required", format!("{path}: required missing {}", missing.join(", ")));
}
for (key, raw) in props {
let Some(prop) = as_obj(Some(raw)) else { continue };
let p = format!("{path}.properties.{key}");
if prop.contains_key("default") { issue!(issues, "no-defaults", p.clone()); }
if !str_field(prop, "description").is_some_and(|d| !d.trim().is_empty()) {
issue!(issues, "description-present", p.clone());
}
let ty = str_field(prop, "type");
if !ty.is_some_and(|t| SUPPORTED_TYPES.contains(&t)) {
issue!(issues, "typed-properties", format!("{p}: must be one of {}", SUPPORTED_TYPES.join(" | ")));
}
if let Some(Value::Array(en)) = prop.get("enum") {
let kind = |v: &Value| match v {
Value::String(_) => "string", Value::Number(_) => "number",
Value::Bool(_) => "boolean", _ => "other",
};
let mut mixed = false;
if let Some(first) = en.first().map(kind) { mixed = en.iter().any(|v| kind(v) != first); }
if en.is_empty() || mixed || en.iter().any(|v| kind(v) == "other") {
issue!(issues, "enum-values", p.clone());
}
}
if ty == Some("array") && as_obj(prop.get("items")).is_none() { issue!(issues, "array-items", p.clone()); }
if ty == Some("object") && as_obj(prop.get("properties")).is_some() { check_object(&p, prop, issues); }
}
}
}
// Validate a parsed tool definition ({name, description, input_schema}).
fn validate_schema(root: &Value) -> Vec<Issue> {
let Some(m) = as_obj(Some(root)) else {
return vec![Issue { rule: "json-parseable", path: "$: input must be a JSON object".into() }];
};
let mut issues = Vec::new();
let name = str_field(m, "name").unwrap_or_default();
let name_ok = (1..=64).contains(&name.len()) && name.chars()
.all(|c| c.is_ascii_lowercase() || c.is_ascii_digit() || c == '_' || c == '-');
if !name_ok { issue!(issues, "non-empty-name", "name: must be 1-64 chars of [a-z0-9_-]"); }
if !str_field(m, "description").is_some_and(|d| !d.trim().is_empty()) {
issue!(issues, "description-present", "description: the tool needs a description");
}
match as_obj(m.get("input_schema")) {
Some(s) if str_field(s, "type") == Some("object") => check_object("input_schema", s, &mut issues),
_ => issue!(issues, "json-parseable", "input_schema: must be an object with type: \"object\""),
}
issues
}
fn main() {
// Demo: validate a small broken definition and print the issues.
let broken = json!({"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 validate_schema(&broken) { println!("{} {}", 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 →