← Back to all work

pantry-mcp

An MCP server over a normalized SQLite database of recipes, inventory and cooking decisions. Ranks what you've already saved by how little you'd have to buy.

Python · SQLite · MCP · pytest · GitHub Actions

Entity-relationship diagram of the pantry-mcp SQLite schema, showing recipes, ingredients, recipe_ingredients, inventory, tags, recipe_tags, recipe_links and decisions tables with their foreign keys.
Eight tables. recipe_ingredients carries quantity and unit; inventory is what's physically in the kitchen; decisions is the journal that makes it a data lake rather than a recipe box.

The problem

Ask a model what to cook and it invents something plausible — ignoring the recipes you already like, and not knowing what’s in your kitchen. The fix isn’t a better prompt. It’s a real data store and a set of operations narrow enough that the model can’t wander.

Underneath is a data-modeling problem. “2 tbsp butter”, “30g butter” and “1/4 stick” are one ingredient at three quantities in three unit systems, and a naive schema will tell you you’re short while you’re holding a full stick.

Scope

Interface18 MCP tools — search, inventory, ranking, import, scaling, rating, shopping list, decision journal
Schema8 tables; base_quantity + dimension hold normalized units, match_key resolves ingredient identity
ImportParses a site’s own structured data rather than reconstructing recipes from model memory
Tests7 modules, fixture-based — no network in CI

Three decisions

Import from structured data, never from the model. Letting the model read a page and write out the recipe works, and it silently rounds. Recipe sites publish machine-readable data, so the importer parses that. The model picks which recipe; it never transcribes quantities. A language model is the wrong tool for a job with an exact answer.

Normalize units on write, not on read. Converting at query time puts conversion in the hot path of every ranking query, where a failure becomes a wrong answer rather than a loud error. On write means one canonical unit per ingredient, ranking is plain SQL, and anything unparseable fails at import with a human present.

Scaling returns a projection instead of saving. Doubling a recipe looks like an edit and isn’t. If scaling wrote back, saved recipes would drift every time someone cooked for a different number of people. Scaling returns a computed view; changing an actual ingredient creates a linked variant.

Verification

Tests concentrate where this kind of tool fails quietly: test_units.py and test_scaling.py cover conversion and proportional math, test_identity.py covers ingredient matching. Parsing and import tests run against fixtures, so CI doesn’t depend on someone else’s markup staying still.

Constraints

Next

Links

github.com/ryantthomas/pantry-mcp