Designing MCP Tools a Model Can Actually Use
Once you've got an MCP server running, the interesting work isn't the transport — it's that your tool descriptions and schemas are read by a model, not a person. They're documentation and prompt at the same time, and the model has no colleague to ask, no source to read, and no memory of last time. That changes what good looks like. The highest-leverage thing you can do isn't naming or structure, it's making failures explain themselves: an error that says what to do next turns a dead end into a retry the model completes on its own.
Write descriptions for someone with no context
A model choosing between your tools sees names and descriptions and nothing else. So the description has to answer three questions, not one: what does this do, when should I reach for it, and when should I not?
That last part does more work than people expect. This platform's own MCP tools use exactly this shape — each one states its purpose, then a "Use when" line, then a "Don't use when" line pointing at the tool you should have picked instead. For example, the tool that returns a git URL for a new application ends with "Don't use when: You want to deploy new version to an existing application" and names the other tool.
That single line prevents the most common failure mode in a multi-tool server: the model picks a plausible neighbour and does the wrong thing confidently.
Constrain the schema so wrong calls are impossible
Every free-text parameter is a guess waiting to happen. Every enum is a guess you've eliminated.
- Use enums wherever the set is known. "protocol: HTTPS | TCP | UDP" cannot be got wrong; "protocol: string" can.
- Mark required fields required, and give optional ones sensible defaults so a minimal call succeeds.
- Describe the parameter, not its type. "Page to fetch. Numeration starts from 0" tells the model something the type can't.
- Prefer IDs you handed out over names the model might invent. If a tool needs an app ID, there should be another tool that lists them.
The test is whether a model that has never seen your system could compose a valid call from the schema alone. If it needs to know a convention you haven't written down, write it down.
Errors are prompts
This is the one worth rewriting your tools for.
When a model gets an error, that text goes straight back into its context and becomes the basis for its next attempt. A bad error ends the interaction; a good one contains the fix.
# ends the conversation
Error: invalid id
# lets the model recover by itself
App 42 not found. Use jrnm_get_user_apps to list applications you own.
The pattern: say what was wrong, then name the action that resolves it. If a required variable is missing, name it. If the ID doesn't exist, point at the tool that lists valid ones. If a value is out of range, state the range.
You're not writing for a log file — you're writing the next instruction the model will follow.
Label what a tool does to the world
MCP tools carry annotations for whether they're read-only, destructive, and idempotent, and hosts use them to decide when to ask the user before proceeding. Getting them right is how your server participates in that safety story rather than opting out of it.
Two honest points about them:
- They're hints, not enforcement. Your server still has to authorise every call itself; an annotation doesn't stop anything.
- Read-only doesn't mean harmless. A tool that returns configuration or credentials changes nothing and still hands sensitive data to whatever is on the other end — worth thinking about explicitly when you design what a tool returns.
Mark anything that mutates as such, mark deletion destructive, and let the client prompt.
Fewer tools, better chosen
Every tool costs context and adds a chance of picking the wrong one. Twelve well-named tools beat forty granular ones, and a model that has to choose among near-duplicates will sometimes choose badly.
Practical trims:
- Merge variations into parameters. One "list items" tool with a filter beats four list tools.
- Don't expose internals. If a tool exists only as a step in a workflow, consider whether the workflow should be the tool.
- Delete what nobody calls. Server-side logging of tool usage tells you which ones earn their place.
Return small, structured results
Whatever you return lands in a context window, so size matters in a way it doesn't for a normal API.
- Paginate, and say so in the description, including how to get the next page.
- Return fields, not prose. Structured results are easier to act on and cheaper to carry.
- Trim aggressively. If a caller rarely needs a field, leave it out and offer a detail tool.
- Never return secrets casually. A tool that echoes environment variable values has made every transcript sensitive.
The same goes for latency: the client is waiting, and a slow tool call is a stalled conversation. Do the expensive thing asynchronously and return a handle if you must.
Remember it's a public endpoint
Design decisions don't stop at usability. Your MCP server is reachable from the internet the moment it starts, so treat every tool as an endpoint: authenticate the caller, validate arguments as untrusted input, and rate-limit.
That last one matters more than for a normal API, because an agent that misunderstands a tool can retry in a tight loop with no human noticing. A limit that survives restarts is worth having before you need it, and shipping the whole thing via git push makes iterating on the descriptions cheap.
FAQ
How should I write MCP tool descriptions?
State what it does, when to use it, and when to use a different tool instead. The "don't use when" line prevents the most common wrong-tool errors.
What's the highest-impact improvement to a tool?
Its error messages. Say what was wrong and name the tool or parameter that fixes it, so the model can recover without a human.
How many tools should a server expose?
As few as cover the job. Each one costs context and adds a chance of choosing wrongly; merge variants into parameters.
Do the read-only and destructive annotations protect anything?
No. They're hints that let clients prompt for confirmation. Your server must still authorise every call.
How large should tool results be?
Small. Results land in a context window — paginate, return structured fields, and omit anything rarely needed.
Do I need rate limiting on an MCP server?
Yes. It's a public endpoint, and a confused agent can retry in a loop far faster than a person would.
Wrapping up
Designing tools for a model is mostly ordinary API design with the audience changed: no tribal knowledge, no ability to read your source, and a very literal reading of whatever you wrote. Say when not to use each tool, make invalid calls impossible in the schema rather than caught at runtime, and treat every error string as the next instruction someone will follow. Get that last one right and your server starts recovering from its own mistakes.