// Field note
Validate Agent Tool Calls Before They Execute
Validate Agent Tool Calls Before They Execute
An agent's tool call is a promise. The model says it's going to call update_record with an id, a status, and a reason. If any of those arguments are wrong, the tool runs anyway — and tools don't fail politely. They create records, delete files, spend money, and send messages to people.
The failure mode everyone imagines is the malicious one: an attacker steers the agent into a bad call. The failure mode that actually happens every day is dumber than that. The model samples its output token by token, and sometimes the sample doesn't match the schema. A missing field here, a string where a number belongs there, an enum value that was almost right.
JSON Schema exists precisely because this class of bug is everywhere. It's the contract between what a tool declares and what a caller may send. The gap is that almost nobody validates agent output against it before letting the call through.
Why this matters now
Tool calling turned every model into a distributed system where the caller is a probability distribution. That has two consequences that don't get enough airtime:
Malformed arguments are a certainty, not an edge case. Frameworks that do validate are often best-effort about it — they coerce, they fill defaults, they warn instead of reject. And as darinor's own MCP security checklist covers, clients apply defaults when a tool's schema is missing annotations: readOnlyHint: false, destructiveHint: true. A genuinely safe tool with no annotations gets treated as destructive by default. Validate the shape and you at least know what you're actually calling.
The cost of a bad call isn't the error — it's the wrong execution. A malformed call that gets rejected is a cheap retry. A malformed call that gets coerced is a record with the wrong status, a config with a default where a real value belonged, a destructive operation that a missing flag didn't prevent. The tool executed. That's the outcome you can't take back.
The five-minute contract check
You don't need a validation framework to close this gap. You need the tool's schema, the model's output, and a validator. All three are free, and two of them run in your browser.
Step 1 — Get the tool's schema.
Every MCP tool declares its input shape in inputSchema. If you're probing an unfamiliar server, the MCP Server Probe enumerates every exposed tool with its schema in one handshake — no install, nothing uploaded. If you're working with your own tools, the schema is the one you wrote. Either way, you now have the contract.
Step 2 — Paste schema and arguments into the validator.
Open the JSON Schema Validator, drop the tool's inputSchema on one side and the arguments the model produced on the other. It runs entirely in your browser — which matters, because the arguments you're checking are often a real request payload, a config file, or a tool call that's about to do something with side effects. Nothing leaves your machine.
Step 3 — Read the violations by path.
Every failure comes back with its exact location: /user/email, /items/3/quantity. This is where the check earns its keep — you're not guessing whether the call is fine, you're reading which field is missing, which type is wrong, which enum value doesn't exist. That path is also what you log when the call fails in production, because it's the same shape every debugging session needs.
Step 4 — Decide: reject, repair, or proceed.
- Reject — the call doesn't match the contract. This is the default for anything with side effects. The agent retries with a corrected call or the user gets asked.
- Repair — the violation is a fixable coercion: trailing whitespace, wrong casing, a missing field with an obvious default. Repair is a judgment call; do it only when the correction is unambiguous.
- Proceed — the call validates. This is the only branch that should ever touch a real side effect.
The rule that keeps this simple: validate before the side effect, never after. A validation error logged after the tool ran is an incident report, not a guardrail.
Step 5 — Wire it into the loop.
Once the check works by hand, it belongs in the harness: validate the model's tool arguments against the tool's declared schema, and if validation fails, feed the violations back to the model as a tool result — models are surprisingly good at fixing their own calls when you hand them the path. That turns validation from a gate into a repair loop, and it's the difference between an agent that flails and one that converges.
What this catches — and what it doesn't
Shape validation catches the boring failures, which are most of them: wrong types, missing required fields, out-of-range values, bad formats, unknown enum members. It is the difference between a call that runs and a call that was never supposed to run.
It does not catch semantic prompt injection — a tool description that reads benign but instructs the model to exfiltrate data. No static check catches that; it needs behavioral testing. It doesn't catch an over-permissioned tool roster, or a server whose schema lies about what the tool actually does. Those are separate checks, and they're covered by the AI Agent Config Checker and the MCP Tool Schema Linter respectively.
Validation is the floor, not the ceiling. But it's the floor almost nobody has installed.
The pattern
Every tool call is a promise. Validate it before you honor it. The schema is already there — your tool declared it — and the check takes five minutes. The alternative is a call that runs with the wrong shape and a debugging session that starts with "it executed, so I don't know what went wrong."