| name | mcp-tool-design |
| description | Guide for designing MCP tool content that LLMs can reliably select and use. Covers writing effective tool descriptions, structuring input/output contracts via JSON Schema, splitting tools by single responsibility, avoiding misrouting between similar tools, and coordinating system prompt instructions with tool descriptions. Use when the user says "tool description design", "tool interface design", "MCP tool descriptions", "tool splitting", "input output contract", "tool misrouting", "tool selection", "how to write tool descriptions", or wants to understand how LLMs choose between tools based on description content. For platform-level MCP optimization (searchHint, alwaysLoad, annotations), see mcp-tool-enhancement instead.
|
MCP Tool Design: Descriptions, Contracts, and Routing
Design tool interfaces that LLMs can reliably select and use correctly. This skill covers the content that goes inside tool descriptions and schemas โ not platform-specific metadata like searchHint or alwaysLoad. For Claude Code-specific discoverability and optimization, see mcp-tool-enhancement.
Part 1: How LLMs Select Tools
When an LLM needs to call a tool, it reads the tool's description text to decide which tool fits the user's request. The model never sees your implementation code โ the description is its only window into what the tool does.
This means:
- A vague description ("Analyzes data") makes the tool invisible when multiple similar tools exist
- A detailed description with clear boundaries makes the tool selectable with high accuracy
- The description is effectively a prompt that guides the model's tool selection reasoning
In Claude Code, each tool's description is generated by tool.prompt() and injected into the API schema. The model receives name, description, and input_schema โ nothing else. Every design decision in this skill flows from this fact.
Part 2: Writing Descriptions That Guide Selection
2.1 Lead with Differentiation
The first 200 characters of your description carry the most weight. MCP tool descriptions are truncated at 2048 characters โ anything beyond that is replaced with 'โฆ [truncated]'. Front-load the most important information: what makes this tool different from alternatives.
Bad โ generic, undifferentiated:
"Searches code in the repository"
Good โ leads with what's unique:
"Semantic code search that finds functionally similar code even when
variable names and syntax differ. Unlike text-matching search, this tool
understands code intent โ use it when searching for patterns, refactoring
targets, or similar implementations across languages.
Returns: file path, line range, similarity score, and code snippet."
2.2 Include the Four Essential Elements
Every tool description should contain these four components:
- Input formats โ What data types, shapes, and constraints does the tool expect?
- Example queries โ 1-2 concrete usage examples that act as few-shot guidance
- Edge cases โ What the tool cannot handle, or what happens with unexpected input
- Boundary explanations โ When NOT to use this tool; what to use instead
Example combining all four:
"Execute read-only SQL queries against the project database.
Input: A valid SQL SELECT statement. Supports JOINs, subqueries, and
aggregate functions. Maximum query length: 4096 characters.
Example: SELECT u.name, COUNT(o.id) FROM users u JOIN orders o ON u.id = o.user_id GROUP BY u.name
Edge cases: CTEs (WITH clauses) are supported but recursive CTEs time out
after 5 seconds. EXPLAIN queries return the plan as plain text, not a table.
Do NOT use this tool for write operations (INSERT, UPDATE, DELETE) โ use
the database_write tool instead. Do NOT use for schema changes โ use
database_migrate."
2.3 Use the "ALWAYS / NEVER Pair" Pattern
The most effective routing pattern in Claude Code creates a two-directional instruction: one tool gains traffic, the other explicitly loses it.
ALWAYS use Grep for search tasks. NEVER invoke `grep` or `rg` as a Bash command.
This pattern works because it eliminates ambiguity โ the model knows both where to go and where NOT to go. Apply it when your tool competes with another for the same user intent.
Template:
ALWAYS use [your_tool] for [specific task]. NEVER use [competing_tool] for this โ [reason].
2.4 Use "When to Use / When NOT to Use" Boundaries
Positive and negative scope boundaries help the model reason about edge cases. Claude Code's tools consistently use this pattern:
# From the Write tool description:
"Prefer the Edit tool for modifying existing files โ it only sends the diff.
Only use this tool to create new files or for complete rewrites."
# From the ExitPlanMode tool:
"IMPORTANT: Only use this tool when the task requires planning the
implementation steps of a task that requires writing code. For research
tasks where you're gathering information โ do NOT use this tool."
Template:
Use this tool when: [positive conditions โ what scenarios fit]
Do NOT use this tool when: [negative conditions โ what to use instead]
2.5 Describe the Output Format
Tell the model what the tool returns so it can plan multi-step workflows. If the model knows your tool returns {file_path, line_range, score}, it can chain the output into a subsequent Read call without guessing.
"Returns a JSON array of matches, each with:
- file_path: absolute path to the matching file
- line_range: [start, end] line numbers
- score: similarity score from 0.0 to 1.0
- snippet: the matching code block (max 20 lines)"
Part 3: Input/Output Contracts via JSON Schema
3.1 Annotate Every Parameter
Every input parameter should have a .describe() annotation (or "description" field in JSON Schema) that explains what the parameter does, what values are valid, and any constraints.
{
type: "object",
properties: {
file_path: {
type: "string",
description: "The absolute path to the file to modify"
},
old_string: {
type: "string",
description: "The text to replace"
},
new_string: {
type: "string",
description: "The text to replace it with (must be different from old_string)"
},
replace_all: {
type: "boolean",
default: false,
description: "Replace all occurrences of old_string (default false)"
}
},
required: ["file_path", "old_string", "new_string"]
}
Constraints encoded in descriptions โ like "(must be absolute, not relative)" or "(must be different from old_string)" โ prevent common mistakes without requiring server-side validation round-trips.
3.2 Constrain Inputs with Schema Validation
Use enums, defaults, and additionalProperties: false to narrow the input space. The tighter the schema, the fewer ways the model can misuse the tool.
output_mode: {
type: "string",
enum: ["content", "files_with_matches", "count"],
description: "Output mode: 'content' shows matching lines, 'files_with_matches' shows file paths (default), 'count' shows match counts."
}
output_mode: {
type: "string",
description: "How to format the output"
}
Practical guidelines:
- Use
enum whenever the parameter has a fixed set of valid values
- Set
default values for optional parameters so the model doesn't have to guess
- Use
additionalProperties: false (or strictObject in Zod) to reject unexpected fields
- Include examples in descriptions:
"e.g., '*.js', '**/*.tsx'"
3.3 Define Output Schemas
When your tool returns structured data, define the output shape explicitly. This enables the model to reason about chaining tools together.
{
filePath: { type: "string", description: "The file path that was edited" },
oldString: { type: "string", description: "The original string that was replaced" },
newString: { type: "string", description: "The new string that replaced it" },
structuredPatch: {
type: "array",
description: "Diff patch showing the changes",
items: { }
},
userModified: {
type: "boolean",
description: "Whether the user modified the proposed changes"
}
}
Part 4: Tool Splitting โ Single Responsibility
4.1 The Read / Edit / Write Pyramid
Claude Code splits file operations into three separate tools:
| Tool | Responsibility | Input | Output |
|---|
| Read | Read-only file access | file_path, offset, limit | File contents with line numbers |
| Edit | In-place modification (diff) | file_path, old_string, new_string | Structured patch |
| Write | Create new or full rewrite | file_path, content | Write confirmation |
Each tool has a single responsibility, distinct inputs, and distinct outputs. The model selects the right level of operation without needing a "mode" parameter. The tools also cross-reference each other: Edit says "ALWAYS prefer editing existing files," Write says "Prefer the Edit tool for modifying existing files."
4.2 When to Split
Split a tool when:
- It has a "mode" or "action" parameter that changes behavior fundamentally (e.g.,
action: "read" | "write" | "delete")
- Its description needs to explain multiple unrelated use cases in a single paragraph
- Users/agents frequently invoke it for the wrong sub-function
- Different sub-functions have different permission profiles (read-only vs. destructive)
4.3 Anti-Pattern: The "God Tool"
A single analyze_document tool that can extract data, summarize, and verify claims forces the model to guess which mode to use. Split it:
| Before | After |
|---|
analyze_document (does everything) | extract_data_points โ Extract structured data from a document |
| summarize_content โ Generate a concise summary |
| verify_claim_against_source โ Check if a claim matches the source |
Each sub-tool now has a clear description, distinct inputs, and a predictable output format.
4.4 Naming Split Tools
Use specific verbs that signal the tool's purpose:
extract_, summarize_, verify_, transform_, query_, deploy_
- Avoid generic names:
process_, handle_, do_, run_, execute_
- Include the domain:
query_database not query, deploy_preview not deploy
Part 5: Avoiding Misrouting
5.1 Diagnostic: Why Tools Get Misrouted
Misrouting happens when two or more tools have descriptions that are too similar for the model to reliably choose between them. The classic case:
analyze_content: "Analyzes content and extracts key information"
analyze_document: "Analyzes documents and extracts important data"
These descriptions are nearly identical โ the model will randomly pick one.
5.2 Fix: Differentiate Descriptions First
Rewrite descriptions to emphasize what each tool does differently:
extract_web_results: "Extracts structured data from web pages and HTML content.
Input: URL or raw HTML. Returns: title, headings, links, and text content
as structured JSON. Use for web scraping and online content extraction.
Do NOT use for uploaded documents โ use extract_document_data instead."
extract_document_data: "Extracts structured data from uploaded files (PDF, DOCX, XLSX).
Input: file path to an uploaded document. Returns: text content, tables,
and metadata as structured JSON. Use for document analysis and data extraction.
Do NOT use for web content โ use extract_web_results instead."
5.3 Fix: Rename When Differentiation Isn't Enough
When descriptions alone can't resolve ambiguity, rename the tools to make their purpose obvious from the name itself:
| Ambiguous | Clear |
|---|
analyze_content | extract_web_results |
get_info | fetch_customer_profile |
process_data | transform_csv_to_json |
Renaming works because the tool name is a strong hint โ even though the description is the primary selector, a descriptive name reinforces the routing signal.
5.4 Tool Deduplication
Claude Code's assembleToolPool() uses uniqBy by name, with built-in tools placed first. If your MCP tool has the same name as a built-in, the built-in wins silently. Always choose unique names that don't collide with: Read, Write, Edit, Bash, Grep, Glob, Agent, WebFetch, WebSearch.
Part 6: System Prompt and Tool Description Coherence
6.1 The "Double Reinforcement" Pattern
Claude Code reinforces tool routing from two directions: the system prompt says "Do NOT use Bash for file operations โ use dedicated tools," and the Bash tool's own description says "Avoid using this tool for find, grep, cat commands โ use dedicated tools instead." Both push the model toward the same routing decision.
Apply this to your tools: if your system prompt mentions when to use a tool, make sure the tool's own description echoes the same guidance.
6.2 System Prompt Overriding Descriptions
A system prompt instruction like "always search the web for current information" can accidentally make the model prefer any tool with "search" or "web" in its description โ even when a different tool is more appropriate. Watch for:
- Broad directives that match tool keywords unintentionally
always / never instructions that create implicit tool preferences
- Industry terms in the system prompt that collide with tool names
6.3 Keyword Sensitivity Hierarchy
Instructions with stronger keywords override weaker ones. Use this hierarchy deliberately:
| Strength | Keywords | Effect |
|---|
| Highest | CRITICAL, MUST | Near-absolute compliance |
| High | IMPORTANT, REQUIRED | Strong compliance |
| Medium | ALWAYS, NEVER | Directive โ followed unless conflicting with higher |
| Low | Prefer, Avoid, Consider | Suggestive โ overridden by stronger instructions |
When writing system prompts and tool descriptions, match the keyword strength to the importance of the routing rule. Reserve CRITICAL for rules that must never be violated.
Quick Reference: Tool Design Checklist
When designing or reviewing MCP tools, verify:
See Also
mcp-error-response โ Error response design for MCP tools: classification (transient, validation, business, permission), the isError flag, empty-vs-error distinction, structured error metadata, and subagent error propagation. Use that skill when your tool's content and routing are designed but you need to define how it handles and reports failures.
mcp-tool-enhancement โ Platform-level optimization for Claude Code: searchHint, alwaysLoad, annotations, MCP Resources as content catalogs, and .mcp.json configuration. Use that skill when your tool content is well-designed but needs to be discoverable and competitive within Claude Code's tool pool.