Tools are the primary way MCP clients interact with your server. They represent functions that can be invoked with parameters and return results. This guide covers everything you need to know about creating powerful and reliable tools.Documentation Index
Fetch the complete documentation index at: https://docs.mcp-use.com/llms.txt
Use this file to discover all available pages before exploring further.
Understanding Tools
Tools in MCP are:- Invocable Functions: Clients can call them with parameters
- Typed: Parameters and returns have defined types
- Async: All tool handlers can be asynchronous
Basic Tool Structure
Every tool has three main components:Input Validation with Zod
Tools use Zod schemas for input validation. The server automatically validates inputs before calling your handler, so you can trust that the parameters match your schema.Basic Input Types
Tool Callbacks
Basic Response
The simplest tool response is text. Use thetext() helper for clean, readable code:
Multiple Content Items
Tools can return multiple content items using themix() helper:
Tool Annotations
Add metadata to tools for better client integration:Returning Widgets from Tools
This is the recommended way to expose widgets to a model. SinceexposeAsTool defaults to false, widgets are registered as MCP resources only — defining a custom tool that calls widget() gives you full control over the tool’s name, description, schema, and business logic.
You must include the
widget: { name, ... } config in your tool definition when returning widgets. This sets up all the registration-time metadata needed for proper widget rendering. The widget file must exist in your resources/ folder, but does not need exposeAsTool: true — leaving it unset (or false) is the correct setup for this pattern.widget: { name, invoking, invoked, ... }on tool definition - Configures all widget metadata at registration timewidget({ props, output, message })helper - Returns runtime data only:props- Widget-only data passed touseWidget().props(hidden from model)output- Optional response helper (text(), object(), etc.) that the model seesmessage- Optional text message override
- The widget must exist in your
resources/folder as a.tsxfile or folder
OpenAI Apps SDK Integration
For ChatGPT and OpenAI compatible clients:Error Handling with error()
Theerror() helper provides a standardized way to return error responses from tools. It sets the isError flag to true, allowing clients to distinguish between successful results and error conditions. This ensures consistent error handling across your MCP server.
The error() helper creates a properly formatted error response with:
isError: trueflag to indicate failure- Text content with your error message
- Proper MIME type metadata
Client Identity & Caller Context
Every tool handler receivesctx.client, which exposes both session-level client information and per-invocation caller context.
Session-level client info
These values come from the MCPinitialize handshake and remain stable for the lifetime of the connection:
Per-invocation caller context — ctx.client.user()
ctx.client.user() returns normalized metadata from params._meta that some clients send with every tools/call request. It returns undefined for clients that do not include this metadata (Inspector, Claude Desktop, CLI, etc.).
UserContext:
| Field | Type | Description |
|---|---|---|
subject | string | Stable opaque user identifier (same across conversations) |
conversationId | string | Current chat thread ID (changes per chat) |
locale | string | BCP-47 locale, e.g. "it-IT" — server-side, set at session start (see note below) |
location | object | { city, region, country, timezone, latitude, longitude } |
userAgent | string | Browser / host user-agent string |
timezoneOffsetMinutes | number | UTC offset in minutes |
locale vs useWidget().locale: ctx.client.user()?.locale is detected server-side from the user’s ChatGPT account language at session start. Inside a widget, useWidget().locale is the preferred alternative — it reads the same preference client-side (from window.openai.locale for the Apps SDK, or HostContext.locale for SEP-1865 hosts) and is therefore fresher and browser-aware. The values are usually the same but can differ when the account language differs from the browser language.ChatGPT multi-tenant model
ChatGPT establishes a single MCP session for all users of a deployed app. The MCP session ID alone is not enough to identify individual callers — usectx.client.user() for that:
Logging from Tools
Tools can send log messages to clients during execution usingctx.log(). This is useful for reporting progress, debugging tool behavior, and providing real-time feedback during long-running operations.
ctx.log() function accepts a log level ('debug', 'info', 'notice', 'warning', 'error', 'critical', 'alert', 'emergency'), a message string, and an optional logger name. See Server Logging for complete documentation on log levels and best practices.
Notifying Clients of Tool Changes
When dynamically adding or removing tools, notify clients to refresh their tools cache:Testing
Use the built-in inspector for interactive testing:- Start your server with the inspector
- Navigate to
http://localhost:3000/inspector - Select your tool from the list
- Enter parameter values
- Execute and view results
Next Steps
- Response Helpers - Utility functions for creating responses
- Resources Guide - Managing static and dynamic content
- UI Widgets - Creating interactive UI components
- Examples - Real-world tool implementations