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.
The SDK handles this in two layers. Core catches the request off the tool-call
stream, 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.
- 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.
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