Why LLMs Silently Skip Your MCP Tools (And How to Fix It)
March 24, 2026 · 8 min read
You built an MCP server. You defined your tools. Claude connects, discovers them... and then never calls them. No error. No warning. It just ignores them.
This happens more often than you'd think. The Model Context Protocol spec is loose enough that technically valid tool definitions can be practically useless to an LLM. Here's what goes wrong and how to fix it.
1. Vague descriptions
Bad
{
"name": "get_data",
"description": "Gets data"
}An LLM picks tools based on descriptions. "Gets data" tells it nothing. Which data? From where? When should it use this instead of another tool?
Good
{
"name": "get_customer_orders",
"description": "Retrieve all orders for a customer by
their customer ID. Returns order date, total,
status, and line items. Use when the user asks
about purchase history or order status."
}The description should tell the LLM: what it does, what it returns, and whento use it. 10–300 characters. Be specific.
2. Missing parameter descriptions
Bad
"properties": {
"id": { "type": "string" },
"fmt": { "type": "string" }
}The LLM has to guess what id means and what values fmt accepts. It will guess wrong.
Good
"properties": {
"customer_id": {
"type": "string",
"description": "Unique customer identifier,
e.g. 'cust_abc123'"
},
"format": {
"type": "string",
"enum": ["summary", "detailed"],
"description": "Response detail level.
'summary' = totals only,
'detailed' = line items."
}
},
"required": ["customer_id"]Every parameter needs a description. Enums help the LLM pick valid values. requiredtells it what's mandatory.
3. No annotations
Annotations are MCP behavior hints that tell the LLM what side effects a tool has:
"annotations": {
"readOnlyHint": true,
"destructiveHint": false,
"idempotentHint": true,
"openWorldHint": false
}Without these, the LLM doesn't know if calling your tool will delete data, modify state, or is safe to retry. Claude and other MCP clients use these hints to decide whether to auto-approve tool calls or ask the user first.
Critical: If your tool is named delete_*, remove_*, or destroy_* and you haven't set destructiveHint: true, you're asking for trouble.
4. No output schema
Most MCP servers omit outputSchema. This means the LLM doesn't know what your tool returns until it calls it. For chained tool calls (where output from tool A feeds into tool B), this is a problem.
"outputSchema": {
"type": "object",
"description": "Customer orders with totals",
"properties": {
"orders": {
"type": "array",
"items": {
"type": "object",
"properties": {
"order_id": { "type": "string" },
"total": { "type": "number" },
"status": {
"type": "string",
"enum": ["pending", "shipped",
"delivered"]
}
}
}
}
}
}5. Duplicate or cryptic names
Tool names should be unique and descriptive. handler1, process, do_thingtell the LLM nothing. The name is the first signal the LLM uses to match your tool to a user's request.
Use the pattern: verb_noun — get_orders, create_invoice, search_products, delete_user.
How to check your tools
You can validate your tools/list response against these rules (and more) with the MCP Server Validator. Paste your JSON or point it at a URL — it runs 9 checks and gives you a score.
For CI/CD, there's a GitHub Actions workflow that fails the build if your tool definitions don't pass.
The checklist
Before shipping your MCP server:
- Every tool has a descriptive name (
verb_noun) - Every tool has a description (10–300 chars, explains what/when/why)
- Every parameter has a description
requiredarray is set on inputSchema- Enums used for constrained values
- Annotations set (especially
destructiveHintfor write/delete operations) - Output schemas defined for tools used in chains
- No duplicate tool names
- Tested with at least one LLM client (Claude, Cursor, etc.)
Validate your MCP tools now
The MCP spec is permissive — it won't reject bad definitions. But LLMs are picky. The difference between a tool that works and one that gets silently skipped is usually a better description and a few annotations.