Tool Schema Builder — JavaScript 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 JavaScript 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). ES2022, stdlib only.
*/
export const SUPPORTED_TYPES = ['string', 'number', 'integer', 'boolean', 'object', 'array'];
const isObj = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
// The recursive strict-mode walk — every object nests the same rules.
function checkObject(path, obj, issues) {
if (obj.additionalProperties !== false) {
issues.push({ rule: 'no-additional-properties', path });
}
const props = isObj(obj.properties) ? obj.properties : {};
const keys = Object.keys(props).filter((k) => isObj(props[k]));
const required = Array.isArray(obj.required) ? obj.required : [];
const missing = keys.filter((k) => !required.includes(k));
if (missing.length > 0) {
issues.push({ rule: 'all-required', path: `${path}: required missing ${missing.join(', ')}` });
}
for (const key of keys) {
const prop = props[key];
const p = `${path}.properties.${key}`;
if ('default' in prop) issues.push({ rule: 'no-defaults', path: p });
if (typeof prop.description !== 'string' || !prop.description.trim()) {
issues.push({ rule: 'description-present', path: p });
}
if (!SUPPORTED_TYPES.includes(prop.type)) {
issues.push({ rule: 'typed-properties', path: `${p}: must be one of ${SUPPORTED_TYPES.join(' | ')}` });
}
if (Array.isArray(prop.enum)) {
const kinds = new Set(prop.enum.map((v) => typeof v));
if (prop.enum.length === 0 || kinds.size > 1 || kinds.has('object') || kinds.has('undefined')) {
issues.push({ rule: 'enum-values', path: p });
}
}
if (prop.type === 'array' && !isObj(prop.items)) issues.push({ rule: 'array-items', path: p });
if (prop.type === 'object' && isObj(prop.properties)) checkObject(p, prop, issues);
}
}
/** Validate a JSON tool definition ({name, description, input_schema}). */
export function validateToolSchema(input) {
let root;
try {
root = JSON.parse(input);
} catch (e) {
return { parseError: String(e), issues: [] };
}
if (!isObj(root)) {
return { issues: [{ rule: 'json-parseable', path: '$: input must be a JSON object' }] };
}
const issues = [];
if (!/^[a-z0-9_-]{1,64}$/.test(root.name ?? '')) {
issues.push({ rule: 'non-empty-name', path: 'name: must be 1-64 chars of [a-z0-9_-]' });
}
if (typeof root.description !== 'string' || !root.description.trim()) {
issues.push({ rule: 'description-present', path: 'description: the tool needs a description' });
}
const schema = isObj(root.input_schema) ? root.input_schema : null;
if (schema && schema.type === 'object') checkObject('input_schema', schema, issues);
else issues.push({ rule: 'json-parseable', path: 'input_schema: must be an object with type: "object"' });
return { issues };
}
// Demo: validate a small broken definition and print the issues.
const broken = JSON.stringify({ 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 (const { rule, path } of validateToolSchema(broken).issues) console.log(`${rule} ${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 →