Tool Schema Builder — PHP 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 PHP implementation — the same logic the interactive tool runs, in a shareable, citable form.
<?php
/**
* 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). PHP 8.2, stdlib only.
*/
const SUPPORTED_TYPES = ['string', 'number', 'integer', 'boolean', 'object', 'array'];
/** The recursive strict-mode walk — every object nests the same rules. */
function checkObject(string $path, array $obj, array &$issues): void
{
if (($obj['additionalProperties'] ?? null) !== false) $issues[] = ['no-additional-properties', $path];
$props = is_array($obj['properties'] ?? null) ? $obj['properties'] : [];
$keys = array_keys(array_filter($props, 'is_array')); // objects only, as the lib's asObj
$required = is_array($obj['required'] ?? null) ? $obj['required'] : [];
$missing = array_values(array_diff($keys, $required));
if ($missing !== []) $issues[] = ['all-required', "$path: required missing " . implode(', ', $missing)];
foreach ($keys as $key) {
$prop = $props[$key];
$p = "$path.properties.$key";
if (array_key_exists('default', $prop)) $issues[] = ['no-defaults', $p];
$desc = $prop['description'] ?? null;
if (!is_string($desc) || trim($desc) === '') $issues[] = ['description-present', $p];
if (!in_array($prop['type'] ?? null, SUPPORTED_TYPES, true)) {
$issues[] = ['typed-properties', "$p: must be one of " . implode(' | ', SUPPORTED_TYPES)];
}
if (isset($prop['enum']) && is_array($prop['enum'])) {
$kinds = array_unique(array_map('gettype', $prop['enum']));
$bad = count($kinds) > 1 || in_array('array', $kinds, true) || in_array('NULL', $kinds, true);
if ($prop['enum'] === [] || $bad) $issues[] = ['enum-values', $p];
}
if (($prop['type'] ?? null) === 'array' && !is_array($prop['items'] ?? null)) {
$issues[] = ['array-items', $p];
}
if (($prop['type'] ?? null) === 'object' && is_array($prop['properties'] ?? null)) {
checkObject($p, $prop, $issues);
}
}
}
/** Validate a JSON tool definition; returns [rule, where] issue pairs. */
function validateToolSchema(string $json): array
{
$root = json_decode($json, true);
if (!is_array($root) || array_is_list($root)) {
$why = json_last_error() !== JSON_ERROR_NONE ? json_last_error_msg() : 'input must be a JSON object';
return [['json-parseable', "\$: $why"]];
}
$issues = [];
if (!isset($root['name']) || !preg_match('/^[a-z0-9_-]{1,64}$/', (string) $root['name'])) {
$issues[] = ['non-empty-name', 'name: must be 1-64 chars of [a-z0-9_-]'];
}
$desc = $root['description'] ?? null;
if (!is_string($desc) || trim($desc) === '') {
$issues[] = ['description-present', 'description: the tool needs a description'];
}
$schema = $root['input_schema'] ?? null;
if (is_array($schema) && !array_is_list($schema) && ($schema['type'] ?? null) === 'object') {
checkObject('input_schema', $schema, $issues);
} else {
$issues[] = ['json-parseable', 'input_schema: must be an object with type: "object"'];
}
return $issues;
}
// Demo: validate a small broken definition and print the issues.
$broken = json_encode(['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 (validateToolSchema($broken) as [$rule, $where]) {
echo "$rule $where\n";
}
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 →