docs/clients/elicitation.mdx
import { VersionBadge } from "/snippets/version-badge.mdx";
<VersionBadge version="2.10.0" />Use this when you need to respond to server requests for user input during tool execution.
Elicitation allows MCP servers to request structured input from users during operations. Instead of requiring all inputs upfront, servers can interactively ask for missing parameters, request clarification, or gather additional context.
Two routes reach that outcome, and the protocol version the client negotiates decides which one applies. On older versions the server pushes an elicitation request down to the client, over the connection the initialize handshake opens; that is the flow the next few sections describe. On 2026-07-28 and later the server instead returns a description of what it needs, and the client answers with a fresh call — see input-required rounds. You write the same elicitation_handler either way — FastMCP routes it to whichever mechanism the connection supports.
from fastmcp import Client
from fastmcp.client.elicitation import ElicitResult, ElicitRequestParams, RequestContext
async def elicitation_handler(
message: str,
response_type: type | None,
params: ElicitRequestParams,
context: RequestContext
) -> ElicitResult | object:
"""
Handle server requests for user input.
Args:
message: The prompt to display to the user
response_type: Python dataclass type for form responses (None for URL requests or empty schemas)
params: Original MCP elicitation parameters
context: Request context with metadata
Returns:
- Data directly (implicitly accepts the elicitation)
- ElicitResult for explicit control over the action
"""
# Present the message and collect input
user_input = input(f"{message}: ")
if not user_input:
return ElicitResult(action="decline")
# URL requests and empty-object schemas have no response type to construct,
# so accepting is the whole response.
if response_type is None:
return ElicitResult(action="accept")
# Create response using the provided dataclass type
return response_type(value=user_input)
client = Client(
"my_mcp_server.py",
mode="legacy",
elicitation_handler=elicitation_handler,
)
When a server needs user input, it sends an elicitation request with a message prompt. Form elicitation requests include a JSON schema describing the expected response structure, and FastMCP automatically converts that schema into a Python dataclass type. URL elicitation requests and empty-object schemas use response_type=None.
The handler receives four parameters:
<Card icon="code" title="Handler Parameters"> <ResponseField name="message" type="str"> The prompt message to display to the user </ResponseField> <ResponseField name="response_type" type="type | None"> A Python dataclass type that FastMCP created from a form request's JSON schema. Use this to construct your response with proper typing. For URL requests or empty-object schemas, this will be `None`. </ResponseField> <ResponseField name="params" type="ElicitRequestParams"> The original MCP elicitation parameters. Form requests carry the raw JSON schema on `params.requested_schema`; URL requests carry `params.url` instead and have no schema. </ResponseField> <ResponseField name="context" type="RequestContext"> Request context containing metadata about the elicitation request </ResponseField> </Card>You can return data directly, which implicitly accepts the elicitation:
async def elicitation_handler(message, response_type, params, context):
user_input = input(f"{message}: ")
return response_type(value=user_input) # Implicit accept
Or return an ElicitResult for explicit control over the action:
from fastmcp.client.elicitation import ElicitResult
async def elicitation_handler(message, response_type, params, context):
user_input = input(f"{message}: ")
if not user_input:
return ElicitResult(action="decline") # User declined
if user_input == "cancel":
return ElicitResult(action="cancel") # Cancel entire operation
return ElicitResult(
action="accept",
content=response_type(value=user_input)
)
Action types:
accept: User provided valid input. Include the data in the content field.decline: User chose not to provide the requested information. Omit content.cancel: User cancelled the entire operation. Omit content.A file management tool might ask which directory to create:
from fastmcp import Client
from fastmcp.client.elicitation import ElicitResult
async def elicitation_handler(message, response_type, params, context):
print(f"Server asks: {message}")
user_response = input("Your response: ")
if not user_response:
return ElicitResult(action="decline")
# Use the response_type dataclass to create a properly structured response
return response_type(value=user_response)
client = Client(
"my_mcp_server.py",
mode="legacy",
elicitation_handler=elicitation_handler
)
On protocol version 2026-07-28 and later, a server can ask for input before it returns a final result. Nothing is held open: the tool returns a description of what it needs, which completes that round as an ordinary response, and the client answers by issuing a new call_tool, get_prompt, or read_resource request carrying the answer. fastmcp.Client drives that loop for you — it fulfils each round's requests using the callbacks you already configured (your elicitation_handler, sampling_handler, and roots) and repeats until the call reaches a terminal result. No extra wiring is needed beyond the handlers described above.
The input_required_max_rounds parameter caps how many rounds the client will answer before giving up, guarding against a server that never terminates. It defaults to 10.
client = Client(
"https://example.com/mcp",
mode="auto",
elicitation_handler=elicitation_handler,
input_required_max_rounds=5,
)