Bhanu Chaddha
Menu

agentic-ai · part 8 of 8

Part 8 - Your Tool Schema Is a Prompt, Not an API

Posts, Series

Most teams treat tool design as an afterthought. Give the agent a function, describe it loosely, let the model sort it out. Then the agent calls the wrong tool, or the right tool with the wrong arguments, and the fix goes into the system prompt: "always use search_orders before refund_order." Three months later the prompt is four thousand tokens of hedges, and every one of them is a tool description that should have been written properly the first time.

Tool calling looks like an API integration problem. It is not. An API signature is read once by an engineer with the docs open, a debugger, and a colleague to ask. A tool schema is read on every invocation by a model with none of those. It has your parameter names, your description string, and whatever came back the last time it guessed. That is the entire channel.

TL;DR

A tool definition is prompt engineering with a JSON Schema attached, and it should be evaluated like a prompt: versioned, tested, and iterated against traces. MCP standardizes how tools are transported and discovered; it standardizes nothing about whether your tools are any good.

  • Tool descriptions are read by the model, not by you. Name parameters unambiguously, make implicit knowledge explicit, and write the description like you are onboarding a teammate who will never get to ask a follow-up question.
  • Consolidate around tasks, not endpoints. One tool per REST endpoint is the most common shortcut and the most common source of production failure. Agents need tools shaped like the job, not like your service boundaries.
  • Errors are the agent's only feedback channel. An error the model cannot act on is a dead end. Say what went wrong, whether retrying helps, and what a correct call looks like.
  • More tools makes selection worse, not better. Fewer, better-chosen tools beat a large catalog. Past a few dozen, retrieve over the catalog rather than pasting all of it into context.
  • MCP solves distribution, not design. The 2026-07-28 revision is a serious piece of protocol engineering, and it will not save a badly designed tool.

Hero: a tool definition sits between the model and the system, carrying four contracts: the name and description the model reads, the parameter schema it must fill, the response it gets back, and the error surface when the call fails

The tool definition is the entire interface between a probabilistic caller and a deterministic system. Every one of those four surfaces is read by the model, and only one of them is usually designed.

Why do agents call the wrong tool?

Because the model is doing retrieval, not lookup. It is matching the user's intent against a list of names and description strings and picking the closest one. When two tools have overlapping descriptions, the match is a coin flip, and no amount of temperature tuning fixes an ambiguous contract.

This is the same failure mode Topic 6 described for retrieval: the right thing was not in context, or it was there and got crowded out. Semantically similar entries compete. Long catalogs dilute. The difference is that a retrieval miss produces a wrong answer, while a tool-selection miss produces a wrong action, sometimes against a production database.

The naming layer matters more than teams expect. Anthropic's tool-writing guidance reports that namespacing by service and resource, asana_search versus asana_projects_search, produces measurable evaluation differences, and that choosing between prefix-based and suffix-based namespacing alone moves the number. That is a tell. If the position of _search in a name is measurable, then everything above it, the description, the parameter names, the enum values, is load-bearing.

Let's say you're building the email-processing agent this series keeps returning to. A first pass at tools usually mirrors the CRM's REST surface: get_customer, list_orders, get_order, list_tickets, create_ticket, update_ticket. Six tools, each a faithful wrapper around an endpoint, every one well-named. The agent still stalls, because none of them are shaped like the thing it is actually trying to do, which is find out whether this person has an open problem.

# Endpoint-shaped: the agent has to orchestrate four calls
# and know the ordering rule that lives only in your head.
customer = get_customer(email)
orders   = list_orders(customer.id)
tickets  = list_tickets(customer.id)
recent   = [t for t in tickets if t.updated_at > cutoff]

# Task-shaped: one call, one contract, ordering rule
# enforced in code where it belongs.
context = get_customer_context(
    email,
    include=["open_tickets", "recent_orders"],
    since="90d",
)

The second version is not just fewer calls. It moves business logic out of the model's reasoning and into your code, which is exactly the move Topic 5 called the ReAct graduation. Every ordering rule left in the prompt is a rule the model follows probabilistically. Every one moved into a tool boundary is a rule that holds.

How many tools is too many?

Fewer than you think, and the fix is retrieval rather than restraint.

Tool-selection accuracy degrades as the catalog grows, and faster when the catalog holds semantically similar entries. The useful recent work here is chance-corrected: a 2026 paper introduces Bits-over-Random, which measures selection against what random picking would achieve, precisely because raw accuracy flatters small catalogs. Across registries of 199, 370, and 3,251 tools, adaptive retrieval presenting an average of 4 to 8 tools matched or beat fixed baselines presenting far more. Downstream, Claude Sonnet 4.6 selecting from short retrieved lists scored 93.1% against 87.1% for a fixed five-tool presentation, and on medium-difficulty queries the gap widened to 76.8% against 60.9%.

That cuts against the intuitive fix. The answer to "the agent picked the wrong tool" is not "give it more tools so the right one is definitely there." It is "give it fewer tools, chosen well." A catalog of 300 tools is not an agent capability; it is a retrieval problem you have not built yet.

Supporting: tool catalog size plotted against selection reliability, with three zones: under 20 tools inline in context, 20 to 50 tools grouped and namespaced, over 50 tools requiring retrieval over a tool index

The practical thresholds, judgment calls rather than measured constants: under roughly twenty tools, put them all in context and spend your effort on descriptions. Between twenty and fifty, namespace aggressively and consider splitting by phase so the agent only sees tools relevant to its current step. Past fifty, you need a retrieval layer over a tool index, the same shape as document retrieval and with the same hybrid-search concerns.

There is a second reason to keep the catalog tight, one Topic 4's context argument already set up. Every tool definition sits in context on every turn. A hundred verbose schemas is a five-figure token bill per conversation before the agent has done anything, and it is a permanent tax, paid on the trivial requests too.

What makes an error message actionable?

An error is actionable when the model can decide what to do next from the error text alone. That is the whole standard, and most tool errors fail it.

Errors are the only feedback channel a tool has. The model cannot read your logs, attach a debugger, or check the status page. The error string is the entire explanation it will ever receive, and it will reason forward from that string whether or not the string is useful. Return {"error": "400 Bad Request"} and the model will still do something, but what it does is unconstrained: retry identically, invent a parameter, or abandon a recoverable task and apologize to the user.

Three things a production tool error should carry. What failed, specifically, not the HTTP status. Whether retrying can help, which the tool knows and the model can only guess. And what a correct call looks like, as a concrete example rather than a restatement of the schema.

# Dead end: the model has no basis for choosing a next action.
{"error": "400 Bad Request"}

# Actionable: names the failure, classifies it, shows the fix.
{
  "error": "invalid_date_range",
  "message": "since='90d' is not supported; use an ISO 8601 date.",
  "retryable": True,
  "example": {"email": "[email protected]", "since": "2026-04-01"},
}

The retryable flag deserves emphasis, because conflating retryable and non-retryable failures is a reliable way to break an agent in production. Without it, models retry things that will never succeed, burning tokens in a loop, and abandon things that would have worked on a second attempt. Classification also forces a useful question: who fixes this? Transient network failures and rate limits belong to the orchestration layer, retried silently in code before the model ever sees them. Validation failures, permission denials, and empty results belong to the model, because recovering from them requires changing the plan.

And every tool that mutates state needs to be safe to call twice. Agents retry after timeouts where the write actually succeeded, and they retry because a re-plan brought them back through the same step. An idempotency key on every write is not defensive programming here, it is a correctness requirement of putting a probabilistic caller in front of a mutating API.

Supporting: two-layer error recovery, with transient failures like timeouts and rate limits retried silently by the orchestration layer, and semantic failures like validation errors and permission denials surfaced to the model with actionable text

What does MCP actually standardize?

Transport, discovery, and authorization. Not tool quality.

This is where most MCP conversations go wrong. The Model Context Protocol is genuinely useful infrastructure, and the 2026-07-28 revision is its largest change since launch: the initialize handshake and protocol-level session are gone in favor of a stateless core where each request carries its own context in _meta, so a remote server that previously needed sticky sessions can now sit behind a round-robin load balancer. Extensions became a formal framework with reverse-DNS identifiers and independent versioning. Tasks graduated out of core into an extension. Authorization hardened toward standard OAuth 2.0 and OIDC practice, including mandatory iss validation per RFC 9207. Roots, Sampling, and Logging entered a deprecation lifecycle with a twelve-month minimum before removal.

Every one of those improves how tools are shipped, hosted, and secured. None of them touches whether get_order is the right tool to expose or whether its description tells the model when to use it. The protocol's 2026 roadmap confirms this by omission: its stated priorities are transport scalability, Tasks lifecycle semantics, governance, and enterprise readiness like audit trails and SSO. Tool design quality is not on the list, and it should not be. That is not the protocol's job.

Which is why "we're using MCP" is not an answer to "why does your agent misuse its tools," in the same way "we're using LangGraph" is not an answer to an orchestration question. MCP moves the tool contract across a wire reliably. It has no opinion about what is written on it.

One change in that revision does reach into design: tool schemas now accept full JSON Schema 2020-12, so conditionals can require a parameter only when another field takes a particular value, expressed in the schema rather than in a description sentence the model may or may not honor. Every constraint you move from prose into schema is one that gets validated instead of hoped for.

Supporting: the MCP layer stack, showing transport, discovery, and authorization as protocol responsibilities, and naming, descriptions, granularity, error surfaces, and evaluation as the responsibilities the protocol explicitly does not cover

The part nobody mentions: tool design has no owner

In most teams, tool definitions are written by whoever built the underlying service, in the fifteen minutes after the service was finished. They are reviewed as code, which means reviewers check the implementation and skim the description string. Nobody reviews the description as a prompt, because on the org chart it is not a prompt. It is a docstring.

This is why tool quality lags everything else in an agentic system. Prompts get a registry, versioning, and eventually an eval suite, because everyone agrees prompts are the AI part. Tool schemas get whatever the last person typed. But an agent's behavior is determined at least as much by its tool contracts as by its system prompt, and the tool contracts are the half nobody is watching. That is the same pattern the opening article called the spine: the work that decides whether the system survives contact with real users is the work that does not demo well.

The fix is unglamorous and organizational. Tool definitions belong in the same versioned, reviewed, evaluated pipeline as prompts. Changing a tool description should require a diff and an eval run, because it is a behavior change. Anthropic reports that tools iterated against evals measurably outperformed the hand-written originals on their Slack and Asana implementations. That result is only surprising if you still think of the description as documentation.

Supporting: a tool definition review checklist contrasting how tools are typically reviewed as code against how they should be reviewed as prompts, covering naming, description, granularity, errors, and eval coverage

Write the schema for the reader you actually have

The model is not a careless engineer who should have read your docs. It is a reader with one shot, no context beyond what you handed it, and no way to ask. Every ambiguous parameter name, every piece of knowledge left implicit because "obviously you call search first," every error that says 400 instead of what to do, is a gap the model fills with a guess.

Good tool design is mostly the discipline of not making it guess: name the parameter customer_email instead of input, shape the tool around the task instead of your service boundaries, return errors that name the fix. Then version the whole thing and test it, because you just wrote a prompt.

Tool calling is not the easy part you do after the hard parts. It is where the agent touches the world, and the schema is the only thing standing between a probabilistic caller and your production database.

Coming Up in This Series

Next up: Model routing and cascading: cheap first, expensive only when needed. Most systems run every request through the biggest model available, because that is what worked in the POC and nobody wants to be the person who made quality worse to save money. But the majority of production traffic does not need frontier capability, and the teams that figure out which requests actually do are running the same quality at a fraction of the cost. The hard part is not the routing logic. It is knowing, with evidence, which requests you are allowed to send down.


If this resonated and you're building production AI systems, follow along. The series covers the 21 things I think senior AI engineers and architects need to reason about: RAG pipelines, tool design, security, evaluation, cost, and the operational patterns that separate demos from systems you can actually run.