Feedback

Chat Icon

Practical MCP with FastMCP & LangChain

Engineering the Agentic Experience

Building an Advanced Netflix MCP: Client Implementation Guide
85%

Handlers

The four files in handlers/ are the callbacks registered on the Client. Each handles a different type of server-initiated event.

Elicitation: handlers/elicitation.py

Elicitation is how the server pauses a tool call and asks the user a question before continuing. The search_movies tool uses this both to confirm a single match and to let the user pick from several candidates.

# handlers/elicitation.py
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:
    """Handle server requests for user input."""
    if response_type is None:
        return ElicitResult(action="accept")

    print()
    user_input = input(f"[Server asks]: {message}\n> Your choice: ")

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

    return response_type(value=user_input.strip())

The function signature matches what FastMCP expects:

  • message is the question the server sent.

  • response_type is a dataclass type (class) that FastMCP generates from the server's JSON schema. The handler instantiates it with the user's input to produce a response in the shape the server expects.

Depending on the response_type, the handler performs different logic:

  • If response_type is None, the server only needs acknowledgement, so we return ElicitResult(action="accept") immediately.

  • If the user presses Enter without typing anything, we return ElicitResult(action="decline") to signal cancellation — the server converts this into a ToolError and tells the LLM the selection was cancelled.

  • Otherwise we wrap the raw string in response_type(value=...) and return it. The server deserialises this, extracts the value, and continues the tool execution from where it paused.

Sampling: handlers/sampling.py

Sampling is how the server delegates an LLM call to the client. The summarize_movie tool uses this to generate a synopsis after fetching data from OMDB.

# handlers/sampling.py
from dotenv import load_dotenv

from fastmcp.client.sampling.handlers.openai import OpenAISamplingHandler

load_dotenv()

sampling_handler = OpenAISamplingHandler(
    default_model="gpt-5-mini",
)

This is deliberately the simplest file in the project.

  • OpenAISamplingHandler is a ready-made FastMCP class that reads OPENAI_API_KEY from the environment and handles the full sampling round-trip: it receives the server's messages and system_prompt, calls the OpenAI API, and returns the generated text back to the server.

  • We just create an instance with a default model and assign it to sampling_handler.

  • The load_dotenv() call here ensures the API key is available even if this module is imported before main.py has had a chance to call it.

Progress: handlers/progress.py

The get_top_movies tool calls ctx.report_progress() after each movie is processed. This handler receives those notifications and prints a progress line to the terminal.

# handlers/progress.py
async def progress_handler(
    progress: float,
    total: float | None,
    message: str | None,
) -> None:
    """Handle progress updates from the MCP server."""
    if total is not None and total > 0:
        percentage = (progress / total) *

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!