Tools MCP and External Integration
Tool Schema Validation — Patterns
Tool schema validation › Patterns
One declaration, three consumers
Problem. The schema shown to the model, the schema the validator enforces, and the types the implementation uses are typically three separate artefacts. They agree on the day they are written and diverge immediately afterwards, and the divergence is only discovered when a call that validated cleanly fails inside the tool.
Approach. Derive all three from a single source document, with the implementation generating or asserting against it at build time rather than at review time. Where a runtime requires a narrowed form of the declaration, generate that narrowing mechanically and fail the build if the narrowing drops a constraint the implementation depends on.
Trade-off. The single source must be expressive enough for the strictest consumer and narrow enough for the most limited one, which usually means writing to the lowest common denominator and enforcing the remainder separately. Build-time generation also makes schema changes a release event rather than a configuration edit.
Errors written for the caller that made them
Problem. A validator's natural output is a machine path and a constraint name. Returned to a model, this produces correction attempts that change the wrong field, because nothing in the message connects the structural path back to the argument the model thought it was setting.
Approach. Translate each failure into a single sentence naming the argument as the model knows it, what was received, what is permitted, and — where the permitted set is small or derivable — the specific allowed values. Return at most a few failures at a time, ordered by how likely each is to be the root cause, rather than a complete list the model will try to fix simultaneously.
Trade-off. Translation is per-constraint work that has to be maintained alongside the schema, and over-helpful messages can leak information about valid identifiers or internal enumerations to whoever is driving the conversation. Limiting the number of reported failures also means genuinely multi-error payloads take more round trips.
Validate at the boundary you control, on both sides
Problem. Host-side validation before dispatch catches model errors but protects nothing if a different caller reaches the tool directly. Implementation-side validation protects the tool but gives the model no chance to correct, because by then the call has crossed a protocol boundary and the error comes back flattened.
Approach. Run both, for different purposes. The host check exists to produce a correctable message and to avoid spending an upstream call on an obviously invalid payload. The implementation check exists as the actual trust boundary and treats every caller as untrusted, including your own host. Keep them derived from the same declaration so they cannot disagree about what is valid.
Trade-off. Validation runs twice on every call, which costs latency and duplicates error handling. The duplication is deliberate — one is an ergonomic aid, the other is enforcement — but it will be proposed for removal by someone optimising the hot path.
Tighten through an observation window
Problem. Making a field required, narrowing an enumeration, or adding a bound breaks every caller that was relying on the looser contract, and you cannot tell in advance how many there are because the loose contract never recorded violations.
Approach. Introduce the stricter constraint in a mode that evaluates and records but does not reject, keep it there long enough to cover the slowest-moving caller, and promote it to enforcement only when the recorded violation rate is zero or the remaining violators are identified and contacted. Report violations against the calling identity, not just the tool, so the follow-up has a destination.
Trade-off. It takes real elapsed time, and a constraint sitting in observation mode is easily forgotten — the pattern needs an owner and a deadline or it becomes a permanent state. During the window the system is knowingly accepting payloads it has already decided are wrong.