MCP elicitation UI
What the user sees when a server asks for input mid-tool-call
The Model Context Protocol lets a server pause a running tool call and send the client an
elicitation/create request — a short message plus a JSON Schema of the fields it
needs (a missing required value, a confirmation, which workspace to target). The client
collects the input and returns accept / decline / cancel,
and the server resumes. It's optional and server-initiated: the model never asks, the server
interrupts.
On the current revision, 2026-07-28 (new in 2.0), the
mechanics change but the experience doesn't. There's no open stream to push a request down,
so the server ends the call with an input_required result that carries the
same request. The client shows the same sheet, then sends the call again with the user's
answer. A multi-step flow repeats this, and the client stops after 8 rounds. You're only
asked once if you say no: Cancel ends the tool call there, and after
Decline the sheet doesn't come back for that call even if the server
asks again.
The SDK handles this in two layers. Core catches the request (off the
tool-call stream, or from an input_required result on
2026-07-28), flattens the schema into typed fields, attaches the originating server's identity, and
hands it to whatever MCPElicitationHandler the app registered — or auto-declines if
there is none. Components ships a ready-made SwiftUI implementation of that
handler so most apps don't build their own: a presenter plus the sheet below.
The sheet
Form mode — the schema rendered as typed controls
URL mode — the server hands back a link instead of a form
What the built-in sheet does for you
- Names the server. The header is the connected server's own
serverInfo.title, with a· not <your app>line — the spec requires clients to show which server is asking and forbids using elicitation to phish for credentials, so the framing is the SDK's, never the server's. - Typed controls per field.
string→ text field;format: "date"→ date picker;format: "email"/"uri"→ text field with on-submit validation;number/integerwithminimumandmaximum→ slider with a live readout, otherwise a field with the range as its placeholder;boolean→ toggle; a single-valueenum→ a segmented control (up to three options) or a menu; an array ofenum→ a checkbox list. - Humanized labels.
assignee_emailrenders as "Assignee email" when the server gave notitle; required fields get a*. - Real submit validation. Errors appear per-field only after the first Submit attempt, not while the user is still typing.
- Only sends real answers. Fields the user never touched are dropped from the
acceptpayload rather than sent as empty strings — the same rule Core applies to model-supplied tool arguments. - Decline vs Cancel. Decline tells the server "I won't provide this";
Cancel (and Esc, and dragging the sheet away) means "I didn't decide" — two distinct
responses the server can act on differently. On
2026-07-28, Cancel also ends the tool call, and after Decline a server that asks again is declined without the sheet reappearing. The model gets a short note saying the user cancelled or declined. - Times out. An unanswered prompt auto-cancels after two minutes so a tool
call can't hang forever waiting on a sheet nobody is looking at. On
2026-07-28that ends the tool call like any other Cancel.
Want your own UI? Conform any type to
MCPElicitationHandler — one async method taking an
MCPElicitationRequest (parsed message, typed fields,
server identity, optional URL) and returning .accept / .decline /
.cancel — and pass it in MCPClientHandlers(elicitation:). Register
nothing and Core never advertises the capability, so a conformant server never asks.
Try it
The MCP reference server exposes an elicitInputs tool
that requests input with a mix of field types — the quickest way to see the sheet without
touching a real account, right in LocalLM Lab itself, no code required:
MCP Servers panel → Add Server — URL https://example-server.modelcontextprotocol.io/mcp,
any name, auth type None. Add opens a browser to a generic consent
screen; approve it.
Enable only elicitInputs (toggle the other 8 tools off — keeps the context
budget clean and the model from wandering off to call something else). Then in Prompt
Playground: "Call the elicitInputs tool."
The sheet appears with the mixed field types. Its header names whatever the server calls itself (≈ "example-servers/everything is asking for input").
Want to see it from the SDK side instead? None of the public example apps wire the
presenter up yet (components-demo included) — the three lines it takes are
in the SDK guide's §3c.
Contact
Questions, or building something with this? Contact neuron@thisbrain.ai or join our Discord.
↑ Top