Weekly grocery shopping in 20 minutes: Claude, two MCPs and a household skill
I do my entire weekly grocery shopping in 20 minutes, standing in front of my fridge, talking to Claude. It sounds like magic. Here is what is behind it: two MCP servers, a small OAuth proxy, an ngrok tunnel, one skill file with our household rules, and a fair amount of debugging.
The planning meeting. Attendees: me, the fridge, Claude.
Before
I’m a dad of two in Munich. A week needs a meal plan, mostly dinners for three to four people. Our plan lives in a Microsoft To Do list called „Supermarkt“: one task per dish, prefixed with the weekday, ticked off when it’s eaten. That list goes back years, thousands of entries. Groceries come from Knuspr, the German brand of the Czech delivery service Rohlik. The annoying part was the translation in between: from „Daal on Friday“ to lentils (we have), coconut milk (probably), coriander (no), and then searching each item in the app.
The setup: two MCPs and a proxy
I did not write either MCP server. Both are open source: rohlik-mcp by Tomáš Pavlín (search products, cart, order history, frequent items, delivery slots) and microsoft-todo-mcp-server by Jordan Burke. My first prompt was simply:
I wanna use the knuspr mcp that is available on Github. Can you set this up running on this computer, place a proper AUTH in front such that I can use this with claude website "integrations" such that in runs in all my claudes. Use ngrok with token <ngrok-token> to make my local mcp accessible. Would that work? here is the repo: https://github.com/tomaspavlin/rohlik-mcp
„In all my claudes“ was the point: I wanted it on the phone app, not only in a terminal. Remote connectors on claude.ai need OAuth, and both servers only speak stdio. So Claude wrote a small Express gateway that spawns one MCP process per session, exposes it over SSE and puts a minimal OAuth 2.0 server in front (discovery document, authorize page, token endpoint, PKCE). The first attempt answered claude.ai’s login redirect with „Cannot GET /authorize“, which is how I learned what claude.ai expects. Then I asked for the second server:
ok. you need proper proxying for the todo. The standard proxy runs on 3000 and projects sse to knuspr. But todo must go to /todo/sse route i guess
and of course we need authorization again
The result is one port, two routes, one OAuth client. These are the relevant lines of the gateway:
// OAuth discovery so claude.ai can log in
app.get('/.well-known/oauth-authorization-server', ...);
app.get('/authorize', ...); // tiny approve page, checks client_id + PKCE
app.post('/oauth/token', ...);
// Knuspr / Rohlik: spawns rohlik-mcp/dist/index.js per session
app.get('/sse', requireAuth, makeSSEHandler(rohlikSessions, spawnRohlik, '/message'));
app.post('/message', requireAuth, makeMessageHandler(rohlikSessions));
// Microsoft To Do: spawns microsoft-todo-mcp-server/dist/cli.js per session
app.get('/todo/sse', requireAuth, makeSSEHandler(todoSessions, spawnTodo, '/todo/message'));
app.post('/todo/message', requireAuth, makeMessageHandler(todoSessions));
A start.sh loads the credentials from a local .env, starts the gateway, starts ngrok and prints the two connector URLs to paste into claude.ai under Settings, Integrations: https://<tunnel>/sse for Knuspr and https://<tunnel>/todo/sse for To Do, both with the same OAuth client ID and secret.
The To Do side needs an app registration in Azure, which works with a private Microsoft account. That took two evenings: an invalid redirect URI (the local auth helper listens on its own port and the callback must match exactly), a login error as soon as I picked „personal account“, and a bundled server that crashed with Dynamic require of "fs" is not supported under Node 25. Claude fixed each one, but only because I pasted the exact error every time.
Months later I found the shortcut for the desktop. In Claude Code you don’t need any of the proxy, because stdio servers can be registered directly:
Hi, in this repo there are two MCPs running locally which both now require auth for integration in claude (you). But can't you use there outputs right in the console here if you spin them up yourself? Or spin them up in non-auth mode?
# Knuspr / Rohlik (credentials are your Knuspr login)
claude mcp add rohlik -s local \
-e ROHLIK_USERNAME=<email> -e ROHLIK_PASSWORD=<password> \
-e ROHLIK_BASE_URL=https://www.knuspr.de \
-- node /path/to/rohlik-mcp/dist/index.js
# Microsoft To Do (tokens.json comes from the one-time OAuth login)
claude mcp add mstodo -s local \
-e MSTODO_TOKEN_FILE=/path/to/tokens.json \
-- node /path/to/microsoft-todo-mcp-server/dist/cli.js
So today: the phone goes through the tunnel, the laptop talks to the servers directly.
Two ways in: the phone through tunnel and gateway, the laptop straight over stdio.
The skill: our household in one file
The MCPs give Claude hands. What makes the output useful is a skill called wocheneinkauf-pruefen („check the weekly shop“), which grew over months of corrections. It contains:
* the household size
* what is always in stock and never goes on a list (olive oil, spices, canned tomatoes, pasta, tahini)
* a stock table from „low“ to „very high“
* rules for checking against what we have
* product IDs of our regulars or favorites
* brand preferences
* a fixed output format; and a list of known friction points.
The rules section, translated:
- Stock check: only the current cart or the last 3 Knuspr orders can be used
to judge the plan. Only count long-life food (cheese, eggs, tins) from more
than one order back - not lettuce, fresh berries, avocado.
- Product lookup: use get_frequent_items to see which products we buy.
- Bread is always bought fresh at the bakery -> put it on the list anyway,
marked "bakery".
- Every salad and every sandwich needs bread.
- Items ordered in the last 3 days or already in the cart -> NOT on the list.
- Merge identical ingredients across dishes (not 3x garlic).
[…]
Known friction points:
- TODO MCP goes offline periodically (ngrok tunnel) -> silent fail, not an
auth problem. Ask Sebastian to restart the server.
- Knuspr rate limiting on get_order_detail: start with the newest order.
- Cart duplicates: always call get_cart_content before add_to_cart.
None of this was designed upfront. It is what you get when you correct the same thing twice („no, the sugo is here, the mozzarella too“) and then write it down.
My favourite part: the finance check
Every answer ends with a 💰 finance check. The basis: Claude analysed eleven Knuspr orders from May and sorted every product we buy into three tiers, by price per kilo and by „supply or treat“: sensible, OK, luxury. Bananas at 1.95 €/kg are sensible. Cheese turned out to be 39 % of everything we spent in the fridge section, with a small French soft cheese at 56.90 €/kg on top. The skill gets a format, thresholds and one tone rule (translated):
## 💰 Finance check
- Sensible ✅: X items | OK 🟡: Y items | Luxury 🔴: Z items (≈ N €)
- [concrete observation if something stands out]
- [alternative if relevant]
Name things only if: luxury > 25 % of the items, OR luxury total > 15 €,
OR ≥ 2 products from the same luxury group (e.g. 2x premium cheese).
If everything is sensible/OK -> one positive line ("solid, no luxury"), done.
Do not moralise - Sebastian knows what he buys. Only concrete facts
(€/kg, number of premium items, alternative product ID).
Actively flag deviations from memory: salmon brand != Fjoru, eggs above plan.
And this is what a real one looked like on an evening in October (translated):
## 💰 Finance check
- Luxury 🔴: salmon Fish?Fresh x2 (11.06 €, + wrong brand) · Picandou (4.79 €)
· bakery bread (8.18 €). Cheese variety: Picandou, cheddar, grated mozzarella,
Grana, cream cheese.
- Cost drivers are salmon and bread; the salmon is also the wrong brand.
Shall I (1) swap the salmon for Fjoru, (2) add fresh mozzarella,
(3) burger mince and (4) a second bag of flour? Then the week is complete.
It has never stopped us from buying the good cheese. But once a week a robot tells me in two lines what the good cheese costs, and who put the wrong salmon in the cart. That is exactly the amount of financial advice I want from my groceries.
The prompts of a normal week
Most of my weekly prompts are German and dictated; I translated them here. Three kinds of weeks, three kinds of prompts.
1. There already is a plan
There already is a plan, 3 recipes are in TODO, shop for those. In addition we need buttermilk for pancakes
Claude read the three dishes from To Do, took the last deliveries as a baseline and put ten items in the cart. Then I pasted a link to a fig and goat’s cheese quiche. The site blocked it, so Claude rebuilt the ingredients from an equivalent recipe and added only what was missing. Ten minutes later I switched to a tarte flambée. Claude swapped the cream for crème fraîche and bases, ran the finance check („solid, no luxury“) and asked for a delivery slot.
2. It is sunny outside
Hey, it is sunny outside, propose sunny recipes, I choose 5 and we map them to the weekdays according to amount of work for the recipe and the people in the household
A summer week at the end of August went like this. Before proposing anything, Claude pulled our whole To Do history: 4,074 ticked-off supermarket items back to 2017. So the summer proposals were dishes we actually cook. I picked a chicken couscous bowl for Wednesday, a feta salad for Thursday (plus Schupfnudeln, because the kids never eat much salad) and couscous-carrot patties for Friday. Eleven of twelve items landed in the cart; the missing vine tomatoes became a packaged tray, because loose produce cannot be added through the API.
3. The whole week in one message
Hello, next week we'd like to cook - broccoli mushroom pasta à la Ottolenghi (pasta here), salad with goat's cheese (Picandou, salad seeds), salmon bowl with edamame (shelled) and avocado, vegetable omelette, bolognese (Friday) with frozen mince 2x, hamburgers with homemade brioche, piadina (homemade) with broccoli, tomatoes, mozzarella, prosciutto.
Milk is badly missing right now.
I guess bowl is Wednesday, salad Thursday, bolo Fri, piadina Sat, burger Sun
Vegetable omelette is for lunch (small portion)
I need vegetables for the kids
[…]
Let's go - please fill in TODO as well
When I already know the plan, one dictated message does it all. Claude checked cart and last delivery, put 16 of 20 items in and found alternatives for the four that were sold out („pod edamame, you’ll have to shell them“). Eight dishes went into To Do. Then I shopped around in the app myself and just said „check“. It found the wrong salmon brand, no mince left for the burgers and only grated mozzarella for the piadina. That was the finance check you saw above.
Next morning.
What went wrong
- The tunnel dies, Claude doesn’t notice. When ngrok drops, the phone app doesn’t throw an error. Once Claude politely told me it „can’t see any Microsoft To Do tools in this session“ and asked me to paste the recipes by hand. That’s why the skill now says: silent fail means tunnel, not auth.
- The order history was broken.
get_order_detail returned „Unknown price / Unknown date“ and quantity 1 for everything: the server read fields that Knuspr’s API no longer returns. Claude fixed the mapping, added optional categories and retry with backoff on HTTP 429, and I opened a pull request upstream. By then the maintainer had fixed the mapping independently, so the PR shrank to the parts that were really new. - Rate limits. When I asked whether a soda maker would pay off, Claude had to read every past order and ran into 429s. It ended up dumping all orders, about a year’s worth, to local JSON at one request per second. That archive has been useful ever since.
- Loose produce. Items sold by weight could not be added reliably through the cart API. Claude now picks the packaged variant.
- We share one account. Twice Claude reported that items appeared in the cart that it had not added: three packs of mince instead of one, extra courgettes. Someone else in the family was shopping at the same time. Funny, and a good reason for the rule „always read the cart before writing to it“.
If you want to try this
- Start in Claude Code with stdio servers. You only need the OAuth proxy and a tunnel if you want it on your phone.
- Keep your meal history somewhere an MCP can read. Years of ticked-off dishes are the best recipe recommender you have.
- Write the skill from your corrections, not upfront. Each „no, we always have that“ becomes a rule.
- Do not paste tokens into prompts. I did, in the very first one. Put them in a
.env.
Meal plan, shopping and fridge: 20 to 30 minutes.
Meal planning, shopping and putting things in the fridge now take 20 to 30 minutes a week. The servers were written by other people, the glue by Claude, and the household knowledge by us, one correction at a time.