DocumentationComponentsTheme Creator
Theme CreatorGitHubIntroduction
InstallationUsageThemingMotion
Components
ChatBubbleStreamingResponseCitationsToolCallPlanListParameterDiffModelSelectorElicitationArtifactPanelPromptInputKeyboardToolbar
AI agentsContributing
  1. Components
  2. Elicitation

Elicitation

An MCP server's request for input, rendered as a real form — the schema it sends becomes labelled fields that validate, and Submit, Decline and dismiss return the typed answer.

$ pnpm add @tessinaui/ui

When to use

  • A server needs structured input mid-call. An MCP server sends elicitation/create in form mode and the answer decides what happens next.
  • A short, schema-described form inside a transcript. Any agent that has to collect a handful of typed values from the user can send a schema instead of asking in prose.
  • Only when the agent cannot reasonably carry on without the answer. Resolve or default what you can first; every request interrupts the person.
  • A schema you did not author. The renderer turns whatever flat object schema arrives into labelled, validated fields, so you write no form per tool.
  • Say who is asking and why. serverName and message put the requester and the reason in front of the user before any field.

When not to use

  • Credentials, payment or any sensitive value. URL mode (mode: "url") sends the user to a page, and the spec requires showing the full URL and asking for consent before opening it. That is a different component, not a form.
  • Sampling and roots requests. sampling/createMessage and roots/list go to the model or the client, not the user.
  • A form you write yourself. Compose Form and the field components directly; this renderer exists for schemas you did not author.
  • Approving a tool call. A yes or no on an action belongs on ToolCall, which has Deny and Approve.
  • A long or branching form. The schema is flat, so a multi-step flow needs your own form.

Installation

Terminal
pnpm add @tessinaui/ui

Usage

example.tsx
import { Elicitation } from "@tessinaui/ui/elicitation";

<Elicitation
  serverName="Acme Code"
  message="Which repository should I create?"
  requestedSchema={{
    type: "object",
    properties: {
      name: { type: "string", title: "Repository name", minLength: 1 },
      visibility: { type: "string", title: "Visibility", enum: ["public", "private"] },
    },
    required: ["name"],
  }}
  status={status}
  onAction={(result) => {
    respond(result);
    setStatus(result.action === "accept" ? "accepted" : result.action === "decline" ? "declined" : "cancelled");
  }}
/>

Under MCP 2026-07-28 a tool call can come back as an InputRequiredResult instead of a result. Its inputRequests map holds elicitation/create entries; render one Elicitation per form-mode entry, then retry the call with each answer under the same key and the requestState echoed back unchanged:

example.tsx
const pending = result.inputRequests ?? {};

{Object.entries(pending).map(([key, request]) =>
  request.method === "elicitation/create" && request.params.mode !== "url" ? (
    <Elicitation
      key={key}
      serverName={server.name}
      message={request.params.message}
      requestedSchema={request.params.requestedSchema}
      onAction={(answer) => {
        responses[key] = answer;
        if (Object.keys(responses).length === Object.keys(pending).length) {
          client.callTool({ name, arguments: args, inputResponses: responses, requestState: result.requestState });
        }
      }}
    />
  ) : null,
)}

The card is a snapshot, like the rest of the AI kit: it owns no state machine. onAction receives the ElicitResult to send, and you move status on — after a decision the form collapses to the request and a status line.

Examples

Default

States

After a decision the form collapses to the request and a status line, carried by a word and a glyph as well as a colour.

Formats and numbers

Enums

All four enum spellings. Four options or fewer render as radios; more render as a Select; a multi-select renders as a checkbox group.

Unknown types

A server outside the spec still gets a usable form: the property key labels a field with no title, and a type the spec does not define renders as a text field.

On color

RTL

dir="rtl" mirrors the card and switches its own strings — the server line, the buttons, the required legend and every validation message — to Arabic. An inherited direction mirrors the layout only; pass strings for another language.

Schema mapping

SchemaRenders asValue sent
stringFieldstring
string · format: "email"Field type="email"string
string · format: "uri"Field type="url"string
string · format: "date"DatePicker with a typed input"YYYY-MM-DD"
string · format: "date-time"DatePicker with its date-and-time panelISO 8601 (toISOString())
number / integerNumberFieldnumber
booleanCheckboxboolean, always sent
enum · oneOf · enum + enumNames, ≤ 4 optionsRadioGroupstring
the same, 5 or more optionsSelectstring
array of enum / anyOfCheckboxGroupstring[]
anything elseField (text)string

Each field's label is its title, or the property key when there is none. A description becomes the field's supporting text, and default pre-fills it. An optional field left empty is left out of content.

Validation

Submit validates every field against the schema before anything is sent: required, minLength / maxLength (counted in characters), minimum / maximum, whole numbers for integer, email and URI shape, and minItems / maxItems. A pattern is not part of the MCP schema, but it is checked when a server sends one anyway, and one the browser cannot compile is ignored.

Problems appear under each field and in an error summary above the form, which takes focus; each entry in it moves focus to its field. Editing a field clears its error. A boolean never fails: required asks for the key to be present, and a checkbox always sends one.

API Reference

Elicitation

PropTypeDefaultDescription
messagestring—Why the server is asking. Rendered as plain text; never a link
requestedSchemaElicitationRequestedSchema—The flat object schema of what it asks for. Any prefix renders
serverNamestring—Who is asking, shown above the message
statusElicitationStatus"input-required"input-required · accepted · declined · cancelled. An unknown value reads as input-required
onAction(result: ElicitationResult) => void—Renders Submit, Decline and ×, and receives the answer to send
defaultValuesRecord<string, unknown>—Starting values by key; they win over the schema's default
tone"default" | "on-color"inheritedInherited from a wrapping <Surface>, else "default"
dir"ltr" | "rtl"—Text direction; "rtl" also picks the Arabic strings
stringsPartial<ElicitationStrings>—Per-entry string overrides

Everything else lands on the root <form>.

Helpers

The schema logic is exported for agents that validate on their own side too:

ExportSignatureDescription
planFields(schema) => ElicitationFieldPlan[]One plan per property: its kind, label, options and whether it is required
validateField(plan, value, strings, locale?) => string | undefinedThe first rule the value breaks, as a message
serializeContent(plans, valueOf) => Record<string, ElicitationValue>The typed content of an accept

DOM contract

AttributeWhereValues
data-slotevery partelicitation, elicitation-server, elicitation-message, elicitation-dismiss, elicitation-field, elicitation-decline, elicitation-submit, elicitation-status, elicitation-announcer
data-statusthe rootinput-required · accepted · declined · cancelled
data-tonethe rootdefault · on-color
data-kindeach field wrappertext · email · uri · date · date-time · number · integer · boolean · choice · choices · fallback
data-field-nameeach field wrapperthe property key — the error summary's links find the control through it

Accessibility

There is no APG pattern for a generated form. The card is a house Form, and the contract is:

  • Every field has a real label. The schema's title, else the property key — never a placeholder. Radios and checkbox groups are named groups.
  • Who is asking is part of the form's name. The form is named by the server line and the message together ("Acme Code is asking for information. Which repository should I create?"), so a screen-reader user navigating by landmark hears who asks and what for. The spec requires that the user can tell which server is asking. After a decision the outcome joins the name ("… Submitted"), so an answered request and the same request asked again are two distinct landmarks.
  • Errors follow the error-summary model. Each field shows its own message and aria-invalid; an error summary (role="alert") lists them all, takes focus, and each entry moves focus to its field — the first control a keyboard user could reach there, for a radio group or a date picker too.
  • Decline and dismiss are different answers. Decline sends decline (the user refused); × sends cancel (the user dismissed without choosing). Neither carries content, and none of the three takes focus on arrival. Decline comes before Submit, as Deny comes before Approve on ToolCall.
  • Enter submits. Submit is the form's only submit button, so Enter in a text field runs the same validation as a click.
  • Focus is never lost. After a decision the controls go, and focus moves to the status line, which says what happened.
  • One announcement, on arrival. A request is announced once — "Acme Code is asking for information." — including one that mounts already waiting. Nothing else speaks except the error summary. Standalone, the card mounts one empty polite region; inside a ChatBubbleList it reports to the list's region.

AT matrix: not yet screen-reader tested, hence beta.

Notes

  • Escape does not dismiss. The card sits in a transcript, not a dialog, and an Escape that discarded a half-filled form would lose work. When you render it in a dialog, let the dialog's Escape call onAction({ action: "cancel" }).
  • No skeleton. A request arrives whole in one InputRequiredResult; the wait before it belongs to the tool call's own card.
  • The browser never validates. Submit's click is handled before the form's submit event, so native constraint messages never appear and nothing is ever submitted natively. The schema is the one validator.
ModelSelectorArtifactPanel

On this page

When to useWhen not to useInstallationUsageExamplesDefaultStatesFormats and numbersEnumsUnknown typesOn colorRTLSchema mappingValidationAPI ReferenceElicitationHelpersDOM contractAccessibilityNotes