Skip to main content
Elicitation enables MCP tools to request additional information from users during execution. This creates interactive workflows where tools can dynamically gather data, credentials, or approvals as needed.

Understanding Elicitation

When a tool needs user input, it sends an elicitation request to the client. The client handles presenting this request to the user and returning their response. This enables:
  • Interactive workflows: Tools can ask for input mid-execution
  • Secure credentials: Request sensitive data without storing it
  • Dynamic decisions: Get user approval for actions
  • OAuth flows: Redirect users for authentication
Two Modes Available: Form mode for structured data collection, and URL mode for directing users to external authentication or approval pages.

Elicitation Modes

Form Mode

Collect structured data with JSON schema validation. Best for:
  • Non-sensitive information
  • Structured input requirements
  • Client-side form rendering

URL Mode

Direct users to external URLs. Required for:
  • Sensitive credentials (passwords, API keys)
  • OAuth authentication flows
  • External approval processes
  • Third-party integrations
Server API: Servers use a simplified API like ctx.elicit(message, zodSchema) or ctx.elicit(message, url). The mode is automatically detected and the request is sent to the client with the appropriate JSON schema or URL.Server-Side Validation with mcp-use: When using the mcp-use server library with Zod schemas (simplified API), returned data is automatically validated server-side before reaching your tool logic. This is a convenience feature provided by mcp-use, ensuring type safety and data integrity.

Configuration

With MCPClient

Provide an elicitationCallback function when initializing the MCPClient:
import { MCPClient } from "mcp-use";
import type {
  ElicitRequestFormParams,
  ElicitRequestURLParams,
  ElicitResult,
} from "@modelcontextprotocol/sdk/types.js";

async function elicitationCallback(
  params: ElicitRequestFormParams | ElicitRequestURLParams
): Promise<ElicitResult> {
  if (params.mode === "url") {
    // URL mode: Direct user to external URL
    console.log(`Please visit: ${params.url}`);
    console.log(`Reason: ${params.message}`);

    // Show URL to user (open in browser, display in UI, etc.)
    // In a real app, you might open a popup or new tab

    // Wait for user confirmation
    const userConsent = await promptUser("Did you complete the authorization?");

    return {
      action: userConsent ? "accept" : "decline",
    };
  } else {
    // Form mode: Collect structured data
    console.log(`Server requests: ${params.message}`);

    const schema = params.requestedSchema;
    const userData: Record<string, any> = {};

    // Collect data based on schema properties
    if (schema.type === "object" && schema.properties) {
      for (const [fieldName, fieldSchema] of Object.entries(
        schema.properties
      )) {
        const value = await promptUser(
          `Enter ${fieldSchema.title || fieldName}:`,
          fieldSchema.default
        );
        userData[fieldName] = value;
      }
    }

    return {
      action: "accept",
      data: userData,
    };
  }
}

const client = new MCPClient(config, { elicitationCallback });

With React Hook

Use the onElicitation prop in the useMcp hook:
import { useMcp } from "mcp-use/react";
import type {
  ElicitRequestFormParams,
  ElicitRequestURLParams,
  ElicitResult,
} from "@modelcontextprotocol/sdk/types.js";

function MyComponent() {
  const { tools, callTool, state } = useMcp({
    url: "http://localhost:3000/mcp",
    onElicitation: async (params) => {
      if (params.mode === "url") {
        // URL mode: Open external URL
        const confirmed = window.confirm(
          `${params.message}\n\nOpen ${params.url}?`
        );

        if (confirmed) {
          window.open(params.url, "_blank");

          // Wait for user to complete external action
          const completed = window.confirm("Did you complete the action?");
          return {
            action: completed ? "accept" : "decline",
          };
        }

        return { action: "decline" };
      } else {
        // Form mode: Show form to collect data
        const formData = await showElicitationForm(
          params.message,
          params.requestedSchema
        );

        return {
          action: formData ? "accept" : "cancel",
          data: formData,
        };
      }
    },
  });

  // ... rest of component
}

Form Mode Elicitation

Form mode collects structured data from users with JSON schema validation.
Validation Flow: The client receives a JSON Schema to guide user input. When data is returned, the server validates it against the original Zod schema (if using simplified API) or JSON Schema (if using verbose API). This provides defense-in-depth validation.

Request Parameters

FieldTypeDescription
mode"form" (optional)Specifies form mode (can be omitted for backwards compatibility)
messagestringHuman-readable prompt explaining what information is needed
requestedSchemaobjectJSON Schema defining the expected response structure

Client-Side vs Server-Side Validation

Client-Side (Optional):
  • You can validate data before sending to improve UX
  • Shows errors immediately without round-trip
  • Not required - server validates anyway
Server-Side (Automatic):
  • Server always validates returned data
  • Ensures data integrity regardless of client behavior
  • Protects against malicious or buggy clients
  • Returns clear validation error messages
While the server always validates data, implementing client-side validation improves UX by catching errors before the round-trip:
async function handleFormElicitation(
  params: ElicitRequestFormParams
): Promise<ElicitResult> {
  const schema = params.requestedSchema;

  if (schema.type === "object" && schema.properties) {
    const formData: Record<string, any> = {};

    // Build form based on schema
    for (const [fieldName, fieldDef] of Object.entries(schema.properties)) {
      const value = await showInputField({
        name: fieldName,
        title: fieldDef.title || fieldName,
        description: fieldDef.description,
        type: fieldDef.type, // 'string', 'number', 'boolean', etc.
        default: fieldDef.default,
        required: schema.required?.includes(fieldName),
        // Optional: Add client-side validation
        min: fieldDef.minimum || fieldDef.minLength,
        max: fieldDef.maximum || fieldDef.maxLength,
        pattern: fieldDef.pattern,
        format: fieldDef.format,
      });

      formData[fieldName] = value;
    }

    // Optional: Validate before sending
    // This improves UX but server will validate anyway
    const validationErrors = validateAgainstSchema(formData, schema);
    if (validationErrors.length > 0) {
      // Show errors to user, let them fix
      await showValidationErrors(validationErrors);
      // Retry or cancel
    }

    return {
      action: "accept",
      data: formData,
    };
  }

  return { action: "cancel" };
}
Best Practice: Implement client-side validation for better UX, but remember that the server always validates as the final authority. Never rely solely on client-side validation.

URL Mode Elicitation

URL mode directs users to external URLs for sensitive operations. This mode MUST be used for:
  • Authentication credentials
  • API keys and tokens
  • OAuth authorization flows
  • Payment information
  • Any sensitive personal data
Security Requirement: Never use form mode for sensitive data. URL mode ensures credentials and sensitive information do not pass through the MCP client, maintaining proper security boundaries.

Request Parameters

FieldTypeDescription
mode"url"Specifies URL mode (required)
messagestringHuman-readable explanation of why the user needs to visit the URL
urlstringThe URL to direct the user to
elicitationIdstringUnique identifier for tracking this elicitation (auto-generated by server)

Example: OAuth Authorization

async function handleUrlElicitation(
  params: ElicitRequestURLParams
): Promise<ElicitResult> {
  // Display the URL to the user
  const userMessage = `
    ${params.message}
    
    Please visit this URL to continue:
    ${params.url}
  `;

  console.log(userMessage);

  // In a browser environment, open in new tab
  if (typeof window !== "undefined") {
    window.open(params.url, "_blank", "noopener,noreferrer");
  }

  // Wait for user confirmation
  const completed = await confirmWithUser(
    "Have you completed the authorization?"
  );

  if (completed) {
    return { action: "accept" };
  } else {
    return { action: "decline" };
  }
}

Response Structure

The elicitation callback must return an ElicitResult object:
interface ElicitResult {
  action: "accept" | "decline" | "cancel";
  data?: any; // Only for 'accept' action in form mode
}

Response Actions

ActionWhen to UseData Field
acceptUser provided valid inputRequired for form mode, optional for URL mode
declineUser explicitly declined to provide informationOmit
cancelUser dismissed the request without making a choiceOmit

Error Handling

If no elicitation callback is provided but a tool requests user input, the tool call will fail:
// Without elicitation callback
const client = new MCPClient(config);
// No elicitationCallback provided

// Tool that requires elicitation will fail
const session = await client.createSession("server-name");
try {
  await session.callTool("collect-user-info", {});
} catch (error) {
  console.error("Elicitation not supported:", error);
  // MCP error: Client does not support elicitation
}

Next Steps