Use UserError for errors that should be shown to the end user:
import { FastMCP, UserError } from "fastmcp";import { z } from "zod";const server = new FastMCP({ name: "My Server", version: "1.0.0",});server.addTool({ name: "download", description: "Download a file from a URL", parameters: z.object({ url: z.string().url(), }), execute: async (args) => { // Check for blacklisted domains if (args.url.includes("blocked-site.com")) { throw new UserError("This domain is not allowed"); } // Attempt download try { const response = await fetch(args.url); if (!response.ok) { throw new UserError( `Failed to download: ${response.status} ${response.statusText}` ); } return await response.text(); } catch (error) { if (error instanceof UserError) { throw error; // Re-throw UserError } // Convert network errors to user-friendly messages throw new UserError("Unable to connect to the URL. Please check the address and try again."); } },});
Validation failures - Invalid input that passed schema validation but failed business rules
Permission errors - User lacks required permissions
Resource not found - Requested resource doesn’t exist
External API failures - Third-party service errors that users need to know about
Configuration errors - Missing or invalid configuration
Do not use UserError for programming errors (bugs) or unexpected internal errors. Let those bubble up as regular errors for proper logging and debugging.
FastMCP automatically handles schema validation errors. You don’t need to throw UserError for these:
import { z } from "zod";server.addTool({ name: "createUser", description: "Create a new user", parameters: z.object({ email: z.string().email("Must be a valid email address"), age: z.number().min(18, "Must be at least 18 years old"), }), execute: async (args) => { // Validation happens automatically before execute is called return `Created user with email: ${args.email}`; },});// Client receives validation errors automatically:// - "Must be a valid email address" if email is invalid// - "Must be at least 18 years old" if age < 18
Tell users exactly what went wrong and what they can do to fix it.
// Badthrow new UserError("Invalid input");// Goodthrow new UserError("File size exceeds 10MB limit. Please upload a smaller file.");
2
Avoid exposing sensitive information
Don’t include internal paths, API keys, or system details.
// Badthrow new UserError(`Database error: ${dbError.stack}`);// Goodthrow new UserError("Unable to save data. Please try again later.");
3
Use consistent language
Maintain a consistent tone and style across all error messages.
// Consistent stylethrow new UserError("Unable to process request. The file format is not supported.");throw new UserError("Unable to save changes. The session has expired.");