Tool Use Design for AI Agents: Contracts, Consequences, and MCP
How to design tools an agent won't misuse. Consequence levels, idempotency, permission architecture, and the Model Context Protocol (MCP).
The quality of your agent is determined by the quality of your tools. A well-designed tool is hard to misuse. A poorly-designed tool fails silently in ways that are catastrophic in production.
Tool design principles
- Single responsibility: one tool, one job. A tool that searches AND summarises AND stores is a tool the model will misuse.
- Clear, honest names: get_user_profile() not fetch_data(). The model reads the name to decide when to call.
- Rich descriptions: the docstring is a prompt. Tell the model what the tool returns, what it requires, and when NOT to use it.
- Explicit error responses: return structured errors the model can reason about, not exceptions that crash the loop.
Consequence levels: the most important design decision
| Level | Examples | Recommended handling |
|---|---|---|
| Read-only | search(), get_user(), list_files() | Allow freely — no confirmation needed |
| Reversible write | update_draft(), create_note() | Allow with logging — can undo |
| Irreversible | send_email(), delete_record(), purchase() | Require explicit human confirmation |
| High-blast | bulk_delete(), broadcast_message() | Always block — never allow agent to call directly |
Prompt injection attacks specifically target agents with high-consequence tools. An agent that can send emails or make purchases will eventually receive a prompt telling it to. Design your tool permission architecture assuming the model will be compromised.
The Model Context Protocol (MCP)
MCP is an open standard (Anthropic, 2024) for connecting AI models to external tools and data sources. Instead of writing custom tool integrations per model and per provider, MCP defines a universal interface: any MCP server exposes tools that any MCP-compatible client (Claude, Cursor, etc.) can call.
from mcp import FastMCP
app = FastMCP("my-tools")
@app.tool()
def get_weather(city: str) -> dict:
"""Get current weather for a city.
Returns temperature (celsius), condition, humidity.
Do NOT use for historical data — use get_historical_weather instead.
"""
return fetch_weather_api(city)
Idempotency and retries
Agents retry failed tool calls. If your tool is not idempotent, a network error followed by a retry can create duplicate records, double charges, or duplicate emails. Make write operations idempotent by accepting an idempotency key, or gate retries at the orchestration layer.
Design tools in the Agents Lab →: Build and test tool definitions and see how the agent decides when to call them.
- Model Context Protocol — Anthropic
- Tool Use in Claude — Anthropic Documentation
- Toolformer: Language Models Can Teach Themselves to Use Tools (Schick et al., 2023)
Try it interactively
GenAI Systems Lab is a free platform for AI engineers — configure real failure modes, break things, and build the judgment that gets you hired.
Open GenAI Systems Lab →