Mmmmealplan
Generate a weekly meal plan and Migros shopping list.
Mmm + Migros + Meal-planning. A Claude Code skill that turns a YAML config + a recipe library into a real weekly plan: it pulls live promotions and nutrition data from the unofficial Migros MCP, respects your macros and restrictions, and writes the plan and a categorised shopping list straight to disk.
Invocation
/mmmmealplan— plan the next 7 days starting Monday/mmmmealplan --days N— plan the next N days starting today
Prerequisites
~/.claude/skills/mmmmealplan/config.yamlmust exist (copy fromconfig.yaml.example)- The Migros MCP server (
mcp__migros__*) must be available — see README for install steps
Configuration
Read ~/.claude/skills/mmmmealplan/config.yaml at the start of every run. If the file does not exist, output:
config.yaml not found at ~/.claude/skills/mmmmealplan/config.yaml.
Copy config.yaml.example and edit it to your preferences:
cp ~/.claude/skills/mmmmealplan/config.yaml.example ~/.claude/skills/mmmmealplan/config.yaml
Then re-run /mmmmealplan.
Then stop. Do not proceed.
Schema
| Field | Type | Required | Notes |
|---|---|---|---|
household_size | int | yes | Default servings count if slot_servings is not set for a slot |
slot_servings.breakfast | int | optional | Override servings just for breakfast (e.g., 2 people share breakfast but only 1 has dinner). Default: household_size |
slot_servings.lunch | int | optional | Same; default: household_size |
slot_servings.dinner | int | optional | Same; default: household_size |
macros.protein_g | int | yes | Daily target in grams (applies to the primary eater — see is_primary below) |
macros.carbs_g | int | yes | Daily target in grams |
macros.fat_g | int | yes | Daily target in grams |
macros.kcal | int | yes | Daily target |
restrictions | list[str] | yes | Hard constraints; recipes violating these are rejected |
dislikes | list[str] | yes | Soft; avoid when possible |
meal_cadence.breakfast | int 0-7 | yes | Days per week this slot is consumed (per-week; scaled pro-rata for shorter windows) |
meal_cadence.lunch | int 0-7 | yes | Same |
meal_cadence.dinner | int 0-7 | yes | Same |
fixed_slots.<slot> | str OR list | optional | Either a single recipe filename (everyone in the slot eats the same) or a list of {recipe: <filename>, servings: <int>, is_primary: <bool>} entries to split the slot across people. Sum of servings in a list must equal the slot's effective servings count |
constraints.weeknight_dinner_max_minutes | int | optional | Time ceiling Mon-Thu |
equipment | list[str] | yes | Available cooking equipment |
Validation rules
Before generating a plan, validate:
- All required fields present.
- For each
fixed_slotsentry (or each item if it's a list), the corresponding recipe file exists. - For each
fixed_slotsentry (or each item if it's a list), the recipe's tags do not violate any item inrestrictions. (E.g., a fixed breakfast taggedcontains-lactosewith restrictionlactose-freeis a conflict.) This is a tag-based check; for ingredient-level allergen verification using real Migros data, see Step 6.5 of the Run flow. - If
fixed_slots.<slot>is a list, the sum ofservingsacross items must equal the slot's effective servings (slot_servings.<slot>if defined, elsehousehold_size). Exactly one item should haveis_primary: true(the recipe whose nutrition counts toward the user's macro targets); if none is marked, the first item is treated as primary.
If any validation fails, abort with a specific error pointing at the conflict. Do not produce partial output.
Recipe library
At run start, scan ~/.claude/skills/mmmmealplan/recipes/<slot>/*.md for each slot in meal_cadence. For each file, parse the YAML frontmatter to extract: name, slot, servings, time_minutes, tags, ingredients, nutrition_per_serving (optional).
Build an in-memory list of available library recipes per slot. This list is used in two places:
- Fixed slot resolution — match
fixed_slots.<slot>against filenames (without.md). - LLM generation prompt — pass the list of (filename, name, tags, time_minutes) per slot as candidate library recipes the LLM can select from.
If a recipe file is malformed (invalid YAML frontmatter, missing required fields), skip it and emit a warning in the output: Skipped recipe <path>: <reason>. Do not abort.
Required frontmatter fields
name, slot, servings, time_minutes, tags, ingredients. nutrition_per_serving is optional.
If nutrition_per_serving is absent, the run flow will compute it from Migros product data during ingredient resolution (Step 6 of the Run flow).
Migros MCP usage
Read lib/migros-helpers.md when you need to call any Migros MCP tool. It documents the patterns for ingredient resolution, caching, nutrition parsing, and failure handling.
Run flow
When invoked, execute these steps in order. Do not skip steps. If any step fails irrecoverably, abort with a clear error.
Step 1 — Determine planning window
- Default: next Mon–Sun (7 days).
- If invoked with
--days N, plan N days starting today. - Compute the ISO week label for output filenames:
YYYY-WXX(e.g.,2026-W18). For non-week-aligned windows useYYYY-MM-DDinstead.
Step 2 — Load config and recipe library
- Read
~/.claude/skills/mmmmealplan/config.yaml. Apply the validation rules from the Configuration section. Abort on validation failure. - Scan
~/.claude/skills/mmmmealplan/recipes/<slot>/*.mdper the Recipe library section. Build the in-memory candidate list. - Read the last 2 plan files from
~/.claude/skills/mmmmealplan/plans/(sorted by filename). Extract the recipe names used. Build arecently_usedset per slot.
Step 3 — Compute slot demand
meal_cadence is expressed per week (assumes a 7-day window). For shorter or longer windows, scale pro-rata.
For each slot in meal_cadence:
scaled_cadence = round(meal_cadence[slot] × window_days / 7)— scale per-week cadence to the actual windowtotal_needed = scaled_cadencefixed_count = number of days in window with fixed_slots[slot] set— usually all days if a fixed slot is setfree_count = max(0, total_needed - fixed_count)free_countis what the LLM must filleffective_servings = slot_servings[slot] if defined else household_size— drives ingredient quantity scaling for that slot
Examples:
- Cadence
breakfast: 7, fixedbreakfast: overnight-oats, window 7 days → scaled_cadence = 7, fixed_count = 7, free_count = 0. - Cadence
dinner: 4, no fixed dinner, window 3 days → scaled_cadence = round(4 × 3/7) = 2, fixed_count = 0, free_count = 2. - Cadence
lunch: 0, any window → scaled_cadence = 0, free_count = 0 (slot is skipped).
Step 4 — Query Migros promotions
Call mcp__migros__get_promotions. See lib/migros-helpers.md for parsing and failure handling. Result is a list of promoted products (name, discount). If failed/empty, set promotions = [] and continue.
Step 5 — Generate the candidate plan (LLM step)
Construct a prompt for the model with these inputs:
- Planning window (start date, end date, day count)
- Free slots per day (from step 3)
- Fixed slots per day (recipes already chosen)
- Config: macros targets, restrictions, dislikes, equipment, time constraints
- Library candidates per slot: list of
{filename, name, tags, time_minutes} recently_usedset per slot (avoid repeating)- Promotions list
Ask the model to produce, in JSON:
{
"days": [
{
"date": "2026-04-28",
"weekday": "Mon",
"meals": {
"breakfast": { "source": "library", "filename": "overnight-oats" },
"dinner": { "source": "new", "name": "Sheet-pan chicken & broccoli", "ingredients": ["chicken breast 200g", "