Contracts · interoperability
MCP Tool Contracts and Schema Normalization
Two servers advertise refund_issue. One takes pence, one takes pounds. One is idempotent, one is not. Routing between them looks like a configuration choice and is actually a coin flip on a financial operation.
The short answer
An MCP tool contract is the full specification a caller needs to use a tool safely: its JSON Schema plus units, precision, idempotency behaviour, error taxonomy, side-effect scope and latency envelope. Schema alone does not make two tools interchangeable, so routing between nominally equivalent tools requires a normalization layer that maps each server’s contract onto one canonical form. Six mismatch classes, a normalization design, and versioning rules that keep a canonical contract stable.
Key takeaways
- 01JSON Schema specifies shape, not meaning. Two schema-identical tools can behave completely differently.
- 02Units are the most common and most expensive mismatch. Pence versus pounds is a hundredfold error that validates cleanly.
- 03Idempotency is part of the contract. A retry against a non-idempotent tool is a second side effect, not a second attempt.
- 04Error taxonomy determines retry behaviour. “Failed” without a retriable/terminal distinction guarantees either duplicates or stuck workflows.
- 05Normalize at the control plane, once — not in each agent, where the mapping is re-implemented and re-broken.
- 06The canonical contract is the stable interface. Server contracts are adapters behind it, and vendors get swapped without touching agents.
What JSON Schema does not tell you
MCP tools declare parameters as JSON Schema, which is a good choice and solves a real problem: a model can construct a syntactically valid call without documentation. What schema cannot express is everything that determines whether the call does what you meant.
Two servers both offer refund_issue(order_id: string, amount: number). Identical schemas. One expects minor units, the other major. One deduplicates on an idempotency header, the other issues a second refund. One returns a distinct code for “already refunded”, the other returns a generic failure indistinguishable from a timeout.
A model choosing between these has no basis for the choice, because the differences are not in the material it sees. A router treating them as interchangeable is making a hundredfold error on the first mismatch and a duplicate payment on the second. Neither failure is a bug in either server; both servers are internally consistent and correctly documented in their own terms.
The contract is therefore larger than the schema, and the gap has to be closed somewhere. The only question is whether it is closed once, deliberately, in the control plane — or repeatedly, accidentally, in each agent.
Key facts
- ▸JSON Schema specifies structure. It cannot express units, idempotency, error semantics or side-effect scope.
- ▸Two schema-identical tools can differ by a factor of 100 in effect.
- ▸The contract gap gets closed either once in the control plane or many times in agent code.
Nine fields a contract needs beyond schema
Each of these has caused a real defect when left unstated. Write them down per tool; it is a short document and it is the artefact a normalization layer is built from.
| Field | Example | What goes wrong without it |
|---|---|---|
| Units and scale | amount: minor units (pence), integer | Hundredfold errors that pass validation |
| Precision and rounding | 2dp, half-up, applied server-side | Penny discrepancies that fail reconciliation |
| Idempotency | Deduplicates on Idempotency-Key, 24h window | Retries become duplicate side effects |
| Error taxonomy | Retriable / terminal / ambiguous, per code | Callers retry terminal failures and give up on retriable ones |
| Side-effect scope | Writes ledger; sends customer email; no third-party call | Risk class is guessed rather than known |
| Latency envelope | p95 800ms, timeout 10s, no partial effect on timeout | Timeouts treated as failures when the effect landed |
| Ordering guarantees | No ordering guarantee across calls | Workflows assume sequencing that does not exist |
| Rate and quota | 60/min per tenant, shared with 3 other tools | Bursts consume another tool’s quota invisibly |
| Authorization semantics | Acts as calling user; refuses cross-tenant | Confused-deputy assumptions baked in silently |
The latency envelope row carries a subtle and expensive requirement: state explicitly whether a timeout can leave an effect applied. A tool where a timeout means “definitely nothing happened” can be retried freely. A tool where a timeout means “possibly committed” cannot be retried without an idempotency key. Most tools are the second kind and are documented as though they were the first.
Six mismatch classes
Ranked by how much damage they do when two tools are treated as equivalent.
Units and scale
The most common and the most expensive. Pence versus pounds, seconds versus milliseconds, bytes versus kilobytes. Validates perfectly, fails by orders of magnitude.
Normalize to canonical units at the boundary and never let a native-unit value reach an agent or a policy rule. A ceiling of 5,000 means nothing if half your routes interpret it as £50.
Idempotency
One route deduplicates, one does not. A retry that is safe on route A produces a second payment on route B, and the router chose the route.
This is why routebook eligibility must include idempotency behaviour for any mutating capability. A non-idempotent route is not an eligible fallback for an idempotent primary.
Error semantics
Route A distinguishes “already refunded” from “system unavailable”. Route B returns failure for both. The caller retries a terminal condition indefinitely, or abandons a retriable one immediately.
Normalize errors into three buckets — retriable, terminal, ambiguous — and treat any unmapped code as ambiguous, which is the conservative reading.
Side-effect scope
Route A writes the ledger. Route B writes the ledger and emails the customer. Same capability name, materially different consequence, and the second one is irreversible.
This changes the risk class, which changes the approval requirement. A route whose side effects exceed the canonical contract is not an equivalent route.
Authorization semantics
Route A acts as the calling user and refuses cross-tenant access. Route B acts as a service account with estate-wide reach. Routing between them silently changes the effective privilege of the operation.
Include the credential and authorization model in eligibility, and never fall back from a per-user route to a service-account one — that is the fallback rule in a different costume.
Ordering and consistency
Route A reads its own writes immediately. Route B reads a replica that lags by seconds. A workflow that writes then reads gets stale data on one route and fresh on the other, intermittently.
Make read-your-writes an explicit contract property and an eligibility condition for workflow steps that depend on it.
Designing the normalization layer
The pattern is a canonical contract per capability, with each server’s native contract mapped onto it by an adapter. Familiar from integration architecture, and the discipline is the same: the canonical form is the stable interface, adapters absorb the variation.
- 01Define the canonical contract first, from requirements — not from whichever server you integrated first. A canonical form derived from one vendor’s quirks becomes that vendor’s contract with extra steps, and it will fight you when you add the second.
- 02Canonical units and precision, always explicit. Minor units, integers, named currency. No implicit scaling anywhere in the system.
- 03Adapters are one-directional and total. Every canonical field maps to something native, or the adapter declares the capability unsupported for that route. Partial adapters produce routes that work for most calls, which is the worst possible reliability profile.
- 04Errors normalize into the three-bucket taxonomy, with unmapped codes defaulting to ambiguous. Ambiguous is the conservative default because it forces the caller to check rather than retry.
- 05Policy evaluates the canonical form. This is the reason the layer earns its cost: one ceiling, one allow-list, one approval threshold, applied identically regardless of which route serves the call.
- 06Adapters are versioned and tested against recorded responses. A vendor changing behaviour breaks an adapter, and you want that discovered by a test rather than by a reconciliation failure.
One capability, two servers, the mismatches made explicit. The canonical form is what agents and policy see; neither native contract is exposed.
refund.issue — amount: integer, minor units; currency: ISO-4217; idempotency: required key, 24h; errors: retriable | terminal | ambiguous; side effects: ledger write onlyrefund_issue(order_id, amount_pence: int); dedupes on Idempotency-Key 24h; codes: 409 ALREADY_REFUNDED (terminal), 503 (retriable), timeout (ambiguous)issueRefund(orderRef, amount: decimal GBP); no dedupe; returns generic {ok:false} on all failures; also sends customer emailrefund.issue. It is registered as a separate capability, refund.issue_and_notify, with its own risk class and no idempotency guaranteeThis is the useful result. Rather than pretending route B is a fallback and discovering the difference during an incident, the mismatch is resolved at design time by declaring it a different capability. Two capabilities with honest contracts beat one capability with a lie in it.
Built on this thinking
Policy on canonical contracts, not on whichever route answered
Barzel Central Gateway presents capabilities rather than server-specific tools, so one ceiling and one allow-list apply regardless of which route serves the call — and a route whose contract cannot satisfy the canonical form is simply not eligible. Routebooks carry idempotency and side-effect scope as eligibility conditions.
Versioning without breaking policy
Two version numbers, and conflating them is a common source of confusion: the native contract version each server publishes, and the canonical contract version your estate targets. Agents and policy bind to the canonical version. Adapters absorb native changes.
Four rules keep this stable.
- Canonical versions change rarely, and never to accommodate one vendor
- If a vendor adds a field, the adapter ignores it. The canonical contract changes when a requirement changes.
- Native widening is a breaking change for policy
- An enum becoming a free string is caller-compatible and policy-breaking. The adapter must keep enforcing the narrower canonical constraint, which is precisely the value of having one.
- Adapters pin native versions
- An unpinned adapter is an adapter that will silently start mapping something different. See change impact analysis.
- Deprecate canonical versions on a clock
- Announce, warn in-band, refuse new callers, refuse everyone. The same sequence as any other capability deprecation, and it needs the same enforcement.
The payoff is vendor substitution. When the canonical contract is the interface agents and policy target, swapping a payments provider is an adapter change plus a routebook entry — not a migration touching every agent and every rule. That is the return on what otherwise looks like bureaucratic overhead.
Five mistakes worth naming
Canonical form copied from the first integration
It becomes that vendor’s contract with extra steps, and it fights you on the second vendor.
Partial adapters
A route that works for most calls is worse than one that works for none, because the failures are rare enough to be surprising.
Normalizing in agent code
Every agent re-implements the mapping, and they will disagree. Normalize once, in the control plane.
Treating timeout as failure
For most mutating tools a timeout means possibly-committed. Classify it as ambiguous and require an idempotency key.
Ignoring side-effect scope in equivalence
A route that also emails the customer is not the same capability, however similar the name.
When normalization is not worth it
For one server per capability, this is pure overhead. There is no variation to absorb and the canonical layer adds a hop, a mapping and a thing to keep in sync. Build it when a second candidate appears for a mutating capability — not before, and possibly never for read-only ones.
Normalization also cannot manufacture a guarantee a server does not provide. If no route offers idempotency, the canonical contract cannot promise it; the honest options are to implement deduplication in the adapter with your own state, or to declare the capability non-idempotent and design workflows accordingly. Adapters that quietly assert guarantees they cannot deliver are worse than no adapter, because everything downstream now trusts a fiction.
And a contract is a description, which can be wrong. A server documented as idempotent that is not will pass every test you write against the documentation. Verify the properties that matter — send the same idempotency key twice and check — rather than trusting the contract because somebody wrote it down.
Frequently asked questions
What is an MCP tool contract?
The complete specification needed to call a tool safely: its JSON Schema plus units and scale, precision and rounding, idempotency behaviour, error taxonomy, side-effect scope, latency envelope, ordering guarantees, rate limits and authorization semantics.
Why is JSON Schema not enough to describe an MCP tool?
Because schema specifies structure, not meaning. Two tools with identical schemas can expect different units, differ on whether a retry duplicates the effect, and classify failures differently — none of which is visible in the schema a model reads.
What is the most common mismatch between equivalent MCP tools?
Units and scale — pence versus pounds, seconds versus milliseconds. It validates perfectly and fails by orders of magnitude, which is why canonical units must be explicit and native-unit values must never reach an agent or a policy rule.
Why does idempotency belong in a tool contract?
Because a retry against a non-idempotent tool is a second side effect rather than a second attempt. If one route deduplicates and another does not, a retry that is safe on the first produces a duplicate payment on the second — and the router chose the route.
How should tool errors be normalized?
Into three buckets: retriable, terminal and ambiguous, with any unmapped code defaulting to ambiguous. Ambiguous is the conservative default because it forces the caller to check state rather than blindly retry. Timeouts on mutating tools are ambiguous, not failures.
Where should schema normalization happen?
Once, in the control plane. Normalizing in agent code means every agent re-implements the mapping and they will disagree. Policy should evaluate the canonical form so one ceiling and one allow-list apply regardless of which route serves the call.
What if two servers’ contracts cannot be reconciled?
Declare them different capabilities. A route that also emails the customer is not the same capability as one that only writes a ledger entry, however similar the tool name. Two capabilities with honest contracts beat one capability with a lie in it.
How do you version tool contracts?
Keep two versions distinct: each server’s native contract version, and the canonical contract version agents and policy bind to. Canonical versions change when requirements change, never to accommodate one vendor. Adapters pin native versions and absorb the differences.
What is the business benefit of canonical contracts?
Vendor substitution. When the canonical contract is what agents and policy target, swapping a provider is an adapter change plus a routebook entry rather than a migration touching every agent and every rule.
Can you trust a documented contract?
No — verify the properties that matter. A server documented as idempotent that is not will pass every test written against its documentation. Send the same idempotency key twice and check the result rather than trusting the description.
Glossary
- MCP tool contract
- The complete specification required to call a tool safely: parameter schema, units and precision, idempotency behaviour, error taxonomy, side-effect scope and latency envelope.
- Schema normalization
- Mapping several servers’ differing tool contracts onto one canonical contract so a caller and a policy engine can treat them as equivalent.
- Canonical contract
- The single internal definition of a capability that agents and policy target, with each server’s native contract adapted to it.
- Idempotency behaviour
- Whether repeating a call with the same key produces the same effect once, or repeats the effect — a contract property that schema cannot express.
- Error taxonomy
- The classification of failure responses into retriable, terminal and ambiguous, which determines whether a caller may safely retry.
Sources and further reading
Schema and versioning semantics come from the specifications cited below. The six mismatch classes, the contract fields beyond schema and the normalization design are our own, from operating servers with overlapping capability surfaces.
- 01 · JSON SchemaJSON Schema Specification ↗How tool parameter contracts are expressed, and what a validator can enforce.
- 02 · MCP projectModel Context Protocol — specification ↗Normative source for tool schemas, capability negotiation and the authorization model.
- 03 · JSON-RPC Working GroupJSON-RPC 2.0 Specification ↗The request/response envelope every MCP tool call travels in.
- 04 · SemVerSemantic Versioning 2.0.0 ↗The versioning contract tool schemas should honour but frequently do not.
- 05 · Kubernetes projectKubernetes — cluster architecture ↗The canonical control-plane / data-plane separation, and the closest well-understood analogue.
- 06 · GoogleGoogle SRE — Service Level Objectives ↗Why an estate needs objectives and error budgets, not just dashboards.
- 07 · CNCFOpen Policy Agent — documentation ↗Reference implementation of decoupled policy decisions and policy as code.
- 08 · OWASP GenAI Security ProjectOWASP GenAI LLM Top 10 (2026) ↗Consensus risk list; excessive agency and prompt injection are the entries governance exists to bound.
Last reviewed 2 September 2026. External links open in a new tab; we do not control their content.
Cite this article
Alex, M. (2026). MCP Tool Contracts and Schema Normalization. Real Biz Digital. https://realbizdigital.net/insights/mcp-tool-contracts/
Try the mechanics on a live server
To watch a real tools/list response before you point a client at anything that governs production — Barzel Scripture Intelligence is free and public at scripture-intelligence-server.mcpize.run: no signup, no key, 54 tools. Setup is in the reference.
Buy it on the marketplace
Barzel Central Gateway is this layer, sold as a running product
Twenty-five tools covering identity-aware policy, tool routing, risk scoring, approvals, routebooks, workflow simulation and SIEM evidence. Ten policy inputs, six enforcement outcomes, per-user OAuth/OIDC. The Community tier is free, so an evaluation costs an afternoon rather than a purchase order.
| Plan | Price | Included | Right for |
|---|---|---|---|
| Community | Free | 1,000 tool calls/mo · full policy engine, registry, audit | Evaluating the estate, or a single team proving the path works |
| Starter | $10/mo | 10,000 calls/mo | One or two production agents against a handful of servers |
| Team | $79/mo | 100,000 calls/mo | A platform team governing an estate of 5–20 servers |
| Business | $149/mo | 250,000 calls/mo | Estate-wide governance with SIEM evidence and multi-team routing |
Sold on the MCPize marketplace · prices as listed 2 Sep 2026 · the listing is authoritative
Written by
Mark Alex
Founder of Real Biz Digital and architect of the Barzel ecosystem — five MCP servers published and callable in public. Software developer, technology entrepreneur and mechatronics engineer, working across AI agent governance, MCP security, AI infrastructure, FinOps and intelligent operations.