When users interact with AI agents, a tool may require confirmation or additional input before it runs. Asking for every possible detail at the start makes users answer questions that may never matter. Letting the model guess can lead to the wrong action.
MCP elicitation allows the server to request missing input during a tool call. The MCP client presents the question to the user and returns their response, allowing the server to decide whether to continue. The server controls what to ask and when. The client controls how the question appears.
If you’re building an agent into your SaaS product, your application needs to handle these requests and present them to your users. This article explains the request flow, how to support it in a customer-facing agent, and when to use elicitation instead of tool arguments or approval policies.
What is MCP elicitation?
MCP elicitation is a feature of the Model Context Protocol that allows a server to ask the user a question midway through a request. The server sends an elicitation/create request with a message and, in form mode, a requestedSchema. The client shows it, collects the answer, and returns one of three actions: accept, decline, or cancel. When the user accepts a form request, the client returns their answers in content.
Elicitation has two modes:
- Form mode collects structured data inside the client, using a JSON Schema that the server sends. Form mode was introduced in the 2025-06-18 revision.
- URL mode sends the user to a web page outside the client for steps that must not pass through it, such as OAuth or entering a secret. It was added on 2025-11-25.
The 2026-07-28 specification supports both modes. Elicitation works inside tools/call, prompts/get, and resources/read.
How elicitation works
Elicitation uses the Multi Round-Trip Requests pattern (MRTR). The server returns an input_required result containing the question, and the client retries the original call with the answers attached.
sequenceDiagram
participant User
participant Client as Application (MCP client)
participant Server as MCP server
Client->>Server: tools/call delete_project (id: 1)
Server-->>Client: resultType: input_required<br/>inputRequests + requestState
Client->>User: Render form from requestedSchema
User-->>Client: Accept, decline, or cancel
Client->>Server: tools/call delete_project (id: 2)<br/>inputResponses + same requestState
Server-->>Client: resultType: complete
Each retry is a separate request. The server can encode the context it needs to resume the operation in requestState, and the client returns that string unchanged. A server instance can then pick up the call again without stored state.
The following example asks the user to approve a deletion and choose a backup retention period. The JSON-RPC wrapper and _meta fields are left out.
{
"resultType": "input_required",
"inputRequests": {
"confirm_delete": {
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Delete project prj_123? This cannot be undone.",
"requestedSchema": {
"type": "object",
"properties": {
"confirm": { "type": "boolean", "title": "I understand this is permanent", "default": false },
"retention": {
"type": "string",
"title": "Backup retention",
"oneOf": [
{ "const": "none", "title": "Delete immediately" },
{ "const": "7d", "title": "Keep a backup for 7 days" },
{ "const": "30d", "title": "Keep a backup for 30 days" }
],
"default": "7d"
}
},
"required": ["confirm", "retention"]
}
}
}
},
"requestState": "<opaque-server-state>"
}
The client retries the same tools/call with a new request ID and puts the user’s answer under the matching key. These are the additional fields in the retried request’s params. The original method and arguments, the JSON-RPC wrapper, and the required _meta fields are omitted:
{
"inputResponses": {
"confirm_delete": {
"action": "accept",
"content": { "confirm": true, "retention": "7d" }
}
},
"requestState": "<opaque-server-state>"
}
If the user declines or cancels, the client sends that action instead, and the server decides what toample, do next, for ex returning an error or asking again.

Form elicitation and schemas
In form mode the server sends a JSON Schema, the client renders one field per property, and the answers come back in content.
| Type | Common supported constraints |
|---|---|
| `string` | `minLength`, `maxLength`, `format` (`email`, `uri`, `date`, `date-time`), `default` |
| `number`, `integer` | `minimum`, `maximum`, `default` |
| `boolean` | `default` |
| Single-select enum | `enum`, or `oneOf` with `const` and `title` pairs |
| Multi-select enum | `array` with `minItems`, `maxItems`, and `items` as an enum |
Nested data, such as an address object or a list of line items, cannot be requested in one form, and $ref is not supported. Split it into flat fields or make it a tool argument the model fills. An enum can show a label that differs from its value: with oneOf, the user sees “Keep a backup for 7 days” and the server receives 7d. Clients are expected to validate the form but not required to, so validate content on the server before acting on it.
MCP elicitation in customer-facing AI agents
The server supplies the schema. Your application still needs to present it and return the response.
Claude Code and VS Code provide interfaces for elicitation. Claude Code shows a terminal dialog for form mode and opens the browser for URL mode. VS Code shows forms in its Ask Questions UI. Cursor and Goose also document support.
In your SaaS product, your application must connect the MCP client’s request handler to the user interface. A common setup, and the one used in the rest of this section, is a backend that runs the agent loop and hosts the MCP client, with a browser frontend for the chat. MCP does not require this split.
The client must declare support, handle the request, collect the response, and return it to the server:
- Declare the capability. Under 2026-07-28, client capabilities travel in
_meta.io.modelcontextprotocol/clientCapabilitieson each request. The SDKs fill this in from the client’s configuration. Declare only the modes you implement. An emptyelicitation: {}means form only. - Handle the request. Register a handler that receives the
messageandrequestedSchema(or theurl) and returnsaccept,decline, orcancel. - Collect the response. The spec requires the client to identify which server is asking, let the user review and modify their answers before sending, and offer decline and cancel. Showing the form inside the chat is your design choice.
- Return it to the server. Under MRTR the client sends the original request again with
inputResponsesand the samerequestState. The official SDKs do this for you.
SDK support for elicitation
| Client surface | Elicitation support (September 2026) | Notes |
|---|---|---|
| MCP TypeScript SDK v2 | Form and URL modes | Handler via `setRequestHandler("elicitation/create")`; the `inputRequired` option retries automatically, up to `maxRounds` (default 10) |
| MCP Python SDK v2 | Form and URL modes | Pass `elicitation_callback` to `Client`. Registration advertises form and URL support. On 2026-07-28 connections, the client dispatches embedded requests to the callback and retries automatically |
| Vercel AI SDK | Form mode documented | `capabilities: { elicitation: {} }` on `createMCPClient`, then `onElicitationRequest(ElicitationRequestSchema, handler)`; the linked MCP guide does not document URL mode or MRTR |
| OpenAI Agents SDK | Not documented | The MCP guide documents `require_approval` for per-tool-call approval, not an elicitation handler |
| Anthropic MCP connector (Messages API `mcp_servers`) | Not supported | Only tool calls are supported; run your own MCP client with the SDK's client-side helpers if you need elicitation |
This TypeScript SDK v2 handler forwards form requests to your application’s chat UI. askUserInChat is a function you write, and transport setup is omitted. It is an illustration, not a complete client:
import { Client } from "@modelcontextprotocol/client";
const client = new Client(
{ name: "acme-assistant", version: "1.0.0" },
{
capabilities: { elicitation: { form: {} } },
inputRequired: { maxRounds: 3 },
}
);
client.setRequestHandler("elicitation/create", async (request) => {
if (request.params.mode === "url") {
throw new Error("This client supports form elicitation only");
}
const { message, requestedSchema } = request.params;
// Application-defined: sends the request to the browser and resolves with the user's decision
const result = await askUserInChat({ serverName: "billing", message, requestedSchema });
return result.submitted
? { action: "accept", content: result.values }
: { action: result.declined ? "decline" : "cancel" };
});
Connecting the handler to your chat UI
The SDK handles the MCP exchange. Your application must carry the question to the browser and return the user’s answer. The handler runs on your backend, inside the agent loop. The user answers in your web application. askUserInChat must send the request to the browser and route the response back to the pending call:
sequenceDiagram
participant Browser as Chat UI (browser)
participant Backend as Your backend<br/>(agent loop + MCP client)
participant Server as MCP server
Browser->>Backend: "Delete the Acme project"
Backend->>Server: tools/call delete_project
Server-->>Backend: input_required (form schema)
Backend-->>Browser: app event: input_required<br/>(message, schema, pending id)
Browser->>Backend: POST /agent/input {pending id, values}
Backend->>Server: tools/call delete_project (retry)<br/>inputResponses + requestState
Server-->>Backend: complete
Backend-->>Browser: stream: tool result, assistant reply
The event to the browser and the endpoint it posts back to are your application’s transport, not part of MCP. You can keep the turn open or save the request and resume later:
- Hold the turn open. The handler saves a pending-input record, streams an event to the browser over the same SSE or WebSocket channel as the assistant’s tokens, and waits on a promise. An endpoint resolves that promise when the user submits. Add a timeout that returns
cancel, or an unanswered form can leave pending work open indefinitely. With legacy back-channel elicitation, on protocol versions before 2026-07-28, your MCP implementation must keep the pending interaction available until it receives the response. - Park and resume. Because an MRTR retry is a separate request, you can save the original operation,
inputRequests, andrequestStatewith the conversation, end the turn, and send the retry when the user answers, even from another process minutes later. Tie the pending reply to the authenticated user and conversation, and check that link when the browser submits. In the TypeScript SDK, setinputRequired: { autoFulfill: false }to manage the rounds yourself. A call that needs input then rejects with a typed error that you handle. This lets users answer after leaving and reopening the conversation.
In both cases, display the server’s message directly and label it separately from the assistant’s reply.
For URL mode, the handler collects consent to open the page. Credentials stay outside the MCP exchange. The client shows the full URL and its domain, asks the user to agree, and opens the page in a browser context that neither the MCP client nor the model can inspect. It must not fetch the URL ahead of time. The URL itself must contain no end-user secrets or personal information and must not grant pre-authenticated access to a protected resource. An accept only means the user agreed to open the page. The server finds out whether the outside step finished when the original call is retried.
When to use elicitation
Use tool arguments for known values, elicitation for missing user input, and approval gates for permission to run a tool.
| You need | Use | Why |
|---|---|---|
| A value the model can work out from the conversation | A required tool argument | No extra elicitation round trip; the model supplies the argument |
| A value only the user knows, checked against a schema | Form elicitation | The server defines the schema; the client can validate the form, and the server validates the returned content |
| Approval before a step inside a tool that cannot be undone or costs money | Form elicitation with a boolean | The server checks the answer right before it acts |
| Approval before running a tool at all | A client-side approval gate | Does not depend on the server; your UI decides per tool call |
| An error the model can fix, such as a missing field | A structured tool error | Lets the model correct the call and retry without another user prompt |
| A choice between look-alike matches, such as two contacts named Sam Lee | Form elicitation with an enum | Labels show the user what differs; the server receives the ID |
| A credential, token, or payment detail | URL mode or your own connect flow | Form mode must never carry these |
| Settings the user should set once, not per conversation | Your product's onboarding UI | Do not make the agent ask every time |
Reasons not to use elicitation:
- Frequent prompts can reduce attention. Anthropic reported that Claude Code users approved roughly 93% of permission prompts, and paid less attention as the number of prompts grew. That figure is about permission prompts, not elicitation forms, but the same pattern applies. Reserve confirmation for actions whose consequences justify interrupting the user.
- It adds a user interaction. Under MRTR, the client retries the original request after collecting the answer. For a value the model can get from context, a tool argument is faster.
Form elicitation must never collect sensitive data
Credentials need a separate flow because form responses pass through the MCP client.
Servers must not use form mode to collect secrets or credentials used to access services or authorize transactions: passwords, API keys, access tokens, and payment credentials. Names and emails are allowed. A confirmation to proceed with a paid step is different from collecting payment credentials. Form content can end up in the model context or logs, which is why the line is drawn there.
Use URL mode for credentials and other sensitive interactions. The server sends the user to a page, typically on its own domain, where the user signs in or enters the secret, and the server stores the result under that user. The server must check that the person who opens the page is the same user who started the elicitation. Otherwise, the link can be sent to a victim, and the victim’s account ends up tied to the attacker’s session. The server must also not reuse the client’s MCP token to call the third-party API, which the security best practices forbid.
That leaves the rest of the work: running each provider’s authorization flow, storing and refreshing credentials, and giving the agent scoped access without exposing them. Keep customers’ third-party credentials out of the model context.
Nango connects your products and agents to 900+ APIs. It provides 7,000+ pre-built tools and covers every integration type: auth, tool calls, triggers, and syncs. You can get started in 10 minutes, then extend infinitely on a platform built for scale. Coding agents such as Claude Code, Cursor, and Codex can write the integration code.
Nango’s Connect UI handles authorization, and agent sessions provide scoped tool access:
- Connect UI and connect links: your backend creates a Connect session for the end user, the user approves access to the external API on a page the agent never sees, and Nango stores and refreshes the tokens. To start this from a tool, point URL-mode elicitation at an authenticated route in your application. Verify that the visitor is the user who initiated the request, then create a Nango Connect session and open Connect UI or its hosted link.
- Agent sessions: each session gets an MCP URL and a session token scoped to one customer’s connections and toolset, and tools run with those stored connections. The agent never sees raw credentials and cannot change the scope of its own session.
FAQ
What is elicitation in MCP?
Elicitation is the MCP feature that lets a server request input from the user during a request, through the client. The client returns accept, decline, or cancel. An accepted form also includes the submitted data.
Does my product’s agent need to support elicitation?
Only if it connects to MCP servers that use it. A tool that requires elicitation cannot complete through that flow unless the client declares the capability and answers the request. The MCP TypeScript and Python SDKs and the Vercel AI SDK document elicitation handlers. The OpenAI Agents SDK’s MCP guide does not document one. Anthropic’s hosted MCP connector supports tool calls only.
What is form elicitation?
Form elicitation is the mode that stays inside the client: it shows a form built from a limited JSON Schema and returns the values to the server. Use it only for non-sensitive data.
Elicitation vs sampling vs prompts: what’s the difference?
Elicitation requests user input mid-request. Sampling requests model output through the client and was deprecated in 2026-07-28. Prompts are templates the server provides and the user picks to start a request.
Can elicitation collect passwords or API keys?
Not in form mode. The spec forbids passwords, API keys, access tokens, and payment credentials in forms. Use URL mode or your product’s own connect flow, and store the credential server-side.
How do I trigger an OAuth flow from an MCP tool?
Return a URL-mode elicitation pointing to an authenticated route in your application. That route checks the visitor is the user who started the request, redirects to the provider’s authorization page, and stores the returned tokens under that user. With Nango, the route creates a connect session and opens Connect UI or its hosted link. Nango handles provider authorization and credential storage. Your application confirms that the connection was created and creates a new agent session when needed.
Conclusion
Use elicitation when a tool needs information or confirmation from the user and the conversation does not already contain it. Supported coding clients provide the interaction UI. In your own product, your application hosts the MCP client and provides the user interface, including a way to save pending requests and resume when the user responds. Keep credentials out of forms. Use URL mode or a connect flow, and let Nango store the third-party credentials and give your MCP client a scoped session token.
Related reading: