Loading...

Elicitations

Handle user input requests from MCP servers through the built-in elicitation interface

MCP Elicitation allows MCP servers to request additional information from users during tool execution. When an MCP server needs user input, Tambo automatically displays an interactive form in the message input area, making it seamless for users to provide the requested information.

For more details on the elicitation specification, see the MCP Elicitation documentation.

What is Elicitation?

Elicitation is a protocol feature that allows MCP servers to pause execution and request structured input from the user. This is useful when:

  • A tool needs additional information that wasn't provided in the initial request
  • An MCP server requires user confirmation before proceeding with an action
  • Multiple pieces of information are needed to complete a task

Built-in Support

The message-input component automatically handles elicitation requests from MCP servers. When an elicitation request is received, the message input area is replaced with a dynamic form that matches the server's requested schema.

import { MessageInput } from "@/components/ui/message-input";

// Elicitation is automatically handled - no additional setup needed
<MessageInput contextKey="my-thread">
  <MessageInput.Textarea />
  <MessageInput.SubmitButton />
</MessageInput>;

The elicitation UI automatically:

  • Renders appropriate input fields based on the requested schema (text, number, boolean, enum)
  • Validates user input according to the schema constraints
  • Provides Accept, Decline, and Cancel actions
  • Returns the user's response to the MCP server

How It Works

Here's the typical flow when an MCP server requests elicitation:

Example Use Cases

Confirmation Dialogs

An MCP server can request confirmation before performing a destructive action:

Elicitation Confirmation

Multi-field Forms

MCP servers can request multiple fields at once:

Elicitation Multi-field

Enum Selection

When the server needs the user to choose from a predefined set of options:

Elicitation Enum

import { TamboProvider } from "@tambo-ai/react";
import { TamboMcpProvider } from "@tambo-ai/react/mcp";

function App() {
  return (
    <TamboProvider components={...} mcpServers={[]}>
      <TamboMcpProvider>
        {/* Your app - elicitation from server-side MCP servers will work */}
      </TamboMcpProvider>
    </TamboProvider>
  );
}

Without the TamboMcpProvider, server-side MCP servers cannot send elicitation requests to your application, as there's no connection to Tambo's internal MCP server.

Advanced: Custom Elicitation Handlers

While the built-in elicitation UI handles most use cases, you can implement custom elicitation handling if needed.

Custom Elicitation UI

You can access the current elicitation request and respond to it using the useTamboElicitationContext hook:

import { useTamboElicitationContext } from "@tambo-ai/react/mcp";

function CustomElicitationUI() {
  const { elicitation, resolveElicitation } = useTamboElicitationContext();

  if (!elicitation) return null;

  const handleAccept = () => {
    resolveElicitation?.({
      action: "accept",
      content: {
        /* user's input */
      },
    });
  };

  const handleDecline = () => {
    resolveElicitation?.({ action: "decline" });
  };

  const handleCancel = () => {
    resolveElicitation?.({ action: "cancel" });
  };

  return (
    <div>
      <p>{elicitation.message}</p>
      {/* Render form fields based on elicitation.requestedSchema */}
      <button onClick={handleAccept}>Accept</button>
      <button onClick={handleDecline}>Decline</button>
      <button type="button" onClick={handleCancel}>
        Cancel
      </button>
    </div>
  );
}

Provider-level Handler

For advanced scenarios where you need programmatic control over elicitation flow—such as conditionally approving requests, implementing custom validation logic, or integrating with external systems—you can provide a handler function that intercepts elicitation requests before they reach the UI.

The handler is an async callback that receives the elicitation request and returns a promise with the response. This allows you to implement custom decision logic, show your own UI, or even automatically respond based on the request content.

Apply a custom handler to all MCP servers:

import { TamboProvider } from "@tambo-ai/react";
import { TamboMcpProvider } from "@tambo-ai/react/mcp";

function App() {
  const handleElicitation = async (request, extra, serverInfo) => {
    // request: { params: { message, requestedSchema } }
    // extra: { signal } - AbortSignal for cancellation
    // serverInfo: { name, url, description, ... } - MCP server config

    console.log(`Elicitation from ${serverInfo.name}: ${request.params.message}`);

    // Show custom UI and collect user input
    const userInput = await showCustomElicitationUI(request.params);

    return {
      action: "accept",
      content: userInput,
    };
  };

  return (
    <TamboProvider mcpServers={[...]}>
      <TamboMcpProvider
        handlers={{
          elicitation: handleElicitation,
        }}
      >
        {/* Your app */}
      </TamboMcpProvider>
    </TamboProvider>
  );
}

Per-server Handler

You can also override the handler for specific MCP servers, which is useful when different servers require different approval workflows or UI patterns:

import { TamboProvider } from "@tambo-ai/react";
import { TamboMcpProvider, MCPTransport } from "@tambo-ai/react/mcp";

function App() {
  const handleGitHubElicitation = async (request, extra) => {
    // Custom handling for GitHub MCP server only
    return {
      action: "accept",
      content: { confirmed: true },
    };
  };

  return (
    <TamboProvider
      mcpServers={[
        {
          url: "https://github-mcp.example.com",
          transport: MCPTransport.HTTP,
          handlers: {
            elicitation: handleGitHubElicitation,
          },
        },
      ]}
    >
      <TamboMcpProvider>{/* Your app */}</TamboMcpProvider>
    </TamboProvider>
  );
}

Per-server handlers take precedence over provider-level handlers.

Handler Parameters

Elicitation Request

{
  params: {
    message: string;  // Human-readable message from server
    requestedSchema: {
      type: "object";
      properties: Record<string, FieldSchema>;
      required?: string[];
    };
  }
}

Extra Parameters

{
  signal: AbortSignal; // Fires if server cancels the request
}

Response

{
  action: "accept" | "decline" | "cancel";
  content?: Record<string, unknown>;  // User's input (required if action is "accept")
}
  • accept: User provided input and wants to continue
  • decline: User chose not to provide input but tool should continue
  • cancel: User wants to abort the entire operation