Live — App Store
RecipeFix
Takes a recipe you found online and rewrites it so you can actually eat it, gluten free, dairy free, vegan or otherwise, without ruining the dish.
Adapts any recipe to a dietary constraint without wrecking the dish — and exposes the whole engine as an MCP server.
- Platform
- iOS, Web (PWA)
- Status
- Live — App Store
- Demonstrates
- MCP server in production
- Launched
- April 2026
02
What it is for
For people who cook from internet recipes and cannot eat them as written. Celiac and gluten-sensitive cooks, vegans, the dairy-free, parents managing a child's nut allergy, and anyone on keto, paleo, Whole30 or low-FODMAP.
The web is full of recipes and almost none of them are addressed to those people. The usual options are to search for a dedicated version of the dish, which returns something worse, or to guess at a substitution and wreck the texture. This does the substitution properly instead: at a ratio that works, with the quantities rescaled and every step rewritten so the swap survives contact with the method.
03
Why it exists
Every recipe on the internet is written for one diet: the author's.
Swapping an ingredient is not a lookup. Coconut flour absorbs roughly four times the liquid wheat flour does, so "use coconut flour instead" without changing anything else produces a different and worse dish. The people with the problem are anyone cooking around a constraint — a medical one, a religious one, or a household member's — and what they did before was guess, or scroll past.
04
Screens




05
What it does
Recipe adaptation
URL, pasted text, or a photo, adapted to fourteen dietary constraints with the swaps and reasoning shown.
Diet compliance gate
A deterministic guardrail on presence-rule diets that blocks a violation and triggers one regeneration.
Culinary integrity pass
Warns on orphan ingredients and substitutions the steps never acknowledge — the failures that ruin a dish.
Deterministic scaling
Serving arithmetic done in code, never by a model, including the rule that you cannot use one and a half eggs.
MCP server
Thirty-five tools over OAuth, each scoped by the caller's own row-level security.
Cook Mode
Step-by-step cooking with timers, on a screen designed to be read with wet hands.
Substitution guides
Fifty-one published guides carrying the same ratios the adapter applies.
Browser extension
Chrome and Firefox, so a recipe can be adapted from the page it lives on.
06
What it runs on
Client
- 01
- React
- 02
- Vite
- 03
- shadcn/ui
- 04
- Tailwind
- 05
- Swift — WKWebView shell
Backend
- 01
- Supabase Postgres
- 02
- 56 Deno edge functions
- 03
- Row-level security
- 04
- 122 migrations
Services
- 01
- Gemini gateway
- 02
- Anthropic
- 03
- OpenAI (fallback)
- 04
- Stripe
- 05
- Apple IAP
- 06
- PostHog
07
How it's built
A Vite and React front end prerendered at build time, served alongside 56 Supabase edge functions and 122 migrations. The iOS app is a WKWebView shell with a native tab bar, native in-app purchase, Apple Sign-In, a widget and a share extension, generated from a project manifest by XcodeGen so the Xcode project is reproducible rather than committed drift.
The engineering lives in the shared layer rather than in the functions. Three providers — a Gemini gateway, Anthropic and OpenAI as fallback — sit behind a circuit breaker that classifies upstream failures as permanent or transient and skips a dead provider rather than retrying into it. Requests are cached on a SHA-256 hash of a recursively canonicalised request object. Two payment rails, Stripe on the web and Apple IAP in the app, converge on one entitlement flag.
The MCP server is the distribution surface: 35 tools over HTTP with OAuth, hosted as an edge function, where every tool calls Postgres as the signed-in user so row-level security is the authorisation boundary and the MCP path has no privileged access.
08
Architecture
One figure, read left to right. Border treatment carries the node type; the color is the project's.
- Service
- Store
- External
- Model
09
A piece of the code
supabase/functions/adapt-recipe/index.ts
/** Deep-sort keys. NB: JSON.stringify(obj, keysArray) FILTERS nested objects * to those keys - ingredientScale serialized as {} so "2 lb chicken" and * "9 cups flour" produced IDENTICAL cache keys (wrong recipes served). */function canonicalize(v: unknown): unknown { if (Array.isArray(v)) return v.map(canonicalize); if (v && typeof v === "object") { const out: Record<string, unknown> = {}; for (const k of Object.keys(v as Record<string, unknown>).sort()) { out[k] = canonicalize((v as Record<string, unknown>)[k]); } return out; } return v;}async function buildCacheKey(obj: Record<string, unknown>): Promise<string> { return sha256Hex(JSON.stringify(canonicalize(obj)));}Excerpt — trimmed for reading, not a full file.
10
The hard parts
01 — Problem
Someone selects gluten-free and cooks what comes back. If the model ships wheat flour that is a medical event, not a bad recipe. The obvious fix is to check every diet with a rulebook, and the obvious fix is worse than nothing.
Approach
A diet is checked only where a violation is a property of the ingredient itself, independent of quantity. Presence-rules — vegan, vegetarian, dairy-free, gluten-free, nut-free, halal — are checked and block. Threshold-rules — keto, low-carb, low-sodium — are deliberately not checked, because a correct check needs per-ingredient nutrition keyed to quantity. Low-FODMAP is checked only for ingredients that are high-FODMAP at any normal portion, because the serving thresholds are licensed data the product does not hold.
Tradeoff
Anyone on keto gets no deterministic guarantee, and the product does not pretend otherwise. A regex there would be security theatre: it could not tell five grams of sugar from fifty, and shipping it would imply a guarantee that cannot be made.
02 — Problem
Diet compliance answers whether it is safe to eat. An integrity pass answers whether it is internally consistent. Neither answers the question the product is actually judged on: if you cook this, does it come out right?
Approach
Two checks that are decidable without judging taste. Orphan ingredients — listed but never used in any step, a pure defect. And uncoupled substitutions: if an ingredient changed and nothing in the steps or notes acknowledges the consequence, the instructions are still describing the original dish and it will fail in the pan.
Tradeoff
These warn rather than block, because unlike a diet violation there is no proof of harm — a recipe can legitimately mention a consequence in wording the check does not match. Blocking on a heuristic would throw away good recipes. The severity of an automated check is matched to the strength of the evidence behind it.
03 — Problem
Adaptation results are cached on a hash of the request. The original implementation selected which fields to hash with JSON.stringify's second argument, which does not do what it appears to do: it filters nested objects to the same key list.
Approach
The ingredient-scale object serialised as an empty object, so "2 lb chicken" and "9 cups flour" produced identical cache keys and users were served the wrong recipe. It was replaced with a hand-written recursive canonicaliser that deep-sorts keys and preserves nested structure before hashing.
Tradeoff
More code than the one-liner it replaced, and the one-liner looked correct — which is the entire lesson. The failure is recorded in the comment above the function so nobody reintroduces it.
04 — Problem
When the diet gate fails there are two repair strategies: a targeted patch, or a full regeneration. The patch costs one extra model call if it works and two if it does not, because the failed patch still has to be followed by the regeneration. Which is right depends on how often the patch works, and nobody knew.
Approach
Both were instrumented, and the metrics endpoint reports the number that decides it. Over the last thirty days: three repairs triggered, zero repaired by patch, three fell back to full regeneration.
Tradeoff
The measurement currently says the patch stage is pure overhead on the slowest path in the product. The sample is three. So it stays, instrumented, until the number means something — which is a less satisfying answer than deleting it.
Ask about RecipeFix
Answers are drawn from the notes and source behind this case study, and cite what they read.
11
What it did
132
Adaptations, last 30 days
Vegan
Most requested
2.4% caught
Compliance gate
31
MCP tools
Measured 26 August 2026