# Write an MCP tool description a model will actually pick right

> Draft an MCP tool name, description and input schema that tell a model when to use the tool, when not to, and how to fill every argument.

- **Author:** [Jonas Schmidt (@jonas_schmidt)](https://promptabide.com/jonas_schmidt)
- **Tested on:** Claude · Opus 5.5
- **You fill in:** `tool_behavior`, `usage`, `neighbors`
- **Published:** 2026-09-05
- **Updated:** 2026-09-24
- **Tags:** `ai-agents`, `prompt-engineering`, `coding`, `automation`
- **Keywords:** how to write mcp tool descriptions, mcp server tool schema example, model context protocol tool naming, agent picks wrong tool fix, json schema for llm tool calling
- **Views:** 1612
- **Likes:** 56

**Best for:** Developers building MCP servers or agent tools who see models calling the wrong tool or inventing arguments.

## Prompt

```
Write the MCP tool definition (name, description, inputSchema) for this tool, so that a model picks it at the right moment and fills its arguments correctly.

What the tool does: {{tool_behavior}}
Who calls it, and typical requests: {{usage}}
Similar tools on the same server: {{neighbors}}

Rules:
- name: snake_case, verb_noun, clearly distinct from the neighbors.
- description: the first sentence says when to use it. Then say when NOT to use it, naming the neighbor to use instead. Then say what it returns and its limits (page size, latency, what's not searchable). Under 120 words.
- inputSchema: JSON Schema. Every property gets a description with a concrete example. Use enums where the values are fixed. Mark required fields. No property the model can't know how to fill.
- Output valid JSON.

Then write 3 test requests: one that should call this tool, one that should call a neighbor, and one that's ambiguous. For each, say which tool should fire and why.
```

## Variables

- `{{tool_behavior}}` — What the tool does, its filters and what it returns (e.g. Full-text search over helpdesk tickets; filters for status, date range, customer email; 25 per page)
- `{{usage}}` — Who calls it and what they typically ask (e.g. Support agents asking Claude in Slack about open tickets)
- `{{neighbors}}` — Other tools on the same server it could be confused with (e.g. get_ticket (by ID), search_help_articles (knowledge base))

## Example input

```
tool_behavior: Full-text search across customer support tickets in our helpdesk. Filters: status (open, pending, solved), updated date range, customer email. Returns up to 25 tickets per page (id, subject, status, updated_at, customer email, 200-character snippet) plus a next-page cursor. Takes about 2 seconds. Doesn't search attachments or internal notes.
usage: Our support agents ask Claude in Slack things like "any open tickets about refund delays this week?" or "what has acme.com complained about lately?"
neighbors: get_ticket (fetches one ticket's full thread by ID), search_help_articles (searches public knowledge-base articles)
```

## Output

*Produced by Claude · Opus 5.5.*

````
```json
{
  "name": "search_tickets",
  "description": "Use this to find support tickets matching a topic, customer or status, e.g. 'open tickets about refund delays this week'. Do not use it to read one ticket's full conversation when you already have its ID; call get_ticket instead. Do not use it for how-to or policy questions; call search_help_articles instead. Returns up to 25 tickets per call (id, subject, status, updated_at, customer_email, a 200-character snippet) and a cursor for the next page. Searches ticket subjects and customer/agent replies only, not attachments or internal notes. Takes about 2 seconds, so prefer one well-filtered call over many broad ones.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "query": {
        "type": "string",
        "description": "Keywords to match in subject and replies, e.g. 'refund delay'. Use plain words, not boolean syntax. Omit to list tickets by filters only."
      },
      "status": {
        "type": "string",
        "enum": ["open", "pending", "solved"],
        "description": "Ticket status. Omit to include all statuses."
      },
      "updated_after": {
        "type": "string",
        "format": "date",
        "description": "Only tickets updated on or after this date (YYYY-MM-DD), e.g. '2026-09-21' for 'this week'."
      },
      "updated_before": {
        "type": "string",
        "format": "date",
        "description": "Only tickets updated on or before this date (YYYY-MM-DD)."
      },
      "customer_email": {
        "type": "string",
        "description": "Exact address ('priya@acme.com') or a domain starting with @ ('@acme.com') to match a whole company."
      },
      "cursor": {
        "type": "string",
        "description": "The next_cursor value from a previous search_tickets result. Only use it to fetch the next page."
      }
    },
    "required": [],
    "additionalProperties": false
  }
}
```

**Test requests**
1. *"Any open tickets about refund delays this week?"* → **search_tickets** with `query: "refund delay"`, `status: "open"` and `updated_after` set to Monday's date. A topic plus status plus time range is exactly this tool's job.
2. *"What's our policy on refunds after 30 days?"* → **search_help_articles**. It's a policy question, not a request about customer tickets, and the description says so.
3. *"What's going on with ticket 48213 from Acme?"* → **get_ticket** is right, because an ID is given. A model might call search_tickets with `@acme.com` instead. The "when you already have its ID" line in the description is there to prevent that. If testing shows it still happens, name the ID case in the first sentence.
````

## Why it works

Models choose tools mostly from the **first sentence of the description**, so the prompt makes that sentence about *when to use it*, not what it is. The **"when NOT to use it, naming the neighbor"** rule is the most effective fix for wrong-tool calls between similar tools. **Stating limits** (page size, 2-second latency, no attachments) stops the model promising results the tool can't give. **Enums and example values** in every property cut down invented arguments. The **three test requests**, including an ambiguous one, give you a small evaluation to run before shipping.

## When not to use it

A good description can't rescue a badly scoped tool. If the model keeps confusing two tools, merging or splitting them may be the real fix. The test requests are predictions, not results: run them against the model and client you actually use, since tool-selection behavior differs between models. It doesn't cover auth, rate limits or the server implementation.

---

Canonical HTML: https://promptabide.com/bides/write-mcp-tool-description-and-schema
Agent guide: https://promptabide.com/llms.txt · https://promptabide.com/agent-instructions.md
Sitemap: https://promptabide.com/sitemap.xml
