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.
Response Helpers: Throughout this guide, you’ll see code examples using response helpers like text(), object(), and mix(). These utilities simplify creating tool responses with proper typing and metadata. See Response Helpers for a complete reference.
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.
import { z } from 'zod';server.tool({ name: 'process_message', description: 'Process a message with various options', schema: z.object({ // Required string message: z.string().describe('The message to process'), // Optional number with default count: z.number().default(10).describe('Number of items'), // Optional boolean with default verbose: z.boolean().default(false).describe('Enable verbose output'), // Optional object config: z.object({ setting1: z.string(), setting2: z.number() }).optional().describe('Configuration object'), // Array of strings items: z.array(z.string()).describe('List of items to process') })}, async ({ message, count, verbose, config, items }) => { // message, count, verbose, config, items are fully typed and validated // count will be 10 if not provided // verbose will be false if not provided return text('Results...')})
Tools can return interactive UI widgets using the widget() helper with the widget config option. This automatically configures the tool to render widgets in the Inspector and ChatGPT.
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.
Copy
Ask AI
import { widget, text } from 'mcp-use/server';import { z } from 'zod';server.tool({ name: 'get-weather', description: 'Get current weather for a city', schema: z.object({ city: z.string().describe('City name') }), // Widget config sets all registration-time metadata widget: { name: 'weather-display', // Must match a widget in resources/ invoking: 'Fetching weather...', invoked: 'Weather loaded' }}, async ({ city }) => { // Fetch weather data const weatherData = await fetchWeather(city); // Return widget with runtime data only return widget({ props: weatherData, output: text(`Weather in ${city}: ${weatherData.temp}°C`), message: `Current weather in ${city}` });});
How it works:
widget: { name, invoking, invoked, ... } on tool definition - Configures all widget metadata at registration time
Recommended Approach: The recommended way to create OpenAI Apps SDK widgets is to use the widget helper with returnsWidget option or automatic widget mounting, which automatically registers widgets from the resources/ folder as tools. The manual approach below is shown for reference.
The error() 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: true flag to indicate failure
Text content with your error message
Proper MIME type metadata
Copy
Ask AI
import { object, error } from 'mcp-use/server';server.tool({ name: 'external_api', description: 'Call external API', schema: z.object({ endpoint: z.string().url().describe('The API endpoint URL') })}, async ({ endpoint }) => { try { const data = await callExternalAPI(endpoint); return object(data); } catch (err) { // Use error() helper to signal failure to the client return error( `Unable to fetch data from ${endpoint}.\n` + `Error: ${err.message}\n` + `Please check the endpoint and try again.` ); }})
Tools can send log messages to clients during execution using ctx.log(). This is useful for reporting progress, debugging tool behavior, and providing real-time feedback during long-running operations.
The 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.