{
  "markdown": "# 🍽️ plan-to-eat-mcp\n\n> **Stop typing recipes. Start telling them.**\n> Hand your favorite LLM the keys to your Plan to Eat recipe book, planner,\n> and shopping list — over the Model Context Protocol.\n\nA drop-in MCP server, a CLI, and a standalone Node client that give Claude,\nChatGPT, any MCP-aware assistant, or your own shell full read/write access to\nyour [Plan to Eat](https://www.plantoeat.com/ref/d7d2327524) account.\n\n```\nYou:  \"Save the Bon Appétit miso pasta from this URL and tag it weeknight.\"\nLLM:  ✓ created recipe #48,872,094 — 7 ingredients, 25 min, tagged weeknight.\n\nYou:  \"Plan dinner around it Tuesday and add the missing pantry items to my\n       shopping list.\"\nLLM:  ✓ event created 2026-05-05 / dinner / 4 servings.\n      ✓ shopping list now reflects miso, mirin, and dashi.\n```\n\n…or, when you'd rather not talk to anything:\n\n```console\n$ plan-to-eat get-planner-week 2026-05-04\nstart_date  2026-05-04\nend_date    2026-05-10\n\nID        DATE        SECTION    KIND        RECIPE_ID  RECIPE_TITLE        DESCRIPTION            SERVINGS\n────────  ──────────  ─────────  ──────────  ─────────  ──────────────────  ─────────────────────  ────────\n67698872  2026-05-05  breakfast  recipe      17314561   Breakfast Sandwich                         8\n67483177  2026-05-05  dinner     ingredient                                 Asparagus (in season)  0\n67710784  2026-05-06  dinner     note                                       Cucumber salad         0\n\n3 rows\n```\n\nThat's it. No recipe re-typing. No copy-pasting URLs into a phone app. Just\ntalk.\n\n---\n\n## ❤️ Why Plan to Eat?\n\nIf you're not already using it, [Plan to Eat](https://www.plantoeat.com/ref/d7d2327524)\nis genuinely the best meal-planning app I've ever used:\n\n- **A clip-anywhere recipe importer** that actually works on real-world food blogs.\n- **Drag-and-drop weekly planner** that automatically rolls ingredients into a\n  shopping list, with smart units and pantry deduplication.\n- **Yours forever** — your recipe book is portable, exportable, and not held\n  hostage by an algorithm.\n- **Family-friendly** — share recipes and menus with friends in-app.\n- **14-day free trial, no credit card needed.**\n\n👉 **[Sign up with my referral link](https://www.plantoeat.com/ref/d7d2327524)** —\nyou get the trial, and I get a tiny thank-you. Win/win.\n\n---\n\n## ✨ What this gives you\n\n- 🤖 **MCP server out of the box** — point Claude Desktop, Claude Code, OpenClaw,\n  or any MCP-compatible host at it and start talking to your recipe book.\n- ⌨️ **A CLI over the same tools** — every MCP tool is also a `plan-to-eat`\n  subcommand, with tables for humans and `--json` for scripts. One registry\n  feeds both surfaces, so they can't drift.\n- 🔐 **Auto-auth & auto-recovery** — set your credentials once, the server\n  handles login, caches the cookie session to disk, and silently re-auths\n  whenever Plan to Eat invalidates it.\n- 🧰 **36 tools, all the verbs that matter** — full recipe CRUD, full\n  meal-planner CRUD (add / move / duplicate / delete recipes, notes, and\n  ingredient entries), reorder events in a slot, leftovers as a first-class\n  workflow, freezer tracking, browse courses & cuisines & tags, count what's\n  in your queue.\n- 🗓️ **Real planner control** — view a week's plan with recipe titles\n  pre-joined, schedule recipes on dates, attach prep notes, reschedule with\n  one tool call, change servings, duplicate, search for duplicates, reorder\n  same-slot events.\n- 🛒 **A shopping list you can actually work with** — read it with the store\n  and aisle each item is filed under, add items (Plan to Eat guesses the aisle\n  and reuses the store you last picked), retitle or re-quantify a line, move\n  items between stores, remove them, put them back.\n- ❄️ **Freezer tracking** — after cooking, mark N portions as frozen with\n  `freeze_recipe_portions`; check what's stashed with `list_frozen_recipes`;\n  consume entries when you eat them (soft-delete, history preserved).\n- 🍳 **Real CRUD** — including ingredient lists with proper units, directions,\n  prep/cook times, nutrition, ratings, and tags.\n- 📦 **Tiny runtime** — no Playwright, no headless browser, no native modules.\n  Just `fetch`, the MCP SDK, and Zod. Boots in under a second.\n- 🦺 **100% TypeScript** — fully typed `Recipe`, `Ingredient`, `PlannerEvent`\n  shapes plus a generic `_json<T>` so your tools never have to guess what\n  comes back.\n- 📚 **A library too** — `core/client.ts` is a clean, plain-Node API client you\n  can drop into any script.\n- 🧠 **Bundled Claude Code skill** — `.claude/skills/plan-to-eat/SKILL.md`\n  teaches any agent the common workflows and gotchas (the supper-vs-dinner\n  alias, the `description`-vs-`title` mismatch, etc.) so it doesn't have to\n  rediscover them.\n\n> Reverse-engineered from the live web app. There's no public Plan to Eat API,\n> but the desktop site uses these same endpoints internally. Use at your own\n> risk — they could change anything at any time.\n\n---\n\n## 🚀 Quick start\n\n```bash\nnpx -y plan-to-eat-mcp          # run the MCP server\nnpm i -g plan-to-eat-mcp        # …or install both bins on PATH\n```\n\nRequires Node 18+ (for built-in `fetch`) and a\n[Plan to Eat](https://www.plantoeat.com/ref/d7d2327524) account.\n\nThe package ships two bins: `plan-to-eat-mcp` (the MCP server) and\n`plan-to-eat` (the CLI). To run the CLI through `npx` without installing, note\nthat you have to select it explicitly, since the default bin is the server:\n\n```bash\nnpx -y -p plan-to-eat-mcp plan-to-eat --help\n```\n\n<details>\n<summary>Building from a clone instead (for development, or the Claude Code plugin)</summary>\n\n```bash\ngit clone https://github.com/alex-zwingli/plan-to-eat-mcp.git\ncd plan-to-eat-mcp\nnpm install\nnpm run build\n```\n\nThe build emits CommonJS to `dist/`.\n</details>\n\n### Configuration\n\n| Var | Required | Default | Description |\n|---|---|---|---|\n| `PLAN_TO_EAT_USERNAME` | yes | — | Plan to Eat login email |\n| `PLAN_TO_EAT_PASSWORD` | yes | — | Plan to Eat password |\n| `PLAN_TO_EAT_SESSION_FILE` | no | `~/.plan-to-eat-session.json` | Where the cookie session is cached. Set to `\"\"` to disable caching. |\n\nThe CLI also reads a `.env` in the working directory (shell variables win). The\nMCP server does not — MCP hosts pass env explicitly, as shown below.\n\n---\n\n## 🤖 Use it with an agent\n\nAny host that can launch a **local stdio MCP server** works. The server needs\none command, two env vars, and nothing else — no ports, no OAuth, no daemon.\n\n### Claude Code, the easy way: install the plugin\n\nThe repo ships as a Claude Code plugin — MCP server *and* both skills in one\nstep:\n\n```bash\nexport PLAN_TO_EAT_USERNAME=you@example.com\nexport PLAN_TO_EAT_PASSWORD=hunter2\n\ngit clone https://github.com/alex-zwingli/plan-to-eat-mcp.git\ncd plan-to-eat-mcp && npm install && npm run build\n\nclaude plugin marketplace add \"$(pwd)\"\nclaude plugin install plan-to-eat@plan-to-eat\n```\n\nThat gives you:\n\n| Component | What it is |\n|---|---|\n| MCP server `plan-to-eat` | all 36 tools |\n| Skill `plan-to-eat` | how to *use* the MCP tools well — workflows and gotchas |\n| Skill `plan-to-eat-cli` | the same, for agents driving the CLI instead |\n\nConfirm with `claude plugin details plan-to-eat@plan-to-eat` and `claude mcp list`.\nThe server reads `PLAN_TO_EAT_USERNAME` / `PLAN_TO_EAT_PASSWORD` from the\nenvironment Claude Code was launched with, so export them in your shell profile\nrather than committing them anywhere.\n\n### Claude Code, manually\n\n```bash\nclaude mcp add plan-to-eat \\\n  --env PLAN_TO_EAT_USERNAME=you@example.com \\\n  --env PLAN_TO_EAT_PASSWORD=hunter2 \\\n  -- npx -y plan-to-eat-mcp\n```\n\nAdd `--scope user` to make it available in every project instead of just this\none. Check it connected with `claude mcp list`, or `/mcp` inside a session.\n\n### Claude Desktop\n\nEdit `claude_desktop_config.json` — **Settings → Developer → Edit Config**, or:\n\n- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`\n- Windows: `%APPDATA%\\Claude\\claude_desktop_config.json`\n\n```jsonc\n{\n  \"mcpServers\": {\n    \"plan-to-eat\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"plan-to-eat-mcp\"],\n      \"env\": {\n        \"PLAN_TO_EAT_USERNAME\": \"you@example.com\",\n        \"PLAN_TO_EAT_PASSWORD\": \"hunter2\"\n      }\n    }\n  }\n}\n```\n\nRestart Claude Desktop, and you're cooking.\n\n### Any other MCP host\n\nCursor, Windsurf, Zed, Cline, Continue, OpenClaw, VS Code's MCP support, and\ncustom SDK clients all take the same three things. Point them at:\n\n| Field | Value |\n|---|---|\n| Transport | stdio |\n| Command | `npx` |\n| Args | `[\"-y\", \"plan-to-eat-mcp\"]` |\n| Env | `PLAN_TO_EAT_USERNAME`, `PLAN_TO_EAT_PASSWORD` |\n\nMost of them use the same `mcpServers` JSON block as Claude Desktop above —\noften in `.cursor/mcp.json`, `.vscode/mcp.json`, or the host's settings UI.\n\nIf you installed globally (`npm i -g plan-to-eat-mcp`), use the\n`plan-to-eat-mcp` bin as the command and drop the args entirely. If you built\nfrom a clone, the command is `node` with\n`[\"/absolute/path/to/plan-to-eat-mcp/dist/mcp/server.js\"]`.\n\n> **Absolute paths matter** for the clone route. MCP hosts don't launch servers\n> from your project directory, so a relative path will fail to resolve. The\n> `npx` command above sidesteps this entirely.\n\n### Verify it works\n\n```bash\nPLAN_TO_EAT_USERNAME=you@example.com PLAN_TO_EAT_PASSWORD=hunter2 npx -y plan-to-eat-mcp\n```\n\nYou should see `[plan-to-eat] mcp server ready on stdio (36 tools)` on stderr.\nThat's the server waiting for a client — Ctrl-C out. If instead you get a\ncredentials error, fix that before wiring up a host, where the failure is\nharder to see.\n\n### Teach your agent the workflows\n\nTwo skills ship in `.claude/skills/`, covering the common flows and the sharp\nedges (the supper-vs-dinner alias, the `description`-vs-`title` mismatch on\nnote entries, checking for duplicates before scheduling):\n\n- **`plan-to-eat`** — for agents calling the MCP tools.\n- **`plan-to-eat-cli`** — for agents that have a shell but no MCP server. Same\n  30 capabilities, driven through subcommands, with `--json` for parsing.\n\nThe plugin install above registers both. Agents on other hosts can read them as\nplain context — point them at the files, or paste one into your system prompt.\n\nOr install just the skills, into any of 18+ agents, with the\n[skills.sh](https://www.skills.sh) CLI:\n\n```bash\nnpx skills add alex-zwingli/plan-to-eat-mcp\n```\n\nThat copies both `SKILL.md` files into `.agents/skills/` and symlinks them for\nClaude Code, Cursor, Codex, Copilot, Gemini CLI and the rest. Add\n`--skill plan-to-eat-cli` to take only one. Note that this installs the *skills*\nand not the server — each skill's setup section walks the agent through building\nthe MCP server or CLI if it isn't already there.\n\nFor [OpenClaw](https://docs.openclaw.ai) agents, both skills are on\n[ClawHub](https://clawhub.ai):\n\n```bash\nclawhub install plan-to-eat        # MCP tools\nclawhub install plan-to-eat-cli    # shell / CLI\n```\n\nTheir frontmatter declares the credentials and binaries each one needs under\n`metadata.openclaw`, so ClawHub can check your environment at install time.\n\n---\n\n## ⌨️ Use it from the terminal\n\nEvery MCP tool is also a subcommand. Underscores become dashes; both spellings\nwork.\n\n```bash\nnpm i -g plan-to-eat-mcp\nplan-to-eat --help\n```\n\nOr without installing — note the `-p`, since the package's default bin is the\nMCP server, not the CLI:\n\n```bash\nnpx -y -p plan-to-eat-mcp plan-to-eat --help\n```\n\n```console\n$ plan-to-eat list-frozen-recipes\nID      RECIPE_ID  COUNT  SERVINGS  FROZEN_ON\n──────  ─────────  ─────  ────────  ──────────\n227854  44676380   6      1.0       2025-05-04\n227908  17314561   3      1.0       2025-05-05\n\n2 rows\n\n$ plan-to-eat add-planner-note \"Defrost chicken\" 2026-05-05 dinner\nid       67710999\ndate     2026-05-05\nsection  dinner\n\n$ plan-to-eat get-counts --json | jq .frozen\n10\n```\n\n- **Positional or flagged** — `get-recipe 123` and `get-recipe --id 123` are the\n  same. `<command> --help` lists which arguments are positional.\n- **`--json`** prints the raw payload instead of a table, for piping into `jq`.\n- **Arrays** repeat the flag (`--event_ids 11 --event_ids 22`) or take JSON\n  (`--event_ids '[11,22]'`). Object arguments take JSON:\n  `--ingredients '{\"title\":\"bread\",\"amount\":\"2\"}'`.\n- **Validation is the same Zod schema the MCP server uses**, so a bad enum or a\n  malformed date fails the same way in both surfaces.\n\n---\n\n## 🧰 The 36 tools\n\nEach is an MCP tool *and* a CLI subcommand — `add_planner_recipe` the tool is\n`plan-to-eat add-planner-recipe` in the shell. Full reference with input\nschemas and return shapes: **[docs/TOOLS.md](./docs/TOOLS.md)**.\n\n**Recipes**\n\n| Tool | What it does |\n|---|---|\n| `list_recipes` | Your whole recipe book (caps at ~500 entries). |\n| `get_recipe` | One recipe with directions, ingredients, tags, prep_notes, comments. |\n| `create_recipe` | Create. Only `title` is required. |\n| `update_recipe` | Patch any subset of fields. |\n| `delete_recipe` | Delete by id. |\n\n**Planner — read**\n\n| Tool | What it does |\n|---|---|\n| `list_planner_events` | All planner entries, no date filter. |\n| `get_planner_week` | Events in a date range, with `recipe_title` pre-joined. `end_date` defaults to `start_date + 6 days`. |\n\n**Planner — write**\n\n| Tool | What it does |\n|---|---|\n| `add_planner_recipe` | Schedule a recipe on a date + section. |\n| `add_planner_ingredient` | Attach a freeform ingredient (\"2 lbs ground beef\") to a meal slot. |\n| `add_planner_note` | Attach a freeform note (\"Defrost chicken\") to a meal slot. |\n| `add_leftover_meal` | Schedule a leftover from a previously planned recipe event (duplicate with `plan_leftover` + optional move). |\n| `move_planner_event` | Reschedule any planner event to a new date/section. |\n| `reorder_planner_events` | Reorder events within a section (pass ids in desired order). |\n| `update_planner_entry_text` | Edit the text of a note or ingredient entry. |\n| `set_planner_servings` | Change servings on a recipe event. |\n| `duplicate_planner_event` | Duplicate any event. Optional `plan_leftover`. |\n| `delete_planner_event` | Delete by id. |\n| `find_planned_dates` | Find planner events for a recipe in a date range. Useful for duplicate checks. |\n\n**Freezer**\n\n| Tool | What it does |\n|---|---|\n| `list_frozen_recipes` | What's currently in the freezer. `{include_consumed: true}` to also see history. |\n| `freeze_recipe_portions` | Mark N portions of a cooked recipe as frozen, tied to the planner event they came from. |\n| `delete_frozen_recipe` | Mark a frozen entry as consumed (soft-delete: API zeroes count, row persists). |\n\n**Shopping list**\n\nA line is addressed by its `item_ids` array, not a scalar id — Plan to Eat\nmerges duplicate ingredients into one line that keeps every underlying row id.\n\n| Tool | What it does |\n|---|---|\n| `get_shopping_list` | The list, each line with the store (`store_title`) and aisle it's filed under, and which recipes pulled it in. |\n| `add_shopping_list_items` | Add items. Only `title` required; the aisle is guessed and the store defaults to the one last used for that item. |\n| `update_shopping_list_items` | Retitle / re-quantify a line, or move any number of lines to a different store or aisle. |\n| `remove_shopping_list_items` | Take lines off the list (soft delete). |\n| `restore_shopping_list_items` | Put removed lines back. |\n\n**Lookup tables & extras**\n\n| Tool | What it does |\n|---|---|\n| `list_courses` / `list_cuisines` / `list_main_ingredients` / `list_tags` | Lookup tables. |\n| `list_stores` / `list_grocery_categories` | Your stores and grocery aisles — the ids `store_id` and `category_id` want. |\n| `list_menus` | Saved menus. |\n| `list_friends` | Friends list. |\n| `get_counts` | `{ friends, queued, frozen }` from the recipe-book widget. |\n| `update_planner_options` | Set planner display preferences (timezone, start day, nutrition columns). Rarely needed. |\n\n---\n\n## 📚 Use the client as a library\n\n```ts\nimport { PlanToEat, type Recipe } from 'plan-to-eat-mcp';\n\nconst pte = new PlanToEat();\nawait pte.login(process.env.PLAN_TO_EAT_USERNAME!, process.env.PLAN_TO_EAT_PASSWORD!);\n// Or restore: pte.importSession(JSON.parse(fs.readFileSync('session.json', 'utf8')));\n\nconst recipes = await pte.listRecipes();\nconst detail: Recipe = await pte.getRecipe(recipes[0].id);\n\nconst created = await pte.createRecipe({\n  title: 'Grilled cheese',\n  servings: 1,\n  directions: 'Butter bread. Cheese. Pan. Flip.',\n  ingredients: [\n    { title: 'bread',  amount: '2', unit: 'slice', position: 1 },\n    { title: 'cheese', amount: '1', unit: 'slice', position: 2 },\n  ],\n  tag_titles: 'easy,lunch',\n});\n\nawait pte.updateRecipe(created.id, { rating: 5 });\nawait pte.deleteRecipe(created.id);\n```\n\nThe client stashes credentials internally on first login so it can transparently\nre-authenticate if the cached cookies expire mid-session.\n\nThe tool registry is exported too, if you want to build your own adapter over\nthe same 36 capabilities:\n\n```ts\nimport { toolsByName, createSession } from 'plan-to-eat-mcp';\n\nconst { pte, ensure } = createSession();\nawait ensure();\n\nconst getRecipe = toolsByName.get('get_recipe')!;\nconsole.log(getRecipe.description);          // same text the MCP host sees\nconst recipe = await getRecipe.run(pte, { id: 123 });\n```\n\n---\n\n## 🧪 API reference (the parts that work)\n\nAll paths under `https://app.plantoeat.com`.\n\n### JSON endpoints (`/api/v1/*`)\n\n| Method | Path | Notes |\n|---|---|---|\n| GET | `/api/v1/recipes` | Whole recipe book. Caps at 500 entries; pagination params don't seem to work. |\n| GET | `/api/v1/recipes/:id` | Single recipe with `directions`, `ingredients`, `tags`, `prep_notes`, `comments`. |\n| POST | `/api/v1/recipes` | Create. Body: `{ \"recipe\": {...} }`. Returns 201 + full recipe. |\n| PUT | `/api/v1/recipes/:id` | Update. Same body shape. |\n| DELETE | `/api/v1/recipes/:id` | Delete. Returns the deleted recipe. |\n| GET | `/api/v1/courses`, `/cuisines`, `/main_ingredients`, `/tags` | Lookup tables. |\n| GET | `/api/v1/events` | Planner entries (calendar). Returns the entire calendar — filter by date client-side. |\n| GET | `/api/v1/menus` | Saved menus. |\n| GET | `/api/v1/shopping_list` | Sync metadata only (`updated_items`, `last_sync_time`) — not the list. |\n| GET | `/api/v1/shopping_list/items` | The shopping list itself, one entry per merged line. |\n| GET | `/api/v1/stores` | Grocery stores. |\n| GET | `/api/v1/grocery_categories` | Grocery aisles. |\n| GET | `/api/v1/friends` | Friends. |\n| GET | `/api/v1/frozen_recipes` | Freezer: `[{id, recipe_id, count, servings, frozen_on}]`. |\n| DELETE | `/api/v1/frozen_recipes/:id` | Soft-delete (sets `count: 0`; row persists). Returns the updated row. |\n| GET | `/recipes/counts/` | `{ friends, queued, frozen }`. (Note: not under `/api/v1`.) |\n\n### Planner write endpoints (`/planner/*`)\n\nA different style: form-encoded bodies, `text/javascript` (empty) responses. The server mutates state and the UI re-fetches separately. Required headers: `X-CSRF-Token`, `X-Requested-With: XMLHttpRequest`, `Accept: text/javascript`. Because the response body is empty, the client recovers any new event ID by diffing `/api/v1/events` before and after.\n\n| Method | Path | Body |\n|---|---|---|\n| POST | `/planner/create` | `rid=<recipeId>&date=YYYY-MM-DD&section=...` (recipe), or `date=...&section=...&eventType=note\\|ingredient&title=<text>` |\n| POST | `/planner/create/` | `rid=<id>&frozen_id=&date=...&section=...` (frozen-recipe variant — note the trailing slash) |\n| POST | `/planner/update` | `eventid=<id>&date=...&section=...&readonly=false` (move/reschedule) |\n| POST | `/planner/update/<id>` | `description=<text>` (edit note/ingredient text) |\n| POST | `/planner/update_serving` | `event=<id>&serving=<n>` |\n| POST | `/planner/duplicate` | `id=<id>&plan_leftover=true\\|false&readonly=false` |\n| POST | `/planner/update_order` | `ids=e<id1>,e<id2>` — reorder events in a section |\n| POST | `/planner/destroy` | `id=<id>&readonly=false` |\n| POST | `/planner/update_planner_options` | Nested Rails keys: `user[time_zone]=...&calendar_settings[show_calories]=1&...` |\n| POST | `/frozen_recipes` | `id=<recipe_id>&eid=<event_id>&count=<n>&servings=<per-portion>` — freeze N portions |\n| GET | `/planner/search_dates` | Returns rendered HTML, **not JSON** — not used by the client. We filter `/api/v1/events` instead. |\n\n`section` values are `breakfast`, `lunch`, `dinner`, `snacks`. Note that the server may normalize `dinner` → `supper` based on the user's per-account preference; reads will reflect the canonical name.\n\n### Authentication\n\nCookie-based. The client supports two flows:\n\n1. **Login.** `POST /login` with `login[email]` and `login[password]` plus the\n   `authenticity_token` from the meta tag on `GET /login`. Server returns a 302\n   and sets cookies.\n2. **Reuse cookies.** The \"remember me\" pair `ptermid2` (user id) and\n   `ptermxt2` (long-lived token) is sufficient on its own — stash those and\n   skip re-login until they're invalidated.\n\nWrite requests need an `X-CSRF-Token` header. The client grabs it from the\n`<meta name=\"csrf-token\">` tag on the `/recipes` page on demand.\n\nHTTP Basic auth on `/api/v1/...` is **not** supported (returns 401).\n\n### Recipe payload shape\n\nThe single-recipe response has 65 fields. Writable on POST/PUT include:\n`title`, `description`, `source`, `url`, `servings`, `yield`, `scaling`,\n`prep_time`, `cook_time`, `total_time` (minutes),\n`course_id`, `cuisine_id`, `main_ingredient_id`, `tag_titles` (comma list),\n`directions` (free text), `private`, `draft`, `rating`,\nnutrition strings (`calories`, `sodium`, etc.), and ingredients.\n\n**Ingredients use Rails nested-attributes** — the wire field is\n`recipe_ingredients_attributes`, not `ingredients`. The client and the\n`create_recipe` / `update_recipe` tools accept the friendlier name\n`ingredients` and translate. Each entry:\n\n```json\n{ \"title\": \"water\", \"amount\": \"1\", \"unit\": \"cup\", \"note\": \"\", \"position\": 1 }\n```\n\nTo delete an existing ingredient on update, include its `id` plus\n`\"_destroy\": true`. The server back-fills `amount_float`, `metric_amount`,\n`metric_unit`, and `similar_titles`.\n\n---\n\n## 📁 Layout\n\nOne package, three layers. The logic lives in `core/`; the MCP server and the\nCLI are thin adapters over the same registry.\n\n```\nsrc/\n  core/\n    client.ts     API client — no MCP, no CLI, no I/O beyond fetch\n    tools.ts      the tool registry: name, description, Zod shape, handler\n    session.ts    credentials + cookie-cache bootstrap, shared by both adapters\n    version.ts    version read from package.json at runtime\n  mcp/server.ts   registry -> MCP tools (stdio transport)\n  cli/\n    main.ts       registry -> subcommands, help, dispatch\n    args.ts       argv -> Zod-validated arguments\n    render.ts     results -> tables (or raw JSON under --json)\n  scripts/        smoke tests and the docs coverage check\n  index.ts        public entrypoint for library consumers\n  server.ts       back-compat shim, see below\n```\n\n**Adding a tool means adding one entry to `src/core/tools.ts`.** It appears in\nthe MCP server and the CLI at once, with the same name, schema, and\ndescription — they can't drift apart.\n\nOther files worth knowing:\n\n- `docs/TOOLS.md` — per-tool reference with input/output shapes and gotchas.\n  Hand-written; `npm run check:docs` fails if it and the registry disagree\n  about which tools exist.\n- `.claude/skills/plan-to-eat/SKILL.md` — how to use the MCP tools well.\n- `.claude/skills/plan-to-eat-cli/SKILL.md` — the same, for the CLI.\n- `.claude-plugin/` — Claude Code plugin and marketplace manifests.\n- `.mcp.json` — the plugin's MCP server declaration.\n- `dist/` — emitted by `npm run build`.\n\n> **Upgrading from ≤0.4?** The server moved from `dist/server.js` to\n> `dist/mcp/server.js`. The old path still works — it's a shim that loads the\n> new one — so existing host configs and deployments keep running. New configs\n> should use the new path or the `plan-to-eat-mcp` bin.\n\nPlaywright was used during reverse engineering and is kept as a devDependency\nfor any future API-discovery work; the runtime depends only on `fetch`, the\nMCP SDK, and Zod.\n\n### Scripts\n\n| Command | What it does |\n|---|---|\n| `npm run build` | Compile `src/**/*.ts` to `dist/`. |\n| `npm run watch` | Same, in watch mode. |\n| `npm start` | Run the compiled MCP server (`dist/mcp/server.js`). |\n| `npm run cli -- <command>` | Run the CLI without linking it globally. |\n| `npm run verify` | Smoke-test the client end-to-end against your account (recipes). |\n| `npm run verify:planner` | Smoke-test the planner write endpoints (creates + cleans up test events on a date 6 months out). |\n| `npm test` | Smoke-test the MCP server end-to-end (spawns it and calls tools). |\n| `npm run check:docs` | Check `docs/TOOLS.md` covers exactly the registered tools. |\n| `npm run clean` | Remove `dist/`. |\n\nThe `verify*` and `test` scripts hit your real account. They create and delete\ntheir own test data, but they are not a dry run — see\n[CONTRIBUTING.md](./CONTRIBUTING.md).\n\n---\n\n## 🤝 Contributing\n\nBug reports, new endpoints, and upstream-breakage fixes are all welcome — see\n**[CONTRIBUTING.md](./CONTRIBUTING.md)**. The one thing to know up front: the\ntest scripts run against a **real** Plan to Eat account and create real data\n(then clean it up). Use your own.\n\n---\n\n## 🙏 Support Plan to Eat\n\nThis whole project exists because Plan to Eat is great. If you find this\nuseful, the best thing you can do is\n**[give Plan to Eat a try with my referral link](https://www.plantoeat.com/ref/d7d2327524)**.\nFree 14-day trial, no card needed.\n\n## License\n\nMIT — see [LICENSE](./LICENSE). Do whatever you want with it; just don't blame\nme if Plan to Eat ships a breaking change.\n",
  "bytes": 25708,
  "sha": "804c9455f629589b917a981f0b9df306985892ad9ed0fb32cb39f7da0605e98e",
  "repo_slug": "alex-zwingli/plan-to-eat-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_alex_zwingli_plan_to_eat_ee7bccce/readme"
}