Skip to main content
Glama
mealplans.py17.2 kB
""" Meal Plan tools for Mealie MCP server. Provides tools for managing meal plans - listing, creating, updating, and deleting meal plan entries, plus generation helpers. """ import json import sys from datetime import date, timedelta from pathlib import Path from typing import Optional # Handle imports for both module usage and standalone execution try: from ..client import MealieClient, MealieAPIError except ImportError: # Add parent directory to path for standalone execution sys.path.insert(0, str(Path(__file__).parent.parent)) from client import MealieClient, MealieAPIError def mealplans_list( start_date: Optional[str] = None, end_date: Optional[str] = None ) -> str: """List meal plans for a date range. Args: start_date: Start date in YYYY-MM-DD format (defaults to today) end_date: End date in YYYY-MM-DD format (defaults to 7 days from start) Returns: JSON string with list of meal plan entries """ try: with MealieClient() as client: # Default to current week if no dates provided if not start_date: start = date.today() else: start = date.fromisoformat(start_date) if not end_date: end = start + timedelta(days=7) else: end = date.fromisoformat(end_date) params = { "start_date": start.isoformat(), "end_date": end.isoformat(), } response = client.get("/api/households/mealplans", params=params) # Format response if isinstance(response, list): entries = [] for entry in response: recipe = entry.get("recipe") entries.append({ "id": entry.get("id"), "date": entry.get("date"), "entry_type": entry.get("entryType"), "title": entry.get("title"), "text": entry.get("text"), "recipe_id": entry.get("recipeId"), "recipe_name": recipe.get("name") if isinstance(recipe, dict) else None, "recipe_slug": recipe.get("slug") if isinstance(recipe, dict) else None, }) result = { "start_date": start.isoformat(), "end_date": end.isoformat(), "count": len(entries), "entries": entries } return json.dumps(result, indent=2) return json.dumps(response, indent=2) except MealieAPIError as e: return json.dumps({ "error": str(e), "status_code": e.status_code, "response_body": e.response_body }, indent=2) except Exception as e: return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2) def mealplans_today() -> str: """Get today's meal plan entries. Returns: JSON string with today's meals organized by entry type """ try: with MealieClient() as client: response = client.get("/api/households/mealplans/today") if not response: return json.dumps({ "date": date.today().isoformat(), "count": 0, "meals": {} }, indent=2) # Ensure response is a list if not isinstance(response, list): response = [response] # Organize by entry type meals_by_type = {} for entry in response: entry_type = entry.get("entryType", "meal").lower() if entry_type not in meals_by_type: meals_by_type[entry_type] = [] recipe = entry.get("recipe") meals_by_type[entry_type].append({ "id": entry.get("id"), "title": entry.get("title"), "text": entry.get("text"), "recipe_id": entry.get("recipeId"), "recipe_name": recipe.get("name") if isinstance(recipe, dict) else None, "recipe_slug": recipe.get("slug") if isinstance(recipe, dict) else None, }) result = { "date": date.today().isoformat(), "count": len(response), "meals": meals_by_type } return json.dumps(result, indent=2) except MealieAPIError as e: return json.dumps({ "error": str(e), "status_code": e.status_code, "response_body": e.response_body }, indent=2) except Exception as e: return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2) def mealplans_get(mealplan_id: str) -> str: """Get a specific meal plan entry by ID. Args: mealplan_id: The meal plan entry ID Returns: JSON string with meal plan entry details """ try: with MealieClient() as client: response = client.get(f"/api/households/mealplans/{mealplan_id}") return json.dumps(response, indent=2) except MealieAPIError as e: return json.dumps({ "error": str(e), "status_code": e.status_code, "response_body": e.response_body }, indent=2) except Exception as e: return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2) def mealplans_create( meal_date: str, entry_type: str, recipe_id: Optional[str] = None, title: Optional[str] = None, text: Optional[str] = None ) -> str: """Create a new meal plan entry. Args: meal_date: Date for the meal in YYYY-MM-DD format entry_type: Type of meal - breakfast, lunch, dinner, side, or snack recipe_id: Optional recipe ID to associate with this entry title: Optional title for the entry (used if no recipe) text: Optional note or description Returns: JSON string with created meal plan entry """ try: # Validate entry type valid_types = ["breakfast", "lunch", "dinner", "side", "snack"] if entry_type.lower() not in valid_types: return json.dumps({ "error": f"Invalid entry_type '{entry_type}'. Must be one of: {', '.join(valid_types)}" }, indent=2) with MealieClient() as client: payload = { "date": meal_date, "entryType": entry_type.lower(), } if recipe_id: payload["recipeId"] = recipe_id if title: payload["title"] = title if text: payload["text"] = text response = client.post("/api/households/mealplans", json=payload) return json.dumps({ "success": True, "message": f"Meal plan entry created for {meal_date}", "entry": response }, indent=2) except MealieAPIError as e: return json.dumps({ "error": str(e), "status_code": e.status_code, "response_body": e.response_body }, indent=2) except Exception as e: return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2) def mealplans_update( mealplan_id: str, meal_date: Optional[str] = None, entry_type: Optional[str] = None, recipe_id: Optional[str] = None, title: Optional[str] = None, text: Optional[str] = None ) -> str: """Update an existing meal plan entry. Args: mealplan_id: The meal plan entry ID to update meal_date: Optional new date in YYYY-MM-DD format entry_type: Optional new meal type - breakfast, lunch, dinner, side, or snack recipe_id: Optional new recipe ID title: Optional new title text: Optional new note or description Returns: JSON string with updated meal plan entry """ try: # Validate entry type if provided if entry_type: valid_types = ["breakfast", "lunch", "dinner", "side", "snack"] if entry_type.lower() not in valid_types: return json.dumps({ "error": f"Invalid entry_type '{entry_type}'. Must be one of: {', '.join(valid_types)}" }, indent=2) with MealieClient() as client: # First get the existing entry existing = client.get(f"/api/households/mealplans/{mealplan_id}") if not existing: return json.dumps({ "error": f"Meal plan entry '{mealplan_id}' not found" }, indent=2) # Build update payload payload = { "id": mealplan_id, "date": meal_date or existing.get("date"), "entryType": (entry_type.lower() if entry_type else existing.get("entryType")), } # Handle optional fields if recipe_id is not None: payload["recipeId"] = recipe_id elif "recipeId" in existing: payload["recipeId"] = existing["recipeId"] if title is not None: payload["title"] = title elif "title" in existing: payload["title"] = existing["title"] if text is not None: payload["text"] = text elif "text" in existing: payload["text"] = existing["text"] response = client.put(f"/api/households/mealplans/{mealplan_id}", json=payload) return json.dumps({ "success": True, "message": f"Meal plan entry '{mealplan_id}' updated", "entry": response }, indent=2) except MealieAPIError as e: return json.dumps({ "error": str(e), "status_code": e.status_code, "response_body": e.response_body }, indent=2) except Exception as e: return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2) def mealplans_delete(mealplan_id: str) -> str: """Delete a meal plan entry. Args: mealplan_id: The meal plan entry ID to delete Returns: JSON string confirming deletion """ try: with MealieClient() as client: client.delete(f"/api/households/mealplans/{mealplan_id}") return json.dumps({ "success": True, "message": f"Meal plan entry '{mealplan_id}' deleted" }, indent=2) except MealieAPIError as e: return json.dumps({ "error": str(e), "status_code": e.status_code, "response_body": e.response_body }, indent=2) except Exception as e: return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2) def mealplans_random( entry_type: Optional[str] = None ) -> str: """Get a random meal suggestion from Mealie. Args: entry_type: Optional meal type filter - breakfast, lunch, dinner, side, or snack Returns: JSON string with suggested recipe """ try: with MealieClient() as client: # Use random endpoint response = client.post("/api/households/mealplans/random", json={}) if not response: return json.dumps({ "error": "No random meal suggestion available" }, indent=2) # Extract relevant info recipe = response.get("recipe") if isinstance(response, dict) else response if isinstance(recipe, dict): return json.dumps({ "success": True, "suggestion": { "recipe_id": recipe.get("id"), "name": recipe.get("name"), "slug": recipe.get("slug"), "description": recipe.get("description"), "total_time": recipe.get("totalTime"), "tags": [tag.get("name") for tag in recipe.get("tags", [])], } }, indent=2) return json.dumps({ "success": True, "suggestion": response }, indent=2) except MealieAPIError as e: # If random endpoint doesn't exist, fall back to getting a random recipe try: with MealieClient() as client: # Get recipes and pick one import random response = client.get("/api/recipes", params={"perPage": 100, "page": 1}) if isinstance(response, dict) and "items" in response: recipes = response["items"] if recipes: recipe = random.choice(recipes) return json.dumps({ "success": True, "suggestion": { "recipe_id": recipe.get("id"), "name": recipe.get("name"), "slug": recipe.get("slug"), "description": recipe.get("description"), "total_time": recipe.get("totalTime"), "tags": [tag.get("name") for tag in recipe.get("tags", [])], } }, indent=2) return json.dumps({ "error": "No recipes available for suggestion" }, indent=2) except Exception as fallback_error: return json.dumps({ "error": str(e), "status_code": e.status_code, "response_body": e.response_body }, indent=2) except Exception as e: return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2) def mealplans_get_by_date(meal_date: str) -> str: """Get all meal plan entries for a specific date. Args: meal_date: Date in YYYY-MM-DD format Returns: JSON string with all meals for that date """ try: with MealieClient() as client: params = { "start_date": meal_date, "end_date": meal_date, } response = client.get("/api/households/mealplans", params=params) if not response: return json.dumps({ "date": meal_date, "count": 0, "meals": {} }, indent=2) # Ensure response is a list if not isinstance(response, list): response = [response] # Organize by entry type meals_by_type = {} for entry in response: entry_type = entry.get("entryType", "meal").lower() if entry_type not in meals_by_type: meals_by_type[entry_type] = [] recipe = entry.get("recipe") meals_by_type[entry_type].append({ "id": entry.get("id"), "title": entry.get("title"), "text": entry.get("text"), "recipe_id": entry.get("recipeId"), "recipe_name": recipe.get("name") if isinstance(recipe, dict) else None, "recipe_slug": recipe.get("slug") if isinstance(recipe, dict) else None, }) result = { "date": meal_date, "count": len(response), "meals": meals_by_type } return json.dumps(result, indent=2) except MealieAPIError as e: return json.dumps({ "error": str(e), "status_code": e.status_code, "response_body": e.response_body }, indent=2) except Exception as e: return json.dumps({"error": f"Unexpected error: {str(e)}"}, indent=2) if __name__ == "__main__": """ Test the meal plan tools against the live Mealie instance. """ from dotenv import load_dotenv print("Testing Mealie Meal Plan Tools...") print("=" * 60) # Load environment variables load_dotenv() # Test 1: List meal plans for current week print("\n1. Testing mealplans_list (current week)...") print("-" * 60) result = mealplans_list() print(result) # Test 2: Get today's meals print("\n2. Testing mealplans_today...") print("-" * 60) result = mealplans_today() print(result) # Test 3: Get random meal suggestion print("\n3. Testing mealplans_random...") print("-" * 60) result = mealplans_random() print(result) # Test 4: Get meals for specific date print("\n4. Testing mealplans_get_by_date...") print("-" * 60) result = mealplans_get_by_date(date.today().isoformat()) print(result) print("\n" + "=" * 60) print("All tests completed!")

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/mdlopresti/mealie-mcp'

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