This feature implements the MCP Elicitation specification. FrontMCP handles all protocol details automatically.
Why Elicitation?
In the Model Context Protocol, elicitation fills a critical gap:| Aspect | Tool | Elicitation |
|---|---|---|
| Direction | Model triggers, server executes | Server requests, user responds |
| Interaction | One-way (model → server) | Two-way (server ↔ user) |
| Use case | Actions, calculations | Confirmations, approvals, OAuth |
| Blocking | No | Yes (waits for user response) |
- Approval workflows — confirm destructive operations before execution
- Multi-step forms — collect additional information during tool execution
- OAuth flows — redirect users to external authentication (URL mode)
- Human-in-the-loop — require explicit user consent for sensitive actions
- Disambiguation — ask users to clarify ambiguous requests
Basic Usage
Class Style (ToolContext)
Usethis.elicit() within any tool that extends ToolContext:
Class Style (AgentContext)
Agents are LLM-powered autonomous units that can also use elicitation. Overrideexecute() to add custom logic like approval workflows:
Elicitation Modes
FrontMCP supports two elicitation modes per the MCP specification:Form Mode (Default)
Displays a form to collect structured input directly in the client:URL Mode
Redirects the user to an external URL for out-of-band interaction (OAuth, payments, etc.):- Encrypted session ID (AES-256-GCM)
- Expiration timestamp (defaults to elicitation TTL)
- HMAC signature for tamper detection
Elicitation Result
Theelicit() method returns an ElicitResult with the following structure:
Status Values
| Status | Description | Content Present |
|---|---|---|
accept | User submitted the form / completed the action | Yes |
cancel | User cancelled the elicitation | No |
decline | User declined to provide the requested input | No |
Handling Responses
Schema Validation
When the client returns an elicitation result withaction: 'accept', FrontMCP automatically validates the content against the original schema you provided. This ensures type safety and data integrity.
How It Works
- When you call
this.elicit(message, schema), FrontMCP stores the schema alongside the pending elicitation record - When the user responds, FrontMCP validates the content against the stored schema
- Invalid content is rejected with an
InvalidInputError(same as tool input validation)
Validation Behavior
| Scenario | Validation | Result |
|---|---|---|
accept with valid content | ✅ Validated | Success |
accept with invalid content | ✅ Validated | InvalidInputError thrown |
cancel or decline | ⏭️ Skipped | Success (no content expected) |
| No schema stored (backward compat) | ⏭️ Skipped | Success |
Error Handling
Invalid elicitation content throws the sameInvalidInputError used for tool input validation. This allows LLMs (especially in fallback mode) to recognize the error format and retry with corrected data:
- path: The JSON path to the invalid field (e.g.,
["user", "age"]) - message: Human-readable validation error message
Example: Schema Mismatch
Timeout Handling
Elicitation requests have a configurable timeout. When the timeout expires, anElicitationTimeoutError is thrown to kill the tool execution and release resources.
Default Timeout
The default timeout is 5 minutes (300,000ms):Custom Timeout
Specify a custom timeout with thettl option:
Handling Timeouts
Timeouts throw an exception to ensure the tool execution is terminated:Single Elicit Per Session
Only one elicitation can be pending per session at a time. If a new elicitation is requested while one is pending, the previous one is automatically cancelled withstatus: 'cancel'.
Elicitation Hooks
FrontMCP provides flow hooks to intercept elicitation requests and results. Hooks are methods inside plugin classes that run before, after, or around specific flow stages.Hook Types
| Decorator | Description |
|---|---|
.Will('stage') | Runs before the stage executes |
.Did('stage') | Runs after the stage completes |
.Stage('stage') | Runs as the stage (replaces default) |
.Around('stage') | Wraps stage execution (call next() to continue) |
ElicitationRequestHook
Intercepts the elicitation request flow before sending to the client. Stages:| Stage | Description |
|---|---|
parseInput | Parse and validate the elicitation parameters |
validateRequest | Validate mode requirements (URL mode needs elicitationId) |
generateElicitId | Generate unique elicitation ID |
storePendingRecord | Store pending elicitation in the store |
buildRequestParams | Build the MCP elicitation request parameters |
finalize | Final processing before sending |
ElicitationResultHook
Intercepts the elicitation result flow when the user responds. Stages:| Stage | Description |
|---|---|
parseInput | Parse the result payload |
lookupPending | Lookup the pending elicitation record by session ID |
validateContent | Validate content against stored schema (skipped for cancel/decline) |
buildResult | Build the typed result object |
publishResult | Publish result via pub/sub (for distributed deployments) |
finalize | Final processing and cleanup |
Example: Plugin with Elicitation Hooks
Registering the Plugin
Use Cases
Input Sanitization (BEFORE buildResult):Client Capability Detection
Not all MCP clients support elicitation. FrontMCP automatically checks client capabilities before sending elicitation requests.Automatic Validation
When you callthis.elicit(), FrontMCP:
- Checks if the client declared
elicitationcapability during initialization - Verifies the client supports the requested mode (
formorurl) - Throws
ElicitationNotSupportedErrorif elicitation is not supported
Manual Capability Check
You can check elicitation support before callingelicit():
Capability Helper
ThesupportsElicitation helper function:
Universal LLM Support
FrontMCP elicitation works with all LLMs, not just those that support the MCP elicitation protocol. For clients that don’t support elicitation (like OpenAI, Google Gemini, Cursor, etc.), FrontMCP automatically falls back to an LLM-mediated approach.Zero code changes required! Your tools use the same
this.elicit() API regardless of client capabilities. FrontMCP handles detection and routing automatically.How It Works
Standard Elicitation (Claude, supporting clients): Fallback Elicitation (OpenAI, Gemini, etc.):Client Compatibility
| Client | Elicitation Support | Mechanism |
|---|---|---|
| Claude Desktop | ✅ Native | Standard MCP protocol |
| Claude.ai | ✅ Native | Standard MCP protocol |
| OpenAI ChatGPT | ✅ Automatic | Fallback via sendElicitationResult |
| Google Gemini | ✅ Automatic | Fallback via sendElicitationResult |
| Cursor | ✅ Automatic | Fallback via sendElicitationResult |
| Custom Clients | Depends | Check experimental.elicitation capability |
Fallback Response Format
When a tool callselicit() and the client doesn’t support elicitation, FrontMCP returns a structured response:
sendElicitationResult tool:
The sendElicitationResult Tool
This system tool is automatically registered for clients that don’t support elicitation. It:- Is hidden from clients that support standard elicitation
- Accepts the
elicitId, useraction, andcontent - Re-invokes the original tool with the result pre-injected
- Returns the original tool’s final result
| Parameter | Type | Description |
|---|---|---|
elicitId | string | The elicitation ID from the pending request |
action | 'accept' | 'cancel' | 'decline' | User’s action |
content | unknown | User’s response (required when action is 'accept') |
Key Benefits
- Transparent to developers — Same
this.elicit()API for all clients - Automatic detection — Framework checks client capabilities at runtime
- Works with Redis — Fallback state is stored in Redis for distributed deployments
- Type-safe — Schema is converted to JSON Schema for LLM understanding
Error Handling
ElicitationNotSupportedError
Thrown when:- Client doesn’t support elicitation
- Client doesn’t support the requested mode (form/url)
- No session is available
- Transport is not available
ElicitationTimeoutError
Thrown when the user doesn’t respond within the TTL:Configuration Options
Theelicit() method accepts an options object:
| Option | Description | Default |
|---|---|---|
mode | Elicitation mode ('form' or 'url') | 'form' |
ttl | Timeout in milliseconds before throwing | 300000 |
elicitationId | Unique ID for URL mode correlation | Auto (form) / Required (url) |
Distributed Deployments
By default, elicitation state is stored in-memory on the server that initiated the elicit request. This works for single-node deployments but fails in distributed environments where different nodes may handle the initial request and the user’s response.The Problem
In a distributed deployment:Redis Mode (Recommended for Production)
When Redis is configured, FrontMCP automatically uses Redis for elicitation state storage and pub/sub for cross-node result routing:Single-Node Mode (Development)
Without Redis, FrontMCP uses in-memory storage with a warning:- Local development
- Single-node deployments
- Testing environments
Sticky Sessions (Alternative)
If you cannot use Redis, configure your load balancer for session affinity to ensure requests from the same client always reach the same server: Nginx:- Enable “Stickiness” in target group settings
- Use application-based cookie (recommended) or duration-based stickiness
- Set appropriate stickiness duration (longer than your elicitation TTL)
Vercel Edge Functions
Edge functions are stateless and require Redis for elicitation. An error is thrown if elicitation is attempted without Redis on Edge:Deployment Mode Summary
| Environment | Storage | Notes |
|---|---|---|
| Local development | In-memory | Automatic, no configuration needed |
| Single-node production | In-memory | Works but shows warning |
| Multi-node production | Redis | Configure redis in @FrontMcp |
| Vercel Edge | Redis (required) | Use Upstash or similar |
| Kubernetes | Redis | Use Redis cluster for HA |
| Load balanced (no Redis) | Sticky sessions | Configure session affinity |
Encrypted Elicitation
When collecting sensitive user data (passwords, PII, payment information), FrontMCP can encrypt all elicitation data at rest using session-derived keys.Why Encrypt?
Elicitation often collects sensitive data:- Account credentials and API keys
- Personal information (SSN, address, phone)
- Financial data (card numbers, bank accounts)
- Health information (HIPAA-protected data)
Security Model
Encrypted elicitation provides zero-knowledge storage:- Session-Derived Keys: Each session gets a unique encryption key derived from
HKDF-SHA256(serverSecret + sessionId) - AES-256-GCM: Authenticated encryption prevents tampering
- Cross-Session Isolation: Session B cannot decrypt Session A’s data, even with database access
- Fail-Safe Decryption: Decryption fails silently for tampered or corrupted data
Configuration
Set one of these environment variables to enable encryption:How It Works
Cross-Session Isolation
Even if an attacker gains database access, they cannot decrypt elicitation data without:- The server secret (
MCP_ELICITATION_SECRET) - The specific sessionId used to encrypt the data
Best Practices
Do:- Use a strong random secret (32+ characters)
- Rotate secrets periodically (with care for in-flight elicitations)
- Set secrets via environment variables, not code
- Use
MCP_ELICITATION_SECRETfor dedicated elicitation security
- Store secrets in version control
- Use weak or predictable secrets
- Share secrets across environments (dev/staging/prod)
- Log or expose encrypted data for debugging
Real-World Examples
Approval Workflow
Multi-Step Confirmation
OAuth Authorization (URL Mode)
The
generateElicitationToken() creates an opaque token containing the encrypted session ID. When the OAuth callback returns, the token is automatically validated and decrypted—you never need to expose raw session IDs in URLs.Graceful Fallback
MCP Protocol Integration
Elicitation integrates with the MCP protocol via:| Flow | Description |
|---|---|
elicitation/create | Server sends elicitation request to client |
| Result callback | Client sends result with action and content |
Client Capabilities
Clients advertise elicitation support during MCP initialization:Protocol Flow
- Tool calls
this.elicit(message, schema, options) - FrontMCP validates client capabilities
- FrontMCP sends
elicitation/createrequest to client - Client displays form/redirects user based on mode
- User interacts with the elicitation UI
- Client sends result back with
action(‘accept’, ‘cancel’, ‘decline’) and optionalcontent - FrontMCP resolves the promise with typed
ElicitResult
Best Practices
Do:- Use descriptive messages that clearly explain what information is needed
- Define clear Zod schemas with
.describe()on each field - Set appropriate timeouts based on the complexity of the request
- Handle all three status values (
accept,cancel,decline) - Provide graceful fallbacks for clients without elicitation support
- Use form mode for simple confirmations and structured data collection
- Use URL mode only for external authentication flows
- Suppress timeout errors—let them propagate to terminate execution
- Request elicitation for every tool—only when user input is truly needed
- Set extremely long timeouts that could leave resources hanging
- Assume all clients support elicitation—always check or handle errors
- Chain multiple dependent elicitations without clear user guidance
- Use elicitation for data that should be provided upfront in tool input