New in version: 2.10.0

User elicitation allows MCP servers to request structured input from users during tool execution. Instead of requiring all inputs upfront, tools can interactively ask for missing parameters, clarification, or additional context as needed.

Most of the examples in this document assume you have a FastMCP server instance named mcp and show how to use the ctx.elicit method to request user input from an @mcp.tool-decorated function.

What is Elicitation?

Elicitation enables tools to pause execution and request specific information from users. This is particularly useful for:

  • Missing parameters: Ask for required information not provided initially
  • Clarification requests: Get user confirmation or choices for ambiguous scenarios
  • Progressive disclosure: Collect complex information step-by-step
  • Dynamic workflows: Adapt tool behavior based on user responses

For example, a file management tool might ask “Which directory should I create?” or a data analysis tool might request “What date range should I analyze?”

Basic Usage

Use the ctx.elicit() method within any tool function to request user input:

from fastmcp import FastMCP, Context
from dataclasses import dataclass

mcp = FastMCP("Elicitation Server")

@dataclass
class UserInfo:
    name: str
    age: int

@mcp.tool
async def collect_user_info(ctx: Context) -> str:
    """Collect user information through interactive prompts."""
    result = await ctx.elicit(
        message="Please provide your information",
        response_type=UserInfo
    )
    
    if result.action == "accept":
        user = result.data
        return f"Hello {user.name}, you are {user.age} years old"
    elif result.action == "decline":
        return "Information not provided"
    else:  # cancel
        return "Operation cancelled"

Method Signature

Context Elicitation Method

ctx.elicit
async method

Elicitation Actions

The elicitation result contains an action field indicating how the user responded:

  • accept: User provided valid input - data is available in the data field
  • decline: User chose not to provide the requested information and the data field is None
  • cancel: User cancelled the entire operation and the data field is None
@mcp.tool
async def my_tool(ctx: Context) -> str:
    result = await ctx.elicit("Choose an action")

    if result.action == "accept":
        return "Accepted!"
    elif result.action == "decline":
        return "Declined!"
    else:
        return "Cancelled!"

FastMCP also provides typed result classes for pattern matching on the action field:

from fastmcp.server.elicitation import (
    AcceptedElicitation, 
    DeclinedElicitation, 
    CancelledElicitation,
)

@mcp.tool
async def pattern_example(ctx: Context) -> str:
    result = await ctx.elicit("Enter your name:", response_type=str)
    
    match result:
        case AcceptedElicitation(data=name):
            return f"Hello {name}!"
        case DeclinedElicitation():
            return "No name provided"
        case CancelledElicitation():
            return "Operation cancelled"

Response Types

The server must send a schema to the client indicating the type of data it expects in response to the elicitation request. If the request is accept-ed, the client must send a response that matches the schema.

The MCP spec only supports a limited subset of JSON Schema types for elicitation responses. Specifically, it only supports JSON objects with primitive properties including string, number (or integer), boolean and enum fields.

FastMCP makes it easy to request a broader range of types, including scalars (e.g. str), by automatically wrapping them in MCP-compatible object schemas.

Scalar Types

You can request simple scalar data types for basic input, such as a string, integer, or boolean.

When you request a scalar type, FastMCP automatically wraps it in an object schema for MCP spec compatibility. Clients will see a corresponding schema requesting a single “value” field of the requested type. Once clients respond, the provided object is “unwrapped” and the scalar value is returned to your tool function as the data field of the ElicitationResult object.

As a developer, this means you do not have to worry about creating or accessing a structured object when you only need a scalar value.

@mcp.tool
async def get_user_name(ctx: Context) -> str:
    """Get the user's name."""
    result = await ctx.elicit("What's your name?", response_type=str)
    
    if result.action == "accept":
        return f"Hello, {result.data}!"
    return "No name provided"

Constrained Options

Often you’ll want to constrain the user’s response to a specific set of values. You can do this by using a Literal type or a Python enum as the response type, or by passing a list of strings to the response_type parameter as a convenient shortcut.

@mcp.tool
async def set_priority(ctx: Context) -> str:
    """Set task priority level."""
    result = await ctx.elicit(
        "What priority level?", 
        response_type=["low", "medium", "high"],
    )
    
    if result.action == "accept":
        return f"Priority set to: {result.data}"

Structured Responses

You can request structured data with multiple fields by using a dataclass, typed dict, or Pydantic model as the response type. Note that the MCP spec only supports shallow objects with scalar (string, number, boolean) or enum properties.

from dataclasses import dataclass
from typing import Literal

@dataclass
class TaskDetails:
    title: str
    description: str
    priority: Literal["low", "medium", "high"]
    due_date: str

@mcp.tool
async def create_task(ctx: Context) -> str:
    """Create a new task with user-provided details."""
    result = await ctx.elicit(
        "Please provide task details",
        response_type=TaskDetails
    )
    
    if result.action == "accept":
        task = result.data
        return f"Created task: {task.title} (Priority: {task.priority})"
    return "Task creation cancelled"

Multi-Turn Elicitation

Tools can make multiple elicitation calls to gather information progressively:

@mcp.tool
async def plan_meeting(ctx: Context) -> str:
    """Plan a meeting by gathering details step by step."""
    
    # Get meeting title
    title_result = await ctx.elicit("What's the meeting title?", response_type=str)
    if title_result.action != "accept":
        return "Meeting planning cancelled"
    
    # Get duration
    duration_result = await ctx.elicit("Duration in minutes?", response_type=int)
    if duration_result.action != "accept":
        return "Meeting planning cancelled"
    
    # Get priority
    priority_result = await ctx.elicit(
        "Is this urgent?", 
        response_type=Literal["yes", "no"]
    )
    if priority_result.action != "accept":
        return "Meeting planning cancelled"
    
    urgent = priority_result.data == "yes"
    return f"Meeting '{title_result.data}' planned for {duration_result.data} minutes (Urgent: {urgent})"

Client Requirements

Elicitation requires the client to implement an elicitation handler. See Client Elicitation for details on how clients can handle these requests.

If a client doesn’t support elicitation, calls to ctx.elicit() will raise an error indicating that elicitation is not supported.