Feedback

Chat Icon

Practical MCP with FastMCP & LangChain

Engineering the Agentic Experience

Building a Functional MCP Client
53%

Elicitation Handler

We need to implement the handlers for the different features that we are going to use in our client. We're going to start with the elicitation handler.

When the server needs to ask the user for more information to continue processing a request, the user will be prompted with a list of choices or a question to clarify the user's intent. The elicitation handler is the bridge for that: it receives the server's question, prompts the user (in the terminal in our case), and sends the answer back so the tool can continue.

Without this handler, any server-side ctx.elicit() call would immediately fail with an error.

We're going to write a simple handler.

The example that you'll find in the official documentation is a bit too minimal but it's useful to understand the basic structure:

from fastmcp import Client
from fastmcp.client.elicitation import ElicitResult
from fastmcp.client.elicitation import ElicitRequestParams
from fastmcp.client.elicitation import RequestContext

async def elicitation_handler(
    message: str,
    response_type: type | None,
    params: ElicitRequestParams,
    context: RequestContext
) -> ElicitResult | object:
    """
    Handle server requests for user input.

    Args:
        message: The prompt to display to the user
        response_type: Python dataclass type for the response (None if no data expected)
        params: Original MCP elicitation parameters including raw JSON schema
        context: Request context with metadata

    Returns:
        - Data directly (implicitly accepts the elicitation)
        - ElicitResult for explicit control over the action
    """
    # Present the message and collect input
    user_input = input(f"{message}: ")

    if not user_input:
        return ElicitResult(action="decline")

    # Create response using the provided dataclass type
    return response_type(value=user_input)

We're going to reuse the official example and add more custom logic to extract the options from the schema and display them in a numbered menu for better user experience.

Here is what we will do:

If the user asks for a dog breed that is similar to one of the known breeds but not exactly the same (e.g. "labra" instead of "labrador"), we can elicit the breed from the user with a list of valid options.

Note that on the server (later in the next chapter) we will limit elicitation to a single choice for the sake of simplicity.

For example:

  • User asks:
How old is my 5-year-old Chiwawa in human years?
  • The server might elicit the breed of the dog using the following message:
Breed 'Chiwawa' not found. Did you mean one of these: chihuahua?
  1. chihuahua
  2. Abandon or quit

First of all, create the folder for handlers:

mkdir -p $HOME/workspace/puppy_guide/client/handlers

Then create the elicitation.py file:

cat > $HOME/workspace/puppy_guide/client/handlers/elicitation.py << 'EOF'
from fastmcp.client.elicitation import ElicitRequestParams
from fastmcp.client.elicitation import ElicitResult
from fastmcp.client.elicitation import RequestContext


async def elicitation_handler(
    message: str,
    response_type: type | None,
    params: ElicitRequestParams,
    context: RequestContext,
) -> ElicitResult | object:
    """
    Called whenever the server needs extra input from the user mid-tool.

    message       -- the question the server is asking
    response_type -- a dataclass FastMCP built from the server's schema;
                     we call response_type(value=...) to send the answer back

Practical MCP with FastMCP & LangChain

Engineering the Agentic Experience

Enroll now to unlock current content and receive all future updates for free. Your purchase supports the author and fuels the creation of more exciting content. Act fast, as the price will rise as the course nears completion!