Skip to content

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 →