Skip to content
Model Context Protocol (MCP)Tools over MCP

Mid · 8 chapters

Tools over MCP

Declaring them, behavioral annotations (read-only vs destructive vs idempotent hints), and how they reach the model's context

MCP does not repair poor tool contracts; weak schemas, misleading annotations, and opaque errors still cause unsafe calls and silent failures in production.

Read these first

What you will know

  • Declare a tool on a server and have a model call it end to end

    Declare it on the server, list it from the client, and watch the model call it end to end.

  • Use behavioural annotations to mark a tool read-only or destructive

    Annotations let a client know a tool is read-only or destructive, which drives confirmation prompts.

  • Run a client trace that distinguishes read-only, destructive, and idempotent annotations and records how each affects confirmation or retry policy

    These flags are advisory metadata; treating them as authorization or assuming every client enforces them can turn retries into duplicate writes.

  • Show what the client actually forwards to the model

    The client forwards name, description and schema; your implementation stays invisible.

  • Diagnose a tool that appears but is never chosen

    A tool that lists but never gets chosen has a description problem, exactly as with any other tool.


Chapters

  1. 1

    Declaring and calling a tool

    Declaring a tool on an MCP server means giving it three things: a name, a description, and a JSON Schema for its input. The client discovers it through tools/list, renders those three things into the request it sends the provider, and a selection by the model comes back as tools/call carrying arguments shaped by that schema; your handler runs and returns content the host places into the conversation. Five hops, three of which you own — and the whole path is short enough to drive from one thirty-line script, which is the fastest way to make every later symptom locate itself.

  2. 2

    The name, description and schema

    A forty-line docstring, precise type hints and a careful implementation are invisible: the client forwards the tool's name, description and input schema as it chose to render them, and the model sees nothing else. Moving the safety rails out of the handler and into the declaration takes a description from roughly 60 tokens to 190, which costs about two cents across a forty-turn session with prompt caching in play and buys a model that has been told about the seven-day recovery window. Porting a tool from your agent's own definitions onto an MCP server improves no part of that contract, because a vague description travels unchanged.

  3. 3

    Behavioural tool annotations

    The readOnly, destructive, idempotent and openWorld hints tell a host what kind of operation a tool is, so it can decide about confirmation dialogs and retries. A client that reads a destructive hint and does nothing with it is fully compliant, and so is one that never looks at annotations at all — the specification tells clients to treat annotations from untrusted servers as untrusted, because the party making the safety claim is the party whose behaviour is in question. The defaults matter as much as the values: an unannotated tool reads as writing, destructive, non-idempotent and open-world, so leaving them off a read-only lookup has a cost.

  4. 4

    Enforcement in the tool handler

    One server, three hosts, three different confirmation policies — and one of those hosts, a CI job driving a bare client, shows no dialog at all, which is compliant behaviour. A server that needs an operation gated cannot borrow another application's UI to do it: it has to establish who is calling from a token it validates itself, check the audience claim so a token minted for a different server is rejected at parse time, resolve arguments before comparing them, and ask its own policy store before the side effect happens. That is what an authorization ticket has to contain to be honestly closed across three hosts.

  5. 5

    Protocol errors and tool errors

    MCP carries failure in two channels read by two different parties: a JSON-RPC error replaces the result and is handled by the client, while a result marked isError: true travels into the conversation where the model reads it. Turning every exception into the first means a lookup for a missing invoice is re-issued with the same id seven times, burning roughly 29,000 input tokens and 31 seconds before the model tells the user something false. The dividing line is whether your tool logic ran and produced a verdict — and the wording of a verdict is prompt text, so it should say what is true, name the next action, and say when repeating is pointless.

  6. 6

    Idempotency keys for tool calls

    Tag charge_card as idempotent and a careful host will retry a failed call on the strength of it; the customer is charged £240 twice and the defect is the annotation, not the host. Removing the flag fixes one row of a seven-row table — host retry policies that ignore annotations, transport replays, a model re-issuing after an ambiguous error, a second host and your own retry wrapper are all untouched. Duplicate suppression lives in a key the server derives from the caller, the tool name and the canonicalised arguments, recorded before the side effect and carrying the outcome afterwards, or it does not exist.

  7. 7

    Descriptions and tool selection

    A tool that appears in tools/list, is forwarded by the host, and is selected three times in four hundred sessions has not been throttled by anything — it is losing a comparison on description against whatever else is connected. The artefact that settles it is the provider request the host sent: if your entry is in the tools array with the text you wrote, the protocol did its job. Three words against forty-six is not a close comparison, and a rewrite whose every clause closes a specific gap took selection from 3 to 247 of 291 answerable questions, with the explicit comparison against the neighbour carrying most of it.

  8. 8

    Consolidating a tool surface

    Each near-neighbour makes every other description work harder, because a description must distinguish itself from thirty-three siblings rather than from a handful — and eleven of a generated server's tools were about orders alone. Default annotations across all of them are evidence that nobody designed the surface, not the cause of the misrouting. When a host hard-codes four of your tool names it has done your consolidation for you, badly, and told you which four matter. Consolidating 34 endpoint wrappers into 9 designed tools took first-choice accuracy from 61% to 89% over 120 replayed tasks and the tool array from 6,460 tokens to 2,180.

External resources