Introduction
Tools are one of the three core primitives in the Model Context Protocol. While resources provide read-only data and prompts offer reusable templates, tools give LLMs the ability to perform actions — executing functions, calling APIs, modifying data, and interacting with external systems. Tools are the bridge between an LLM's reasoning and real-world side effects.
Key Concepts
A tool in MCP is an executable function that a server exposes for LLMs to invoke. Each tool has a well-defined structure:
- name: A unique identifier for the tool (e.g.,
get_weather) - title: A human-readable display name (e.g., "Weather Information Provider")
- description: A natural-language explanation of what the tool does, helping the LLM decide when to use it
- inputSchema: A JSON Schema object defining the expected arguments
Clients discover available tools by sending a tools/list request. The server responds with an array of tool definitions.
Here is an example of a tools/list response:
json{"tools":[{"name":"get_weather","title":"Weather Information Provider","description":"Get current weather for a location","inputSchema":{"type":"object","properties":{"location":{"type":"string","description":"City name or zip code"}},"required":["location"]}}]}
This response tells the client exactly what tools are available, what arguments they accept, and how to describe them to the LLM.
Real World Context
Consider a coding assistant that can search documentation, run tests, or deploy code. Each of these capabilities is exposed as an MCP tool. The LLM reads the tool descriptions, decides which tool to use based on the user's request, and the client orchestrates the invocation. This pattern appears in IDE extensions, chatbots connected to databases, and AI agents that interact with cloud services.
Deep Dive
To invoke a tool, the client sends a tools/call request with the tool name and its arguments. The server executes the function and returns a result.
Here is a complete request and response example:
json{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather","arguments":{"location":"New York"}}}
The server processes the request and returns a result containing a content array:
json{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Temperature: 72°F, Partly cloudy"}],"isError":false}}
The content array in the result can contain multiple content types:
- text: Plain text or structured text output
- image: Base64-encoded image data with a MIME type
- audio: Base64-encoded audio data with a MIME type
- resource: A reference to an MCP resource URI for follow-up reads
Each content item has a type field that tells the client how to render or process it.
Error handling in MCP tools operates at two levels. Tool-level errors are indicated by the isError flag in the result — this means the tool executed but encountered a problem (e.g., city not found). Protocol-level errors use standard JSON-RPC error responses for issues like invalid method names or malformed requests. This separation lets the LLM understand and recover from tool failures gracefully.
Common Pitfalls
- Confusing tool errors with protocol errors: An
isError: truein the result means the tool ran but failed logically. A JSON-RPC error means the request itself was invalid. - Missing required arguments: If the
inputSchemamarks a field as required and the client omits it, the server should reject the call. - Overly vague descriptions: Tool descriptions guide the LLM's decision-making. Vague descriptions lead to incorrect tool selection.
Best Practices
- Write clear, specific tool descriptions so the LLM can accurately choose the right tool.
- Use JSON Schema validation on
inputSchemato catch malformed arguments early. - Return meaningful error messages with
isError: truerather than throwing protocol-level exceptions for business logic failures. - Keep tool names concise and use snake_case for consistency.
Summary
MCP tools are executable functions that LLMs can invoke through a standardized protocol. They are discovered via tools/list, invoked via tools/call, and return structured results in a content array. The protocol separates tool-level errors from transport-level errors, giving LLMs the context they need to handle failures intelligently.
Code Examples
{"tools":[{"name":"get_weather","title":"Weather Information Provider","description":"Get current weather for a location","inputSchema":{"type":"object","properties":{"location":{"type":"string","description":"City name or zip code"}},"required":["location"]}}]}
{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather","arguments":{"location":"New York"}}}
{"jsonrpc":"2.0","id":2,"result":{"content":[{"type":"text","text":"Temperature: 72°F, Partly cloudy"}],"isError":false}}