MCP elicitation lets a server ask the user for information through the client while an MCP interaction is in progress. Use form mode for small, non-secret structured values. Use URL mode for credentials, payment details, external OAuth, or any interaction whose sensitive data must not pass through the MCP client.
This distinction is a protocol security requirement in MCP 2025-11-25, not a stylistic preference. A server must not request passwords, API keys, access tokens, or payment credentials through form mode, and a client must let the user review, decline, or cancel (MCP elicitation specification).
Choose the mode from the data path
| Need | Mode | What the MCP client sees |
|---|---|---|
| Choose an environment | Form | The selected value |
| Enter a report date | Form | The submitted date |
| Supply an API key | URL | Only the URL and consent state |
| Authorize the MCP server to a third party | URL | Only the URL and consent state |
| Authorize the MCP client to the MCP server | Neither | Use MCP authorization |
URL mode does not authorize the MCP client to call the MCP server. That is the separate MCP OAuth flow. URL mode can instead let an already authenticated MCP user connect the MCP server to a third-party service.
Before sending either mode, check the client's negotiated capabilities. A current client can advertise elicitation.form, elicitation.url, or both. An empty elicitation object means form-only support for backward compatibility. A server must not send a mode the client did not declare.
Build a bounded form request
Suppose create_incident_summary needs a reporting window and detail level. Instead of letting the model invent them, the server can send:
{
"jsonrpc": "2.0",
"id": 41,
"method": "elicitation/create",
"params": {
"mode": "form",
"message": "Choose the reporting window for this incident summary.",
"requestedSchema": {
"type": "object",
"properties": {
"startDate": {
"type": "string",
"title": "Start date",
"format": "date"
},
"detail": {
"type": "string",
"title": "Detail level",
"oneOf": [
{ "const": "brief", "title": "Brief" },
{ "const": "standard", "title": "Standard" }
],
"default": "standard"
}
},
"required": ["startDate", "detail"]
}
}
}
Form schemas use a restricted JSON Schema subset designed for simple interfaces: a flat object with primitive fields, plus bounded enum arrays. Nested objects and arbitrary arrays of objects are intentionally unsupported. Keep the form small; if the task needs a complex editor, an interactive MCP App may fit better.
The client returns one of three actions:
{
"jsonrpc": "2.0",
"id": 41,
"result": {
"action": "accept",
"content": {
"startDate": "2026-07-01",
"detail": "standard"
}
}
}
accept means the user explicitly submitted. decline means the user explicitly rejected the request. cancel means they dismissed or failed to complete it without making that choice. Handle these states differently: offer a lower-information path after decline, but allow a later retry after cancellation when appropriate.
Both client and server should validate accepted content. Client validation improves the interaction; server validation protects the operation. Validate the calendar date itself rather than trusting Date.parse, which can normalize invalid dates in some JavaScript environments.
Design a URL flow for sensitive interactions
Assume the incident tool needs read access to a third-party status provider. The MCP server acts as an OAuth client to that provider under MCP’s separate authorization requirements and sends a URL-mode request:
{
"jsonrpc": "2.0",
"id": 42,
"method": "elicitation/create",
"params": {
"mode": "url",
"elicitationId": "el_98c1a34b",
"url": "https://mcp.example.com/connect/status-provider?request=opaque-one-time-value",
"message": "Connect the status provider so this server can read incident updates."
}
}
An accept response means the user consented to open the interaction. It does not mean the OAuth or payment flow completed. The interaction occurs out of band. When it finishes, the server may send notifications/elicitation/complete containing the original elicitationId. Because completion notifications may never arrive, the client should retain manual retry and cancel controls.
If a tool discovers that setup is required before it can proceed, the server may instead return JSON-RPC error -32042 with one or more required URL elicitations. The client can complete them and retry the original operation. Cap retries and preserve an idempotency key for writes.
Bind the flow to a verified user
The server needs durable state for URL mode:
type PendingElicitation = Readonly<{
elicitationId: string;
principalId: string;
tenantId: string;
purpose: "connect_status_provider";
nonceHash: string;
expiresAt: string;
completedAt: string | null;
}>;
This is application pseudocode. The important rule is that the association comes from the authenticated MCP principal, not from a claimed email address, tool argument, or MCP session ID alone. The specification requires servers to bind elicitation state securely to the user; for remote servers, identity should come from authorization credentials where possible.
At the browser endpoint:
- require HTTPS outside local development;
- authenticate the person opening the page;
- verify the one-time value, expiry, purpose, principal, and tenant;
- run the third-party authorization flow with its own state and PKCE controls;
- store third-party tokens encrypted and bound to the verified user;
- mark the elicitation complete exactly once; and
- notify only the MCP client that initiated it.
This binding prevents a phishing victim from accidentally connecting their third-party account to an attacker's MCP session.
Handle URLs as hostile input
The server must not put credentials, personal information, pre-authenticated access, or bearer secrets into the URL. Use an opaque, short-lived, single-use value that reveals nothing if logged.
The client must not prefetch a URL or its metadata, because even a preview can trigger a request and disclose network information. Before navigation, show the full URL, highlight the domain, warn about suspicious internationalized names, and require explicit navigation approval. MCP records this as a consent state for the interaction; it is not proof of legal consent for the external activity. Open the URL in a secure browser context that the model and MCP client cannot inspect.
Do not render arbitrary links embedded in a form-mode message as trusted actions. The specification recommends that only the dedicated URL field in URL mode receive link treatment.
Make the user experience honest
A good elicitation explains:
- which MCP server is asking;
- what information or action is needed;
- why it is needed now;
- what happens after accept, decline, or cancel; and
- which domain will receive a URL-mode interaction.
Avoid coercive messages such as “Approve to continue” when a safe alternate path exists. Do not pre-check consent boxes or treat a closed dialog as approval. Rate-limit requests so a server cannot trap the user in repeated prompts.
For accessibility, move focus into a form dialog, provide programmatic labels and error messages, preserve the user's values after validation errors, return focus to the invoking control, and ensure decline and cancel are keyboard reachable.
Failure modes to test
- The client supports form mode but not URL mode.
- The user accepts but submitted content fails server validation.
- The user declines, cancels, or closes the browser.
- The URL expires before it is opened.
- A different authenticated user opens the link.
- The third-party callback is delivered twice.
- Completion notification is lost.
- The original tool call is retried after completion.
- The elicitation ID belongs to another tenant.
- A malicious server supplies a misleading or lookalike domain.
For a long-running interaction, associate the elicitation with an experimental MCP Task using the related-task metadata defined in the Tasks utility.
Release checklist
- Negotiate support for the exact elicitation mode.
- Use form mode only for small, non-secret structured data.
- Use URL mode for credentials, payments, and third-party authorization.
- Never confuse URL elicitation with MCP client authorization.
- Validate accepted data on both sides.
- Bind state to a verified principal and tenant, not only a session.
- Use opaque, expiring, single-use URL state.
- Require consent before navigation and never prefetch.
- Handle accept, decline, and cancel independently.
- Preserve manual recovery when notifications are lost.