Lesson 3: Five Common Tool Types: Read, Write, Execute, Search, Call
Learning goals:
- Sort common tools into five categories by how much damage they can do, and name the typical signature of each
- Explain why command-execution tools sit in a different risk class from the other four
- Explain why search tools return matching snippets instead of whole files
Prerequisites: finished Lesson 2, you understand the round-trip shape of a tool call | Prev: Lesson 2 << | Next: Lesson 4 >>
Start with a table
What sorts this table? Not the alphabet. It's "how wide a range of harm one call can cause" — the blast radius of that call. A read-only tool has a blast radius of roughly zero: reading the wrong file just derails this one turn of the conversation. Writing a file can overwrite existing content. Executing a command can do anything at all to the whole system. As we walk through each category, you'll see that beyond "what it can do," each one carries a pitfall that only that category trips over.
Read: safest, but not zero risk
A read-file tool usually has a signature like this:
The return value is the file contents themselves, usually with line numbers so the model can reference them later:
Reading a file changes no state. If the model reads the wrong thing, or reads too much, the worst outcome is some irrelevant content in this one turn — and the model tends to notice it read the wrong thing and read again. That's why it's called the "safest" category: not that it carries no risk, but that the risk can't escape the bounds of this conversation.
The real risk is reading a file it should never have touched. If the agent has permission to read ~/.ssh/id_rsa or the project's .env, an innocent-looking "show me what's in this directory" can lift a secret key verbatim into the conversation context. From there, the leak has already happened the moment that context gets emitted by the model, written to a log, or carried out by some later "call external API" tool. That's why read-file tools almost always go together with a path allowlist or a sandbox, rather than "it's read-only, just hand it the keys." Lesson 5 covers how to set that kind of boundary in detail.
Write: where the consequences stop being symmetric
A write-file tool has one more parameter than read, and one less bit of safety:
The return value is usually simple, just a status:
The problem isn't the return value, it's the call itself. If a read goes wrong, you read again and nothing has changed. If a write goes wrong — say the model fills in the wrong path, or the content is missing half of what it should be — the original file's contents are already overwritten and can't be recovered, unless there's version control or a backup. This is the "asymmetry between read tools and write tools": the two call shapes look almost identical (a path plus a couple of parameters), but one can be retried freely and the other gambles on every single call.
So a responsible write tool adds a layer of protection — for instance, requiring that the file was read before it can be edited (to stop the model editing from memory), or returning a diff of old versus new content instead of a bare "success," so the caller (the host application) has a chance to show the change before it actually hits disk. Those aren't the focus here; Lesson 4 opens them up when it covers interface design.
Execute: a risk class of its own
The command-execution tool has the plainest-looking signature of the five:
One string goes in; stdout, stderr, and an exit code come out:
The catch is that this command field is essentially an open-ended entry point — it isn't a specific, schema-bounded operation like "delete this file" or "read this line," it's an arbitrary shell script. rm -rf, a curl that ships data to an outside server, an npm install that pulls in a poisoned package — all of it fits inside that one string. The other four categories (read, write, search, call API), however you design their signatures, are limited by their parameter structure in what they can do. A command-execution tool's capability boundary is the whole operating system's capability boundary. That's why it's in a class of its own: not "a bit riskier," but a different order of magnitude of risk.
For exactly that reason, the official docs design operating-system-level isolation specifically for this category: filesystem access and network access are two separate sandbox layers, and even if the model is steered by a prompt injection and insists on running a dangerous command, the OS boundary holds regardless — it doesn't depend on whether the model "wants" to cooperate1. The stated motivation is blunt: the goal is that even a successful prompt injection is fully contained and can't escape the sandbox2. Lesson 5 covers how to configure that isolation; for now, hold on to one thing: wherever the "execute command" signature shows up, treat it by default as the category in the five that most needs extra constraint.
Search: returns locations, not the whole world
A search tool (say, one that finds a keyword or regex across a codebase) often carries a "limit how much comes back" parameter in its signature:
The return value isn't the files themselves, it's "where the match is and what the surrounding context looks like":
If this tool just stuffed the full contents of every matched file back in, two problems show up. The first is a token problem: one search hits 50 files, each a few hundred lines, all of it poured into the context — and this single tool call has eaten the whole turn's input budget, leaving nothing for the model to keep working with3. Writing tool descriptions and controlling the bounds of input and output is itself a basic requirement for making a tool usable4. The second problem matters more: the point of search isn't "read through everything that might be relevant," it's "help the model figure out where to look next." Return the match locations plus a short snippet of context, the model reads those snippets and judges for itself — "of these results, the second one looks like what I'm after, let me read that file's full contents on its own." That's how a search tool and a read tool work together: search narrows the range, read gets the detail. Returning "match locations" rather than "whole files" is exactly the follow-up lead the model needs, instead of a one-time dump of everything that might be useful.
Call external API: failure is the norm, not the exception
The first four categories mostly stay inside the local system. Calling an external API is different — it crosses the network, to a service you don't control:
A normal return looks like this:
But an external service will rate-limit you, time out, reject a request for missing permissions, and change its own interface in the gap between your calls. These aren't "unexpected situations," they're the normal running conditions for this category. What actually decides whether the tool is any good isn't "what it returns when things are fine," it's "what it returns when things fail":
This error message isn't for you, it's for the model — whether it should retry or switch strategy next depends on whether it can read that error field. The MCP specification writes this straight into the protocol: clients should provide tool execution errors to language models so the model has a chance to self-correct and try again5. In other words, a tool that silently swallows a 429 and returns nothing but "call failed" is robbing the model of the chance to correct itself; a tool that carries back concrete detail like retry_after is the one designing "failure" as a normal part of the workflow.
The call-external-API category also drags in another layer of risk: if this agent can read private data at the same time as it's exposed to untrusted content (a chunk of web text a user pasted, say) and it can also send messages or requests outward, those three together are what security research calls the "lethal trifecta" — the attacker doesn't need to break into your system, they just hide an instruction in content the agent will read and let the agent carry the private data out itself6. Lesson 5 opens this topic up on its own; for now, know this: calling an external API is the last and most critical link in that chain, because it's the exit through which data actually leaves your system.
Recap
- The risk across the five categories isn't evenly spread: read file has the lightest consequences, write file is where things start being irreversible, execute command's blast radius equals the whole operating system, and search and call-external-API each have their own separate pitfalls
- The core difference between read tools and write tools is whether you can safely retry — read wrong and you just read again, write wrong and the original content may be gone for good
- Command-execution tools need an OS-level sandbox to fall back on because their
command parameter is an open string, not bounded by a schema structure the way the other four are1 2
- Search tools return match locations plus snippets rather than whole files, first to save tokens, and second to split "locate" from "read the detail," handing the model a lead it can follow up on3
- Call-external-API tools should carry failure information (error type, whether it can be retried) back to the model as-is rather than swallowing it — in this category, failure is the norm, not the exception5
Next lesson, we take the "signatures" of these five categories apart: how to write a good tool name, description, parameter schema, and return value, so the model calls it right the first time.
>> Lesson 4: Designing Tool Interfaces: Name, Description, Parameters, Return Value