What is Tool?
A Tool is the primary action primitive in MCP — it represents an executable function that the server exposes and the LLM can invoke autonomously to interact with external systems, read data, or produce side effects. Unlike Resources (which are read-only data sources) or Prompts (which are user-initiated templates), Tools are model-controlled: the LLM decides when and how to call them based on the tool's description and the current user goal. This distinction is deliberate — it means tool calls flow through the model's reasoning loop and are subject to the host application's approval policies. In the MCP message flow, the client first calls tools/list to discover available tools, receiving an array of Tool objects. When the LLM selects a tool, the client sends a tools/call request with the tool name and a validated arguments object. The server executes the function and returns a CallToolResult containing content blocks. The required inputSchema field is a full JSON Schema object that both documents expected arguments and enables client-side validation before the request is ever sent. The API surface has grown across spec revisions: outputSchema and execution metadata were added to support structured output contracts and capability negotiation, while annotations (readOnly, destructive, idempotent hints) were introduced so host UIs can render appropriate confirmation dialogs. The name constraint — 1 to 64 characters, restricted to alphanumeric, underscore, hyphen, dot, and slash — is enforced at registration time and must be stable across server restarts because LLM prompts and user workflows often hard-reference tool names.
When to use
Use a Tool when you want the LLM to perform an action (write a file, query a database, call an API). For read-only context, prefer Resources. For templated prompts, use Prompts.
When NOT to use
Don't use a Tool for static read-only data — that belongs in a Resource. Don't use a Tool when the action requires confirmation that the LLM cannot evaluate; use Elicitation to ask the user first.
Notes
inputSchema is fully validated by clients
The inputSchema field must be a valid JSON Schema object (not just a type hint). Compliant MCP clients validate the LLM-generated arguments against this schema before sending tools/call, so schema errors surface before they hit your handler. Omitting additionalProperties: false is a common oversight — without it, the LLM can pass arbitrary extra keys that your handler may silently ignore, making debugging harder.
Name stability matters for production prompts
Tool names are referenced by the LLM in generated text and may appear in saved user workflows or system prompts. Renaming a tool between deployments is a breaking change even if the server advertises the new name correctly — any cached prompt that hard-references the old name will produce a tool-not-found error. Treat tool names like public API endpoints: version or deprecate rather than rename.
Annotations are hints, not enforcement
The ToolAnnotations fields (readOnly, destructive, idempotent) are advisory metadata for the host UI — they let the client render confirmation dialogs or skip them. They carry no enforcement at the protocol level, so a tool annotated readOnly can still write data if the handler does so. Never rely on annotations as a security boundary; enforce access control inside the handler itself.
outputSchema unlocks structured tool results
When outputSchema is provided, the server contracts to return a structuredContent block whose shape matches the schema, in addition to the human-readable content array. This lets downstream agents and tool-chaining logic parse results programmatically without scraping text. If you declare outputSchema, ensure your handler always populates structuredContent — omitting it when the schema is present is a spec violation that well-typed SDKs will surface as a runtime error.
Tool count affects context window pressure
Each tool definition is serialized into the LLM context as part of the system prompt or tool-use block, consuming tokens proportional to the combined size of name, description, and inputSchema. Registering dozens of tools with verbose schemas can materially shorten the effective context available for conversation. Prefer narrow, well-named schemas over catch-all objects, and consider capability-scoped tool lists when the server supports multiple client personas.
Fields
| Field | Type | Required | Purpose |
|---|---|---|---|
| name | string | yes | 1–64 chars. Alphanumeric, underscore, dash, dot, slash. |
| title | string? | no | Human-friendly display name. |
| description | string? | no | What the tool does. The LLM uses this to decide when to call it. |
| inputSchema | JSON Schema | yes | Validates the tool's input arguments. |
| outputSchema | JSON Schema? | no | Describes the structured output. |
| annotations | ToolAnnotations? | no | readOnlyHint, destructiveHint, idempotentHint, openWorldHint. |
| execution | ToolExecution? | no | Execution behavior (ephemeral vs. persistent). |
| icons | Icon[]? | no | Visual representations of the tool. |
| _meta | object? | no | Reserved metadata field for protocol-level data. |
Examples
A simple search tool
{
"name": "search_docs",
"title": "Search documentation",
"description": "Search the local docs for a query string.",
"inputSchema": {
"type": "object",
"properties": { "query": { "type": "string" } },
"required": ["query"]
},
"annotations": { "readOnlyHint": true, "idempotentHint": true }
}
Tool with no parameters
{
"name": "get_time",
"description": "Return current UTC time.",
"inputSchema": { "type": "object" }
}
Common mistakes
❌ Naming a tool 'Run Query'
✅ Use 'run_query' — names must match the [a-zA-Z0-9_.-/]{1,64} format.
❌ Omitting the description on a tool
✅ Always include a clear description — the LLM uses it to decide when to call the tool.
❌ Putting a destructive operation behind readOnlyHint:true
✅ Use annotations honestly — wrong hints break user trust.