The rack decides the work

Walk into a tool-and-cutter grinder bay and you can predict most of what it will ever produce before you know anything about the operator. The wall rack of collets and arbors is the answer. A cutter that has no holder cannot be mounted; a taper that does not seat cannot be trusted; a job that needs a size the rack does not carry does not get done, no matter how good the machine or the hand. The spindle is not the constraint. The interface between spindle and cutter is.

The same is true of an agent, and it is stated far less often. Discussion of agent capability tends to concentrate on the model — its reasoning, its context window, its training. But a language model on its own emits tokens. Every effect it has on anything outside its own output is mediated by a tool call: a named, argument-taking, result-returning invocation that some other program actually executes. What the model can attempt is exactly the set of calls its interfaces admit. The tool set is not an extension of the model’s intelligence. It is the model’s action alphabet — the finite vocabulary from which every course of action must be spelled — and simultaneously its instrument panel, since what the model learns about the world after acting is only whatever those tools hand back.

This article takes that seriously and follows it through. It treats a tool as a formal object, asks what properties of that object determine reliability, and then reads one concrete attempt at a common interface — the Model Context Protocol — from its published specification rather than from anyone’s description of it.

ADVERTISEMENT

A tool is a partial transition function

Start with the smallest honest model. A tool is not a function in the mathematical sense, because it touches the world. Write it as a partial map from arguments and world-state to results and world-state:

t: At×Σ    Rt×Σ t:\ \mathcal{A}_t \times \Sigma \;\rightharpoonup\; \mathcal{R}_t \times \Sigma

Four things are named there, and each corresponds to a design decision that is usually made by accident.

At\mathcal{A}_t is the argument type: what shapes of input the tool accepts at all. The map is partial — the harpoon rather than the arrow — because for some arguments and some world-states the tool has no defined behaviour. Those are preconditions: a file must exist, a booking must not already be cancelled, a handle must not have expired. A precondition that is checkable before execution can reject a bad call cheaply. A precondition that is only discoverable during execution turns into an error, and possibly into damage.

Rt\mathcal{R}_t is the result type: what the caller is told. The second Σ\Sigma is the side effect: what changed. These two are separate, and conflating them is the origin of most agent unreliability. A tool that returns {"status": "ok"} has a result type carrying roughly one bit while its effect on Σ\Sigma may be arbitrarily large. The caller — model and harness alike — has no way to verify that the bit is true.

This is exactly the structure the major tool-calling interfaces implement. Anthropic’s documentation describes a tool as a name, a description and an input_schema, with the model returning a tool_use block naming the tool and its arguments and the application returning a tool_result block, optionally flagged as an error [15]. The Model Context Protocol’s tool definition carries name, optional title, description, inputSchema, optional outputSchema and optional annotations [2]. Different vocabularies, the same four-part object.

ADVERTISEMENT

The reason this framing earns its keep is that it makes the interesting questions unavoidable. How is a proposed element of At\mathcal{A}_t checked before it becomes an action? How finely is the capability carved into distinct tt? What happens when the caller does not learn whether Σ\Sigma changed? And what does Rt\mathcal{R}_t contain when things go wrong?

An SFP transceiver caught part-inserted into its sheet-steel cage on a switch front panel, with an even line of daylight still showing between the moulded shroud and the cage mouth
Figure 1. A schema is a connector key: the fit is decided before anything runs, and a proposal that does not seat is rejected as a proposal rather than discovered as damage.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

Schemas make a proposal checkable before it is an action

The single most consequential property of a typed tool interface is that a call can be rejected as a proposal, before anything happens. That is what a schema buys.

JSON Schema, the format both MCP and the major model APIs use, describes itself plainly: “A JSON Schema document, or simply a schema, is a JSON document used to describe an instance” [4]. Its keywords split into assertions, which produce a boolean validation result, and annotations, which attach information for the application to use [4]. That split matters more than it looks. Assertions are enforcement; annotations — including the description strings on each property — are advice. The schema can guarantee that location is a string. It cannot guarantee that the string names a place the caller intended.

MCP makes the enforcement side normative. A tool’s inputSchemaMUST be a valid JSON Schema object (not null)”, and defaults to the 2020-12 dialect when no $schema field is present [2]. Where an outputSchema is supplied, servers “MUST provide structured results that conform to this schema” and clients “SHOULD validate structured results against this schema” [2]. Two directions of checking, both mechanical.

There is a second, separate mechanism that is easily confused with this, and the specification goes out of its way to head off the confusion: structuredContent “is server-produced result data and is unrelated to LLM ‘structured outputs’ (schema-constrained model generation)” [2]. Model-side structured output is a decoding constraint. Willard and Louf reformulated generation as transitions between the states of a finite-state machine, allowing an index over the vocabulary to be built so that outputs can be constrained to regular expressions and context-free grammars, which they report “adds little overhead to the token sequence generation process” [8]. Anthropic’s documentation exposes the same idea as a per-tool flag, stating that adding strict: true to a custom tool definition ensures Claude’s tool calls “always match your schema exactly” [15].

So there are three distinct guarantees in play, and they are worth separating because vendors and protocols do not always separate them. Constrained decoding makes the model emit something schema-shaped. Server-side input validation makes the executor refuse something that is not. Output-schema validation makes the client check what came back. Only the first is about the model. The other two hold whether or not the model cooperates, which is why they are the ones that survive an adversary.

ADVERTISEMENT

None of the three addresses semantic correctness. The Berkeley Function Calling Leaderboard is instructive precisely because it separates the layers: it evaluates abstract-syntax-tree accuracy for whether a call is well-formed with correctly matched parameters, executable accuracy for whether running the call actually produces the right output, and relevance detection, which tests “whether a model will hallucinate on its function and parameter to generate function code despite lacking the function information” [11]. A perfectly schema-valid call to a tool that should not have been called at all passes the first check and fails the third.

Two outwardly identical plug-in modules lying side by side on a pale bench, their gold-flash contact fingers matched and their internal boards mirrored, the nearer one still rocking after being set down
Figure 2. Every check a connector performs is a check on the shell: two modules can present the same faultless seat and act in opposite directions, so a well-formed call and an apt one are separate claims.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

Granularity is the design decision nobody records

Take one capability — say, editing a document — and you can expose it many ways. As a single edit(path, operation, args) tool with a discriminated union. As five tools: view, create, replace, insert, delete. As one shell tool with a command string. All three make the same set of world-states reachable. They do not produce the same reliability, and they do not give the harness the same powers.

Two forces pull in opposite directions.

Narrow, typed tools make more of the intent visible in the arguments, which is what a harness needs in order to do anything with a call other than run it. A dedicated tool with typed parameters can be gated, rendered, audited or scheduled by name; an opaque command string offers the harness one undifferentiated shape for every possible action. Narrow tools also let preconditions be encoded as types rather than discovered as failures.

Against that, every tool costs context and adds a choice. Anthropic’s documentation is explicit that the tools parameter itself consumes input tokens — names, descriptions and schemas all get sent — and that enabling tool use adds a system prompt whose size it publishes per model [15]. And the choice cost is measurable. MCPVerse assembled more than 550 real, executable tools into an action space exceeding 140,000 tokens and evaluated models under three conditions of increasing scale; the authors report that most models degrade as the tool set grows, while some agentic models instead exploit the larger exploration space to improve [10]. That split is worth reading carefully rather than collapsing into a slogan: it is evidence both that surface size is a real variable and that its effect is not uniform across systems. Anyone claiming a universal optimum tool count is over-reading it.

Granularity also shows up in the boring, decisive place: naming. MCP requires tool-name uniqueness only within a single server, and notes that clients or proxies aggregating tools from multiple servers “MAY encounter naming collisions (for example, two servers each exposing a search tool)” and should adopt a disambiguation strategy — while warning that the server’s own reported name “is not guaranteed to be unique across servers and SHOULD NOT be relied upon for disambiguation” [2]. An agent whose alphabet contains two letters that look identical is not going to spell reliably.

There is a further, quieter granularity trap. Anthropic’s documentation observes that when a user’s prompt lacks enough information to fill a tool’s required parameters, its models differ in whether they ask for the missing value or infer one, and states outright that the asking behaviour “is not guaranteed” [15]. That is a vendor’s description of its own systems, not an independent measurement, but it names a real design consequence: a required parameter is a demand for information that the caller may satisfy by invention. Making a parameter required does not make it correct.

A bulkhead panel part-populated with a row of small distinct keyed jacks, the last insert still tilted in its cutout, and a single large undifferentiated multiway connector of the same overall footprint lying beside it
Figure 3. Carving one capability into many small units is not a matter of taste: a row of distinct keyed jacks can be named, gated and audited where one undifferentiated connector offers a single shape for everything, and every extra piece is another choice to get wrong.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

Idempotency, retry safety, and the half-done call

Here the abstraction leaks, and it leaks in a way no schema can patch.

A caller that issues a tool call and does not receive a response has learned almost nothing. The request may never have arrived. It may have arrived and executed, with the response lost on the way back. It may have executed halfway. These are indistinguishable from outside, and this is not a language-model problem — it is the oldest problem in distributed systems, inherited whole.

The standard escape hatch is idempotency, and RFC 9110 gives the definition that everything else builds on: “A request method is considered ‘idempotent’ if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request” [5]. The specification connects this directly to the failure case above, noting that some requests can be automatically retried by a client in the event of an underlying connection failure [5]. In the transition-function model, writing the state component of the tool as δt\delta_t, the property is:

δt(a, δt(a, σ))=δt(a, σ) \delta_t\big(a,\ \delta_t(a,\ \sigma)\big) = \delta_t(a,\ \sigma)

Repeating the call with the same arguments leaves the world where the first call left it. When that holds, a lost response is merely annoying: retry. When it does not hold, retry is a second charge on the card, a second message to the customer, a second row in the ledger.

This is where the current generation of tool protocols is thinnest, and honesty requires saying so. MCP’s tool definitions carry annotations described as “optional properties describing tool behavior” — but the specification immediately classifies them as advisory rather than enforceable, warning that “clients MUST consider tool annotations to be untrusted unless they come from trusted servers” [2]. A hint that a tool is read-only or non-destructive is a claim made by the same party that would benefit from the claim. The protocol carries no mechanism by which a caller can make an operation idempotent, no request-identity key that a server is obliged to deduplicate on, and no transaction boundary spanning several calls.

What the ecosystem has built instead is a durable handle. The Tasks extension exists because, as its documentation puts it, blocking “ties up a connection for the duration of the operation” and many clients and intermediaries impose timeouts that make this impractical beyond a few seconds [3]. Under Tasks a server may answer a call with a task identifier rather than a result; the specification requires that “the task is durably created before the response is sent”, and instructs clients to store task identifiers durably so polling can resume after a crash or restart [3]. The lifecycle is explicit — working, input_required, completed, failed, cancelled, the last three terminal [3].

Two details in that design deserve more attention than they usually get. First, cancellation is deliberately weak: the documentation states that “cancellation is cooperative — the server acknowledges the intent but is not obligated to stop the work” [3]. An agent that has issued a cancel has not stopped anything; it has asked. Second, a durable handle converts an unanswerable question (“did it run?”) into an answerable one (“what is the state of task X?”), which is the whole trick. It does not make the operation idempotent. It makes the outcome observable, which is the achievable half of the problem.

The same shape appears in MCP’s guidance on stateful tools, which opens by conceding that “MCP has no protocol-level session, so a server cannot rely on implicit per-connection state to relate one tool call to the next” [2]. The recommended pattern is an explicit handle returned by a creation tool and passed back on later calls, with the model responsible for carrying it forward — and the specification is careful to add that “a handle is a name, not a capability”, that the server should validate authorization against it on every call, and that expiry should surface as a tool execution error the model can recover from [2]. State that the model must carry is state the model can drop, duplicate, or hand to the wrong tool.

A workstation screen on a bright lab bench showing a returned tool result still painting in row by row, with an API gateway appliance behind it carrying a single plain unlit status lamp
Figure 4. The returned record is the report: a tool that hands back the state it produced lets the caller check the outcome, where a tool that returns only a status asks to be believed.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

The error surface decides whether recovery is possible

When a call fails, what comes back determines whether the agent has a next move or only a dead end. This is the part of tool design most often left to whatever the underlying library happened to raise.

MCP separates two channels, and the distinction is functional rather than cosmetic. Protocol errors — unknown tool, malformed request, server error — are returned as JSON-RPC errors and are characterised as “issues with the request structure itself that models are less likely to be able to fix”. Tool execution errors — API failures, input validation errors, business logic errors — are returned inside a normal successful result with isError: true, and are characterised as “actionable feedback that language models can use to self-correct and retry with adjusted parameters” [2]. The specification then draws the operational conclusion: clients “SHOULD provide tool execution errors to language models to enable self-correction”, while protocol errors “MAY” be provided though they “are less likely to result in successful recovery” [2].

The example the specification gives is the whole lesson in one line: rather than a bare failure, the error text reads “Invalid departure date: must be in the future. Current date is 08/08/2025” [2]. It names the violated constraint and supplies the fact needed to satisfy it. An error that says only 400 Bad Request is a wall. An error that says which field, which rule, and what the current value of the relevant world-state is, is a next step.

This is a design obligation on the tool author, not a capability of the model. ReAct’s central result was that interleaving reasoning traces with actions in a real environment let a model correct itself against external information rather than compounding its own errors, and the authors attribute the reduction in hallucination and error propagation specifically to interacting with an external interface [7]. The correction loop only closes if the interface says something correctable.

A keyed plug resting in a bench cradle with its nose stopped a finger's width short of a bulkhead receptacle, and a workstation screen behind showing a rejected call with the constraint it broke and the value that would satisfy it
Figure 5. A failure that shows the gap and which way to close it is a next step: an error has to name the constraint it violated and the value that would satisfy it, or the caller is left facing a wall.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

The design rule: return evidence, not status

Which brings the argument to its practical form. The end-to-end argument in system design says that a function “can completely and correctly be implemented only with the knowledge and help of the application standing at the endpoints of the communication system”, and that lower-level implementations, however careful, cannot substitute for the endpoint’s own check — the careful file transfer “must still counter the remaining threats, so it should still provide its own retries based on an end-to-end checksum of the file” [6].

Transpose that to agents. The endpoint is the caller — the harness, and behind it the person accountable for the outcome. A tool that returns a status code is asking that endpoint to accept an intermediate layer’s assertion of success. A tool that returns evidence — the resulting record, the new balance, the diff that was applied, the identifier that can be independently fetched — lets the endpoint check. The grinder’s report is not a light that says done; it is the bright band on the flank, which can be inspected.

This also happens to be the only defence that scales against the trust problem in the next section, and it has a measurable payoff in consistency. The τ-bench authors built a benchmark of dynamic conversations between a simulated user and a tool-using agent under domain policies, evaluated by comparing the final database state against a target state, and introduced a pass^k metric for behavioural consistency across repeated trials. They report that state-of-the-art agents at the time “succeed on <50% of the tasks”, with pass^8 below 25% in the retail domain [9]. Those figures are specific to that benchmark, those models and mid-2024, and should not be read as a current capability estimate. What generalises is the methodological point: they evaluated the world state, not the agent’s account of it, and the gap between single-trial success and eight-trial consistency is exactly the gap that a status-only interface hides.

MCP, read from the specification

It is worth stating plainly what the Model Context Protocol is, from its own text, because it is frequently described as more and occasionally as less than it is.

MCP is a JSON-RPC 2.0 message layer between three roles — hosts, which are the applications that initiate connections; clients, the connectors inside them; and servers, the services that provide context and capabilities [1]. The specification names its own lineage: it “takes some inspiration from the Language Server Protocol”, which standardised how language support is added across an ecosystem of development tools [1]. Servers may offer resources, prompts and tools; the base protocol is characterised as “stateless, self-contained requests” with “per-request capability negotiation”, and optional extensions such as Tasks are opt-in and negotiated [1].

On tools specifically, the current revision does more real work than the description “a standard for connecting tools” suggests. Servers declaring the tools capability must respond to tools/list with the set currently available, and that set “MUST NOT vary per-connection or as a side effect of other requests on the connection” — though it “MAY vary by the authorization presented on the request”, since credentials are per-request input rather than connection state [2]. Servers “SHOULD return tools in a deterministic order”, and the specification gives the reason: deterministic ordering lets clients cache the tool list reliably and “improves LLM prompt cache hit rates when tools are included in model context” [2]. That is an unusually candid piece of protocol design — a wire-format rule written for the economics of the thing consuming it.

What MCP does not standardise is equally worth naming. There is no session. There is no transaction. There is no idempotency key and no exactly-once execution semantics. There is no enforceable statement of what a tool does to the world, only an untrusted annotation. And the specification says so about its own security model: “MCP itself cannot enforce these security principles at the protocol level” [1]. A protocol that tells you where its guarantees stop is more useful than one that does not, but the boundary has to be read.

The description is untrusted text

The last property is the one that most cleanly separates a typed tool interface from a typed function call in ordinary software, and it follows from a single fact: a tool’s description is delivered to the model as text, and the model treats text as instructions.

MCP states the consequence in its trust and safety principles without hedging. Tools “represent arbitrary code execution and must be treated with appropriate caution”, and “descriptions of tool behavior such as annotations should be considered untrusted, unless obtained from a trusted server” [1]. On the tools page the same warning is escalated to a normative requirement on clients [2]. The specification also recommends that there “SHOULD always be a human in the loop with the ability to deny tool invocations”, and that clients “show tool inputs to the user before calling the server, to avoid malicious or accidental data exfiltration” [2].

The underlying vulnerability class predates MCP. Greshake and colleagues showed that an adversary can compromise a language-model application remotely, without any interface to it, by planting prompts in data the application is likely to retrieve; their conclusion is that processing retrieved prompts “can act as arbitrary code execution, manipulate the application’s functionality, and control how and if other APIs are called” [12]. Note the last clause. Indirect injection is not only a route to bad text output; it is a route to tool invocation, which is to say to effects on the world.

Tool descriptions are a particularly clean carrier for this, and it has been demonstrated rather than merely theorised. Beurer-Kellner and Fischer reported in April 2025 that malicious instructions can be embedded in an MCP tool’s description where “AI models see the complete tool descriptions, including hidden instructions, while users typically only see simplified versions in their UI”, and demonstrated both direct exfiltration — a poisoned arithmetic tool inducing an agent to read and leak configuration files and private keys — and tool shadowing, in which one malicious server’s description alters the behaviour of a trusted server’s tool, redirecting an email tool’s recipient [14]. That is a security researcher’s demonstration against particular client implementations at a particular time, not a claim about all deployments, and vendors have shipped mitigations since. The structural point survives the mitigations: the channel that tells the model what a tool does is the same channel an attacker would use to tell the model what to do.

A rack of tool servers seen from below, one unit caught part-slid out on its extended rails from a bay whose printed front legend describes a different machine, with an empty matching bay further along the row
Figure 6. The front of the rack is a claim about what each bay holds, and the claim is made by whoever last filled it: the channel that tells a caller what a tool does is the same channel an attacker would use to tell it what to do.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

Hou and colleagues systematised this into a threat taxonomy spanning four attacker categories — malicious developers, external attackers, malicious users, and plain security flaws — across sixteen distinct threat scenarios mapped to the phases of an MCP server’s lifecycle [13]. Their framing is the right one for practitioners: the tool surface is not a single trust boundary crossed once at installation, but a set of boundaries recrossed at every discovery, update and call.

Two consequences follow for interface design. First, the shown-to-the-user description and the sent-to-the-model description must be the same string, or the human in the loop is reviewing a different system from the one that is running. Second, an argument that carries a secret should not be routed anywhere a third party can read it — a concern concrete enough that MCP’s own header-mirroring feature carries an explicit warning that developers “SHOULD NOT mark sensitive parameters (passwords, API keys, tokens, PII)” for exposure as HTTP headers, since header values are visible to network intermediaries [2].

Predictions, and what would falsify them

These are forecasts, separated deliberately from the sourced analysis above. Horizon: 8 August 2028. Assumption throughout: tool-mediated agents continue to be deployed against systems with real side effects, and no single vendor’s proprietary interface displaces open protocols entirely.

One. Some form of caller-supplied idempotency key, deduplicated by the executor, will be standardised in a major agent tool protocol, because retry ambiguity cannot be solved on the client side alone. Disconfirmed if by the horizon the leading protocols still carry only advisory behaviour annotations with no request-identity mechanism.

Two. Result schemas will become the norm rather than the exception in production tool catalogues, and the fraction of tools shipping an outputSchema will exceed the fraction shipping behaviour annotations. Disconfirmed if surveys of public tool servers show output schemas remaining a minority feature while annotations spread.

Three. Evaluation of agent tool use will shift further from single-trial task success toward repeated-trial consistency and final-world-state checks, following the pass^k pattern rather than the single-run pattern. Disconfirmed if the headline metrics in leading agent benchmarks in 2028 are still single-trial success rates on synthetic tool sets.

Four. Tool descriptions will be treated as an attack surface with its own controls — pinning, signing, or diff review on change — in mainstream client software, not only in security research. Disconfirmed if the default behaviour of widely used clients in 2028 still silently accepts a changed tool description from an already-approved server.

None of these requires a capability discontinuity. Each follows from constraints that are already visible in the specifications and the failure reports.

What to take away

A tool is a partial transition function: typed arguments, preconditions, a result, and an effect on the world that the result may or may not describe. Everything that makes agents reliable or unreliable lives in that gap between the result and the effect.

Schemas close part of the gap by making a proposal checkable before it becomes an action, and they close it on the executor’s side as well as the model’s, which is the half that survives an adversary. Granularity decides how much of the intent is legible to the harness, and it is a real variable with measurable consequences rather than a matter of taste. Idempotency and durable handles decide what happens when the answer is lost — one of them is not available in current protocols, and the other converts an unanswerable question into an observable state. Error surfaces decide whether a failure is a wall or a next step. And the description that tells the model what a tool does is untrusted text, by the specification’s own account.

Which is why the design rule is short. Make the schema tight enough that a wrong call is refused rather than executed. Carve the surface so that the things you need to gate are things you can name. Make repeated calls safe, or make their outcome observable. Say in the error what constraint was violated and what would satisfy it. And return evidence, not status — the ground flank rather than the indicator light — because the endpoint that has to be right about the outcome is the only one that can check it.