Halfway through a tool call, an MCP server discovers it needs something only a human can supply: which of three matching records you meant, whether to really overwrite the production config, a project ID nobody ever set. It cannot just ask. A server is a process on the other end of a JSON-RPC pipe — no window, no keyboard, no relationship with the person whose work it is doing. Elicitation is the protocol answer: the server sends a structured request, the host renders it in its own trusted interface, and the user decides. What follows is the mechanism and its edges: the schema that bounds the question, the three answers a server must handle, the one thing never to elicit, and how to survive nobody answering.
Why a server cannot simply prompt
Everything about elicitation follows from one fact: an MCP server has no user interface. It may be a subprocess speaking over stdio or a remote service answering HTTP from a datacenter; whatever it writes goes to a log or a JSON-RPC frame, not to a screen. Nor does it have any trust relationship with the human — the person authorized a host application, which chose to connect to this server. The human’s attention belongs to the host.
Without a way to ask, a server hitting a missing value has three bad options. It can fail, turning a recoverable gap into an error the model must interpret. It can guess, which is how tools delete the wrong record. Or it can demand the value up front as configuration, forcing it to hold information it has no business storing. Elicitation exists because all three are worse than asking.
The inversion, and where it is not sampling
MCP’s usual direction of travel is client-to-server: the host’s client calls tools/call, the server answers. Elicitation reverses it: mid-request, the server issues a request to the client and waits, suspending the original call inside its own handler. MCP has two such inversions and they are easy to confuse: sampling asks the host to run a model completion on the server’s behalf, while elicitation asks the host to obtain input from the human. That is the whole boundary; the rest of this article stays on the human side.
The request carries two things: a message explaining what is needed and why, and a schema describing the shape of an acceptable answer. The message is what the user reads; the schema is what the host renders and what the server may assume about whatever comes back.
The schema is a constraint, not a hint
The schema reads like documentation but works like a fence. Current revisions deliberately restrict elicitation schemas to a flat object of primitive fields — strings, numbers, booleans and enumerated choices, with titles and descriptions for display — rather than the full generality of JSON Schema. No nesting, no arrays of objects, no arbitrary composition.
That restriction is doing real work. It guarantees a host can mechanically render any valid request as an ordinary form: a checkbox for a boolean, a dropdown for an enumeration, a labelled field for a string — without interpreting a schema language it did not expect. And a server cannot smuggle a data-extraction interrogation into what the user reads as a simple question. The schema also validates: what the user submits is checked against it before it reaches the server, which therefore receives typed values rather than free text to parse.
The host as mediator
The host is not a pass-through; it is where every guarantee lives. It attributes the request — the user must see which named server is asking, not a floating dialog that could have come from the application itself. It renders the request in its own chrome, so a server cannot forge the look of a system prompt or a login screen. And it enforces policy: a host may cap how often a server asks, suppress a muted server, or reject a request without ever showing it, answering on the user’s behalf.
This is the trust posture the protocol takes everywhere — the server proposes, the host disposes — and it is why elicitation can be offered to servers of unknown provenance at all. A server can request; it cannot compel, cannot see what it was not given, and cannot tell a human’s refusal from a policy’s.
Three outcomes: accept, decline, cancel
A server that treats elicitation as a function returning a value has already misunderstood it. There are three outcomes, kept distinct on purpose.
| Outcome | What the user did | What it means |
|---|---|---|
| Accept | Filled the form and submitted | You get schema-valid content; proceed |
| Decline | Explicitly refused this request | A deliberate “no” — do not re-ask |
| Cancel | Dismissed without deciding | No answer at all — intent unknown |
The difference between the last two is the one teams collapse and then regret. Decline is information: the user considered the question and said no, so a well-behaved server records that and stops asking. Cancel is the absence of information — the dialog dismissed, the window closed, the user gone. Treating cancel as “no” silently converts an interruption into a decision; treating decline as cancel produces a server that asks the same question forever. And accept only promises the fields were supplied and valid, not that the values are right.
Handling all three in the tool that asked
The elicitation sits inside a suspended tool call, so each outcome must become a tool result. The common anti-pattern is letting anything other than accept raise, so a user politely declining produces a stack trace and a protocol-level failure. That is wrong on both counts: a declined confirmation is a successful execution of a tool whose correct behaviour was to do nothing.
Return it as a normal tool result whose content says so — “deployment not performed; the user declined confirmation” — not as an error. The model reads that sentence and can explain it, instead of retrying a call it thinks crashed. Reserve the error channel for elicitation that actually broke, and never loop to re-ask on decline; a server that pesters until the user gives in is the coercion this design exists to prevent.
What must never be elicited
There is a hard line, and it is credentials. Passwords, API keys, one-time codes, card numbers: a server must never ask for them through elicitation, and a host should treat a request fishing for them as hostile. The reason is precisely the mechanism’s strength: elicitation renders inside the interface the user already trusts, so a request for a password arrives wearing the host’s clothes — a confused-deputy setup, borrowing the host’s credibility to make a third party’s phishing prompt look official.
Legitimate secrets have their own path: an authorization flow the user completes with the identity provider directly, or configuration the host holds and the server never sees. A mid-tool prompt improves on neither. So: servers should elicit only choices, confirmations and non-secret values; hosts should scan messages and field metadata for credential-shaped asks, warn loudly, and offer a one-click way to cut that server off.
The operation is blocked behind the request
An elicitation is the slowest call your server will ever make: a network hop is milliseconds, a human is seconds, minutes, or never. Meanwhile the handler sits parked mid-execution and whatever it holds stays held.
That makes resource discipline non-negotiable. Do not elicit with an open database transaction, a held lock, or a rented connection — gather the input first, then start the work that needs those things. Set a timeout you can defend and treat expiry as cancel: no decision was made, so undo cleanly and report. Expect the host to have its own timeout and to answer for the user if theirs fires first, and expect the operation to be cancellable while the question is pending, so your cleanup path must survive that. Batch related questions: three fields in one form is one interruption, three elicitations is three chances to lose the user.
Hosts with nobody to ask
Elicitation is a client capability, declared during the initialize handshake. Plenty of hosts will not declare it: a headless CI runner, a batch pipeline, an agent with no human attached. They are not broken; they have nobody to ask.
So the check comes before the ask: inspect what the connected client advertised and choose a path, rather than assuming the facility exists and erroring out when the request bounces. Three good fallbacks, in order of preference — use a safe default and say in the result that you did; expose the same value as an optional tool parameter so the model can supply it from context it already has; or fail with a message naming exactly what is missing, so the model can ask the user in conversation and call again. That last fallback is the point — the model is a fine channel to the human, just a less structured one.
Designing a flow that survives refusal
Add it up and elicitation stops looking like a feature to sprinkle on and starts looking like a design constraint. Three rules earn their keep. Ask rarely: every question is an interruption, and a tool that asks twice per invocation gets uninstalled. Ask early, before expensive or partially committed work. Ask answerably — the user sees a form, not your call stack, so the message must be self-contained and the options must mean something without protocol context.
And assume no answer. The unsupported path, the declined path and the timed-out path are not exotic; on a busy day they are the majority. Each should end in a clean, side-effect-free state that tells the model what happened and what to try instead. A server built that way degrades into a well-behaved non-interactive server when nobody is home — which is what you want, because the interactive version is the optimistic case.