Designing Tool Schemas That AI Models Love
The difference between an MCP server that works flawlessly and one that confuses AI models comes down to tool design. A well-designed tool schema tells the AI exactly what the tool does, what inputs it needs, and what output to expect.
The Three Pillars of Tool Design
1. Clear, Action-Oriented Names
❌ Bad: "data" — What data? What action?
❌ Bad: "process-thing" — Vague, unhelpful
✅ Good: "search-users" — Clear action + target
✅ Good: "create-invoice" — Specific, unambiguous
✅ Good: "get-order-status" — Descriptive verb + noun
2. Descriptive Tool Descriptions
The description is what the AI reads to decide whether to use your tool. Be specific:
// ❌ Bad: Too vague
server.tool("query", "Query the database", ...);
// ✅ Good: Specific about what, when, and limitations
server.tool(
"search-products",
"Search the product catalog by name, category, or price range. Returns up to 20 matching products with name, price, and availability. Use this when the user asks about products, inventory, or pricing.",
...
);
3. Well-Annotated Input Schemas
Every parameter needs a clear description:
server.tool(
"search-products",
"Search the product catalog",
{
query: z.string()
.describe("Search term to match against product name and description"),
category: z.string().optional()
.describe("Filter by category slug (e.g., 'electronics', 'clothing')"),
min_price: z.number().min(0).optional()
.describe("Minimum price in USD (inclusive)"),
max_price: z.number().min(0).optional()
.describe("Maximum price in USD (inclusive)"),
limit: z.number().min(1).max(50).default(20)
.describe("Maximum number of results to return (default: 20)"),
},
async ({ query, category, min_price, max_price, limit }) => {
// Implementation
}
);
Tool Output Best Practices
Structure your output so the AI can parse and reason about it:
// ❌ Bad: Unstructured blob
return { content: [{ type: "text", text: "John Smith 30 john@example.com active" }] };
// ✅ Good: Structured, labeled output
return {
content: [{
type: "text",
text: [
"## User Found",
"",
"| Field | Value |",
"|-------|-------|",
`| Name | ${user.name} |`,
`| Age | ${user.age} |`,
`| Email | ${user.email} |`,
`| Status | ${user.status} |`,
].join("\n"),
}],
};
Annotation Metadata
MCP supports tool annotations that help clients display UI hints:
server.tool(
"delete-user",
"Permanently delete a user account and all associated data",
{ userId: z.string().uuid() },
async ({ userId }) => { /* ... */ },
{
annotations: {
title: "Delete User Account",
readOnlyHint: false,
destructiveHint: true, // Warns the user this is destructive
idempotentHint: true, // Safe to retry
openWorldHint: false, // Does not access external services
},
}
);
The "Golden Rule" of Tool Design
Design your tools as if the AI model is a smart junior developer who has never seen your codebase. It can follow instructions perfectly, but it needs clear, explicit guidance about:
- What the tool does
- What each parameter means
- What values are valid
- What the output format looks like
- When to use this tool vs. another
Key Takeaway
Great tool design is the difference between AI that fumbles and AI that flows. Use action-oriented names, write descriptions the AI can reason about, annotate every parameter, and structure output for easy parsing. The 5 minutes you spend on schema design saves hours of debugging later.