server.tool(config, handler) — config and handler are separate arguments, which gives TypeScript left-to-right generic inference: the schema type is resolved from the config before the handler’s argument types are checked.
Schemas
FastMCP accepts any Standard Schema-compatible validator — Zod, Valibot, ArkType, and others all work through the sameinput field:
Validation vs. advertisement
Tool config has two orthogonal schema layers:input/output— Standard Schema validators used for runtime validation.inputSchema/outputSchema— explicit JSON Schema objects advertised to clients intools/list. If omitted, FastMCP auto-generates JSON Schema frominput/output(via Zod v4’sz.toJSONSchema()for Zod schemas). When auto-generation can’t produce a real schema, it falls back to{ type: 'object' }and emits aconsole.warn.
inputSchema — provide it only when you want to advertise something different from what you validate.
Validation semantics
- Input: when
inputis provided, client-supplied arguments are validated before the handler runs. A failure throwsMcpError(InvalidParams)— a protocol-level error meaning the client sent bad arguments. The handler is never invoked. - Output: when
outputis provided, the handler’s raw return value is validated before result conversion. The output schema describes your handler’s contract (primitives, objects, and arrays are all valid), not the MCP content shape. A failure returnsisError: true— a tool execution error, not a protocol error, because the client’s input was valid.
Return values
Handlers return plain values; FastMCP converts them to MCP content automatically:| Returned value | Conversion |
|---|---|
string | Text content block |
number, boolean | Stringified text content block |
undefined / void | Empty result |
| Plain object | JSON text content block + structuredContent |
| Array | JSON text content block (no structuredContent — the MCP spec requires it to be an object) |
Image(buffer, mimeType) | Image content block |
File(buffer, name, mimeType) | Binary blob content block |
ToolResult(...) | Passed through as-is |
Buffer, Uint8Array) always requires an explicit Image or File wrapper — a MIME type can’t be inferred from raw bytes.
ToolResult is the escape hatch for full control: multiple content blocks, suppressing structuredContent, or constructing raw MCP output.
Name and description inference
Bothname and description are optional. When name is omitted, the handler function’s .name is used. When description is omitted, it is derived from the resolved name by converting camelCase to words (getWeather → "get weather").
Other config fields
| Field | Behavior |
|---|---|
title | Human-readable label for UIs; takes precedence over name for display and is passed through in tools/list |
timeout | Per-call timeout in milliseconds; a timed-out handler propagates as an error to the client |
disabled | When true, the tool is completely inaccessible — hidden from tools/list and rejected with InvalidParams on call. Clients cannot distinguish it from a non-existent tool |
tags | Free-form strings used by transforms like VersionFilter |
auth | Per-tool authorization check — see Authorization |
task | Opt into long-running task execution — see Tasks |
Errors
Two distinct error channels:- Tool execution errors — a thrown non-
McpErrorfrom your handler is caught and returned as{ isError: true }with the message as content. The protocol call itself succeeds. - Protocol errors — a thrown
McpError(from input validation, middleware like rate limiting, or your own code) propagates as a JSON-RPC error.
Dynamic registration
Tools can be registered before or afterrun(). Adding a tool to a running server automatically sends notifications/tools/list_changed to connected clients.