Skip to content
verify mcp Beta VerifyMCP is currently in beta. If you notice any issues, get in touch and we’ll put it right.

MCP elicitation: form mode, URL mode and the risks

By Stuart · 15 Aug 2026 · 8 min read
MCP elicitation: form mode, URL mode and the risks, VerifyMCP

Elicitation is how an MCP server asks the user for something mid-request. A tool call starts, the server realises it needs a value it does not have, and instead of failing it returns a request for input. The client collects the answer and retries the original call.

As of the 2026-07-28 revision it is also the only client feature left. Roots and Sampling are both deprecated, so elicitation is the entire surface through which a server can ask a client for anything.

It comes in two modes, form and URL, and the spec draws a hard line between them: form data passes through the client, URL data does not, so credentials are restricted to URL mode. URL mode carries its own attack, a forwarded link that binds a third party’s tokens to the wrong identity. Both are below, after the mechanics.

How does elicitation work in the 2026-07-28 spec?

The revision made the protocol stateless, so there is no persistent session for a server to park a half-finished request in. Elicitation instead uses multi-round-trip requests: the server answers the original call with an InputRequiredResult carrying an elicitation/create request, and the client retries the whole call with the answer attached in inputResponses. If that result also carried a requestState, the client echoes it back unchanged; if it did not, the retry carries no state at all.

The round trip runs like this:

  1. The client sends tools/call.
  2. The server finds it needs more information, and answers with an InputRequiredResult containing an elicitation/create request, optionally with a requestState attached.
  3. The client presents the request. The user answers, declines, or cancels.
  4. The client sends the same tools/call again, now carrying inputResponses, and echoing requestState verbatim if the server sent one.
  5. The server returns the real result.
The same tools/call is sent twice: the server answers the first with a request for input, and the client re-sends the original call with the answer attached, so nothing is held open between the two.

The retry carries the whole exchange. There is no open channel the server is waiting on, so nothing is held between the two calls except whatever a requestState encodes, where the server chose to issue one.

Both fields on an InputRequiredResult are optional, and the server has to include at least one of them. The echo rule is symmetrical: a client that receives a requestState MUST send back its exact value on the retry, and a client that does not receive one MUST NOT invent one. It is an opaque blob either way, and clients MUST NOT inspect, parse or modify it.

The client declares support per request rather than once per session, in _meta:

{
  "_meta": {
    "io.modelcontextprotocol/clientCapabilities": {
      "elicitation": { "form": {}, "url": {} }
    }
  }
}

An empty elicitation: {} means form mode only, kept for backwards compatibility. A client declaring the capability must support at least one mode, and a server must not send a mode the client did not declare.

What is the difference between form mode and URL mode?

Form mode collects structured data through the client. URL mode sends the user out to a web page and tells the client almost nothing about what happened there. The distinction is a security boundary, not a UI preference.

Form modeURL mode
Where the user typesIn the MCP clientOn the server’s own web page
What the client seesThe submitted dataOnly the URL
Use forNames, choices, non-secret parametersCredentials, payments, third-party OAuth
IntroducedOriginal elicitation2025-11-25

Form-mode schemas are deliberately small. requestedSchema is restricted to a flat object of primitives: strings (with email, uri, date and date-time formats), numbers and integers, booleans, and single or multi-select enums. Nested objects and arrays of objects are not supported, on purpose, so that any client can render a form without implementing a JSON Schema engine.

What do accept, decline and cancel actually mean?

There are three actions, and a server must treat each one differently:

  • accept: the user approved and submitted. In form mode the content field carries the data; in URL mode it is omitted.
  • decline: the user explicitly said no. Offer an alternative.
  • cancel: the user dismissed without deciding, by closing the dialog or pressing Escape. Asking again later is reasonable in a way it is not after a decline.

The common shortcut is to collapse decline and cancel into one failure path. That throws away the difference between a user who said no and a user who never answered, and it is exactly the difference the spec asks you to act on.

URL mode adds one wrinkle: accept means the user consented to open the URL, not that the out-of-band interaction finished. The client never learns the outcome. When it retries, the server works out from its own state whether the flow completed, and either returns a result or issues another InputRequiredResult.

Is URL mode elicitation the same as MCP authorization?

No. They solve different problems, and they sit on opposite sides of the trust boundary.

MCP authorization is the OAuth flow between the client and the MCP server: it decides whether you may talk to the server at all. URL mode elicitation is for the server obtaining third-party credentials on your behalf, where the server acts as an OAuth client to some other service. The spec states plainly that servers must not rely on URL mode elicitation to authorize users for themselves.

Three requirements follow from that split:

  1. Third-party credentials must not transit the MCP client. That is the whole point of sending the user out of band.
  2. The server must not reuse the client’s token against the third party. That is token passthrough, and it is forbidden.
  3. The server owns the resulting tokens, stored and bound to the user’s identity.
The user signs in directly with the third party and the tokens go straight to the server, so the credential never passes through the MCP client.

What is the URL mode phishing attack?

A URL mode elicitation produces a link. Links can be forwarded. The spec sets out the resulting attack:

  1. Alice, a malicious user of an otherwise honest server, triggers an elicitation.
  2. The server generates an authorization URL, acting as an OAuth client to a third party.
  3. Alice’s client shows her the URL and asks for consent.
  4. Rather than clicking it, Alice tricks Bob, a legitimate user of the same server, into opening it.
  5. Bob completes the authorization, believing he is connecting his own account.
  6. The server receives the callback and assumes it belongs to Alice’s request.
  7. The third-party tokens end up bound to Alice’s identity. Alice now holds Bob’s access.

The mitigation is a single requirement stated as a MUST: the server has to verify that the user who opens the URL is the same user the elicitation was generated for. The spec’s suggested pattern is to point the elicitation at your own connect route rather than straight at the third party, check the browser session there, compare its subject against the sub claim from your authorization server, and only then redirect onward.

The client has obligations too. It must not pre-fetch the URL or its metadata, must not open it without explicit consent, must show the full URL for inspection, and must open it in a surface the client and model cannot read (the spec’s own example: SFSafariViewController on iOS is acceptable, WkWebView is not). It should also highlight the domain and warn on Punycode. Servers, for their part, must not put user data in the URL and must not hand out a pre-authenticated one.

What should you do if you are building this?

  • Sort your inputs by sensitivity first. Anything that is a credential, a token or a payment detail goes to URL mode. Everything else can be a form.
  • Keep form schemas flat. The restriction is a compatibility guarantee: exceed it and some clients cannot render your request.
  • Handle all three actions distinctly, especially decline versus cancel.
  • If you use URL mode, write the identity check before the OAuth flow, and test it with two accounts rather than one.
  • Do not store state you do not need. MRTR exists so that servers can stay stateless. Where you must keep state, bind it to an identity derived from the authorization token’s sub claim, never from anything the user typed.

Where to go next

  • The protocol side: MCP authorization covers the OAuth 2.1 profile that URL mode sits alongside but is not part of.
  • What else changed: MCP 2026-07-28 support covers the stateless rework and the deprecations, including why sampling and roots are on the way out. The revision notes have the full list.
  • Checking your own server: point the MCP Inspector at it and trigger the elicitation path deliberately, including the decline and cancel branches.