Skip to main content

Your Tool Description Is a Prompt (And You're Writing It Like a JIRA Ticket)

· 9 min read
TheMCPGuy
MCP Developer & Educator

A magnifying glass hovering over a tool description, with the word 'description' refracting into prompt-shaped fragments

Here is a tool description from a real, public MCP server. The name has been changed because I'm not in the business of public shaming, but the wording is verbatim:

query_data: Queries the data.

Two words. One of which is the tool's own name. The other a tautology. This is what happens when a developer treats description as a field on a struct rather than what it actually is: a prompt fragment that the AI reads to decide whether to call your tool.

If you are writing tool descriptions the way you write Swagger comments, you are writing them wrong. Let's talk about why.

The Misconception

When you define an MCP tool, you provide three things: a name, an inputSchema, and a description. Most developers correctly understand that name and inputSchema are mechanical: they're how the protocol identifies and validates the call. So they treat description the same way: a quick label, half a sentence, just enough to make the linter happy.

But description is not a label. The model never sees the schema as natural language. It sees the schema as structured data. The only natural-language signal the model has, when deciding which of your seventeen tools to call, is the description.

In other words: the description is the prompt that decides whether your tool gets used. Treating it like a code comment is treating a load-bearing wall like a coat of paint.

What the Model Actually Sees

Here is the rough shape of what arrives in the model's context window when an MCP client surfaces your tool:

You have access to the following tools:

- query_data
Queries the data.

- search_customers
Look up customer records in the CRM. Accepts a search string (matches name,
email, or phone) and an optional account-status filter. Returns up to 50
matching customers with id, name, email, account_status, and last_active_at.
Use this when the user asks about specific customers or wants a list of
customers matching some criterion. Do not use for bulk export (max 50) or
for billing data (see get_customer_billing).

Now imagine you're the model. The user just asked: "Can you find John Smith's record?"

Which tool are you going to call?

The model is doing the same thing a junior developer does when they read your team's documentation: picking the most useful-looking option based on the words on the page. If the words on the page say "Queries the data," that tool is going to be either ignored entirely or called catastrophically wrong, because the model has nothing to ground its decision in except the noun "data" and the verb "queries."

The Anatomy of a Good Tool Description

There are four things every tool description should contain. Skip any of them and the model will guess. Models guessing is how you end up debugging at 2am.

1. What it does, in one specific sentence. Not "queries the data." Specifically: "Look up customer records in the CRM by name, email, or phone." Notice the verbs (look up), the object (customer records), the source (CRM), and the parameters (name, email, or phone). Specificity is the entire game.

2. What it returns, in concrete terms. The model is about to incorporate your output into its reasoning. If it knows your tool returns "up to 50 matching customers with id, name, email, account_status, and last_active_at," it can plan a multi-step interaction. If your description says it returns "data," the model might call your tool, then call it again because it doesn't trust the result, then summarise it incorrectly.

3. When to use it. This is the part most developers skip. The description has to position the tool relative to the user's likely intent. "Use this when the user asks about specific customers or wants a list of customers matching some criterion." That sentence is doing the work of about three rounds of trial and error.

4. When not to use it. This is the part nobody puts in. And it's the most important one. "Do not use for bulk export (max 50) or for billing data (see get_customer_billing)." This single negative clause prevents an entire category of misuse. The model has bounded its own search space.

A Worked Example

Here is the same tool, written badly and written well.

Bad:

{
"name": "create_ticket",
"description": "Creates a support ticket."
}

The model will call this when the user mentions tickets. It will guess at the priority field. It will not know whether to put the user's whole message in description or summarise it. It will not know if this tool also notifies the assigned engineer. It will call it once, then call it again with different arguments, because nothing in the description tells it that this operation is not idempotent.

Good:

{
"name": "create_ticket",
"description": "Create a new support ticket in the helpdesk system. The ticket is assigned to the on-call engineer for the relevant team based on the `category` field. The reporter is notified by email; the on-call engineer is paged for severity P0 and P1 tickets. Use this when the user explicitly asks to file a ticket or report an issue. Do not use to comment on an existing ticket (use `add_ticket_comment`) or to escalate an existing ticket (use `escalate_ticket`). This operation is NOT idempotent: calling twice creates two tickets."
}

That is a paragraph. It feels excessive when you're writing it. It is exactly the right length when you remember that the model reads it once and uses it forever.

Tool Descriptions Are Code

Here is the reframing that helps me write better descriptions: the description is part of the contract, not the documentation. If it's wrong, the tool is broken. If it's ambiguous, the tool is fragile. If it's terse, the tool is unsafe.

This has practical consequences:

  • Review descriptions in code review the way you review function signatures. "Is this clear? Is this complete? Does it explain the edge case?" These are not soft questions.
  • Test descriptions empirically. Run your tool against a model with realistic user prompts. If the model calls it at the wrong time, the description is wrong. If the model fails to call it when it should, the description is missing something.
  • Update descriptions when behaviour changes. A function with a stale comment is a minor sin. A tool with a stale description will produce wrong results in production until somebody notices.

The MCP spec itself agrees: since revision 2025-06-18, tools (alongside resources and prompts) carry an optional title field for human-friendly display, so that name can be used as a programmatic identifier and description is free to be aggressively model-oriented. That's the protocol telling you, directly, that these two audiences are different and the description belongs to the model.

The Counterintuitive Bit

Writing good tool descriptions feels like over-engineering. You're sitting there writing a four-sentence paragraph for a function that's eight lines long, and your inner code reviewer is screaming about brevity.

Ignore that voice. It evolved for a different audience.

When you write a Java function, your audience is another developer who has type signatures, tests, the surrounding codebase, and Stack Overflow. Brevity is a virtue because they can recover any missing context.

When you write a tool description, your audience is a language model with one context window, no IDE, no grep, no ability to ask follow-up questions, and a hard incentive to just pick something and try it. It cannot recover missing context. The description is all there is.

In that environment, the brief description is not elegant. It's a failure to communicate.

The Test

Here's a five-minute exercise that will improve every tool description you write.

For each tool in your server, write down the answer to these questions, in plain English:

  1. What is the most specific verb-and-object phrase that describes what this tool does?
  2. What does it return, listed by field?
  3. What is a one-line description of when the user would want this called?
  4. What other tool in this server might the model confuse this with, and what's the difference?
  5. Are there irreversible side effects? Should the model warn the user before calling it?

Now collapse those five answers into a paragraph. Drop the bullet structure. Use complete sentences. That's your description.

If your answer to question 4 is "no other tool," you probably have a server with one tool, or you're missing a tool. If your answer to question 5 is "no side effects," your tool might be a Resource instead. (See the Three Laws of MCP for the Tool-vs-Resource decision.)

The Bigger Pattern

The lesson here generalises. Anywhere you're writing natural language that an LLM will read, you're writing a prompt. Tool descriptions are prompts. Resource descriptions are prompts. System messages are prompts. The strings inside your retrieval pipeline are prompts.

Software engineering has spent forty years training us to write text aimed at compilers (precise, terse) and humans (clear, structured). LLMs are a third audience. They want specificity, examples, constraints, and explicit negative guidance. They reward verbosity in ways that compilers and humans don't.

Once you internalise that, you stop feeling self-conscious about writing four-sentence descriptions for two-line tools. You start feeling self-conscious about the opposite.


If you want a more systematic treatment of tool design (including descriptions, schema, granularity, and the subtle art of when to split a tool in two), the Implementing Tools module of the Java SDK course walks through the patterns end-to-end. The Three Laws of MCP covers the Tool / Resource / Prompt decision, which is where description quality starts to matter most.

Your description is a prompt. Write it like one.