MP-201a · Module 1

Anatomy of a Tool Definition

4 min read

An MCP tool is a function contract exposed over a protocol. Every tool has three required fields: name, description, and inputSchema. The name is a machine identifier — short, snake_case, unique within the server. The description is the single most important field because it is what the LLM reads to decide whether to call your tool. The inputSchema is a JSON Schema object that defines the parameters the tool accepts, their types, and which are required.

The description does double duty. It tells the LLM when to use the tool and how to use it. A vague description like "searches stuff" forces the model to guess. A precise description like "searches the company knowledge base by semantic similarity, returns the top N matching documents with relevance scores" gives the model everything it needs to decide if this tool fits the user's request and how to construct the input. Write descriptions for an LLM audience, not a human one — be explicit about inputs, outputs, and edge cases.

The inputSchema uses standard JSON Schema draft 2020-12. Every property should have a type and a description. Use enum for constrained values, default for optional parameters with sensible fallbacks, and required to mark mandatory fields. The schema is not just validation — it is documentation. The LLM reads property descriptions to understand what values to pass, so "query: string" is worse than "query: the natural-language search phrase to match against document titles and body text."

// MCP tool definition — the contract the LLM sees
const searchTool = {
  name: "knowledge_search",
  description:
    "Search the company knowledge base by semantic similarity. " +
    "Returns the top N matching documents with relevance scores. " +
    "Use this when the user asks about internal policies, procedures, " +
    "or documentation. Does NOT search external websites.",
  inputSchema: {
    type: "object" as const,
    properties: {
      query: {
        type: "string",
        description: "Natural-language search phrase to match against documents",
      },
      limit: {
        type: "number",
        description: "Maximum number of results to return (1-20)",
        default: 5,
      },
      department: {
        type: "string",
        enum: ["engineering", "sales", "hr", "finance", "all"],
        description: "Filter results to a specific department, or 'all' for global search",
        default: "all",
      },
    },
    required: ["query"],
  },
};
  1. Name with intent Choose a name that describes the action: get_customer, search_docs, create_ticket. Avoid generic names like "run" or "process" that tell the LLM nothing about when to use the tool.
  2. Write the description first Before writing any handler code, write the description. State what the tool does, when to use it, what it returns, and what it does NOT do. This forces you to clarify the tool's scope.
  3. Constrain the schema Use enum for known value sets, minimum/maximum for numeric ranges, and pattern for string formats. Every constraint you add is one fewer way the LLM can misuse the tool.