Elicitation
Elicitation is sampling's human sibling. Instead of the LLM generating the next value, the user is prompted through the agent UI and submits the answer themselves.
Tesseron exposes two verbs on ctx, mapping onto MCP elicit's two orthogonal return fields (action, content):
ctx.confirm({ question })returnsPromise<boolean>. For yes/no safety gates. No schema.ctx.elicit({ question, schema, jsonSchema? })returnsPromise<T | null>. For structured content.
Pick by intent: a destructive-op gate is a confirm; a "which warehouse?" is an elicit.
ctx.confirm - yes/no safety gates
Section titled “ctx.confirm - yes/no safety gates”tesseron.action('clearCompleted') .annotate({ destructive: true, requiresConfirmation: true }) .handler(async (_input, ctx) => { const ok = await ctx.confirm({ question: 'Remove 5 completed todos? This cannot be undone.', }); if (!ok) return { removed: 0, cancelled: true }; // ... proceed });Returns true only on explicit accept. Decline, cancel, and absence of elicitation capability all collapse to false - the safe default for destructive ops. You don't need to guard with ctx.agentCapabilities.elicitation; confirm returns false when the client can't prompt.
Under the hood, ctx.confirm sends an elicit request with an empty-properties JSON Schema ({ type: 'object', properties: {}, required: [] }), so MCP clients render a pure Accept/Decline prompt with no input field.
ctx.elicit - structured content
Section titled “ctx.elicit - structured content”import { z } from 'zod';
const warehouseSchema = z.object({ warehouseId: z.string() });
tesseron.action('checkStock') .handler(async (_input, ctx) => { const answer = await ctx.elicit({ question: 'Which warehouse should I check?', schema: warehouseSchema, jsonSchema: z.toJSONSchema(warehouseSchema), }); if (answer === null) return { cancelled: true }; return stock.lookup(answer.warehouseId); });Returns the validated value on accept, null on decline or cancel. Throws ElicitationNotAvailableError (code -32007) when the client didn't advertise elicitation - structured data has no safe default, so the handler must branch explicitly.
jsonSchema is technically optional; if you omit it, the SDK sends a permissive text-only fallback ({ response: string }), which Claude Code renders as a single text input. For good UX, always derive it from your validator - Zod 4 has z.toJSONSchema(schema) built in.
MCP elicit constrains requestedSchema. In protocol 1.2.0, the host validator rejects these shapes with -32602 InvalidParams:
- A schema that is not a JSON object.
- A top-level
typewhose value is anything other than"object". - A truthy top-level
oneOf,anyOf,allOf, ornotkeyword. - A property whose checked
typeis"object". - A property whose checked
typeis"array". - A property whose checked
typeis any other non-primitive value. Present property types must be"string","number","integer", or"boolean".
Two lenient cases are part of the 1.2.0 validator behavior:
- A property without a
typeis accepted. The validator does not infer a type from the property's other keywords. - When a property's
typeis an array, only its first entry is checked. A primitive first entry is accepted even when a later entry is unsupported; an unsupported first entry is rejected.
The lenient cases preserve the current 1.2.0 behavior. Tightening either rule should wait for a future minor, because it would reject schemas that currently pass.
The SDK enforces these rules on send and surfaces the InvalidParams error at the ctx.elicit call site. The gateway checks the same schema again when it receives the request.
Wire format
Section titled “Wire format”Request:
{ "jsonrpc": "2.0", "id": 11, "method": "elicitation/request", "params": { "invocationId": "inv_abc", "question": "Which warehouse should I check?", "schema": { "type": "object", "properties": { "warehouseId": { "type": "string" } }, "required": ["warehouseId"] } }}Response (accept):
{ "jsonrpc": "2.0", "id": 11, "result": { "action": "accept", "value": { "warehouseId": "WH-7" } }}Response (decline / cancel):
{ "jsonrpc": "2.0", "id": 11, "result": { "action": "decline" } }The SDK maps action: 'accept' to the validated value, decline / cancel to null (for ctx.elicit) or false (for ctx.confirm).
Capability gate
Section titled “Capability gate”ctx.agentCapabilities.elicitation reflects what the connected MCP client advertised during initialize. Claude Code advertises elicitation; earlier clients may not.
ctx.confirmis safe in any handler: missing capability returnsfalse, which destructive-op guards treat correctly.ctx.elicitthrowsElicitationNotAvailableErrorwhen capability is missing - catch it or pre-check the flag and provide a non-interactive fallback.
Design hints
Section titled “Design hints”- One question per call. Don't pack a wizard into a schema - chain actions instead.
- Use annotations in tandem.
{ destructive: true, requiresConfirmation: true }tells the agent to warn upfront;ctx.confirmis what gates. - Avoid chained elicitations in one handler - latency accumulates. If you need multi-step input, build a dedicated action per step.
Next: resources - state the agent can read and subscribe to.