Skip to main content
Glama

🍽️ MealPlanner MCP Multi-Agent

A multimodal meal planning agent built using FastMCP (Model Context Protocol). It demonstrates how to build an MCP server with image analysis (via Google Gemini Vision), tools, resources, and prompts.

πŸ—οΈ Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    MCP Client (client.py)                 β”‚
β”‚  Connects to server, calls tools, reads resources        β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                          β”‚  MCP Protocol (stdio)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚               MCP Server (server.py)                      β”‚
β”‚                                                           β”‚
β”‚  πŸ”§ Tools:                                               β”‚
β”‚    β€’ identify_ingredients(image) β†’ Gemini Vision API     β”‚
β”‚    β€’ suggest_recipes(ingredients, diet)                   β”‚
β”‚    β€’ get_nutrition(recipe_name)                           β”‚
β”‚    β€’ get_meal_plan(ingredients, meals, diet)              β”‚
β”‚                                                           β”‚
β”‚  πŸ“¦ Resources:                                           β”‚
β”‚    β€’ recipes://all β†’ Full recipe database                β”‚
β”‚    β€’ recipes://{id} β†’ Single recipe details              β”‚
β”‚    β€’ dietary://profiles β†’ Dietary restrictions           β”‚
β”‚                                                           β”‚
β”‚  πŸ’¬ Prompts:                                             β”‚
β”‚    β€’ analyze_fridge β†’ Image analysis workflow            β”‚
β”‚    β€’ weekly_meal_prep β†’ Meal prep planning               β”‚
β”‚                                                           β”‚
β”‚  🧠 External API:                                        β”‚
β”‚    β€’ Google Gemini 2.0 Flash (Vision/Multimodal)         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Related MCP server: Nano Banana Pro MCP

πŸ”„ End-to-End Workflow

High-Level Sequence

 β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”        β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
 β”‚  User  β”‚        β”‚ MCP Client β”‚       β”‚ MCP Server β”‚       β”‚  Gemini    β”‚
 β”‚        β”‚        β”‚ (client.py)β”‚       β”‚ (server.py)β”‚       β”‚  Vision    β”‚
 β””β”€β”€β”€β”¬β”€β”€β”€β”€β”˜        β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜       β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”˜
     β”‚                   β”‚                    β”‚                     β”‚
     β”‚  1. Run client    β”‚                    β”‚                     β”‚
     β”‚  with --image     β”‚                    β”‚                     β”‚
     │──────────────────>β”‚                    β”‚                     β”‚
     β”‚                   β”‚                    β”‚                     β”‚
     β”‚                   β”‚ 2. Connect via     β”‚                     β”‚
     β”‚                   β”‚    stdio transport β”‚                     β”‚
     β”‚                   │───────────────────>β”‚                     β”‚
     β”‚                   β”‚                    β”‚                     β”‚
     β”‚                   β”‚ 3. list_tools()    β”‚                     β”‚
     β”‚                   β”‚    list_resources()β”‚                     β”‚
     β”‚                   β”‚    list_prompts()  β”‚                     β”‚
     β”‚                   │───────────────────>β”‚                     β”‚
     β”‚                   β”‚<──────────────────-β”‚                     β”‚
     β”‚                   β”‚  (capabilities)    β”‚                     β”‚
     β”‚                   β”‚                    β”‚                     β”‚
     β”‚                   β”‚ 4. read_resource   β”‚                     β”‚
     β”‚                   β”‚  ("recipes://all") β”‚                     β”‚
     β”‚                   │───────────────────>β”‚                     β”‚
     β”‚                   β”‚<──────────────────-β”‚                     β”‚
     β”‚                   β”‚   (8 recipes)      β”‚                     β”‚
     β”‚                   β”‚                    β”‚                     β”‚
     β”‚                   β”‚ 5. call_tool       β”‚                     β”‚
     β”‚                   β”‚  identify_         β”‚                     β”‚
     β”‚                   β”‚  ingredients(img)  β”‚                     β”‚
     β”‚                   │───────────────────>β”‚  6. Send image     β”‚
     β”‚                   β”‚                    β”‚     + prompt        β”‚
     β”‚                   β”‚                    │────────────────────>β”‚
     β”‚                   β”‚                    β”‚<────────────────────│
     β”‚                   β”‚                    β”‚  7. Ingredients JSONβ”‚
     β”‚                   β”‚<──────────────────-β”‚                     β”‚
     β”‚                   β”‚  (ingredients list)β”‚                     β”‚
     β”‚                   β”‚                    β”‚                     β”‚
     β”‚                   β”‚ 8. call_tool       β”‚                     β”‚
     β”‚                   β”‚  suggest_recipes() β”‚                     β”‚
     β”‚                   │───────────────────>β”‚                     β”‚
     β”‚                   β”‚<──────────────────-β”‚                     β”‚
     β”‚                   β”‚ (matched recipes)  β”‚                     β”‚
     β”‚                   β”‚                    β”‚                     β”‚
     β”‚                   β”‚ 9. call_tool       β”‚                     β”‚
     β”‚                   β”‚  get_nutrition()   β”‚                     β”‚
     β”‚                   │───────────────────>β”‚                     β”‚
     β”‚                   β”‚<──────────────────-β”‚                     β”‚
     β”‚                   β”‚  (nutrition info)  β”‚                     β”‚
     β”‚                   β”‚                    β”‚                     β”‚
     β”‚                   β”‚ 10. call_tool      β”‚                     β”‚
     β”‚                   β”‚  get_meal_plan()   β”‚                     β”‚
     β”‚                   │───────────────────>β”‚                     β”‚
     β”‚                   β”‚<──────────────────-β”‚                     β”‚
     β”‚                   β”‚   (daily plan)     β”‚                     β”‚
     β”‚                   β”‚                    β”‚                     β”‚
     β”‚  11. Print resultsβ”‚                    β”‚                     β”‚
     β”‚<──────────────────│                    β”‚                     β”‚
     β”‚  (formatted output)                   β”‚                     β”‚

Step-by-Step Data Flow


Step 1: Client Connects to Server

# client.py β€” The client starts the server as a subprocess via stdio
client = Client("server.py")

async with client:
    # Connection is established using MCP protocol over stdio
    # FastMCP auto-handles: handshake, capability negotiation, lifecycle

What happens internally:

  • Client("server.py") tells FastMCP to launch server.py as a subprocess

  • Communication happens over stdin/stdout (stdio transport)

  • MCP protocol handshake occurs automatically

  • Client discovers server name: "MealPlanner"


Step 2: Discover Server Capabilities

# client.py β€” Ask the server what it can do
tools = await client.list_tools()           # β†’ 4 tools
resources = await client.list_resources()    # β†’ 2 static resources
templates = await client.list_resource_templates()  # β†’ 1 template
prompts = await client.list_prompts()        # β†’ 2 prompts

Server responds with:

Type

Name

Description

πŸ”§ Tool

identify_ingredients

Analyze fridge photo β†’ ingredients (multimodal)

πŸ”§ Tool

suggest_recipes

Match ingredients β†’ recipes

πŸ”§ Tool

get_nutrition

Recipe β†’ nutritional info

πŸ”§ Tool

get_meal_plan

Ingredients β†’ daily meal plan

πŸ“¦ Resource

recipes://all

Full recipe database (8 recipes)

πŸ“¦ Resource

dietary://profiles

4 dietary profiles

πŸ“„ Template

recipes://{recipe_id}

Lookup single recipe by ID

πŸ’¬ Prompt

analyze_fridge

Guided fridge analysis workflow

πŸ’¬ Prompt

weekly_meal_prep

Weekly planning template


Step 3: Read Resources (Recipe Database & Dietary Profiles)

# client.py β€” Read the recipe database
recipes = await client.read_resource("recipes://all")

Server processing (server.py):

@mcp.resource("recipes://all")
def get_all_recipes() -> str:
    # Iterates RECIPE_DATABASE dict β†’ returns JSON summary of 8 recipes
    # Each recipe has: id, name, cuisine, prep_time, difficulty, ingredients

Data returned:

[
  {"id": "pasta_primavera", "name": "Pasta Primavera", "cuisine": "Italian", ...},
  {"id": "chicken_stir_fry", "name": "Chicken Stir Fry", "cuisine": "Asian", ...},
  {"id": "vegetable_omelette", "name": "Vegetable Omelette", ...},
  // ... 8 recipes total
]

Step 4: Multimodal β€” Identify Ingredients from Fridge Photo πŸ“Έ

This is the core multimodal step where an image is analyzed.

# client.py β€” Send fridge image for analysis
result = await client.call_tool(
    "identify_ingredients",
    {"image_path": "/path/to/fridge_photo.jpg"}
)

Server processing (server.py) β€” 4 sub-steps:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                 identify_ingredients(image_path)                 β”‚
β”‚                                                                  β”‚
β”‚  Step A: Read image file from disk                              β”‚
β”‚          image_bytes = Path(image_path).read_bytes()             β”‚
β”‚                                                                  β”‚
β”‚  Step B: Encode to base64                                       β”‚
β”‚          image_base64 = base64.b64encode(image_bytes)            β”‚
β”‚          Detect MIME type: .jpg β†’ "image/jpeg"                  β”‚
β”‚                                                                  β”‚
β”‚  Step C: Send to Google Gemini Vision API                       β”‚
β”‚          gemini_client.models.generate_content(                  β”‚
β”‚              model="gemini-2.0-flash",                          β”‚
β”‚              contents=[text_prompt + inline_image_data]          β”‚
β”‚          )                                                       β”‚
β”‚                                                                  β”‚
β”‚  Step D: Parse & return structured JSON                         β”‚
β”‚          Clean markdown code blocks from response               β”‚
β”‚          Return ingredients list as JSON string                  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

The prompt sent to Gemini:

Analyze this image of a fridge or pantry. Identify ALL visible food ingredients.
Return a JSON object with this structure:
{
    "ingredients": [
        {"name": "...", "category": "produce|dairy|protein|...", "quantity": "..."}
    ],
    "total_count": <number>,
    "freshness_notes": "..."
}

Example response from Gemini:

{
  "ingredients": [
    {"name": "eggs", "category": "protein", "quantity": "6 eggs"},
    {"name": "milk", "category": "dairy", "quantity": "1 carton"},
    {"name": "tomato", "category": "produce", "quantity": "3 pieces"},
    {"name": "cheese", "category": "dairy", "quantity": "1 block"},
    {"name": "butter", "category": "dairy", "quantity": "1 stick"}
  ],
  "total_count": 5,
  "freshness_notes": "All items appear fresh and well-stored"
}

Step 5: Suggest Recipes Based on Ingredients

# client.py β€” Pass identified ingredients to recipe matcher
result = await client.call_tool(
    "suggest_recipes",
    {"ingredients": ["eggs", "milk", "tomato", "cheese", "butter"],
     "dietary_preference": "none"}
)

Server processing (server.py):

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚          suggest_recipes(ingredients, dietary_preference)         β”‚
β”‚                                                                  β”‚
β”‚  1. Normalize ingredients β†’ lowercase                           β”‚
β”‚                                                                  β”‚
β”‚  2. Load dietary profile                                        β”‚
β”‚     "none" β†’ no excluded ingredients                            β”‚
β”‚     "vegan" β†’ excludes eggs, cheese, milk, butter, etc.         β”‚
β”‚                                                                  β”‚
β”‚  3. For EACH recipe in RECIPE_DATABASE (8 recipes):             β”‚
β”‚     a. Skip if recipe has excluded ingredients                  β”‚
β”‚     b. Count how many recipe ingredients are available          β”‚
β”‚     c. Calculate match % = available / total Γ— 100              β”‚
β”‚     d. Include if match β‰₯ 40%                                   β”‚
β”‚                                                                  β”‚
β”‚  4. Sort by match_percentage (highest first)                    β”‚
β”‚                                                                  β”‚
β”‚  5. Return: recipe name, match %, available & missing items     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Example output:

{
  "dietary_preference": "No Restrictions",
  "total_matches": 4,
  "recipes": [
    {
      "name": "Vegetable Omelette",
      "match_percentage": 66.7,
      "available_ingredients": ["eggs", "cheese", "butter"],
      "missing_ingredients": ["bell pepper", "onion", "mushroom"]
    },
    {
      "name": "Grilled Cheese Sandwich",
      "match_percentage": 66.7,
      "available_ingredients": ["cheese", "butter"],
      "missing_ingredients": ["bread"]
    }
  ]
}

Step 6: Get Nutrition Info for Top Recipe

# client.py β€” Get nutrition for the best match
result = await client.call_tool(
    "get_nutrition",
    {"recipe_name": "vegetable_omelette"}
)

Server processing (server.py):

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                  get_nutrition(recipe_name)                       β”‚
β”‚                                                                  β”‚
β”‚  1. Search RECIPE_DATABASE by ID or name (case-insensitive)     β”‚
β”‚  2. If not found β†’ fuzzy match (partial string match)           β”‚
β”‚  3. Return full recipe details:                                 β”‚
β”‚     - Ingredients list                                          β”‚
β”‚     - Step-by-step cooking instructions                         β”‚
β”‚     - Nutrition: calories, protein, carbs, fat, fiber           β”‚
β”‚     - Auto-generated health tips based on values                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Example output:

{
  "recipe": "Vegetable Omelette",
  "nutrition": {
    "calories": 320,
    "protein": "22g",
    "carbs": "8g",
    "fat": "24g",
    "fiber": "2g"
  },
  "instructions": [
    "Whisk eggs with a splash of milk.",
    "Melt butter in a non-stick pan over medium heat.",
    "SautΓ© diced vegetables for 2-3 minutes.",
    "Pour in egg mixture and cook until edges set.",
    "Add cheese, fold, and serve."
  ],
  "health_tips": [
    "πŸ’ͺ Good protein content β€” great for muscle recovery!",
    "πŸ’§ Remember to stay hydrated throughout the day!"
  ]
}

Step 7: Generate Daily Meal Plan

# client.py β€” Create a full day's meal plan
result = await client.call_tool(
    "get_meal_plan",
    {"ingredients": ingredient_names, "meals_per_day": 3, "dietary_preference": "none"}
)

Server processing (server.py):

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      get_meal_plan(ingredients, meals_per_day, diet)             β”‚
β”‚                                                                  β”‚
β”‚  1. Internally calls suggest_recipes() to get matched recipes   β”‚
β”‚  2. Assigns recipes to meal slots:                              β”‚
β”‚     - Meal 1 β†’ "Breakfast" (best match recipe)                 β”‚
β”‚     - Meal 2 β†’ "Lunch" (2nd best match)                        β”‚
β”‚     - Meal 3 β†’ "Dinner" (3rd best match)                       β”‚
β”‚  3. Calculates total estimated calories                         β”‚
β”‚  4. Returns structured meal plan                                β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Example output:

{
  "meal_plan": [
    {"meal": "Breakfast", "recipe": "Vegetable Omelette", "match": "66.7%"},
    {"meal": "Lunch", "recipe": "Grilled Cheese Sandwich", "match": "66.7%"},
    {"meal": "Dinner", "recipe": "Classic Tomato Soup", "match": "50.0%"}
  ],
  "estimated_total_calories": 920,
  "note": "This is a suggestion based on available ingredients. Adjust portions as needed!"
}

Step 8: Client Prints Formatted Results

The client formats all JSON responses into a user-friendly terminal output with emojis, colors, and clear sections.


Complete Data Flow Diagram

    πŸ“Έ Fridge Photo
         β”‚
         β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     base64 + prompt     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  MCP Server  β”‚ ─────────────────────── β”‚ Google Gemini β”‚
  β”‚  Tool:       β”‚                          β”‚ Vision API   β”‚
  β”‚  identify_   β”‚ <─── JSON ingredients ── β”‚ (2.0 Flash)  β”‚
  β”‚  ingredients β”‚                          β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚ ["eggs", "tomato", "cheese", ...]
         β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  MCP Server  β”‚     Compares against
  β”‚  Tool:       β”‚ ──▢ RECIPE_DATABASE (8 recipes)
  β”‚  suggest_    β”‚ ──▢ DIETARY_PROFILES (4 profiles)
  β”‚  recipes     β”‚
  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚ [{name: "Omelette", match: 83%}, ...]
         β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  MCP Server  β”‚     Looks up recipe details
  β”‚  Tool:       β”‚ ──▢ Full instructions
  β”‚  get_        β”‚ ──▢ Nutrition data
  β”‚  nutrition   β”‚ ──▢ Health tips (auto-generated)
  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚ {calories: 320, protein: "22g", ...}
         β–Ό
  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
  β”‚  MCP Server  β”‚     Combines top recipes into
  β”‚  Tool:       β”‚ ──▢ Breakfast / Lunch / Dinner
  β”‚  get_        β”‚ ──▢ Calculates total calories
  β”‚  meal_plan   β”‚
  β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
         β”‚
         β–Ό
    πŸ“‹ Complete Meal Plan
       with Nutrition Info

Demo Mode vs Image Mode

Aspect

Demo Mode (python3 client.py)

Image Mode (python3 client.py --image photo.jpg)

Image analysis

❌ Skipped

βœ… Calls Gemini Vision API

Ingredients

Hardcoded sample list (10 items)

Extracted from image by AI

API key needed

❌ No (server loads but tool not called)

βœ… Yes (GOOGLE_API_KEY in .env)

Recipe matching

βœ… Works

βœ… Works

Nutrition info

βœ… Works

βœ… Works

Meal plan

βœ… Works

βœ… Works


πŸ“ Project Structure

mealplanner_mcp_multi_agent/
β”œβ”€β”€ server.py          # MCP Server β€” tools, resources, prompts
β”œβ”€β”€ client.py          # MCP Client β€” demo script (connects via HTTP)
β”œβ”€β”€ pyproject.toml     # Project config & dependencies (uv)
β”œβ”€β”€ uv.lock            # Locked dependency versions
β”œβ”€β”€ .env.example       # Environment variable template
β”œβ”€β”€ .python-version    # Python version pin
└── README.md          # This file

πŸš€ Setup

1. Install uv (if not already installed)

curl -LsSf https://astral.sh/uv/install.sh | sh

2. Install dependencies

uv sync

This creates a .venv virtual environment and installs all dependencies from pyproject.toml.

3. Get a Google Gemini API key

  • Visit Google AI Studio

  • Create a free API key

  • Copy .env.example to .env and paste your key:

cp .env.example .env
# Edit .env and add your GOOGLE_API_KEY

4. Start the MCP Server (Terminal 1)

uv run python3 server.py

The server starts at http://127.0.0.1:8000/mcp using Streamable HTTP transport.

5. Run the Client (Terminal 2)

# Demo mode (uses sample ingredients)
uv run python3 client.py

# With a real fridge photo (multimodal!)
uv run python3 client.py --image path/to/fridge_photo.jpg

# Connect to a custom server URL
uv run python3 client.py --url http://your-server:8000/mcp

6. Test with MCP Inspector

uv run fastmcp dev server.py

This opens a web UI where you can interactively test all tools, resources, and prompts.

πŸ”§ MCP Concepts Demonstrated

Concept

What It Does

Example in This Project

Tools

Functions the LLM can call

identify_ingredients() β€” sends image to Gemini

Resources

Read-only data for the LLM

recipes://all β€” browse recipe database

Resource Templates

Dynamic resources with parameters

recipes://{recipe_id} β€” get specific recipe

Prompts

Reusable prompt templates

analyze_fridge β€” guided fridge analysis workflow

πŸ–ΌοΈ Multimodal Feature

The identify_ingredients tool is the multimodal component:

  1. Accepts an image file path (JPG, PNG, WebP)

  2. Encodes the image to base64

  3. Sends it to Google Gemini 2.0 Flash with a structured prompt

  4. Returns a JSON list of identified ingredients with categories

This demonstrates how MCP tools can handle non-text inputs (images) alongside text.

πŸ“ Example Output

============================================================
🍽️  Meal Planner MCP Client
============================================================

πŸ“‹ Discovering server capabilities...

πŸ”§ Available Tools:
   β€’ identify_ingredients: Analyze a photo of a fridge or pantry...
   β€’ suggest_recipes: Suggest recipes based on available ingredients...
   β€’ get_nutrition: Get detailed nutritional information...
   β€’ get_meal_plan: Generate a simple daily meal plan...

πŸ“¦ Available Resources:
   β€’ recipes://all: Recipe Database
   β€’ dietary://profiles: Dietary Profiles

πŸ‘¨β€πŸ³ Suggesting Recipes:

   🟒 Vegetable Omelette (83.3% match)
   🟒 Caprese Salad (80.0% match)
   🟑 Classic Tomato Soup (66.7% match)
   🟑 Pasta Primavera (57.1% match)
   🟑 Grilled Cheese Sandwich (66.7% match)

πŸ”‘ Key Learning Points

  1. FastMCP simplifies MCP server creation β€” just use @mcp.tool(), @mcp.resource(), @mcp.prompt() decorators

  2. Multimodal = image + text β€” the server handles image encoding and sends to a vision API

  3. Tools vs Resources β€” Tools perform actions (with side effects); Resources provide read-only data

  4. Prompts β€” Reusable templates that guide the LLM through multi-step workflows

  5. Client connects via stdio β€” FastMCP handles all the MCP protocol details automatically

F
license - not found
-
quality - not tested
C
maintenance

Maintenance

–Maintainers
–Response time
–Release cycle
–Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/vijayaselvam/mealplanner_mcp_multi_agent'

If you have feedback or need assistance with the MCP directory API, please join our Discord server