mealz-cart-optimizer
Provides the recipe catalog used by the menu and basket tools: recipes are ingested from Wikibooks' French cookbook (Livre de cuisine) via an ingest script, keeping their source URL for attribution, so the server can compose a weekly menu and optimize a priced basket over real recipes.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@mealz-cart-optimizerPlan 5 dinners for 4 at Carrefour and find the cheapest in-stock basket"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
mealz-cart-optimizer
An MCP server that lets an AI agent (Claude, ChatGPT or any MCP client) turn a weekly menu into a priced shopping basket at a given store, and swap products that are out of stock. It is built against the contract of Mealz's public SDK.
I built it as a portfolio project for my application to the Software Engineer (Ruby / TypeScript) role at Mealz. Two of the five open topics in the job post are about agentic features and about exposing tools to every AI provider through MCP. This project works on that layer.
The server never calls Mealz's private API. The tools depend on a MealzSource
interface. For the demo, a fake adapter implements it over open data. A real
adapter would implement the same interface against the Mealz API.
What Mealz already does, and what this adds
I read the public SDK (webc-miam@10.9.0 on npm) before designing the tools.
Mealz already covers several parts of the problem:
A basket entry can serve several recipes (
recipe-ids), so shared ingredients are already merged.Each entry lists candidate products (
basket-entries-items), and the UI lets the shopper replace one. Entries carryunavailableandout_of_stockstatuses.The meals planner calls
recipes/planwith a budget, a number of guests and a recipe count.
The job post says Mealz is building its own MCP server and agents. This project is my proposal for what three of those tools could look like, with typed inputs and outputs, explicit reporting of what is missing, and a cart delta in the SDK's format. It also adds one step that the SDK does not show. For each ingredient, it picks among the equivalent products the pack size with the best trade-off between price and leftover food.
Related MCP server: Gamito
The tools
Tool | Input | Output |
| point of sale, number of meals, optional keyword | recipes with their servings and ingredients |
| point of sale, recipe ids, optional servings and waste penalty | basket lines, total cost, waste cost, unmatched and out-of-stock ingredients, warnings, and a cart delta in Mealz's |
| point of sale, ingredient key, product to replace, optional quantity | in-stock alternatives, ranked by the cost of covering that quantity when it is given |
Every tool validates its input with Zod and declares an output schema. The
server returns the result as structuredContent, which the MCP SDK checks
against that schema, and as JSON text for clients that ignore it.
A typical agent session calls compose_menu, then optimize_week_basket with
the chosen recipe ids. If the shopper rejects a product, or an ingredient comes
back in unavailable, the agent passes the line's ingredientKey to
suggest_substitute. The cartDelta can go straight to the retailer cart,
like the SDK's basketSync callbacks do.
Example
This is the basket for the seven demo recipes at a test store, as returned by
optimize_week_basket (abridged):
egg 1 × Œufs de poules élevées en plein air [15 piece] need 14 piece 5.49 € (6 recipes)
flour 1 × Farine Fluide sans Grumeaux [1000 g] need 975 g 0.56 € (5 recipes)
butter 2 × Beurre à teneur réduite en M.G. (60%) doux [250 g] need 300 g 3.86 € (4 recipes)
onion 1 × Oignons Jaunes Filet [1000 g] need 550 g 0.69 € (3 recipes)
...
total 26.27 €, of which 5.23 € is leftover
warnings: "butter: 4 piece left out, no conversion to g"The warning comes from a recipe that asks for "3 noix de beurre". The server cannot turn that into grams, so it says so and leaves it out of the total.
Architecture
agent
│ MCP over stdio
▼
src/mcp/server.ts registers the three tools with their schemas
src/mcp/handlers.ts converts between Mealz shapes and the optimizer
├── src/optimizer/ basket optimizer and unit conversion
├── src/matching/ ingredient profiles and product scoring
└── src/mealz/
MealzSource.ts the interface the tools depend on
FakeMealzSource.ts demo adapter over the open data snapshotsbuildServer takes the MealzSource as a parameter. The entry point passes
the fake adapter. A real adapter would replace it there, and the recipes,
products, prices, stock, alternatives and piece weights would then all come
from Mealz. The open data catalog, the ingredient profiles, the matcher and
the Wikibooks parser exist only to make the demo work. Mealz already computes
how many packs each basket entry needs (quantity). With Mealz plugged in, the
optimizer becomes an alternative way to choose among the entry's candidate
products.
MealzSource uses short field names. Each one maps to a field of the Mealz API:
| Mealz API |
| recipe |
| recipe |
| basket entry |
| item |
| item |
| item |
| entry status other than |
| the SDK's |
How the optimizer picks products
The optimizer first sums the needs of all recipes per ingredient. It keeps one total per dimension (mass, volume, count), so the result does not depend on the order of the recipes. For each candidate product, it converts the totals into the product's unit. Pieces and grams convert through an average weight per ingredient (110 g per onion, 5 g per garlic clove). Volume and mass never convert, because that would need a density table. A quantity that cannot be converted becomes a warning.
Each ingredient is then independent, so the optimizer chooses per ingredient. It scores every in-stock candidate with:
score = price of the packs + wastePenalty × unused fraction of the last packwastePenalty defaults to 1 € and the agent can change it. Products with a
known price win over products without one, so a missing price never looks
free. The optimizer buys several packs of one product per ingredient. It does
not mix two products, such as 500 g + 250 g of butter. Within that model the
per-ingredient choice is optimal, because ingredients do not interact. The
waste penalty applies to the unused fraction of the last pack. It does not
apply to the value of the leftover, so 200 g left in a 250 g pack costs more
than 450 g left in a 1 kg pack. Both points are listed in the next steps.
How products are matched
The demo catalog comes from Open Food Facts. For each ingredient, a profile
lists the Open Food Facts categories to fetch (en:fresh-onions,
en:wheat-flours...). It also lists the words and categories that reject a
product, such as fried onions or garlic powder. The scorer adds 0.6 for an
expected category and 0.2 when the name contains the ingredient. It removes
0.9 for a rejected category and 0.5 for a rejected word. It keeps products
scoring 0.5 or more, and each score comes with its reasons.
The fake adapter offers the optimizer every product in the best score tier. Mealz matches against a retailer's real assortment, which is a harder problem with better data. This matcher only makes the demo catalog usable.
Data and licences
Data | Source | Licence |
Products, pack sizes, categories | ODbL | |
Prices (median of EUR observations) | ODbL | |
Recipes | CC BY-SA |
The snapshots in src/**/fixtures/ keep the demo fast and deterministic. Each
recipe keeps its Wikibooks URL for attribution. These scripts regenerate them:
pnpm tsx scripts/fetchProducts.ts # products from Open Food Facts categories
pnpm tsx scripts/enrichPrices.ts # prices from Open Prices
pnpm tsx scripts/ingestRecipes.ts # recipes from WikibooksLimitations
Prices come from crowd-sourced observations. 57 % of the 508 products in the snapshot have one. In production, the price would come from the retailer through Mealz (
unit-price).Stock is simulated. The fake adapter marks about one product in seven as out of stock, with a deterministic hash.
The demo has seven recipes. Wikibooks has few recipes with structured quantities, and the parser only reads the first tagged ingredient on a line.
Recipe quantities like "3 noix de beurre" have no weight, so they show up as warnings.
Run it
Requires Node 22.13 or later and pnpm.
pnpm install
pnpm test # 55 tests, including an end-to-end run over the MCP server
pnpm mcp # starts the MCP server on stdioClaude Desktop configuration:
{
"mcpServers": {
"mealz-cart": {
"command": "pnpm",
"args": ["tsx", "src/mcp/server.ts"],
"cwd": "/path/to/mealz-cart-optimizer"
}
}
}Next steps
Write a real
MealzSourceadapter against the Mealz API (api.miam.tech).Add a
budgettocompose_menuand back it withrecipes/plan.Let the optimizer mix pack sizes of different products for one ingredient.
Penalize the value of the leftover instead of the unused fraction of a pack.
Return the match confidence, so the agent can ask the shopper when a match is weak.
Add an LP variant once the basket has a global budget constraint.
Reference files
reference/mealz-sdk-public/ holds two interface files copied unchanged from
the public webc-miam@10.9.0 npm package. They document how MealzSource was
aligned with the SDK. No project code imports them. They are licensed under
GNU GPL v3 (https://www.gnu.org/licenses/gpl-3.0.html), separately from the MIT
licence of the rest of the project.
This server cannot be deployed
Maintenance
Related MCP Connectors
AI meal plans that fill your Kroger/Instacart cart - pantry-aware lists, all from chat.
AI-powered kitchen management — pantry, recipes, meal plans, shopping lists
Household-aware cooking brain: pantry, meal suggestions, dietary safety, recipes, shopping lists.
Search live store shelves, verify delivery windows, and run instant AI agent checkouts.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceProvides grocery price and nutritional information search capabilities, allowing AI agents to search for food products, compare prices, and analyze nutritional content across different grocery stores.1-
- FlicenseBqualityDmaintenanceEnables AI agents to generate budget-disciplined, allergy-safe weekly meal plans, shopping lists, and meal swaps using a fully local deterministic engine.20-
- FlicenseNot gradedqualityCmaintenanceEnables AI agents to search Target for groceries, compare unit prices, and manage a shopping cart, stopping short of checkout to keep the human in control.2-
- AlicenseNot gradedqualityCmaintenanceEnables ChatGPT and Codex users to match grocery items against retailer product candidates, explain those matches, and convert recipes into scaled shopping lists for Ocado, Waitrose, Tesco or Asda. It also prepares a retailer URL and copyable list for a Chrome extension handoff, leaving the user to review the basket and check out themselves.AGPL 3.0