# Elicitation

Canonical URL: https://vercel-mcp-reference.vercel.app/client-side/elicitation/
Markdown: https://vercel-mcp-reference.vercel.app/client-side/elicitation.md
Audience: engineer, architect, security, non-technical. MCP spec version: 2026-07-28. Last reviewed: 2026-08-26. Status: stable.

**TL;DR:** Elicitation lets a [server](https://vercel-mcp-reference.vercel.app/glossary/#server) ask the **user**, through the host, for information it needs mid-operation. As of 2026-07-28 it is delivered through the [Multi Round-Trip Requests (MRTR) pattern](https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr): the server answers the triggering call with an `InputRequiredResult` (`resultType: "input_required"`) carrying an `elicitation/create` entry in `inputRequests`, and the [client](https://vercel-mcp-reference.vercel.app/glossary/#client) **retries the original request** with the user's `ElicitResult` in `inputResponses`. It is a **client capability** (declared per request in `_meta` under `io.modelcontextprotocol/clientCapabilities`) with two modes: **form** (structured data described by a restricted JSON Schema, rendered as a UI by the host) and **url** (send the user to an external page for sensitive interactions that **must not** pass through the client). Every request resolves to one of three actions (**accept**, **decline**, or **cancel**) and the user is always in control. The server-supplied schema, message, and URL are **untrusted**: the host gates them, servers must never harvest secrets through a form, and URL mode carries a real phishing risk both sides must defend against.

## Plain-language explanation

This is the second server-to-host inversion in MCP, alongside [sampling](https://vercel-mcp-reference.vercel.app/client-side/sampling-request-handling/). Normally the host drives the server (a tool call). With elicitation the server, partway through some work, realizes it needs something only the user can provide: a missing parameter, a confirmation, a choice between options, or (in url mode) a credential or third-party authorization. Rather than the model guessing or the server failing, the server **asks**, and the host puts a controlled prompt in front of the user. The model is kept out of it: the user answers the host's UI directly.

Unlike sampling, elicitation is **not deprecated** in 2026-07-28. What changed is the delivery: the server no longer opens a request back at the client mid-call. It returns "input required" as the *result* of the call, the host gathers the input on its own time, and the client comes back with a retry. The section on [what changed from 2025-11-25](#what-changed-from-2025-11-25) maps the removed pieces.

## The two modes

| Mode | For | Where the data goes |
|---|---|---|
| **form** | Structured, non-sensitive input (a name, a choice, a number) | In-band: the answer is returned to the client/server |
| **url** | Sensitive interactions: credentials, payments, third-party OAuth | Out-of-band: the user interacts on an external page; only the URL passes through the client, never the data |

The split is a hard security rule, not a convenience: servers **MUST NOT** use form mode for passwords, API keys, tokens, or payment credentials, and **MUST** use url mode for those. Ordinary profile data (a name, an email address) is allowed in form mode; secrets are not.

## The MRTR delivery

1. The client sends the original request (say `tools/call`).
2. The server, needing user input, returns `resultType: "input_required"` with an `elicitation/create` entry in `inputRequests`, plus an opaque `requestState` blob encoding whatever it needs to resume.
3. The host puts the prompt in front of the user (a rendered form, or a consent dialog for a URL).
4. The client **retries the original request** with a **new JSON-RPC id**, the `ElicitResult` keyed into `inputResponses`, and `requestState` echoed back byte for byte, uninspected and unmodified.
5. The server reconstitutes its state from `requestState` and finishes the call, or returns another `input_required` round if it still lacks something (for url mode, if the out-of-band interaction has not completed yet).

```mermaid
sequenceDiagram
    participant User
    participant Host
    participant Client
    participant Server
    Client->>Server: tools/call (id 1)
    Note over Server: needs user input
    Server-->>Client: InputRequiredResult (elicitation/create in inputRequests, requestState)
    Note over Client,Server: first invocation ends here
    alt form mode
        Client->>Host: render a form from requestedSchema (flat primitives)
        Host->>User: show form with provenance and the stated reason
        User-->>Host: fill in, decline, or cancel
        Client->>Server: tools/call retry (id 2, ElicitResult in inputResponses, requestState echoed)
        Server-->>Client: result (resultType complete)
    else url mode
        Client->>Host: show the full URL and ask consent
        User-->>Host: approve opening the URL
        Host->>User: open the URL out of band, in a sandboxed view
        Client->>Server: tools/call retry (id 2, accept with no content, requestState echoed)
        Note over Server: interaction not finished yet, ask again
        Server-->>Client: another InputRequiredResult, or the final result
    end
```

If the client retries without the requested input, the server **SHOULD** respond with a fresh `InputRequiredResult` asking again rather than erroring, and a server **MUST NOT** assume the client will ever fulfill the request or retry at all: a decline or a silent walk-away are both outcomes it has to survive.

Wire status: the pinned stack (`mcp-handler` 2.1.1 on `@modelcontextprotocol/server` 2.0.0) serves the 2026-07-28 contract natively over Streamable HTTP and falls back to stateless 2025-11-25 Streamable HTTP for legacy clients; the SDK `Client` defaults to that legacy handshake unless you opt in to modern version negotiation, so the examples' in-memory test suites exercise only the legacy path.

## Form mode

The `elicitation/create` entry carries a `message` (why it is asking) and a `requestedSchema`. To keep the host's generated UI predictable and safe, that schema is a **restricted subset of JSON Schema**: a flat object of *primitive* properties only.

- **string** (with `minLength`/`maxLength`/`format` where format is `email`, `uri`, `date`, or `date-time`), **number**/**integer** (`minimum`/`maximum`), **boolean**, and **enum**, including titled and untitled enums and single-select and **multi-select** variants, all with optional `default` values the host should pre-populate.
- **No** nested objects, arrays of objects, or advanced JSON Schema. That restriction is the point: the host can always render a simple, reviewable form, and the attack surface stays small.

The host renders the form, lets the user **review and modify** before sending, validates against the schema, and returns the result in the retry. For backwards compatibility, a request that omits `mode` **MUST** be treated as form mode.

## URL mode

For anything sensitive, the server sends `mode: "url"` with a `url` and a `message`. The host shows the user the full URL, gets consent, and opens it **out of band**: the data (a credential, an OAuth consent) is entered on the external page and **never passes through the client or the model**. An `accept` in the retry just signals the user consented to open the URL; it does not mean the interaction finished. The client is never directly told the outcome. It learns it by **retrying the original request**: the server checks the echoed `requestState` (or its own stored state) to see whether the out-of-band interaction completed, and either returns the final result or another `InputRequiredResult`. Clients **SHOULD** give the user manual controls to retry or cancel the original request, since no notification will arrive to prompt them (2025-11-25's `notifications/elicitation/complete` is gone; see [below](#what-changed-from-2025-11-25)).

This is how a server brokers **third-party** authorization (acting as an OAuth client to some external API) without the client ever seeing those tokens; the server stores them bound to the user. It is distinct from [MCP authorization](https://vercel-mcp-reference.vercel.app/security/authorization/) (client to server), a server **MUST NOT** use url elicitation to authorize users for itself, and the [token-passthrough](https://vercel-mcp-reference.vercel.app/client-side/credential-brokering/) prohibition still applies: the server must not reuse the client's bearer for the third party. URL mode remains flagged in the spec as a newer feature whose design may still change in future revisions.

## What changed from 2025-11-25

Keep these in mind when reading older material or serving older clients; you will still meet the legacy shapes wherever the negotiated protocol version is 2025-11-25 or earlier, which today includes any SDK `Client` left at its default legacy negotiation and every in-memory test suite.

- **`elicitation/create` is no longer a server-initiated request.** It rode the open SSE response stream of the triggering call and blocked the server until the client answered. Under 2026-07-28, servers **MUST** deliver it inside an `InputRequiredResult` (MRTR); server-initiated requests are removed.
- **`notifications/elicitation/complete` and the url-mode `elicitationId` are removed** (both were new in 2025-11-25). The client learns the outcome of an out-of-band interaction by retrying the original request, so a server-initiated completion signal, and the identifier used to correlate it, no longer fit the protocol. Servers that need to correlate an elicitation across retries encode their own identifier in `requestState`.
- **`URLElicitationRequiredError` (`-32042`) does not survive into the 2026-07-28 schema.** Its end-the-call-and-retry shape was the right instinct, and MRTR made it the protocol's normal shape: a server that needs a url-mode interaction before proceeding returns `input_required` instead of a special error.

## The three-action model

Every elicitation resolves to exactly one action, returned inside `inputResponses` on the retry, and a server must handle all three distinctly:

- **accept** - the user submitted. Form mode carries the data in `content`; url mode omits it (consent to open, not completion).
- **decline** - the user explicitly said no. Offer an alternative, do not re-ask blindly.
- **cancel** - the user dismissed it (closed the dialog, pressed Escape, the page failed to load). **Not** consent; treat as "no decision yet."

Collapsing decline or cancel into accept, or reading a dismissed dialog as approval, is a consent bug. And an accepted form is still just data: if the form asks "proceed?" and the user submits `approved: false`, that accept is an explicit no.

## Capability declaration

A client that supports elicitation declares the `elicitation` capability, naming the modes it supports (`elicitation: { form: {}, url: {} }`). Since 2026-07-28 removed the initialize handshake, the declaration travels in `_meta` under `io.modelcontextprotocol/clientCapabilities` **on each request**. A client declaring the capability **MUST** support at least one mode, and a server **MUST NOT** put an elicitation mode in `inputRequests` that the client did not advertise. For backwards compatibility, an empty `elicitation: {}` means form mode only. See [capability negotiation](https://vercel-mcp-reference.vercel.app/glossary/#capability-negotiation).

## The serverless shape

The 2025-11-25 version of this section was a warning about `maxDuration`; the 2026-07-28 version is mostly relief. Consequences of the function model described in [Serverless sessions](https://vercel-mcp-reference.vercel.app/internals/serverless-sessions/):

- **User think time is off the clock.** The invocation that needs input ends the moment it returns `input_required`. The user can leave a form open all afternoon; the server pays nothing. The retry is a fresh invocation whose own execution is the only thing `maxDuration` (300s default under Fluid compute, 800s max on Pro and Enterprise) still bounds.
- **`requestState` is the resume mechanism.** The retry can land on any instance, so everything the server needs to continue rides in the integrity-protected blob the client echoes back, not in function memory. This is the same handles-as-arguments discipline the rest of the stateless protocol uses.
- **Do not block a url-mode retry.** If the retry arrives before the out-of-band interaction completes, return another `InputRequiredResult` rather than holding the invocation open polling for completion; the client will come back. Blocking rebuilds the old `maxDuration` squeeze voluntarily.

Elicitation-driven servers that do keep state (collected answers, third-party tokens) must bind it correctly: state storage **MUST** be protected against unauthorized access and, for remote servers, user identification **MUST** be derived from [MCP authorization](https://vercel-mcp-reference.vercel.app/security/authorization/) credentials (the `sub` claim), never from a client-asserted identity or a [session](https://vercel-mcp-reference.vercel.app/glossary/#session) id alone. On Vercel that state lives in an external store (a Marketplace Redis or Postgres), never in function memory.

## Security considerations

Elicitation is a powerful, user-facing channel a malicious or compromised server can abuse, so it is heavily guarded:

- **Never elicit secrets via form mode.** Passwords, API keys, tokens, payment details **MUST** go through url mode. The host should reinforce this and make sensitive-looking form requests suspicious. See the [consent checklist](https://vercel-mcp-reference.vercel.app/security/checklist/#consent--user-approval).
- **The server's schema, message, and URL are untrusted.** Treat labels and descriptions like any [tool output](https://vercel-mcp-reference.vercel.app/client-side/tool-result-rendering/): sanitize, do not render as live markup, and always show **which server** is asking (provenance) and why.
- **Human-in-the-loop, always.** Clear decline and cancel at any time; review-and-modify before send; the host should **rate-limit** so a server cannot spam prompts through repeated `input_required` rounds on the same call.
- **Safe URL handling (url mode).** The client **MUST NOT** auto-prefetch the URL or its metadata, **MUST NOT** open it without explicit consent, **MUST** show the full URL for inspection first, and **MUST** open it in a sandboxed view the client and LLM cannot inspect. It **SHOULD** highlight the domain and warn on Punycode or ambiguous URIs, and **SHOULD NOT** render server-supplied URLs as clickable outside the url-mode `url` field. The server has duties too: it **MUST NOT** put credentials or PII in the URL itself, and **MUST NOT** send a pre-authenticated URL a malicious client could replay to impersonate the user.
- **`requestState` cuts both ways.** The client **MUST** echo it exactly and only on the retry of the same request; it **MUST NOT** inspect, parse, or modify it. The server **MUST** treat the echoed value as attacker-controlled input: integrity-protect it (HMAC or AEAD) whenever it influences authorization or business logic, bind it to the authenticated principal and the originating request, give it a short TTL, and reject anything that fails verification.
- **Bind the elicitation to the user identity: the phishing trap.** A url-mode URL can be lifted and sent to a *different* user. Attacker Alice triggers an elicitation, then tricks victim Bob into completing the OAuth flow, so the third-party tokens bind to Alice: account takeover. The server **MUST** verify that the user who *started* the elicitation is the one who *completes* it (compare the authenticated `sub` behind the browser session against the elicitation's bound identity, resiliently against a tampered URL). Bind state to user identity, never to a session id alone; see [Identity and principals](https://vercel-mcp-reference.vercel.app/security/identity-and-principals/).

## Common pitfalls

- **Requesting secrets in a form** - the prohibited path; use url mode.
- **Rendering server-supplied labels or URLs as live markup or clickable links** - UI injection and phishing surface.
- **Auto-opening the url-mode URL** - must be explicit user consent on an inspectable full URL.
- **Treating cancel as accept** - a dismissed dialog is not approval.
- **Not binding the elicitation to the initiating user** - enables the Alice/Bob token takeover.
- **Assuming nested or complex schemas** - form mode is flat primitives only.
- **Waiting for `notifications/elicitation/complete`** - removed in 2026-07-28, and it was optional even in 2025-11-25; the retry of the original request is how the outcome is learned, so give the user a manual retry control.
- **Inspecting or editing `requestState`, or reusing the original JSON-RPC id on the retry** - both break the MRTR contract; a tampered blob gets the retry rejected by the server's integrity check.
- **Blocking a retry while an out-of-band interaction finishes** - return another `InputRequiredResult` instead of spending `maxDuration` polling.

## Example implementation

- `examples/elicitation-server` (in the repository) - a TypeScript server whose tool requests structured input mid-call with a flat, form-mode schema. Its vitest suite drives the server through an **in-memory client** that declares the `elicitation` capability and answers the request, exercising all three outcomes distinctly: **accept** (uses the supplied data), **decline**, **cancel** (explicitly *not* treated as accept), plus the accept-with-`approved: false` case as an explicit no. It covers form mode only; url-mode third-party auth remains a candidate for a future example. Wire status: the pinned stack (`mcp-handler` 2.1.1 on `@modelcontextprotocol/server` 2.0.0) serves the 2026-07-28 contract natively over Streamable HTTP and falls back to stateless 2025-11-25 Streamable HTTP for legacy clients; the SDK `Client` defaults to that legacy handshake unless you opt in to modern version negotiation, so the examples' in-memory test suites exercise only the legacy path.

The protocol-level definition is in [capability primitives](https://vercel-mcp-reference.vercel.app/internals/primitives/) and the gating discipline in [Consent UX](https://vercel-mcp-reference.vercel.app/client-side/consent-ux/).

## Related

- [Consent UX](https://vercel-mcp-reference.vercel.app/client-side/consent-ux/) - consent is the *approval* gate before an action; elicitation is the *input* gate that gathers what an action needs. Same fail-closed, human-in-the-loop discipline, now living in the MRTR retry decision.
- [Sampling-request handling](https://vercel-mcp-reference.vercel.app/client-side/sampling-request-handling/) - the other server-to-host inversion (the server wants the host's model rather than the user's input), same MRTR delivery, but deprecated.
- [Tool-result rendering](https://vercel-mcp-reference.vercel.app/client-side/tool-result-rendering/) - the same untrusted-server-content rendering care applies to elicitation labels and URLs.
- [Credential brokering](https://vercel-mcp-reference.vercel.app/client-side/credential-brokering/) - url-mode elicitation is how a server obtains third-party credentials without the client seeing them; the token-passthrough prohibition applies.
- [Identity and principals](https://vercel-mcp-reference.vercel.app/security/identity-and-principals/) - binding the elicitation to a verified user identity is what defeats the phishing attack.
- [Serverless sessions](https://vercel-mcp-reference.vercel.app/internals/serverless-sessions/) - the invocation-lifetime mechanics that make MRTR the natural serverless shape.
- [The 2026-07-28 revision](https://vercel-mcp-reference.vercel.app/internals/spec-2026-07-28/) - the full change set behind MRTR and the m11 removals.

## Bibliography

- Model Context Protocol Specification, *Elicitation*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/client/elicitation>
- Model Context Protocol Specification, *Multi Round-Trip Requests*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/basic/patterns/mrtr>
- Model Context Protocol Specification, *Authorization*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/basic/authorization>
- Model Context Protocol Specification, *Key Changes (changelog)*, version 2026-07-28 - <https://modelcontextprotocol.io/specification/2026-07-28/changelog>
- Model Context Protocol, *Security Best Practices* (token passthrough, user identification) - <https://modelcontextprotocol.io/docs/2026-07-28/tutorials/security/security_best_practices>
- Vercel Documentation, *Configuring Maximum Duration for Vercel Functions* - <https://vercel.com/docs/functions/configuring-functions/duration>
- OWASP Top 10 for Large Language Model Applications - <https://owasp.org/www-project-top-10-for-large-language-model-applications/>
