MCP Elicitation
Explore how MCP elicitation enables AI workflows to pause and request structured input from users during tasks. Understand form and URL modes, response handling, and best security practices. This lesson helps you build more interactive and trustworthy AI agents using MCP.
Imagine a developer building a travel booking tool as part of an MCP AI project. The user sends a single message: Book me a flight to London. The tool fires immediately. But two pieces of information are missing: the departure date and the preferred seat type. Without a way to pause and ask, the MCP workflow has two bad options: fail with an error, or make assumptions and book the wrong flight.
This is the problem MCP elicitation solves. Rather than failing or guessing, the server pauses execution, asks the user for exactly what it needs, and picks up right where it left off once the user responds. The workflow continues without restarting, without losing context, and without surprises.
What is MCP elicitation?
MCP elicitation is a client-side primitive that allows an MCP server to pause execution mid-task and request structured information from the user through the client. It was introduced in MCP spec version 2025-06-18 as a first-class feature for building interactive, human-in-the-loop MCP integration workflows.
To understand where elicitation fits in MCP architecture, consider the direction of communication. MCP server tools are server-exposed capabilities that the client calls. The client reaches out to the server to get work done. Elicitation runs in the opposite direction: it is a client-exposed capability that the server calls. The server reaches out to the client to gather what it needs before continuing.
The flow is always fixed: the server initiates the request, the client presents it to the user, the user responds, and the server resumes with the provided information. Neither the server nor the model can skip the user. The client sits in the middle and controls the interaction.
For elicitation to work in any MCP setup, both sides must declare support. During the initialization handshake, the client advertises its elicitation capability, and the server knows it can safely make elicitation requests during the session.
Modes of elicitation
MCP elicitation has two distinct modes, each designed for a different class of interaction.
Form mode
Form mode is the original elicitation mode, available since spec version 2025-06-18. The server sends a JSON Schema describing the data it needs, and the client renders an appropriate form for the user to fill in. The user’s response travels back through the MCP client to the server.
Form mode works well for non-sensitive inputs: names, preferences, dates, configuration values, or any structured data that does not need to stay confidential. A customer support tool might use form mode to ask for a ticket category. A scheduling tool might use it to collect a meeting duration and urgency level. These are the kinds of inputs that belong in a standard MCP workflow because they carry no security risk in transit.
Servers must not use form mode to request passwords, API keys, access tokens, or any sensitive credentials. The data passes through the client, which means it is visible to the MCP layer. For anything sensitive, URL mode is required.
URL mode
URL mode was introduced in MCP spec version 2025-11-25. The server provides a URL, and the client opens it in a secure browser context. The user completes the interaction directly on the server’s web page, and any data they enter goes straight to the server. It never passes through the MCP client.
This makes URL mode the right choice for OAuth authorization flows, API key collection, and payment processing. These are MCP services integrations where credentials must remain confidential and where the interaction often needs to happen on a trusted, branded page outside of the agent environment.
The user responses
Every elicitation request, regardless of mode, results in one of three responses:
accept: The user submitted the requested information; the server continues with the provided data.decline: The user explicitly refused; the server should handle this gracefully and consider offering an alternative path.cancel: The user dismissed the request without making an explicit choice (closed the dialog, pressed Escape). The server can treat this as ambiguous and may re-prompt at an appropriate point.
When the client returns accept in URL mode, it only means the user consented to opening the URL. It does not mean the flow is complete. The server sends a notifications/elicitation/complete notification once the out-of-band interaction finishes, and the client can then retry the original request automatically.
The distinction between decline and cancel matters for mcp workflow design. A decline is a clear signal to stop pursuing that path. A cancel is ambiguous: the user may simply have been interrupted. Handling them differently leads to more natural, forgiving interactions and avoids frustrating users who dismissed a dialog by accident.
Elicitation in code
FastMCP is the leading MCP library for Python and provides a clean, high-level interface for elicitation via await ctx.elicit(). The examples below walk through the feature from simple to practical.
Basic input with response handling
import fastmcpfrom mcp import Contextmcp = fastmcp.FastMCP("booking-assistant")@mcp.toolasync def greet_user(ctx: Context) -> str:# Ask the user for their name before personalizing the responseresult = await ctx.elicit("What is your name?", response_type=str)if result.action == "accept":return f"Hello, {result.data}! How can I help you today?"elif result.action == "decline":# User explicitly said no, so respond without a namereturn "No problem. How can I help you today?"else:# cancel: user dismissed without responding, so keep it openreturn "Whenever you are ready, feel free to ask."
The result.action check is the core pattern. Every elicitation call should handle all three outcomes. Skipping any one of them means the tool will behave unpredictably when users do not follow the happy path.
Structured input for the travel booking scenario
This is the MCP integration pattern from our opening scenario: collecting structured, multi-field data mid-tool-call using a Pydantic model.
from pydantic import BaseModel, Fieldfrom typing import Literalfrom mcp import Contextimport fastmcpmcp = fastmcp.FastMCP("booking-assistant")# Define the schema for the two fields we needclass FlightPreferences(BaseModel):departure_date: str = Field(description="Departure date, e.g. 2025-09-15")seat_type: Literal["window", "aisle", "middle"] = Field(default="aisle",description="Preferred seat type")@mcp.toolasync def book_flight(destination: str, ctx: Context) -> str:# Pause and ask the user for the details the model did not provideresult = await ctx.elicit(f"To book your flight to {destination}, we need a few details.",response_type=FlightPreferences)if result.action == "accept":prefs = result.data# Resume the mcp workflow with the collected informationreturn (f"Booking confirmed: {destination} on {prefs.departure_date}, "f"{prefs.seat_type} seat.")elif result.action == "decline":return "Flight booking cancelled at your request."else:return "Booking paused. Let us know when you would like to continue."
FastMCP converts the Pydantic model into a JSON Schema and sends it to the client as a form mode elicitation request. The client renders each field as an input, validates the response against the schema, and returns the structured data in result.data.
Multi-turn workflow
Elicitation also supports progressive information gathering, collecting one piece at a time across a multi-step MCP workflow rather than presenting everything at once.
from typing import Literalfrom mcp import Contextimport fastmcpmcp = fastmcp.FastMCP("scheduling-assistant")@mcp.toolasync def schedule_meeting(ctx: Context) -> str:# Step 1: collect the meeting titletitle_result = await ctx.elicit("What is the meeting title?", response_type=str)if title_result.action != "accept":return "Meeting scheduling cancelled."# Step 2: collect the durationduration_result = await ctx.elicit("Duration in minutes?", response_type=int)if duration_result.action != "accept":return "Meeting scheduling cancelled."# Step 3: confirm urgency before finalizingurgency_result = await ctx.elicit("Is this meeting urgent?",response_type=Literal["yes", "no"])if urgency_result.action != "accept":return "Meeting scheduling cancelled."urgent = urgency_result.data == "yes"return (f"Meeting '{title_result.data}' scheduled for "f"{duration_result.data} minutes. Urgent: {urgent}.")
Note: Each
await ctx.elicit()call pauses execution and waits for the user before moving to the next step. If the user declines or cancels at any point, the workflow stops cleanly rather than continuing with incomplete information.
Security rules for elicitation
Elicitation touches user input directly, which makes it a surface worth getting right. These rules come from the MCP specification and represent MCP server security best practices that every MCP project should follow before going to production.
Servers must not use form mode to request passwords, API keys, access tokens, or payment credentials
Servers must use URL mode for any authentication, credential, or payment flow
Clients must display which server is making the elicitation request so the user knows who is asking
Clients must give the user a clear option to decline or cancel at any point
Clients must open URLs in a secure browser context, not an embedded WebView that the application or the model could inspect
Clients must show the full URL to the user before they consent to opening it
Security warning: URL mode carries a specific phishing risk. A malicious user can trigger an elicitation URL and then trick a different user into completing it, binding the victim’s credentials to the attacker’s session. To prevent this, servers must bind elicitation state to the authenticated user’s identity by verifying a claim from the user’s authorization token, and confirm that the same user who initiated the elicitation is the one who completes it.
Conclusion
Elicitation is one of the features that moves MCP from a simple tool-calling protocol toward a foundation for genuinely interactive, production-ready AI systems. As the MCP ecosystem grows and teams build more sophisticated MCP projects across tools, platforms, and services, the ability to pause, ask, and adapt mid-workflow becomes essential to building agents that users can actually trust. Getting comfortable with elicitation now means being prepared for the more complex orchestration patterns that come next.