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