Build an LLM tool or function definition visually and export it as an OpenAI function, an Anthropic tool, and an MCP tool. One JSON Schema, three formats.
Defining a tool for a model is mostly clerical work with an unforgiving format. You need a name, a description written for a model rather than a human, and a JSON Schema describing the arguments — and then you need to wrap that schema in whichever envelope your provider expects. Get a brace wrong and the request is rejected. Get the description wrong and the model calls the tool at the wrong moment, which is harder to notice and much harder to debug.
This builder handles the clerical half. You fill in a function name, a description and a list of parameters; it produces valid JSON Schema and shows it in three envelopes at once. Switch tabs to copy whichever you need. It runs entirely in the browser with no request to any model API and no key required — the schema is assembled from form state and rendered directly, so nothing you type is transmitted.
The central point, and the reason a single builder can serve all three targets, is that the schema itself is identical. Only the wrapper differs.
| Target | Envelope shape | Where the schema goes |
|---|---|---|
| OpenAI | { "type": "function", "function": { … } } | function.parameters |
| Anthropic | { "name": …, "description": …, … } | input_schema |
| MCP tool | { "name": …, "description": …, … } | inputSchema (camelCase) |
OpenAI nests the whole definition one level deeper under a function key with a sibling type: "function". Anthropic and MCP are structurally the same as each other — name, description, schema, at the top level — and differ only in the casing of the schema key: input_schema with an underscore for Anthropic, inputSchema in camelCase for MCP. That one-character-class difference is a genuinely common source of a rejected tool definition when porting a server between the two.
In all three, the description key is omitted entirely if you leave the description box empty, rather than emitted as an empty string. If the function name box is empty, the name falls back to unnamed_function so the output stays syntactically valid.
Seven type options are available per parameter, and each maps to a specific JSON Schema property.
| Type | Emitted property |
|---|---|
string | { "type": "string" } |
number | { "type": "number" } |
integer | { "type": "integer" } |
boolean | { "type": "boolean" } |
array | { "type": "array", "items": { "type": "string" } } |
object | { "type": "object" } |
enum | { "type": "string", "enum": [ … ] } |
Two of these deserve a caveat. Array always gets an item schema of { "type": "string" }. That is a deliberate default — a bare {"type": "array"} is under-specified and some validators reject it — but if your array holds numbers or objects you must edit items after copying. Object is emitted with no properties at all, which means a nested object is effectively unconstrained; the builder is one level deep by design. For a nested structure, generate the outer shape here and hand-write the inner one.
Enum is a convenience rather than a distinct JSON Schema type: it emits a string with an enum array built by splitting your comma-separated input, trimming each value and dropping blanks. Typing celsius, fahrenheit produces ["celsius","fahrenheit"]. If you select enum and leave the values box empty, no enum key is added and you are left with a plain string.
Each parameter also carries an optional description, emitted as a description key inside its property object and omitted when blank, and a Required checkbox, which controls membership of the schema's required array. The required array is omitted entirely when nothing is required.
The builder opens on a weather function: name get_weather, description "Get the current weather for a given location", and two parameters. location is a required string described as "City and state, e.g. San Francisco, CA". unit is an optional enum with the values celsius and fahrenheit.
The shared schema that produces is an object with a properties map containing location as a described string and unit as a string with a two-value enum, plus a required array holding only location. The OpenAI tab wraps that under function.parameters with type: "function" alongside. The Anthropic tab puts it in input_schema. The MCP tab puts the identical object in inputSchema. Three outputs, one schema, and the only edits between them are structural.
The builder checks two things about parameter names and reports them below the parameter list.
^[a-zA-Z0-9_-]+$. A name with a space, a dot or a slash is listed as a warning. It is not blocked — JSON Schema itself permits arbitrary property names — but provider validators are stricter than the schema standard, and a name outside that set is a likely rejection.required array twice, so you get a literal duplicate entry there. Rename one.Parameters with an empty name are skipped entirely rather than emitted as "", so a half-finished row will not corrupt the output while you are still typing it.
The schema is the easy part. The descriptions are what determine whether the model calls your tool correctly, and they are the part a generator cannot do for you.
The generated schema is intentionally minimal: type, properties, and required when non-empty. It does not emit a $schema declaration, an additionalProperties setting, OpenAI's strict-mode flag, per-property constraints such as minimum, maxLength, pattern, format or default, or any nested object properties. If your provider configuration requires strict structured outputs, or you need value-range validation, add those keys to the copied JSON by hand.
Nothing here is validated against a live API either. The output is well-formed JSON and well-formed JSON Schema; whether a particular model version accepts a particular name or nesting depth is something only that API can tell you.
| Symptom | Likely cause |
|---|---|
| Anthropic rejects the tool | Schema left under inputSchema. Anthropic wants input_schema. |
| MCP client shows no tool | Schema under input_schema. MCP wants camelCase inputSchema. |
| OpenAI rejects the tool | The function wrapper and its sibling type: "function" were dropped when pasting. |
| Model omits an argument you need | Not marked required, or the description does not make it clear it is mandatory. |
| Model invents values for a closed set | Parameter is a plain string. Make it an enum. |
| Array argument arrives with wrong element types | The default items: {"type": "string"} was left in place for a non-string array. |
| One parameter silently vanished | Two parameters shared a name and were merged. Check the duplicate warning. |
| Tool called at the wrong time | A schema problem this is not. Rewrite the function description to say when it applies. |
If you are writing tools for a single application and a single vendor, the provider's native format is all you need and this builder is a formatting shortcut. If you are exposing the same tools through an MCP server so several clients can use them, the MCP tab is the definition your server advertises — and getting it right is a prerequisite for the client actually listing the tool.
A schema that validates is not a schema that works. The failure mode that costs the most time is a tool that is called at the wrong moment or with a plausible but wrong argument, and neither the JSON nor the API will tell you about it. A short manual pass catches most of it.
None of this is schema work, which is precisely the point: once the builder has taken the formatting off your plate, the description quality is the only variable left, and it is the one that determines whether the tool is actually used correctly.