added all mcp
This commit is contained in:
15
mealie-mcp-bundle/server/tools/__init__.py
Normal file
15
mealie-mcp-bundle/server/tools/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
from .mealplan_tools import register_mealplan_tools
|
||||
from .recipe_tools import register_recipe_tools
|
||||
|
||||
|
||||
def register_all_tools(mcp, mealie):
|
||||
"""Register all tools with the MCP server."""
|
||||
register_recipe_tools(mcp, mealie)
|
||||
register_mealplan_tools(mcp, mealie)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"register_all_tools",
|
||||
"register_recipe_tools",
|
||||
"register_mealplan_tools",
|
||||
]
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
99
mealie-mcp-bundle/server/tools/food_tools.py
Normal file
99
mealie-mcp-bundle/server/tools/food_tools.py
Normal file
@@ -0,0 +1,99 @@
|
||||
import json
|
||||
import logging
|
||||
import traceback
|
||||
from typing import Optional
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mealie import MealieFetcher
|
||||
from utils import format_error_response
|
||||
|
||||
logger = logging.getLogger("mealie-mcp")
|
||||
|
||||
|
||||
def register_food_tools(mcp: FastMCP, mealie: MealieFetcher) -> None:
|
||||
"""Register all food and unit-related tools with the MCP server."""
|
||||
|
||||
@mcp.tool()
|
||||
def get_foods(
|
||||
search: Optional[str] = None,
|
||||
page: Optional[int] = None,
|
||||
per_page: Optional[int] = None,
|
||||
) -> str:
|
||||
"""Get a list of foods from your Mealie database.
|
||||
|
||||
Args:
|
||||
search: Search term to filter foods
|
||||
page: Page number for pagination
|
||||
per_page: Number of items per page
|
||||
|
||||
Returns:
|
||||
str: JSON list of foods
|
||||
"""
|
||||
try:
|
||||
logger.info({"message": "Fetching foods", "search": search})
|
||||
result = mealie.get_foods(search=search, page=page, per_page=per_page)
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
error_msg = f"Error fetching foods: {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug({"message": "Error traceback", "traceback": traceback.format_exc()})
|
||||
return format_error_response(error_msg)
|
||||
|
||||
@mcp.tool()
|
||||
def get_units() -> str:
|
||||
"""Get a list of all units from your Mealie database.
|
||||
|
||||
Returns:
|
||||
str: JSON list of units
|
||||
"""
|
||||
try:
|
||||
logger.info({"message": "Fetching units"})
|
||||
result = mealie.get_units()
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
error_msg = f"Error fetching units: {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug({"message": "Error traceback", "traceback": traceback.format_exc()})
|
||||
return format_error_response(error_msg)
|
||||
|
||||
@mcp.tool()
|
||||
def create_food(name: str, description: Optional[str] = None) -> str:
|
||||
"""Create a new food item in Mealie.
|
||||
|
||||
Args:
|
||||
name: Name of the food
|
||||
description: Optional description
|
||||
|
||||
Returns:
|
||||
str: Confirmation with created food details
|
||||
"""
|
||||
try:
|
||||
logger.info({"message": "Creating food", "name": name})
|
||||
result = mealie.create_food(name=name, description=description)
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
error_msg = f"Error creating food '{name}': {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug({"message": "Error traceback", "traceback": traceback.format_exc()})
|
||||
return format_error_response(error_msg)
|
||||
|
||||
@mcp.tool()
|
||||
def create_unit(name: str, abbreviation: Optional[str] = None) -> str:
|
||||
"""Create a new unit in Mealie.
|
||||
|
||||
Args:
|
||||
name: Name of the unit (e.g., "Teaspoon", "Gram")
|
||||
abbreviation: Optional abbreviation (e.g., "tsp", "g")
|
||||
|
||||
Returns:
|
||||
str: Confirmation with created unit details
|
||||
"""
|
||||
try:
|
||||
logger.info({"message": "Creating unit", "name": name})
|
||||
result = mealie.create_unit(name=name, abbreviation=abbreviation)
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
error_msg = f"Error creating unit '{name}': {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug({"message": "Error traceback", "traceback": traceback.format_exc()})
|
||||
return format_error_response(error_msg)
|
||||
133
mealie-mcp-bundle/server/tools/mealplan_tools.py
Normal file
133
mealie-mcp-bundle/server/tools/mealplan_tools.py
Normal file
@@ -0,0 +1,133 @@
|
||||
import logging
|
||||
import traceback
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from mealie import MealieFetcher
|
||||
from models.mealplan import MealPlanEntry
|
||||
from utils import format_error_response
|
||||
|
||||
logger = logging.getLogger("mealie-mcp")
|
||||
|
||||
|
||||
def register_mealplan_tools(mcp: FastMCP, mealie: MealieFetcher) -> None:
|
||||
"""Register all mealplan-related tools with the MCP server."""
|
||||
|
||||
@mcp.tool()
|
||||
def get_all_mealplans(
|
||||
start_date: Optional[str] = None,
|
||||
end_date: Optional[str] = None,
|
||||
page: Optional[int] = None,
|
||||
per_page: Optional[int] = None,
|
||||
) -> Dict[str, Any]:
|
||||
"""Get all meal plans for the current household with pagination.
|
||||
|
||||
Args:
|
||||
start_date: Start date for filtering meal plans (ISO format YYYY-MM-DD)
|
||||
end_date: End date for filtering meal plans (ISO format YYYY-MM-DD)
|
||||
page: Page number to retrieve
|
||||
per_page: Number of items per page
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: JSON response containing mealplan items and pagination information
|
||||
"""
|
||||
try:
|
||||
logger.info(
|
||||
{
|
||||
"message": "Fetching mealplans",
|
||||
"start_date": start_date,
|
||||
"end_date": end_date,
|
||||
"page": page,
|
||||
"per_page": per_page,
|
||||
}
|
||||
)
|
||||
return mealie.get_mealplans(
|
||||
start_date=start_date,
|
||||
end_date=end_date,
|
||||
page=page,
|
||||
per_page=per_page,
|
||||
)
|
||||
except Exception as e:
|
||||
error_msg = f"Error fetching mealplans: {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug(
|
||||
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
||||
)
|
||||
return format_error_response(error_msg)
|
||||
|
||||
@mcp.tool()
|
||||
def create_mealplan(
|
||||
entry: MealPlanEntry,
|
||||
) -> Dict[str, Any]:
|
||||
"""Create a new meal plan entry.
|
||||
|
||||
Args:
|
||||
entry: MealPlanEntry object containing date, recipe_id, title, and entry_type
|
||||
|
||||
Returns:
|
||||
Dict[str, Any]: JSON response containing the created mealplan entry
|
||||
"""
|
||||
try:
|
||||
logger.info(
|
||||
{
|
||||
"message": "Creating mealplan entry",
|
||||
"entry": entry.model_dump(),
|
||||
}
|
||||
)
|
||||
return mealie.create_mealplan(**entry.model_dump())
|
||||
except Exception as e:
|
||||
error_msg = f"Error creating mealplan entry: {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug(
|
||||
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
||||
)
|
||||
return format_error_response(error_msg)
|
||||
|
||||
@mcp.tool()
|
||||
def create_mealplan_bulk(
|
||||
entries: List[MealPlanEntry],
|
||||
) -> Dict[str, Any]:
|
||||
"""Create multiple meal plan entries in bulk.
|
||||
|
||||
Args:
|
||||
entries: List of MealPlanEntry objects
|
||||
containing date, recipe_id, title, and entry_type
|
||||
Returns:
|
||||
Dict[str, Any]: JSON response containing the created mealplan entries
|
||||
"""
|
||||
try:
|
||||
logger.info(
|
||||
{
|
||||
"message": "Creating bulk mealplan entries",
|
||||
"entries_count": len(entries),
|
||||
}
|
||||
)
|
||||
for entry in entries:
|
||||
mealie.create_mealplan(**entry.model_dump())
|
||||
return {"message": "Bulk mealplan entries created successfully"}
|
||||
except Exception as e:
|
||||
error_msg = f"Error creating bulk mealplan entries: {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug(
|
||||
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
||||
)
|
||||
return format_error_response(error_msg)
|
||||
|
||||
@mcp.tool()
|
||||
def get_todays_mealplan() -> List[Dict[str, Any]]:
|
||||
"""Get the mealplan entries for today.
|
||||
|
||||
Returns:
|
||||
List[Dict[str, Any]]: List of today's mealplan entries
|
||||
"""
|
||||
try:
|
||||
logger.info({"message": "Fetching today's mealplan"})
|
||||
return mealie.get_todays_mealplan()
|
||||
except Exception as e:
|
||||
error_msg = f"Error fetching today's mealplan: {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug(
|
||||
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
||||
)
|
||||
return format_error_response(error_msg)
|
||||
305
mealie-mcp-bundle/server/tools/recipe_tools.py
Normal file
305
mealie-mcp-bundle/server/tools/recipe_tools.py
Normal file
@@ -0,0 +1,305 @@
|
||||
import json
|
||||
import logging
|
||||
import traceback
|
||||
from typing import List, Optional
|
||||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
from mealie import MealieFetcher
|
||||
from models.recipe import Recipe, RecipeIngredient, RecipeInstruction, RecipeNutrition
|
||||
from utils import format_error_response
|
||||
|
||||
logger = logging.getLogger("mealie-mcp")
|
||||
|
||||
|
||||
def register_recipe_tools(mcp: FastMCP, mealie: MealieFetcher) -> None:
|
||||
"""Register all recipe-related tools with the MCP server."""
|
||||
|
||||
@mcp.tool()
|
||||
def get_recipes(
|
||||
search: Optional[str] = None,
|
||||
page: Optional[int] = None,
|
||||
per_page: Optional[int] = None,
|
||||
categories: Optional[List[str]] = None,
|
||||
tags: Optional[List[str]] = None,
|
||||
) -> str:
|
||||
"""Provides a paginated list of recipes with optional filtering.
|
||||
|
||||
Args:
|
||||
search: Filters recipes by name or description.
|
||||
page: Page number for pagination.
|
||||
per_page: Number of items per page.
|
||||
categories: Filter by specific recipe categories.
|
||||
tags: Filter by specific recipe tags.
|
||||
|
||||
Returns:
|
||||
str: Recipe summaries with details like ID, name, description, and image information.
|
||||
"""
|
||||
try:
|
||||
logger.info(
|
||||
{
|
||||
"message": "Fetching recipes",
|
||||
"search": search,
|
||||
"page": page,
|
||||
"per_page": per_page,
|
||||
"categories": categories,
|
||||
"tags": tags,
|
||||
}
|
||||
)
|
||||
result = mealie.get_recipes(
|
||||
search=search,
|
||||
page=page,
|
||||
per_page=per_page,
|
||||
categories=categories,
|
||||
tags=tags,
|
||||
)
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
error_msg = f"Error fetching recipes: {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug(
|
||||
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
||||
)
|
||||
return format_error_response(error_msg)
|
||||
|
||||
@mcp.tool()
|
||||
def get_recipe_detailed(slug: str) -> str:
|
||||
"""Retrieve a specific recipe by its slug identifier. Use this when to get full recipe
|
||||
details for tasks like updating or displaying the recipe.
|
||||
|
||||
Args:
|
||||
slug: The unique text identifier for the recipe, typically found in recipe URLs
|
||||
or from get_recipes results.
|
||||
|
||||
Returns:
|
||||
str: Comprehensive recipe details including ingredients, instructions,
|
||||
nutrition information, notes, and associated metadata.
|
||||
"""
|
||||
try:
|
||||
logger.info({"message": "Fetching recipe", "slug": slug})
|
||||
result = mealie.get_recipe(slug)
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
error_msg = f"Error fetching recipe with slug '{slug}': {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug(
|
||||
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
||||
)
|
||||
return format_error_response(error_msg)
|
||||
|
||||
@mcp.tool()
|
||||
def get_recipe_concise(slug: str) -> str:
|
||||
"""Retrieve a concise version of a specific recipe by its slug identifier. Use this when you only
|
||||
need a summary of the recipe, such as for when mealplaning.
|
||||
|
||||
Args:
|
||||
slug: The unique text identifier for the recipe, typically found in recipe URLs
|
||||
or from get_recipes results.
|
||||
|
||||
|
||||
"""
|
||||
try:
|
||||
logger.info({"message": "Fetching recipe", "slug": slug})
|
||||
recipe_json = mealie.get_recipe(slug)
|
||||
recipe = Recipe.model_validate(recipe_json)
|
||||
return json.dumps(recipe.model_dump(
|
||||
include={
|
||||
"name",
|
||||
"slug",
|
||||
"recipeServings",
|
||||
"recipeYieldQuantity",
|
||||
"recipeYield",
|
||||
"totalTime",
|
||||
"rating",
|
||||
"recipeIngredient",
|
||||
"lastMade",
|
||||
},
|
||||
exclude_none=True,
|
||||
), indent=2)
|
||||
except Exception as e:
|
||||
error_msg = f"Error fetching recipe with slug '{slug}': {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug(
|
||||
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
||||
)
|
||||
return format_error_response(error_msg)
|
||||
|
||||
@mcp.tool()
|
||||
def create_recipe(
|
||||
name: str,
|
||||
ingredients: list[str],
|
||||
instructions: list[str],
|
||||
prep_time: Optional[str] = None,
|
||||
cook_time: Optional[str] = None,
|
||||
total_time: Optional[str] = None,
|
||||
recipe_yield: Optional[str] = None,
|
||||
servings: Optional[int] = None,
|
||||
description: Optional[str] = None,
|
||||
calories: Optional[str] = None,
|
||||
protein: Optional[str] = None,
|
||||
carbohydrates: Optional[str] = None,
|
||||
fat: Optional[str] = None,
|
||||
fiber: Optional[str] = None,
|
||||
sugar: Optional[str] = None,
|
||||
sodium: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Create a new recipe
|
||||
|
||||
Args:
|
||||
name: The name of the new recipe to be created.
|
||||
ingredients: A list of ingredients for the recipe include quantities and units.
|
||||
instructions: A list of instructions for preparing the recipe.
|
||||
prep_time: Preparation time (e.g., "30min", "1h", "1h 30min")
|
||||
cook_time: Cooking time (e.g., "45min", "1h 15min")
|
||||
total_time: Total time (e.g., "90min", "2h")
|
||||
recipe_yield: What the recipe yields (e.g., "4 servings", "12 cookies")
|
||||
servings: Number of servings as integer
|
||||
description: A brief description of the recipe
|
||||
calories: Calories per serving (e.g., "350")
|
||||
protein: Protein content (e.g., "25g")
|
||||
carbohydrates: Carbohydrate content (e.g., "40g")
|
||||
fat: Fat content (e.g., "15g")
|
||||
fiber: Fiber content (e.g., "5g")
|
||||
sugar: Sugar content (e.g., "8g")
|
||||
sodium: Sodium content (e.g., "600mg")
|
||||
|
||||
Returns:
|
||||
str: Confirmation message or details about the created recipe.
|
||||
"""
|
||||
try:
|
||||
logger.info({"message": "Creating recipe", "name": name})
|
||||
slug = mealie.create_recipe(name)
|
||||
recipe_json = mealie.get_recipe(slug)
|
||||
recipe = Recipe.model_validate(recipe_json)
|
||||
recipe.recipeIngredient = [RecipeIngredient(note=i) for i in ingredients]
|
||||
recipe.recipeInstructions = [
|
||||
RecipeInstruction(text=i) for i in instructions
|
||||
]
|
||||
|
||||
# Set time information if provided
|
||||
if prep_time:
|
||||
recipe.prepTime = prep_time
|
||||
if cook_time:
|
||||
recipe.cookTime = cook_time
|
||||
if total_time:
|
||||
recipe.totalTime = total_time
|
||||
if recipe_yield:
|
||||
recipe.recipeYield = recipe_yield
|
||||
if servings:
|
||||
recipe.recipeServings = servings
|
||||
if description:
|
||||
recipe.description = description
|
||||
|
||||
# Set nutrition information if provided
|
||||
if any([calories, protein, carbohydrates, fat, fiber, sugar, sodium]):
|
||||
nutrition = RecipeNutrition()
|
||||
if calories:
|
||||
nutrition.calories = calories
|
||||
if protein:
|
||||
nutrition.proteinContent = protein
|
||||
if carbohydrates:
|
||||
nutrition.carbohydrateContent = carbohydrates
|
||||
if fat:
|
||||
nutrition.fatContent = fat
|
||||
if fiber:
|
||||
nutrition.fiberContent = fiber
|
||||
if sugar:
|
||||
nutrition.sugarContent = sugar
|
||||
if sodium:
|
||||
nutrition.sodiumContent = sodium
|
||||
recipe.nutrition = nutrition
|
||||
result = mealie.update_recipe(slug, recipe.model_dump(exclude_none=True))
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
error_msg = f"Error creating recipe '{name}': {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug(
|
||||
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
||||
)
|
||||
return format_error_response(error_msg)
|
||||
|
||||
@mcp.tool()
|
||||
def update_recipe(
|
||||
slug: str,
|
||||
ingredients: Optional[list[str]] = None,
|
||||
instructions: Optional[list[str]] = None,
|
||||
prep_time: Optional[str] = None,
|
||||
cook_time: Optional[str] = None,
|
||||
total_time: Optional[str] = None,
|
||||
recipe_yield: Optional[str] = None,
|
||||
servings: Optional[int] = None,
|
||||
description: Optional[str] = None,
|
||||
calories: Optional[str] = None,
|
||||
protein: Optional[str] = None,
|
||||
carbohydrates: Optional[str] = None,
|
||||
fat: Optional[str] = None,
|
||||
fiber: Optional[str] = None,
|
||||
sugar: Optional[str] = None,
|
||||
sodium: Optional[str] = None,
|
||||
) -> str:
|
||||
"""Update an existing recipe with new information.
|
||||
|
||||
Args:
|
||||
slug: The unique text identifier for the recipe to be updated.
|
||||
ingredients: A list of ingredients for the recipe include quantities and units.
|
||||
instructions: A list of instructions for preparing the recipe.
|
||||
prep_time: Preparation time (e.g., "PT15M" for 15 minutes in ISO 8601 format)
|
||||
cook_time: Cooking time (e.g., "PT30M" for 30 minutes)
|
||||
total_time: Total time (e.g., "PT45M" for 45 minutes)
|
||||
recipe_yield: What the recipe yields (e.g., "4 servings")
|
||||
servings: Number of servings as integer
|
||||
description: A brief description of the recipe
|
||||
|
||||
Returns:
|
||||
str: Confirmation message or details about the updated recipe.
|
||||
"""
|
||||
try:
|
||||
logger.info({"message": "Updating recipe", "slug": slug})
|
||||
recipe_json = mealie.get_recipe(slug)
|
||||
recipe = Recipe.model_validate(recipe_json)
|
||||
|
||||
if ingredients:
|
||||
recipe.recipeIngredient = [RecipeIngredient(note=i) for i in ingredients]
|
||||
if instructions:
|
||||
recipe.recipeInstructions = [
|
||||
RecipeInstruction(text=i) for i in instructions
|
||||
]
|
||||
if prep_time:
|
||||
recipe.prepTime = prep_time
|
||||
if cook_time:
|
||||
recipe.cookTime = cook_time
|
||||
if total_time:
|
||||
recipe.totalTime = total_time
|
||||
if recipe_yield:
|
||||
recipe.recipeYield = recipe_yield
|
||||
if servings:
|
||||
recipe.recipeServings = servings
|
||||
if description:
|
||||
recipe.description = description
|
||||
if any([calories, protein, carbohydrates, fat, fiber, sugar, sodium]):
|
||||
if not recipe.nutrition:
|
||||
recipe.nutrition = RecipeNutrition()
|
||||
if calories:
|
||||
recipe.nutrition.calories = calories
|
||||
if protein:
|
||||
recipe.nutrition.proteinContent = protein
|
||||
if carbohydrates:
|
||||
recipe.nutrition.carbohydrateContent = carbohydrates
|
||||
if fat:
|
||||
recipe.nutrition.fatContent = fat
|
||||
if fiber:
|
||||
recipe.nutrition.fiberContent = fiber
|
||||
if sugar:
|
||||
recipe.nutrition.sugarContent = sugar
|
||||
if sodium:
|
||||
recipe.nutrition.sodiumContent = sodium
|
||||
|
||||
result = mealie.update_recipe(slug, recipe.model_dump(exclude_none=True))
|
||||
return json.dumps(result, indent=2)
|
||||
except Exception as e:
|
||||
error_msg = f"Error updating recipe '{slug}': {str(e)}"
|
||||
logger.error({"message": error_msg})
|
||||
logger.debug(
|
||||
{"message": "Error traceback", "traceback": traceback.format_exc()}
|
||||
)
|
||||
return format_error_response(error_msg)
|
||||
Reference in New Issue
Block a user