McpTools
The elicit Tool
Human-in-the-loop workflows via the elicit tool
The elicit Tool
The elicit tool implements the MCP elicitation protocol for human-in-the-loop workflows, enabling AI agents to request structured input from users when needed.
Concept
AI agents often need human guidance for:
- Approval decisions - "Should I deploy this to production?"
- Ambiguity resolution - "Which customer did you mean?"
- Data collection - "What's the customer's shipping address?"
- Preference selection - "Which design do you prefer?"
The elicit tool bridges AI agents to human users through multiple UI platforms.
Tool Definition
{
"name": "elicit",
"description": "Request structured input from user via Human Functions",
"inputSchema": {
"type": "object",
"properties": {
"message": {
"type": "string",
"description": "Message explaining what information is needed"
},
"schema": {
"type": "object",
"description": "JSON Schema for requested data (flat object only)"
},
"uiType": {
"type": "string",
"enum": ["slack", "discord", "teams", "web"],
"description": "UI platform to use (default: slack)"
}
},
"required": ["message", "schema"]
}
}How It Works
sequenceDiagram
AI Agent->>MCP Server: elicit tool call
MCP Server->>Human Worker: Create Human Function
Human Worker->>User: Display UI (Slack/Discord/Teams/Web)
User->>Human Worker: Submit response
Human Worker->>MCP Server: Return structured data
MCP Server->>AI Agent: Tool result
- AI Agent requests input via
elicittool - MCP Server converts request to Human Function
- Human Worker sends UI to user on specified platform
- User responds with structured data or declines
- MCP Server polls for completion and returns result
- AI Agent continues workflow with user input
Schema Restrictions
MCP elicitation supports only flat objects with primitive properties:
✅ Valid Schemas
// Boolean decision
{
type: "object",
properties: {
approved: { type: "boolean", description: "Approval decision" },
reason: { type: "string", description: "Reason for decision" }
},
required: ["approved"]
}
// Multiple choice with enum
{
type: "object",
properties: {
priority: {
type: "string",
enum: ["low", "medium", "high"],
description: "Issue priority"
}
}
}
// Number input with constraints
{
type: "object",
properties: {
rating: {
type: "number",
minimum: 1,
maximum: 5,
description: "Rating from 1-5"
}
}
}
// Email validation
{
type: "object",
properties: {
email: {
type: "string",
format: "email",
description: "Contact email"
}
}
}❌ Invalid Schemas
// Nested objects not allowed
{
type: "object",
properties: {
user: {
type: "object", // ❌ Nested object
properties: {
name: { type: "string" }
}
}
}
}
// Arrays not allowed
{
type: "object",
properties: {
tags: {
type: "array", // ❌ Array
items: { type: "string" }
}
}
}User Response Actions
Users can respond in three ways:
1. Accept - Provide Requested Data
// Tool call
{
"name": "elicit",
"arguments": {
"message": "Do you approve this deployment?",
"schema": {
"type": "object",
"properties": {
"approved": { "type": "boolean" },
"reason": { "type": "string" }
}
}
}
}
// User accepts
{
"content": [
{
"type": "text",
"text": "User provided: {\"approved\":true,\"reason\":\"Looks good to deploy\"}"
}
]
}2. Decline - Refuse to Provide Data
// User declines
{
"content": [
{
"type": "text",
"text": "User declined: Not comfortable sharing this information"
}
],
"isError": true
}3. Cancel - Cancel the Request
// User cancels (timeout or explicit cancel)
{
"content": [
{
"type": "text",
"text": "User canceled: Request timed out"
}
],
"isError": true
}UI Platforms
Slack (Current)
Uses jsx-slack for interactive BlockKit forms:
await elicit({
message: 'Approve this expense report?',
schema: {
type: 'object',
properties: {
approved: { type: 'boolean' },
notes: { type: 'string' },
},
},
uiType: 'slack',
})Slack displays:
- Message as header
- Form fields based on schema
- Approve/Decline buttons
- Cancel button
Discord (Planned)
Will use Discord embeds and components:
await elicit({
message: 'Select a deployment environment',
schema: {
type: 'object',
properties: {
environment: {
type: 'string',
enum: ['development', 'staging', 'production'],
},
},
},
uiType: 'discord',
})Teams (Planned)
Will use Adaptive Cards:
await elicit({
message: 'Review this purchase order',
schema: {
type: 'object',
properties: {
approved: { type: 'boolean' },
comments: { type: 'string' },
},
},
uiType: 'teams',
})Web (Planned)
Will use React forms at humans.do:
await elicit({
message: 'Provide your shipping address',
schema: {
type: 'object',
properties: {
street: { type: 'string' },
city: { type: 'string' },
zip: { type: 'string' },
},
},
uiType: 'web',
})Timeouts
- Anonymous users: 3 minutes
- Authenticated users: 5 minutes
- Polling interval: 5 seconds
After timeout, the request is automatically canceled:
{
"content": [
{
"type": "text",
"text": "User canceled: Request timed out after 3 minutes"
}
],
"isError": true
}Use Cases
Approval Workflows
// Deploy to production approval
const approval = await elicit({
message: 'Approve deployment to production?',
schema: {
type: 'object',
properties: {
approved: { type: 'boolean' },
reason: { type: 'string' },
},
required: ['approved'],
},
})
if (approval.approved) {
await deploy('production')
} else {
console.log('Deployment declined:', approval.reason)
}Data Collection
// Collect customer feedback
const feedback = await elicit({
message: 'Please rate your experience',
schema: {
type: 'object',
properties: {
rating: {
type: 'number',
minimum: 1,
maximum: 5,
description: 'Rating from 1-5 stars',
},
comment: {
type: 'string',
description: 'Additional feedback',
},
},
required: ['rating'],
},
})
await db.create('Feedback', {
rating: feedback.rating,
comment: feedback.comment,
timestamp: new Date(),
})Ambiguity Resolution
// Multiple customers with same name
const customers = await db.list('Customer', { name: 'John Smith' })
if (customers.length > 1) {
const selection = await elicit({
message: 'Multiple customers found. Which one?',
schema: {
type: 'object',
properties: {
customerId: {
type: 'string',
enum: customers.map((c) => c.id),
description: 'Select customer ID',
},
},
required: ['customerId'],
},
})
const customer = await db.get('Customer', selection.customerId)
}Form Validation
// Collect and validate contact info
const contact = await elicit({
message: 'Please verify your contact information',
schema: {
type: 'object',
properties: {
email: {
type: 'string',
format: 'email',
description: 'Email address',
},
phone: {
type: 'string',
description: 'Phone number',
},
},
required: ['email'],
},
})
await db.update('Customer', customerId, {
email: contact.email,
phone: contact.phone,
})Preference Selection
// Choose notification preferences
const prefs = await elicit({
message: 'How would you like to be notified?',
schema: {
type: 'object',
properties: {
emailNotifications: {
type: 'boolean',
description: 'Receive email notifications',
},
smsNotifications: {
type: 'boolean',
description: 'Receive SMS notifications',
},
frequency: {
type: 'string',
enum: ['immediate', 'daily', 'weekly'],
description: 'Notification frequency',
},
},
},
})
await db.update('UserPreferences', userId, prefs)Error Handling
Handle User Decline
try {
const approval = await elicit({
message: 'Approve this action?',
schema: {
type: 'object',
properties: {
approved: { type: 'boolean' },
},
},
})
if (approval.approved) {
// Proceed with action
}
} catch (error) {
if (error.message.includes('declined')) {
console.log('User declined the request')
// Handle decline gracefully
} else if (error.message.includes('canceled')) {
console.log('Request timed out or was canceled')
// Handle timeout
}
}Provide Defaults
// Request input with fallback to defaults
let config
try {
config = await elicit({
message: 'Configure deployment settings (or use defaults)',
schema: {
type: 'object',
properties: {
environment: {
type: 'string',
enum: ['staging', 'production'],
},
replicas: {
type: 'number',
minimum: 1,
maximum: 10,
},
},
},
})
} catch (error) {
// Use defaults if user doesn't respond
config = {
environment: 'staging',
replicas: 2,
}
}
await deploy(config)Best Practices
1. Clear Messages
// ❌ Vague
message: 'Approve?'
// ✅ Clear
message: 'Approve deployment of v2.1.0 to production? This will affect 1000+ users.'2. Required vs Optional
// Mark truly required fields
{
type: "object",
properties: {
approved: { type: "boolean" },
reason: { type: "string" } // Optional
},
required: ["approved"] // Only approval is required
}3. Use Enums for Choices
// ❌ Free text
priority: { type: "string" }
// ✅ Constrained choices
priority: {
type: "string",
enum: ["low", "medium", "high"]
}4. Provide Descriptions
{
type: "object",
properties: {
budget: {
type: "number",
description: "Maximum budget in USD", // Help users understand
minimum: 0,
maximum: 10000
}
}
}Integration with do Tool
Combine elicit with do for powerful workflows:
// Get user input, then execute operations
const orderDetails = await elicit({
message: 'Provide order details',
schema: {
type: 'object',
properties: {
productId: { type: 'string' },
quantity: { type: 'number' },
},
},
})
// Use input to create order
const product = await db.get('Product', orderDetails.productId)
const order = await db.create('Order', {
customer: user.current(),
items: [
{
product: product,
quantity: orderDetails.quantity,
},
],
total: product.price * orderDetails.quantity,
})
// Confirm with user
await send($.Email.send, {
to: user.current().email,
subject: 'Order Confirmation',
body: `Your order #${order.id} has been created`,
})Next Steps
- The
doTool - TypeScript execution - Authentication - Auth modes
- Integration Guide - Client integration
- Human Functions - Backend implementation