Install
openclaw skills install @alex-zwingli/plan-to-eatUse when the user wants to interact with Plan to Eat (plantoeat.com) via the connected plan-to-eat MCP server — viewing or updating their meal plan, managing recipes, scheduling notes/ingredients/leftovers on the planner, tracking the freezer, or working on the shopping list. Triggers on phrases like "what's on my meal plan", "plan X for Wednesday dinner", "add a note to Tuesday breakfast", "move dinner to Friday", "freeze leftovers", "what's in the freezer", "what's on my shopping list", "add milk to the shopping list", "move that to Costco".
openclaw skills install @alex-zwingli/plan-to-eatThis skill teaches you to use the plan-to-eat MCP server effectively. The server gives you full read/write access to a Plan to Eat account: recipe book, weekly planner, shopping list.
If the user references Plan to Eat but no plan-to-eat__* tools are available, the MCP server isn't wired into this session.
plan-to-eat CLI is installed, use the plan-to-eat-cli skill instead — same 36 capabilities, driven through subcommands.The server is published to npm as plan-to-eat-mcp and needs Node 18+. Show these commands to the user rather than running them yourself — they change the host's configuration, and the credentials must come from the user's own shell, not from you.
# Claude Code:
claude mcp add plan-to-eat -- npx -y plan-to-eat-mcp@0.7.3 # x-release-please-version
# Claude Desktop, Cursor, Windsurf, Cline, Zed — the same stdio block:
# "command": "npx", "args": ["-y", "plan-to-eat-mcp@0.7.3"] # x-release-please-version
# To skip the per-launch npx resolve, install once and use the
# `plan-to-eat-mcp` bin as the command with no args:
npm i -g plan-to-eat-mcp@0.7.3 # x-release-please-version
No clone or build step. Keep the version pinned when you pass these on: npx -y
runs whatever the registry serves at that moment, so an unpinned command picks
up every future publish silently.
Releases are published from CI with npm trusted publishing, so every tarball carries a SLSA provenance attestation binding it to this repository and the commit it was built from. The user can verify what they installed:
npm audit signatures
Claude Code users who want the skills and the server together can instead install the plugin from a clone:
git clone https://github.com/alex-zwingli/plan-to-eat-mcp.git
cd plan-to-eat-mcp && npm install && npm run build
claude plugin marketplace add "$(pwd)"
claude plugin install plan-to-eat@plan-to-eat
Credentials come from two environment variables the host passes through:
| Var | Required | Default |
|---|---|---|
PLAN_TO_EAT_USERNAME | yes | — |
PLAN_TO_EAT_PASSWORD | yes | — |
PLAN_TO_EAT_SESSION_FILE | no | ~/.plan-to-eat-session.json |
Ask the user to export them in their shell profile. Never ask them to paste a password into the conversation, and never put credentials in a command you run. After installing, the host has to be restarted before the tools appear.
Prefer these MCP tools when they're available: no subprocess per call, and structured results without a JSON round-trip.
Recipe — identified by numeric id. Fetch the catalog with list_recipes (returns ~500 entries with summaries); drill into one with get_recipe for directions/comments/full ingredient list. Recipe IDs are not guessable — always look them up before referencing.
Planner event — a single entry on the calendar. Shape:
{ id, date, section, kind, recipe_id?, description?, servings, ... }
kind is recipe, note, or ingredientsection is breakfast, lunch, dinner, or snacksdate is YYYY-MM-DDdescription; title is null for notesFrozen recipe — a freezer entry tracking N portions of a previously cooked recipe. Shape: { id, recipe_id, count, servings, frozen_on }. count is portions remaining; servings is per-portion size. The API soft-deletes by zeroing count rather than removing the row.
Shopping list line — one row of the list. It has no scalar id: Plan to Eat merges duplicate ingredients (the garlic three planned recipes each need) into one line carrying every underlying row id in an item_ids array. That array is the handle for update_shopping_list_items and remove_shopping_list_items — pass all of it. Each line also names the store it's assigned to (store_title, plus store_id which is null for the account's default store) and the aisle it's filed under (grocery_category_title). recipe_ids tells you which planned recipes pulled it in — an empty one was added by hand.
A "week" is whatever 7-day window the user means. Use get_planner_week with a start_date; end_date defaults to start_date + 6 days. If the user says "this week" without specifying a start day, ask or pick today.
get_planner_week({ start_date: "2026-05-04" })
Recipe events come pre-enriched with recipe_title — you don't need to join against list_recipes. Group the response by date then section for a readable answer.
list_recipes → filter client-side to find the recipe id by title (case-insensitive match)add_planner_recipe({ recipe_id, date, section: "dinner" })set_planner_servings({ event_id, servings }) using the id returned from step 2add_planner_note({ date: "2026-05-04", section: "breakfast", title: "Defrost chicken" })
add_planner_ingredient({ date: "2026-05-06", section: "dinner", title: "2 lbs ground beef" })
Use this for grocery-style additions tied to a meal slot. For the actual shopping list, see the shopping list workflows below.
get_planner_week to find the event idmove_planner_event({ event_id, date: "<Wed>", section: "dinner" })get_planner_week → find event id (must be a recipe event)set_planner_servings({ event_id, servings: 4 })update_planner_entry_text({ id, description: "X" })
Note: this tool uses description on the wire, even though add_planner_note uses title on create. Rails inconsistency — just remember it.
list_recipes → resolve recipe idfind_planned_dates({ recipe_id, start_date, end_date })duplicate_planner_event({ id, plan_leftover: false })
// returns a copy on the same date — then move it
move_planner_event({ event_id: <new id>, date: "<Fri>", section: "dinner" })
Set plan_leftover: true if the user explicitly says "as a leftover".
Use the leftover convenience tool — it does the duplicate + move in one call:
add_leftover_meal({ source_event_id: <Tuesday lasagna event id>, date: "<Thu>", section: "dinner" })
Omit date/section to leave the leftover on the same day/slot as the source.
reorder_planner_events({ event_ids: [<pancakes id>, <bacon id>] })
All ids should be in the same date+section.
get_planner_week).freeze_recipe_portions({ recipe_id, event_id, count: 3, servings: 1.0 }) — event_id is the planner event the portions came from.list_frozen_recipes to surface the new entry id.list_frozen_recipes() // active only (count > 0)
list_frozen_recipes({ include_consumed: true }) // history too
Recipe titles aren't pre-joined — call get_recipe per recipe_id if the user wants names.
delete_frozen_recipe({ id: <frozen entry id> })
This is a soft-delete: API sets count to 0; the entry stays in history.
delete_planner_event({ id })
get_shopping_list()
Report it grouped by store_title — that's how the user shops. Within a store, grocery_category_title is the aisle order. Mention notes (extra_notes) when there are any.
add_shopping_list_items({ items: [
{ title: "Sliced almonds" },
{ title: "Tahini", amount: "1", unit: "jar" },
] })
Don't set category_id or store_id unless the user asked for a specific store or aisle: left off, Plan to Eat guesses the aisle and reuses the store last chosen for that item, which is nearly always what the user wants. To honor "get it at Trader Joe's", look the id up with list_stores first.
get_shopping_list → the line's item_idslist_stores or list_grocery_categories → the idupdate_shopping_list_items({ item_ids, store_id })Re-filing takes as many lines as you pass, so batch a "move all of these to Costco" into one call.
update_shopping_list_items({ item_ids, title: "Hot paprika", amount: "2" })
One line at a time — ids spanning two lines are refused. Only pass the fields that change; the rest are preserved. The call returns the line as it now stands, so take fresh item_ids from the result if you're going to act on it again.
remove_shopping_list_items({ item_ids })
restore_shopping_list_items({ item_ids }) undoes it — but nothing can list removed lines, so keep the ids in your reply if the user might want them back.
section can come back as supper — Plan to Eat normalizes to the user's per-account preference. Always send dinner on input; recognize supper as the same thing on read.null — under the hood the API returns empty bodies, so the MCP recovers the new event by diffing list_planner_events before/after. If a concurrent change happens, the diff can miss. Re-list to find the new event.YYYY-MM-DD. If the user says "Tuesday", convert to a real date relative to today.recipe_id — add_planner_recipe will not create a new recipe. To plan something not in the book, either create_recipe first, or use add_planner_note / add_planner_ingredient for freeform text.find_planned_dates to look up recipes by title — it's keyed on recipe_id. For "what recipes do I have", use list_recipes.item_ids, plural — one line can hold several row ids. Naming any of them affects the whole line, so you don't have to be precise; but don't assume every id you sent still exists afterwards. A text edit consolidates a merged line into one row (keeping the combined quantity), so re-read item_ids from the response.store_id: null isn't "no store" — it's the account's default store, and store_title names it. Read the title, not the id.See docs/TOOLS.md — docs/TOOLS.md in a clone — for argument schemas, return shapes, and notes on each of the 36 tools.