Tutorial: Building a Recipe Server with Elicitation

This tutorial demonstrates a more advanced MCP feature: elicitation. We will build an MCP server that interactively asks for user preferences (dietary needs, cooking time) before finding a recipe. This project takes about 15 minutes.

Elicitation allows your MCP server to have a conversation with the user to gather necessary information mid-request, creating a more dynamic and intelligent workflow.

Prerequisites

  • Python 3.10+
  • npx (comes with Node.js)
  • uv (a fast Python package installer): pip install uv
  • An API key for an LLM provider like OpenAI or Anthropic.

Step 1: Set Up Your Environment

# Create project and enter directory
mkdir recipe-finder
cd recipe-finder

# Initialize and activate a uv environment
uv init
uv venv
source .venv/bin/activate

# Install dependencies
uv add "mcp[cli]"

# Create server files
touch recipe_finder.py
touch recipe_data.py

Step 2: Add Recipe Data

Copy the contents of the recipe_data.py file from the hackathon/elicitation-recipe-server-python directory into your local recipe_data.py. This file contains a simple dictionary of recipes that our server will use.

Step 3: Implement the MCP Server

Open recipe_finder.py and add the following code. This server defines a tool that uses ctx.elicit to ask follow-up questions.

from mcp.server.fastmcp import FastMCP, Context
from mcp.server.elicitation import AcceptedElicitation, DeclinedElicitation, CancelledElicitation
from pydantic import BaseModel, Field
from recipe_data import RECIPES
import random

mcp = FastMCP("Recipe Finder")

class DietaryChoice(BaseModel):
    """Schema for dietary restriction selection"""
    dietary_restriction: str = Field(
        description="Choose your dietary preference",
        enum=["vegetarian", "vegan", "gluten_free", "meat", "no_restriction"]
    )

class CookingTimeChoice(BaseModel):
    """Schema for cooking time selection"""
    cooking_time: str = Field(
        description="How much time do you have for cooking?",
        enum=["quick", "moderate", "elaborate"]
    )


@mcp.tool()
async def find_recipe(ctx: Context) -> str:
    """Find a recipe based on your dietary preferences and time constraints using interactive elicitation."""

    # Step 1: Ask for dietary preferences
    dietary_result = await ctx.elicit(
        message="What are your dietary preferences or restrictions?",
        schema=DietaryChoice
    )

    match dietary_result:
        case AcceptedElicitation(data=data):
            dietary_pref = data.dietary_restriction
        case DeclinedElicitation() | CancelledElicitation():
            return "No problem! Feel free to ask again when you're ready to cook."

    if dietary_pref == "no_restriction":
        dietary_pref = random.choice(list(RECIPES.keys()))

    # Step 2: Ask for cooking time
    time_result = await ctx.elicit(
        message="How much time do you have for cooking?",
        schema=CookingTimeChoice
    )

    match time_result:
        case AcceptedElicitation(data=data):
            time_available = data.cooking_time
        case DeclinedElicitation() | CancelledElicitation():
            return "No problem!"

    # ... (Recipe matching logic) ...
    available_recipes = RECIPES.get(dietary_pref, {}).get(time_available, [])
    if not available_recipes:
        return f"Sorry, no recipes found for your preferences."

    recipe = random.choice(available_recipes)
    return f"I found a recipe for {recipe['name']}! ..."

if __name__ == "__main__":
    mcp.run(transport="sse")

Key Concept: The await ctx.elicit(...) call pauses the tool's execution and sends a request for more information back to the client (in this case, the LLM in the Playground). Once the user provides an answer, the tool's execution resumes.

Step 4: Test the Server

  1. Run the Server

    Because this server uses elicitation, it requires an HTTP-based transport like SSE. The mcp.run command will start a local web server.

    # Make sure your virtual environment is active
    uv run python recipe_finder.py
    # Server will start on http://127.0.0.1:8000
  2. Launch the Inspector and Connect

    Start the Inspector:

    npx @mcpjam/inspector@latest
    In the UI, click + Add Server and configure an HTTP/SSE connection with the URL http://127.0.0.1:8000/sse.

  3. Test in the Playground

    Go to the Playground tab and start a conversation:

    I want to find a recipe.

    The server will respond by asking about your dietary preferences. The Inspector will display this as a form based on the DietaryChoice schema. After you make a selection, it will ask for your time preference. Finally, it will return a recipe that matches your choices.