|
| 1 | +--- |
| 2 | +pcx_content_type: concept |
| 3 | +title: Elicitation |
| 4 | +tags: |
| 5 | + - MCP |
| 6 | +sidebar: |
| 7 | + order: 7 |
| 8 | +--- |
| 9 | + |
| 10 | +import { Render, TypeScriptExample } from "~/components"; |
| 11 | + |
| 12 | +[Elicitation](https://spec.modelcontextprotocol.io/specification/draft/client/elicitation/) is an MCP feature that allows servers to request user input during tool execution. This enables interactive workflows where tools can ask for confirmation, collect form data, or gather additional information before completing their operations. |
| 13 | + |
| 14 | +## How elicitation works |
| 15 | + |
| 16 | +When a tool needs user input, it calls `server.elicitInput()` with a message and a [JSON Schema](https://json-schema.org/) describing the expected input format. The MCP client presents this to the user (typically as a form or dialog), and returns the user's response to the server. |
| 17 | + |
| 18 | +The user can respond in three ways: |
| 19 | +- **Accept**: Provide the requested information and continue |
| 20 | +- **Decline**: Explicitly reject the request |
| 21 | +- **Cancel**: Dismiss without making a choice |
| 22 | + |
| 23 | +## Basic example |
| 24 | + |
| 25 | +Here's a simple tool that asks for user confirmation before incrementing a counter: |
| 26 | + |
| 27 | +<TypeScriptExample> |
| 28 | + |
| 29 | +```ts title="src/index.ts" |
| 30 | +import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; |
| 31 | +import { Agent } from "agents"; |
| 32 | +import { createMcpHandler } from "agents/mcp"; |
| 33 | +import { z } from "zod"; |
| 34 | + |
| 35 | +type Env = { |
| 36 | + MyAgent: DurableObjectNamespace<MyAgent>; |
| 37 | +}; |
| 38 | + |
| 39 | +interface State { |
| 40 | + counter: number; |
| 41 | +} |
| 42 | + |
| 43 | +export class MyAgent extends Agent<Env, State> { |
| 44 | + server = new McpServer({ |
| 45 | + name: "Elicitation Demo", |
| 46 | + version: "1.0.0" |
| 47 | + }); |
| 48 | + |
| 49 | + initialState = { |
| 50 | + counter: 0 |
| 51 | + }; |
| 52 | + |
| 53 | + onStart() { |
| 54 | + this.server.registerTool( |
| 55 | + "increase-counter", |
| 56 | + { |
| 57 | + description: "Increase the counter", |
| 58 | + inputSchema: { |
| 59 | + confirm: z.boolean().describe("Do you want to increase the counter?") |
| 60 | + } |
| 61 | + }, |
| 62 | + async ({ confirm }) => { |
| 63 | + if (!confirm) { |
| 64 | + return { |
| 65 | + content: [{ type: "text", text: "Counter increase cancelled." }] |
| 66 | + }; |
| 67 | + } |
| 68 | + |
| 69 | + // Request additional input via elicitation |
| 70 | + const result = await this.server.server.elicitInput({ |
| 71 | + message: "By how much do you want to increase the counter?", |
| 72 | + requestedSchema: { |
| 73 | + type: "object", |
| 74 | + properties: { |
| 75 | + amount: { |
| 76 | + type: "number", |
| 77 | + title: "Amount", |
| 78 | + description: "The amount to increase the counter by", |
| 79 | + minimum: 1 |
| 80 | + } |
| 81 | + }, |
| 82 | + required: ["amount"] |
| 83 | + } |
| 84 | + }); |
| 85 | + |
| 86 | + if (result.action !== "accept" || !result.content) { |
| 87 | + return { |
| 88 | + content: [{ type: "text", text: "Counter increase cancelled." }] |
| 89 | + }; |
| 90 | + } |
| 91 | + |
| 92 | + const amount = Number(result.content.amount); |
| 93 | + this.setState({ |
| 94 | + counter: this.state.counter + amount |
| 95 | + }); |
| 96 | + |
| 97 | + return { |
| 98 | + content: [ |
| 99 | + { |
| 100 | + type: "text", |
| 101 | + text: `Counter increased by ${amount}, current value is ${this.state.counter}` |
| 102 | + } |
| 103 | + ] |
| 104 | + }; |
| 105 | + } |
| 106 | + ); |
| 107 | + } |
| 108 | + |
| 109 | + async onMcpRequest(request: Request) { |
| 110 | + return createMcpHandler(this.server)(request, this.env, {} as ExecutionContext); |
| 111 | + } |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +</TypeScriptExample> |
| 116 | + |
| 117 | +## Elicitation response types |
| 118 | + |
| 119 | +The `elicitInput()` method returns an `ElicitResult` with one of three action types: |
| 120 | + |
| 121 | +```ts |
| 122 | +type ElicitResult = { |
| 123 | + action: "accept" | "decline" | "cancel"; |
| 124 | + content?: Record<string, unknown>; |
| 125 | +}; |
| 126 | +``` |
| 127 | + |
| 128 | +- **accept**: User provided the requested information (available in `content`) |
| 129 | +- **decline**: User explicitly rejected the request |
| 130 | +- **cancel**: User dismissed without making a choice |
| 131 | + |
| 132 | +Always check the `action` before using `content`: |
| 133 | + |
| 134 | +```ts |
| 135 | +const result = await this.server.server.elicitInput({ |
| 136 | + message: "Enter your email", |
| 137 | + requestedSchema: { |
| 138 | + type: "object", |
| 139 | + properties: { |
| 140 | + email: { type: "string", format: "email" } |
| 141 | + }, |
| 142 | + required: ["email"] |
| 143 | + } |
| 144 | +}); |
| 145 | + |
| 146 | +if (result.action === "accept" && result.content?.email) { |
| 147 | + // Use result.content.email |
| 148 | +} else { |
| 149 | + // Handle decline or cancel |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +## Schema types |
| 154 | + |
| 155 | +Elicitation supports various input types through JSON Schema: |
| 156 | + |
| 157 | +### Boolean (confirmation) |
| 158 | + |
| 159 | +```ts |
| 160 | +requestedSchema: { |
| 161 | + type: "object", |
| 162 | + properties: { |
| 163 | + confirmed: { |
| 164 | + type: "boolean", |
| 165 | + title: "Confirm action", |
| 166 | + description: "Check to confirm" |
| 167 | + } |
| 168 | + } |
| 169 | +} |
| 170 | +``` |
| 171 | + |
| 172 | +### Text input |
| 173 | + |
| 174 | +```ts |
| 175 | +requestedSchema: { |
| 176 | + type: "object", |
| 177 | + properties: { |
| 178 | + name: { |
| 179 | + type: "string", |
| 180 | + title: "Name", |
| 181 | + description: "Enter your name" |
| 182 | + } |
| 183 | + }, |
| 184 | + required: ["name"] |
| 185 | +} |
| 186 | +``` |
| 187 | + |
| 188 | +### Email input |
| 189 | + |
| 190 | +```ts |
| 191 | +requestedSchema: { |
| 192 | + type: "object", |
| 193 | + properties: { |
| 194 | + email: { |
| 195 | + type: "string", |
| 196 | + format: "email", |
| 197 | + title: "Email Address" |
| 198 | + } |
| 199 | + } |
| 200 | +} |
| 201 | +``` |
| 202 | + |
| 203 | +### Number input |
| 204 | + |
| 205 | +```ts |
| 206 | +requestedSchema: { |
| 207 | + type: "object", |
| 208 | + properties: { |
| 209 | + amount: { |
| 210 | + type: "number", |
| 211 | + title: "Amount", |
| 212 | + minimum: 1, |
| 213 | + maximum: 100 |
| 214 | + } |
| 215 | + } |
| 216 | +} |
| 217 | +``` |
| 218 | + |
| 219 | +### Select/dropdown |
| 220 | + |
| 221 | +```ts |
| 222 | +requestedSchema: { |
| 223 | + type: "object", |
| 224 | + properties: { |
| 225 | + role: { |
| 226 | + type: "string", |
| 227 | + title: "Role", |
| 228 | + enum: ["viewer", "editor", "admin"], |
| 229 | + enumNames: ["Viewer", "Editor", "Administrator"] |
| 230 | + } |
| 231 | + } |
| 232 | +} |
| 233 | +``` |
| 234 | + |
| 235 | +### Multiple fields |
| 236 | + |
| 237 | +```ts |
| 238 | +requestedSchema: { |
| 239 | + type: "object", |
| 240 | + properties: { |
| 241 | + username: { type: "string", title: "Username" }, |
| 242 | + email: { type: "string", format: "email", title: "Email" }, |
| 243 | + role: { |
| 244 | + type: "string", |
| 245 | + enum: ["viewer", "editor"], |
| 246 | + title: "Role" |
| 247 | + }, |
| 248 | + sendWelcome: { |
| 249 | + type: "boolean", |
| 250 | + title: "Send welcome email" |
| 251 | + } |
| 252 | + }, |
| 253 | + required: ["username", "email", "role"] |
| 254 | +} |
| 255 | +``` |
| 256 | + |
| 257 | +## Best practices |
| 258 | + |
| 259 | +### Keep messages clear |
| 260 | + |
| 261 | +Use clear, action-oriented messages that explain what you're asking for: |
| 262 | + |
| 263 | +```ts |
| 264 | +// Good |
| 265 | +message: "Enter the email address for the new user account" |
| 266 | + |
| 267 | +// Less clear |
| 268 | +message: "Email" |
| 269 | +``` |
| 270 | + |
| 271 | +### Provide helpful descriptions |
| 272 | + |
| 273 | +Use the `description` field to guide users: |
| 274 | + |
| 275 | +```ts |
| 276 | +properties: { |
| 277 | + apiKey: { |
| 278 | + type: "string", |
| 279 | + title: "API Key", |
| 280 | + description: "Your API key from the dashboard (Settings > API)" |
| 281 | + } |
| 282 | +} |
| 283 | +``` |
| 284 | + |
| 285 | +### Handle all response types |
| 286 | + |
| 287 | +Always handle `accept`, `decline`, and `cancel` appropriately: |
| 288 | + |
| 289 | +```ts |
| 290 | +const result = await this.server.server.elicitInput({...}); |
| 291 | + |
| 292 | +switch (result.action) { |
| 293 | + case "accept": |
| 294 | + if (result.content) { |
| 295 | + // Process the input |
| 296 | + return { content: [{ type: "text", text: "Success!" }] }; |
| 297 | + } |
| 298 | + break; |
| 299 | + case "decline": |
| 300 | + return { content: [{ type: "text", text: "Action declined." }] }; |
| 301 | + case "cancel": |
| 302 | + return { content: [{ type: "text", text: "Action cancelled." }] }; |
| 303 | +} |
| 304 | +``` |
| 305 | + |
| 306 | +### Use required fields appropriately |
| 307 | + |
| 308 | +Mark fields as `required` when they're essential for the operation: |
| 309 | + |
| 310 | +```ts |
| 311 | +requestedSchema: { |
| 312 | + type: "object", |
| 313 | + properties: { |
| 314 | + username: { type: "string", title: "Username" }, |
| 315 | + email: { type: "string", format: "email", title: "Email" } |
| 316 | + }, |
| 317 | + required: ["username", "email"] // Both are mandatory |
| 318 | +} |
| 319 | +``` |
| 320 | + |
| 321 | +## Limitations |
| 322 | + |
| 323 | +- Elicitation is currently only supported in direct MCP connections between clients and servers |
| 324 | +- Complex nested schemas may not be supported by all MCP clients |
| 325 | +- The visual presentation of elicitation forms depends on the MCP client implementation |
| 326 | + |
| 327 | +## Related resources |
| 328 | + |
| 329 | +- [MCP Elicitation Specification](https://spec.modelcontextprotocol.io/specification/draft/client/elicitation/) |
| 330 | +- [MCP Tools Documentation](/agents/model-context-protocol/tools/) |
| 331 | +- [JSON Schema Reference](https://json-schema.org/understanding-json-schema/) |
0 commit comments