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
-
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
-
Launch the Inspector and Connect
Start the Inspector:
In the UI, click + Add Server and configure an HTTP/SSE connection with the URLnpx @mcpjam/inspector@latest
http://127.0.0.1:8000/sse
. -
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.