#!/usr/bin/env python3
"""
Tandoor MCP Server - FastMCP implementation with HTTP transport.
A Model Context Protocol server for interacting with Tandoor Recipe Manager.
Provides 4 consolidated tools for recipe management, meal planning, shopping lists, and lookups.
"""
from typing import Optional, Literal, List, Any
from fastmcp import FastMCP
from tandoor_api import (
recipes as _recipes,
meal_plan as _meal_plan,
shopping_list as _shopping_list,
lookup as _lookup,
)
# Initialize FastMCP server with stateless HTTP for load balancing compatibility
mcp = FastMCP("Tandoor Recipes", stateless_http=True)
@mcp.tool
def tandoor_recipes(
operation: Literal["list", "get", "create", "import", "history", "log", "suggest"] = "list",
query: Optional[str] = None,
keywords: Optional[str] = None,
foods: Optional[str] = None,
rating: Optional[int] = None,
limit: int = 10,
recipe_id: Optional[str] = None,
name: Optional[str] = None,
description: Optional[str] = None,
servings: int = 4,
working_time: int = 0,
waiting_time: int = 0,
ingredients: Optional[str] = None,
instructions: Optional[str] = None,
url: Optional[str] = None,
urls: Optional[List[str]] = None,
from_date: Optional[str] = None,
to_date: Optional[str] = None,
log_rating: Optional[int] = None,
comment: Optional[str] = None,
logs: Optional[List[dict]] = None,
mode: Optional[str] = None,
min_match: Optional[float] = None,
) -> dict:
"""
Manage recipes in Tandoor Recipe Manager.
Operations:
- list: Search recipes (use query, keywords, foods, rating, limit)
- get: Get recipe details (use recipe_id)
- create: Create new recipe (use name, description, servings, working_time, waiting_time, ingredients, instructions)
- import: Import recipe(s) from URL (use url for single, urls for batch)
- history: Get cooking history (use recipe_id, from_date, to_date, limit)
- log: Log a cooked recipe (use recipe_id, servings, log_rating, comment) or bulk (use logs list)
- suggest: Suggest recipes based on pantry (use mode, min_match, limit, keywords)
Args:
operation: The operation to perform (list, get, create, import, history, log, suggest)
query: Search text for recipe names (list)
keywords: Comma-separated keyword IDs to filter by (list, suggest)
foods: Comma-separated food IDs to filter by (list)
rating: Minimum rating 1-5 (list)
limit: Maximum results (list, history, suggest - default 10)
recipe_id: Recipe ID or name (get, history, log)
name: Recipe name (create)
description: Recipe description (create)
servings: Number of servings (create, log - default 4)
working_time: Active prep time in minutes (create)
waiting_time: Passive time in minutes (create)
ingredients: Ingredients, one per line as "amount unit food" (create)
instructions: Step-by-step instructions (create)
url: URL to import recipe from (import)
urls: List of URLs to batch import recipes (import)
from_date: Start date YYYY-MM-DD (history)
to_date: End date YYYY-MM-DD (history)
log_rating: Rating 1-5 for cooked recipe (log)
comment: Notes about the cooking (log)
logs: Bulk log list [{"recipe_id": "1", "servings": 4, "rating": 5, "comment": "..."}]
mode: "full-match" or "best-match" (suggest, default best-match)
min_match: Minimum match percentage 0.0-1.0 (suggest, default 0.5)
Returns:
Recipe data or operation result
"""
return _recipes(
operation=operation,
query=query,
keywords=keywords,
foods=foods,
rating=rating,
limit=limit,
recipe_id=recipe_id,
name=name,
description=description,
servings=servings,
working_time=working_time,
waiting_time=waiting_time,
ingredients=ingredients,
instructions=instructions,
url=url,
urls=urls,
from_date=from_date,
to_date=to_date,
log_rating=log_rating,
comment=comment,
logs=logs,
mode=mode,
min_match=min_match,
)
@mcp.tool
def tandoor_meal_plan(
operation: Literal["types", "list", "create", "create_type", "delete"] = "list",
from_date: Optional[str] = None,
to_date: Optional[str] = None,
meal_type: Optional[int] = None,
recipe: Optional[str] = None,
date: Optional[str] = None,
servings: int = 4,
note: Optional[str] = None,
meals: Optional[List[dict]] = None,
type_name: Optional[str] = None,
type_order: int = 0,
meal_plan_id: Optional[int] = None,
meal_plan_ids: Optional[List[int]] = None,
) -> dict:
"""
Manage meal plans in Tandoor Recipe Manager.
Operations:
- types: Get available meal types (Breakfast, Lunch, Dinner, etc.)
- list: Get meal plans (use from_date, to_date, meal_type to filter)
- create: Add recipe to meal plan (use recipe, date, meal_type, servings, note) or bulk (use meals list)
- create_type: Create a new meal type (use type_name, type_order)
- delete: Delete meal plan entry (use meal_plan_id) or bulk (use meal_plan_ids list)
Args:
operation: The operation to perform (types, list, create, create_type, delete)
from_date: Start date YYYY-MM-DD (list)
to_date: End date YYYY-MM-DD (list)
meal_type: Meal type ID to filter or assign (list, create)
recipe: Recipe ID or name (create)
date: Date for meal YYYY-MM-DD (create)
servings: Number of servings (create, default 4)
note: Optional note (create)
meals: Bulk create list [{"recipe": "...", "date": "...", "meal_type": 1, "servings": 4}]
type_name: Name for new meal type (create_type)
type_order: Display order for new meal type (create_type, default 0)
meal_plan_id: Meal plan entry ID (delete)
meal_plan_ids: Bulk delete list of meal plan IDs
Returns:
Meal types, meal plans, or operation result
"""
return _meal_plan(
operation=operation,
from_date=from_date,
to_date=to_date,
meal_type=meal_type,
recipe=recipe,
date=date,
servings=servings,
note=note,
meals=meals,
type_name=type_name,
type_order=type_order,
meal_plan_id=meal_plan_id,
meal_plan_ids=meal_plan_ids,
)
@mcp.tool
def tandoor_shopping_list(
operation: Literal["list", "add", "update", "remove", "check", "clear", "add_recipe", "add_mealplan"] = "list",
checked: Optional[bool] = None,
food: Optional[str] = None,
amount: Optional[float] = None,
unit: Optional[str] = None,
add_items: Optional[List[dict]] = None,
item_id: Optional[int] = None,
item_ids: Optional[List[int]] = None,
items: Optional[List[str]] = None,
update_pantry: bool = True,
recipe_id: Optional[str] = None,
servings: Optional[int] = None,
from_date: Optional[str] = None,
to_date: Optional[str] = None,
) -> dict:
"""
Manage shopping list in Tandoor Recipe Manager.
Operations:
- list: Get shopping list items (use checked to filter)
- add: Add item to list (use food, amount, unit) or bulk (use add_items list)
- update: Update item (use item_id, amount, checked)
- remove: Remove item (use item_id) or bulk (use item_ids list)
- check: Bulk check/uncheck items (use item_ids or items, checked)
- clear: Clear checked items (use update_pantry to sync with inventory)
- add_recipe: Add a recipe's ingredients to shopping list (use recipe_id, servings)
- add_mealplan: Add meal plan entries to shopping list (use from_date, to_date)
Args:
operation: The operation to perform (list, add, update, remove, check, clear, add_recipe, add_mealplan)
checked: Filter by checked status (list) or set checked status (update, check)
food: Food name or ID (add)
amount: Quantity (add, update)
unit: Unit name or ID (add)
add_items: Bulk add list [{"food": "...", "amount": 1, "unit": "..."}]
item_id: Shopping list entry ID (update, remove)
item_ids: List of item IDs (check, remove bulk)
items: List of food names to check/uncheck (check)
update_pantry: Mark cleared items as on-hand in pantry (clear, default True)
recipe_id: Recipe ID or name to add to shopping list (add_recipe)
servings: Number of servings for recipe (add_recipe, defaults to recipe's servings)
from_date: Start date YYYY-MM-DD for meal plan range (add_mealplan)
to_date: End date YYYY-MM-DD for meal plan range (add_mealplan, defaults to from_date)
Returns:
Shopping list items or operation result
"""
return _shopping_list(
operation=operation,
checked=checked,
food=food,
amount=amount,
unit=unit,
add_items=add_items,
item_id=item_id,
item_ids=item_ids,
items=items,
update_pantry=update_pantry,
recipe_id=recipe_id,
servings=servings,
from_date=from_date,
to_date=to_date,
)
@mcp.tool
def tandoor_lookup(
type: Literal["keywords", "foods", "units", "pantry"],
query: Optional[str] = None,
limit: int = 20,
action: Optional[str] = None,
food_id: Optional[int] = None,
food_name: Optional[str] = None,
on_hand: Optional[bool] = None,
pantry_items: Optional[List[dict]] = None,
) -> dict:
"""
Look up reference data in Tandoor Recipe Manager.
Types:
- keywords: Search recipe keywords/tags
- foods: Search food ingredients
- units: Search measurement units
- pantry: View/update pantry inventory
Args:
type: The type of data to look up (keywords, foods, units, pantry)
query: Search text to filter results
limit: Maximum results (default 20)
action: For pantry - "list" (default) or "update"
food_id: For pantry update - food ID to update
food_name: For pantry update - food name to update (resolved to ID)
on_hand: For pantry update - set on-hand status (True/False)
pantry_items: Bulk update list [{"food_id": 1, "on_hand": true}, {"food_name": "salt", "on_hand": false}]
Returns:
List of matching items with id and name
"""
return _lookup(
type=type,
query=query,
limit=limit,
action=action,
food_id=food_id,
food_name=food_name,
on_hand=on_hand,
pantry_items=pantry_items,
)
# Health check endpoint
@mcp.custom_route("/health", methods=["GET"])
async def health_check(request):
from starlette.responses import JSONResponse
return JSONResponse({"status": "healthy", "service": "tandoor-mcp"})
if __name__ == "__main__":
mcp.run(transport="http", host="0.0.0.0", port=8000)