HomeJournalThis post

MCP Elicitation UX That Earns Consent

A consent-first MCP elicitation interface for forms and URL handoffs, with capability negotiation, provenance, decline states, accessibility, and audit evidence.

JP
JP Casabianca
UI/UX designer and full-stack engineer · Bogotá

MCP elicitation UX decides whether an agent request feels like a clear invitation or a disguised demand. The client must expose who is asking, why each value is needed, where it will go, and what happens when the person declines.

This guide builds that consent surface for structured forms and external URL handoffs. It combines capability negotiation, schema constraints, provenance, accessible review, decline semantics, and a runnable request validator.

Draw the MCP elicitation UX trust boundary

MCP elicitation UX begins when a server asks a client to obtain information from a person, but the server must never inherit the visual authority of the host application. Show the requesting server, the reason, the exact destination, and whether the response will trigger another operation. Keep the request inside a client-owned surface so hostile tool text cannot imitate trusted chrome.

A worked invoice-search request asks only for billing month and account region; it does not ask for a password, access token, or unrelated profile detail. The design position is strict: a valid schema can describe fields, but it cannot establish that collecting them is appropriate. The client remains responsible for rendering, policy, consent, and decline.

A useful adversarial review replaces the server name with a look-alike, puts an urgent sentence in the tool payload, and tries to move the destination below the fold. The design passes only when client chrome, requester identity, and data destination remain distinguishable before the person reads any persuasive server-authored copy.

MCP elicitation UX trust boundaryA server request crosses into client-owned consent UI, then returns only reviewed structured data or a decline outcome. SERVERCLIENT CONSENTpurpose · fields · originsubmit / declinePERSON
  • Declared input
  • Inspectable transformation
  • Measured output
Figure 1: The client, not server prose, owns the trusted consent boundary and the only path to a typed response.

Negotiate MCP elicitation UX capabilities first

A server should discover support before sending a request, then choose a mode the client actually declared. MCP form elicitation fits small non-sensitive structured input; URL mode moves a longer or sensitive interaction to a known web origin. Capability negotiation should include supported modes, field count, schema subset, redirect policy, and whether the client can preserve a resumable request ID.

Unsupported requests return a typed result rather than falling back to improvised chat. In the fixture, a client allows eight fields, both modes, and one account origin. A form with nine properties and a URL on another host are rejected before presentation, which prevents the model from turning a protocol mismatch into persuasive copy.

The current elicitation protocol makes capability declaration and mode selection observable protocol events, so the implementation records both sides of that exchange. I would replay the same request against clients that support forms only, URL handoffs only, both, and neither; each branch needs a typed result and must avoid an improvised chat fallback.

Make MCP elicitation UX explain every field

Place a short purpose beside the request and a specific explanation beside any surprising field. The person should understand which action is paused, why the value is needed now, where it travels, how long it is retained, and what changes after submission. Required and optional states belong in the rendered label, not only inside machine-readable constraints.

Never prefill a consequential choice from untrusted context without showing its provenance. For the worked invoice search, month is required because it bounds the ledger query; region is optional because it only improves routing. The interface shows both decisions before entry. That small disclosure is more useful than a generic privacy link because it connects each value to a concrete downstream use.

For the invoice example, a content review sheet pairs each control with four columns: visible label, collection reason, downstream field, and retention rule. That sheet catches a common mismatch in which the interface says “region” while the server stores a broader location value, and it gives legal, design, and engineering one artifact to approve.

Runnable artifact: The fixture validates negotiated mode, field limits, declared purpose and destination, and the permitted URL origin.

Save this proof as elicitation-contract.test.mjs and run node elicitation-contract.test.mjs. Expected final line: PASS: elicitation contract.

import assert from "node:assert/strict";
const caps={form:true,url:true,maxFields:8};
const validate=r=>{assert.ok(["form","url"].includes(r.mode));assert.ok(caps[r.mode]);assert.ok(r.purpose&&r.destination);if(r.mode==="form")assert.ok(r.schema.properties&&Object.keys(r.schema.properties).length<=caps.maxFields);if(r.mode==="url")assert.equal(new URL(r.url).origin,"https://accounts.example.com");return true};
assert.equal(validate({mode:"form",purpose:"Choose a billing month",destination:"invoice search",schema:{type:"object",properties:{month:{type:"string"}},required:["month"]}}),true);
assert.throws(()=>validate({mode:"url",purpose:"Sign in",destination:"unknown",url:"https://evil.example/"}));
console.log("PASS: elicitation contract");

Treat MCP elicitation UX decline as completion

Decline, cancel, and submit are separate outcomes with stable semantics. Declining should return control to the paused task with an explanation of what cannot proceed, while canceling can mean the person has not made a decision yet. Do not loop the same prompt, shame the person, or reinterpret ordinary conversation as consent.

Agent consent UI should also survive keyboard dismissal, navigation, expired sessions, and repeated requests. Record the request ID and outcome, never the unsubmitted draft. A product can offer a lower-privilege alternative, such as searching by a visible invoice number, but it should not convert that alternative into a second competing call to action. Consent remains revocable until the data leaves the client boundary.

A decline test should begin with an agent paused before a consequential tool call and end with that tool still unexecuted. The trace should contain the request identifier and a decline outcome, but no draft field values; a second trace proves that the bounded invoice-number alternative starts only after an explicit new choice rather than as a disguised retry.

StateUser meaningClient resultTask behavior
SubmitShare reviewed valuesacceptResume
DeclineDo not sharedeclineOffer bounded alternative
CancelNot nowcancelRemain paused
ExpiredRequest is staleerrorStart a new request
Figure 2: Each visible exit has a protocol result and a predictable recovery path.

Constrain MCP elicitation UX form schemas

The rendering subset should be intentionally smaller than general JSON Schema. Support explicit object properties, labels, descriptions, enumerations, bounds, formats, and required fields that the client can present consistently; reject executable extensions, remote references, ambiguous unions, and excessive nesting. Validation messages must identify the field and repair without exposing server internals.

The JSON Schema 2020-12 core specification defines the vocabulary machinery, while a client policy chooses the safe subset. Schema validity is only one gate. The client also checks field sensitivity, purpose, destination, and collection minimization, then displays the reviewed interpretation instead of silently honoring every keyword.

The safe subset is an implementation profile, not a new interpretation of JSON Schema Core. Publish that profile as fixtures—one accepted object and rejected cases for remote references, deep nesting, unsupported unions, excessive properties, and credential-like formats—so client upgrades cannot silently widen what a server is allowed to render.

Secure MCP elicitation UX URL handoffs

URL mode elicitation deserves a visible origin, expiring state handle, bounded redirect set, and a return state that does not claim success before the client verifies it. Open the external interaction with normal browser protections and never inject credentials into a query string. When the user returns, the client asks the server for a typed completion result and relates it to the original request ID.

The MCP Elicitation specification separates form and URL flows and forbids sensitive information in form mode. Treat that boundary as a product rule. The sequence diagram therefore distinguishes open, complete, verify, and resume rather than compressing an external authorization ceremony into a reassuring spinner.

The MCP URL-mode requirements belong beside the handoff code because origin display and sensitive-data boundaries can change with the protocol. A browser test swaps the allowed host after opening, replays an expired state value, and returns without completion; all three cases must leave the paused task unresolved and visibly recoverable.

  1. 1Negotiate

    Confirm URL support and allowed origins.

  2. 2Open

    Show destination and create an expiring state handle.

  3. 3Complete

    Finish on the external origin without leaking credentials.

  4. 4Verify

    Bind the typed result to the original request ID.

Figure 3: A URL handoff verifies origin and completion before the paused agent can resume.

Audit MCP elicitation UX access and recovery

Keyboard order follows the visible form, errors are associated with controls, focus moves to the first invalid field, and the request remains usable at zoom. The WCAG 2.2 Recommendation supplies the accessibility baseline; the protocol does not override it. Test screen-reader announcements for requester, purpose, required state, validation, and the result of declining.

Continue the system boundary through MCP tasks for long-running tools, MCP OAuth audience validation, forms that respect time, and AI agents with permission budgets. Those links cover pause, resource audience, humane completion, and capability scope without mixing their intents into this interface guide.

I test the review surface at 200 percent zoom with keyboard-only input, then verify requester, purpose, required state, and errors against the relevant WCAG 2.2 criteria. The important evidence is not a generic accessibility badge but a short transcript showing focus order, the first-invalid-field move, the decline announcement, and a clean return to the paused task.

Ship MCP elicitation UX with a consent receipt

The release receipt includes server identity, protocol version, capability response, request ID, mode, purpose, destination, field sensitivity review, accepted schema subset, displayed labels, URL origins, redirect rules, retention statement, submit and decline outcomes, keyboard path, error announcements, expiration, retry behavior, and trace redaction. Attach screenshots at narrow and wide widths plus the exact fixture result. Fail release when a password-like field reaches form mode, origin changes are hidden, decline restarts the same demand, draft values enter logs, or completion cannot be tied to the initiating request. This creates a useful operational test: every accepted value can be traced from a person-visible explanation to one authorized destination, and every refusal leaves the surrounding task coherent.

The release packet should contain a redacted request and response pair that another reviewer can follow from capability negotiation through completion. Hashing the server configuration and UI build beside that trace makes a future incident answerable: the team can tell which origin rule, schema profile, and consent copy were actually active without retaining the person’s submitted values.

MCP elicitation UX succeeds when declining is as coherent as submitting and every requested field has a named destination. Treat MCP elicitation UX as an authorization surface whose provenance remains inspectable after the dialog closes.