28.07.2026 offerhopper.ai team 25

Build a Personal AI Grocery Assistant with Claude, ChatGPT or OpenClaw

Build a Personal AI Grocery Assistant with Claude, ChatGPT or OpenClaw
Building a personal AI grocery assistant with offerhopper.ai via MCP — AI-generated (offerhopper.ai)

Someone out there has offerhopper.ai wired into their morning. Every Monday around eight, before anyone has decided to go shopping, a query arrives from an agent running on a schedule, asking whether anything is worth a trip today.


I built offerhopper.ai as a web application — read the story of why offerhopper.ai exists to solve supermarket decision fatigue. You open it when you've already decided to shop, type what you need, and it works out where to go and whether the drive pays for itself. That was the first thing to exist, and for a website it holds up fine.

The MCP server wasn't an afterthought bolted on later. It was built because AI agents making up numbers about trips and prices is a real problem. A language model asked "is it worth driving to Aldi today?" will happily invent a plausible-sounding answer with no route, no live prices, and no actual arithmetic behind it. offerhopper.ai does the arithmetic. Exposing that over the Model Context Protocol (MCP) means an agent gets a real multi-store cost calculation instead of a guess dressed up as one.

What I didn't fully expect is what that turns the tool into once it's the agent making the call rather than you. The website waits for you to remember it exists. An agent doesn't need reminding, and it already knows things about your household that the website never will.

This guide covers turning offerhopper.ai into a personal grocery assistant inside Claude, ChatGPT, or self-hosted agents like OpenClaw. Every worked example below is real output from one German household, including the runs that went wrong. If you'd rather not do any of it by hand, skip to the agentic setup flow at the end and hand this whole page to your agent.

One thing to be clear about before you start. offerhopper.ai answers "where should I go to buy this list, and is the trip worth it". It does not answer "what's on offer near me". You bring the list; it does the routing and the arithmetic.


Quickstart

Three steps to a working answer. Two more to make it run itself.

1. Connect the server

Parameter Value
MCP Server URL https://mcp.offerhopper.ai/mcp
Authentication None (public endpoint)
Tool name plan_optimal_shopping_route
Supported assistants Claude (Web, Mobile, Desktop), ChatGPT (Developer Mode), OpenClaw

On Claude web and mobile, open Settings, then Connectors, then Add Custom Connector, and enter the URL with no authentication. Add it at account level rather than inside a project, or scheduled tasks won't see it later.

On Claude Desktop, add the stdio-to-HTTP bridge configuration in claude_desktop_config.json. Exact JSON is in the Developer Guide.

On ChatGPT with Plus, Team or Enterprise, open Settings, then Apps, enable Developer Mode, create an app, and enter the same URL.

On OpenClaw, install from the ClawHub registry:

openclaw plugins install clawhub:offerhopper.ai

Screenshots and the full walkthrough are in the MCP Developer Setup Guide.

2. Give your assistant the context

Paste the master context block below into your custom instructions, or into a skill if you intend to schedule anything later. Five values carry most of the weight: your PLZ, household size, transport mode, what an hour of your time is worth, and your two shopping lists written in German.

View & Copy the Master Context Prompt

Paste this into your assistant's custom instructions or skill file (replace all bracketed values):

# Grocery Shopping & offerhopper.ai Context

## Household
- Location: [REPLACE ME with your PLZ or town]. All queries use this
  unless I say otherwise.
- Household size: [REPLACE ME with e.g. 3 people, 1 adult and 2 teenagers]
- Transport: [REPLACE ME with Car at €0.30/km, or Bicycle or e-scooter
  at €0/km, or Walking]
- Time value: [REPLACE ME with your €/hour]. Passed as hour_cost.
- Dietary: [REPLACE ME with e.g. milk must be lactose-free, nothing
  else needs to be]
- Shopping rhythm: [REPLACE ME with e.g. every few days, or one weekly shop]

## Baskets
- Fresh basket: [REPLACE ME with your staples, in German]. Size for the
  household over [REPLACE ME with number] days.
- Stock-up list: [REPLACE ME with your non-perishables, in German].
  Long shelf life, so timing matters.

## Rules
1. Write item names in German where possible. Name the specific
   product intent when I care which product arrives (Kopfsalat not
   Salat, Gouda am Stück 400g not Käse). Leave a word broad when I
   genuinely don't mind, such as Obst.
2. Size unstated quantities for my household. Fresh basket for the
   stated number of days, stock-up items for a full restock.
3. Report the verdict, the travel overhead percentage, the basket
   total, and the in-store time cost separately from the headline.
4. Name every selected_product that doesn't match what I asked for,
   and every item priced above its market_average. Do not rely on the
   confidence score. A broad word resolved to a good-value pick, or a
   pack size that beats the unit I counted in, is not a mismatch.
5. Stock-up alerts only when an item is more than 25% off AND the
   verdict is worth_it. Otherwise say nothing is worth a trip.
6. If I mention driving somewhere, pass it as end_location so the
   detour is costed instead of a round trip.
7. Don't propose extra stops the tool didn't route.

3. Run one query

Check my fresh basket. Is it worth a trip today?

That's the whole query. Your location, transport, household sizing and the reporting format all come from the block you just installed, which is the point of installing it. If you skipped step 2, you'll need to spell those out each time instead.

For the household these examples come from, a four-item version of that basket returned one stop at a REWE 643m away, €10.06 all in with €7.79 of it food, 7% travel overhead, verdict worth it.

Read selected_product on every line before you trust the total. A first run usually contains one item the matcher resolved differently than you meant. Three things that look like errors and aren't: a broad word resolved to a good-value pick, a pack size that beat the unit you counted in, and a single-store route.

4. Let it optimise something

Plan my week of dinners. First, probe the market by running two broad
queries (proteins + vegetables/carbs) through offerhopper.ai. Extract
price signals from the results. Then compose a menu that leans on
whatever's cheapest this week. Run the final list for the route.

The price-signal step is the whole trick. Worked example here.

5. Put the stock-up check on a schedule

Weekly, Monday 08:00. In Claude that's Cowork, then Scheduled, then New task. Keep the config in a skill, the run history in a state file, and the task prompt short.

Or skip all five and hand this page to your agent.


How to Write Shopping Lists That Match Correctly

This is the section that will save you the most trouble, so it comes before the configuration.

offerhopper.ai does not do literal string matching against product names. It resolves your words to the most plausible shopping intent, then finds products for that intent. Most of the time this is exactly what you want: it means "Hackfleisch" finds mince without you specifying a brand, pack size or fat content.

The failure mode is not that vague words fail. It's that a word can carry more than one reasonable intent, and the matcher picks one.

Here are two real resolutions from a run using the generic staples list this guide used to recommend:

Asked for Matched to
Salat Cherry Romatomaten 250g
Käse Exquisa Frischkäse Fitline Kräuter 200g

Neither is absurd. You can make a salad out of cherry tomatoes, and Frischkäse is cheese. They're defensible readings of an ambiguous word. They just weren't what the shopper meant, and the basket total was calculated against them.

The fix is to name the intent rather than the category.

Instead of Write
Salat Kopfsalat, or Salatmischung, or Cherry Tomaten
Käse Gouda am Stück 400g, or Emmentaler Scheiben 200g

Sometimes the ambiguity is the point

Not every broad word needs narrowing. Obst returns a sensible fruit picked on value, which is exactly right if what you meant was "some fruit, whatever's good this week". You delegated the choice and the engine made it on price.

Leave a word broad when you genuinely don't mind what arrives, and narrow it when you do. Obst and Äpfel 1kg are both correct instructions. They just say different things, and only one of them is a mistake if you had a specific fruit in mind.

Quantities are compared by value, not by your unit

A run asking for 10 Rollen Toilettenpapier came back with an eight-roll pack. That looks like a miss and isn't one. Rolls are a poor unit for toilet paper because sheet counts vary, so eight rolls at 220 sheets each can beat ten shorter ones on both quantity and price. The engine compares what you actually end up with.

The same logic applies across the list. A 1kg tub of yoghurt will win over six 150g pots when it's better value, even though you counted in pots. This is the optimisation working, not drifting, and correcting it will cost you money.

German gives you the best precision

The search runs against German retail data, so German product nouns are the most reliable input. English works, and a fair amount of normalisation happens on our side to make sure it does, but not for one hundred percent of cases.

Here's one that fell through. A menu-planning run asked for 1 bag mixed salad. No catalogue match existed, so the engine fell back to a synthetic assumed price of €2.50 with no product link and a null relevance score. Re-running the identical query with 1 bag Salatmischung matched a real product, ja! Mischsalat Rohkost Mix 200g, at €1.98 for two packs, relevance 1.0.

The second-order effect was larger than the fix itself. Correcting that one term also improved the vegetable match elsewhere in the same basket, swapping an expensive Wok-Mix for cheaper Suppengemüse, and the basket's overall confidence rose from medium to high with zero estimated items remaining.

Talk to your agent in whatever language you like. Have it write the item list in German where it can.

Naming brands works

Precision is rewarded when you actually care about the product. Swirl Müllbeutel 35L matched exactly: Swirl Reißfest und Dicht Tragegriff 35l, 15 Stück. Brand plus volume plus intent leaves nothing to resolve.

Bad matches get corrected over time

Bad resolutions get corrected. The cherry tomato case above has since been fixed at the matcher level. If a result looks wrong, it's worth re-running before assuming the product doesn't exist.


Reading the Cost Output: Verdict, Savings and Confidence

Three independent questions come back. Collapsing them is the most common misreading.

Is the trip worth making

This judges travel and time overhead against the basket. Nothing else. A single tube of toothpaste for €2.99 requiring a 12-minute drive produces roughly 200% travel overhead and a "not worth making" verdict, even if the toothpaste is half price.

Importantly, worth_it does not mean this is your cheapest option. In a three-menu comparison run for one household, all three candidate menus returned worth_it while differing by €4.59 in all-in cost. The verdict was answering a different question than the one the shopper cared about.

The headline cost is not the all-in cost

total_estimated_cost is product cost plus travel cost. In-store shopping time is tracked separately in cost_analysis.in_store_time_cost and is not in the headline number.

For a single run this rarely changes the decision. For comparing options it does, because the option with more stops carries more in-store time. Add it manually when comparing:

Menu Stores Headline In-store time All-in
A 2 €39.74 €4.25 €43.99
B 1 €37.48 €3.10 €40.58
C 2 €35.60 €3.80 €39.40

Confidence measures the match, not the truth

A confidence score reflects how well the engine thinks it resolved your words. It does not reflect whether the product is the one you wanted. The cherry-tomatoes-as-salad run returned ten of ten items priced, high confidence, zero flagged.

So don't rely on the score. Read selected_product. Two things worth checking every time:

A selected_product name that doesn't resemble what you asked for. That's an intent mismatch, and it's on you to fix the wording.

A price sitting above market_average. That's a pricing question, not a matching one, and it usually has a benign explanation. Sometimes the engine deliberately accepts a worse unit price to avoid a costly extra stop.

Two cautions on market_average itself. It's the bottom-30% average total cost to satisfy your desired quantity, divided by buy_count — so it's an amortised per-pack figure, not a per-kilo number. You cannot rank different products by it because pack sizes and buy_counts differ. And it reflects our data coverage, which is asymmetric: special offers across all the major German grocers and drugstores, live shelf prices for Aldi, REWE, dm, Rossmann and Müller.

That asymmetry shapes routing more than it shapes any individual price. Where we only see a chain's promotions, the offer itself is real and it's in the data. What's missing is everything around it. We know nothing else about that store's shelf, so every other item in your basket has to be estimated there, and a store carrying a pile of estimates rarely wins a whole-basket comparison even when it holds the single cheapest item on your list.

That's the tool working as designed. It's choosing one destination for your entire list, not a winner per item.

Synthetic items

When no catalogue match exists in range, the engine falls back to an assumed price with no product link and a null relevance score. That's the strongest signal you have that your wording needs work. The 1 bag mixed salad case above produced exactly this.


Setting Up Your Assistant's Standing Context

offerhopper.ai knows nothing about you. No login, no profile, no memory of last week. Every search starts from zero.

That's deliberate. It cannot learn that you're vegetarian, that your usual store is the REWE six hundred metres from your door, or that you buy the same twelve items every Thursday. A tool holding nothing about you also can't lose anything about you.

Your assistant is the right place for that context to live. It stays with you and gets passed to offerhopper.ai only when a query needs it.

Decide where it goes first

This choice determines whether your automation works later, so make it now.

If you only ever plan to chat, paste the block below into your assistant's custom instructions: Project Custom Instructions in Claude, MEMORY.md in OpenClaw, Saved Memory in ChatGPT.

If you intend to schedule automated checks, put it in a skill instead. Scheduled runs are separate sessions that can load skills but may not inherit a project's instructions. Putting the config in a skill means your chat sessions and your Monday run read from one source and you edit it once.

The block

Replace every bracketed value before you use this.

# Grocery Shopping & offerhopper.ai Context

## Household
- Location: [REPLACE ME with your PLZ or town]. All queries use this
  unless I say otherwise.
- Household size: [REPLACE ME with e.g. 3 people, 1 adult and 2 teenagers]
- Transport: [REPLACE ME with Car at €0.30/km, or Bicycle or e-scooter
  at €0/km, or Walking]
- Time value: [REPLACE ME with your €/hour]. Passed as hour_cost.
- Dietary: [REPLACE ME with e.g. milk must be lactose-free, nothing
  else needs to be]
- Shopping rhythm: [REPLACE ME with e.g. every few days, or one weekly shop]

## Baskets
- Fresh basket: [REPLACE ME with your staples, in German]. Size for the
  household over [REPLACE ME with number] days.
- Stock-up list: [REPLACE ME with your non-perishables, in German].
  Long shelf life, so timing matters.

## Rules
1. Write item names in German where possible. Name the specific
   product intent when I care which product arrives (Kopfsalat not
   Salat, Gouda am Stück 400g not Käse). Leave a word broad when I
   genuinely don't mind, such as Obst.
2. Size unstated quantities for my household. Fresh basket for the
   stated number of days, stock-up items for a full restock.
3. Report the verdict, the travel overhead percentage, the basket
   total, and the in-store time cost separately from the headline.
4. Name every selected_product that doesn't match what I asked for,
   and every item priced above its market_average. Do not rely on the
   confidence score. A broad word resolved to a good-value pick, or a
   pack size that beats the unit I counted in, is not a mismatch.
5. Stock-up alerts only when an item is more than 25% off AND the
   verdict is worth_it. Otherwise say nothing is worth a trip.
6. If I mention driving somewhere, pass it as end_location so the
   detour is costed instead of a round trip.
7. Don't propose extra stops the tool didn't route.

Why the numbers matter

Household size changes what a bare word means. Joghurt with no quantity defaults to a single 150g pot. For a three-person week it's a 1kg tub, which lets the engine compare bulk against small packs properly. More on this in how offerhopper.ai understands your shopping list, and on comparing those pack sizes against each other in which pack is actually cheaper.

Time value works in both directions, and the default is €12.00/hour.

Above the default, marginal stops die. At €25/hour a 15-minute detour costs €6.25, so saving €1.00 on coffee is a clear loss and the tool will tell you so.

Below the default, they come alive. At €6.00/hour that same detour costs €1.50, and second stops that would never clear the bar for someone billing €25 become sensible. One household at €6.00/hour had two-store routes beat their forced single-store equivalents by €1.25 and €4.28 on two different menus.

So the frequent claim that split-store shopping is almost never worth it isn't a law of German retail. It's a consequence of the default hourly rate. Set yours honestly and the advice changes with it.

The fresh versus stock-up split matters because the two categories obey different rules. Milk and salad get bought whether or not there's a deal. Coffee and detergent should only trigger a trip when a real discount lines up with a route you were going to drive anyway.


MCP Tool Parameter Reference

The tool accepts more than most guides mention. These are the levers worth knowing.

Parameter Default Notes
items required Natural language list. German, specific.
location required German PLZ, city, or address.
end_location none Set for A-to-B trips. Routes stores along the corridor instead of a round trip.
travel_mode car car, bicycle, pedestrian.
hour_cost 12.0 Your time in €/hour.
km_cost mode-dependent Forced to 0.0 for bicycle and pedestrian.
max_radius_km car 15, bicycle 5, pedestrian 2 See note below.
shopping_time_per_store 10 Base minutes per stop, before travel.
max_stores 100 Cap the number of stops.

The radius defaults are derived, not arbitrary. A bicycle basket is limited by what you can carry, and a 5km-plus round trip by bike runs close to an hour for a load small enough to fit on the bike. The worth-it function rejects those trips anyway, so the radius stops you from generating routes that were never going to pass.

Choosing a travel mode

Treat this as a carrying-capacity question rather than a cost question.

Over short distances the costs converge. For a 643m trip at €6.00/hour, the car came out at €0.33 per leg (€0.14 time plus €0.19 distance) and the bicycle at €0.33 per leg, all of it time. The car's speed advantage cancels its running cost almost exactly. What actually differs is whether the basket fits in a backpack.

Use bicycle for e-scooters, e-bikes and cargo bikes. The distinction the engine cares about is whether there's a per-kilometre running cost, not what the vehicle is called.

Turning the trip arithmetic off

If what you want is the cheapest item regardless of effort, set both hour_cost and km_cost to 0. With no time or distance penalty left in the model, only product prices matter, and the engine will route you to a discounter at the far edge of the search radius for one cheap thing. Raise max_radius_km and it will go further still.

That's a faithful answer to the question, and it's almost never a trip worth making. Everything else in this guide exists because the cost of getting there is the part people underestimate. Worth knowing the lever is there. Worth leaving it alone.


Prompt Templates for Common Shopping Queries

A prompt like "what's cheap near me?" forces the assistant to invent a list, and the route it plans will be for that invented list. Give it real items.

These assume you've installed the context block, so they carry only what changes from query to query. Without it, add your location and transport to each one.

The regular shop:

Check my fresh basket. Worth a trip today?

The detour, which is where the tool earns the most. If you're already making the drive, the incremental travel cost is close to zero, and the destination is the one thing the block can't know in advance:

I need [REPLACE ME with items, in German]. I'm already driving to
[REPLACE ME with your destination] this afternoon. Pass it as
end_location and tell me whether it's worth grabbing these on the way
versus a separate trip.

The stock-up check, manual or scheduled:

Run my stock-up list, including drugstores.

Override the standing values inline whenever a trip is unusual:

Check my fresh basket, but I'm on the e-scooter today and only need
enough for two days.

Automating Weekly Checks with Scheduled Tasks

German promotion cycles run weekly, starting Monday or Thursday depending on the chain. A daily check reruns identical flyer data and burns tokens for nothing. Monday at 08:00 is the right slot.

Where scheduling lives

Platform Where Execution
Claude Cowork, on the Scheduled page. Paid plans only, and Cowork is in beta rolling out by plan tier. Remote. Runs while your machine is asleep, unless the task is pointed at a local folder.
OpenClaw Natural language, in any session. Runs through your gateway.
ChatGPT No native background execution for custom MCP connectors. Manual trigger only.

If you set this up previously: scheduling is not something you request inside a normal Claude chat. It's a Cowork feature with its own page. Asking a regular chat session to schedule a recurring task will not create one.

Setting up in Claude

Click Scheduled in the left sidebar, then New task.

"Create with Claude" opens a session that interviews you and proposes the task, shows you the name, cadence and behaviour, and commits when you click Schedule.

"Set up manually" gives you the fields directly: task name, prompt, approval mode, frequency (hourly, daily, weekly, weekdays or manual), model, and optionally a working folder.

Two settings decide whether it works unattended. Leave the folder field empty, because pointing a task at a local folder forces local-only execution and it will then only fire when your machine is awake. And check the approval mode, because a task that pauses for permission hasn't run, it has stalled until you notice.

Once created, the Scheduled page lets you review upcoming and past runs, edit instructions or cadence, pause, resume, delete, or fire a run on demand. Fire one on demand before trusting the schedule.

Setting up in OpenClaw

No JSON, no crontab:

Hi, can you do a stockup check weekly for me every Monday morning 8:00am?

The agent confirms the schedule and timezone back to you.

Giving a scheduled task a memory

Each run is a fresh session. It remembers nothing about last week: not what it reported, not what you bought, not what the price was. Left alone it will tell you about the same pizza offer three Mondays running, and it has no way to know whether €1.79 is a genuine floor or just this chain's every-third-week price.

Fix that with three layers rather than a longer prompt.

The durable config goes in a skill. Location, household size, hourly rate, transport rules and the lists themselves don't change week to week, and scheduled tasks can load skills.

The run state goes in a file saved to your Claude account, not a folder on your computer. The task reads it at the start and appends at the end.

The task prompt holds only the trigger and the reporting rules, short enough to read in one screen.

The obvious use of state is deduplication. The more valuable one is calibration. discount_pct is calculated against that store's own regular_price, so a 51% discount is measured from a baseline the retailer sets. After eight weeks of recorded prices, your threshold stops being "more than 25% off" and becomes "at or below the lowest we've seen", which is the question you actually had. The third use is restock timing: fifteen bin bags last about six weeks, and an alert twelve days after you bought them is noise.

A starting state file. Keep it small, since it's read into context every run:

# Stock-up state

## Observed price floors
| Item | Lowest seen | Store | Date | Shelf price |
|---|---|---|---|---|
| Dr. Oetker Tradizionale Salame 385g | 1.79 | REWE | 2026-07-28 | 3.69 |
| Swirl Müllbeutel 35L, 15 Stück | 2.59 | REWE | 2026-07-28 | 2.59 |

## Purchases
| Date | Item | Quantity | Paid | Reorder after |
|---|---|---|---|---|
| 2026-07-28 | Swirl Müllbeutel 35L | 15 bags | 2.59 | 2026-09-08 |

## Alerts sent
| Date | Item | Price | Bought? |
|---|---|---|---|
| 2026-07-28 | Dr. Oetker Tradizionale | 1.79 | yes |

And the task prompt:

Run my weekly stock-up check.

1. Read stock-up-state.md.
2. Run the stock-up list through offerhopper.ai using my standing config.
3. Compare each returned price against the lowest price recorded in the
   state file, not against the shelf price the store reports.
4. Message me only if an item is at or below its recorded floor AND the
   verdict is worth_it AND the item is past its reorder date. Otherwise
   reply "Nothing worth a trip this week."
5. Append this run's prices to the observed floors table. Log any alert
   sent. Leave the Bought? column for me to fill in.

Your first Monday will be chatty, because an empty state file makes every price look like a floor. It quietens down as the log fills in.


Meal Planning with Live Prices

Most meal planning works backwards. You decide what to cook, then check whether the ingredients are affordable. The better approach flips that: find out what's cheap right now, then plan meals around those ingredients.

offerhopper.ai wasn't built for meal planning, but the price signal it returns on every query makes it a powerful oracle for an agent doing the planning. Every item in the response carries two numbers: price (what the engine found) and market_average (the bottom-30% average cost to satisfy that item's desired quantity, divided by buy_count — an amortised per-pack figure). The ratio between those two tells you whether an ingredient is a deal this week or not.

Here is a real session from Köln, 50667, by car.

Step 1: Probe the market

The first move is a broad query covering all the major ingredient categories. You're not shopping yet. You're asking "what does the market look like today?"

Use offerhopper.ai to price these items from 50667 Köln, max 3 stores:

Hähnchen, Hackfleisch, Lachs, Eier, Joghurt, Nudeln, Reis, Kartoffeln,
Tomaten, Paprika, Zwiebeln, Äpfel, Bananen, Butter

And a second query covering alternative proteins and vegetables:

Use offerhopper.ai to price these items from 50667 Köln, max 3 stores:

Putenfleisch, Thunfisch, Linsen, Couscous, Zucchini, Brokkoli, Möhren,
Spinat, Quark, Sahne

Two calls, twenty-four categories probed. The results come back with real prices and market averages for everything.

Step 2: Read the price signals

From those two runs, extract a value score for every ingredient: 1 - (price / market_average). Higher means a better deal relative to the local norm.

From a real run on 5 August 2026:

Ingredient Price Market avg Value score Signal
Tomaten €0.14/100g €0.40 0.65 outstanding
Spinat €0.89/500g €1.69 0.47 outstanding
Thunfisch €1.99/can €3.76 0.47 outstanding
Putenfleisch €1.80/500g €2.80 0.36 strong
Äpfel €1.59/kg €2.26 0.30 strong
Zwiebeln €1.59/kg €2.31 0.31 strong
Hackfleisch €5.58/500g €7.02 0.21 moderate
Linsen €1.45/500g €1.73 0.16 moderate
Zucchini €0.48/pc €0.55 0.13 moderate
Hähnchen €4.19/500g €4.57 0.08 weak
Kartoffeln €2.49/2kg €2.65 0.06 weak
Lachs €6.39/200g €6.75 0.05 weak

The market is telling you something clear: turkey is 36% below normal while chicken is barely discounted. Tomatoes and spinach are absurdly cheap. Salmon has no deal at all. A menu built from the top half of that table will cost substantially less than one built from the bottom.

Step 3: Compose the menu from the winners

Now tell the agent to plan meals using what's actually cheap:

Based on the price signals above, plan a full week of dinners for 2 people.

Rules:
- Lean heavily on the "outstanding" and "strong" deal items: Putenfleisch,
  Tomaten, Spinat, Thunfisch, Zwiebeln, Äpfel
- Use Linsen, Zucchini, Hackfleisch as secondary ingredients
- Avoid Lachs and Hähnchen this week (no meaningful deals)
- Include Nudeln, Reis, or Kartoffeln as carb bases
- No meal repeated. Reasonable variety across the week.
- Write the final shopping list in German with quantities for 2 people.

Step 4: Validate the composed basket

Run the final composed list through offerhopper.ai to get the actual optimized route and cost:

Use offerhopper.ai to price this from 50667 Köln, max 3 stores:

Putenfleisch 1kg, Hähnchen 500g, Thunfisch 2 Dosen, Eier 10 Stück,
Nudeln 1kg, Reis 1kg, Kartoffeln 2kg, Linsen 500g, Tomaten 1kg,
Zucchini 3 Stück, Spinat 500g, Zwiebeln 1kg, Äpfel 1kg, Joghurt 500g,
Quark 500g, Butter, Brot

This came back at €42.04 product cost, €15.91 in savings, single stop at REWE. Seventeen items covering a full week for two people with high confidence (zero synthetic items, only two low-confidence matches).

Why this works better than "generate 3 menus and compare"

The old approach — draft three menus blindly, price each, pick the cheapest — has a structural flaw. The agent plans meals without knowing what's cheap, then discovers the prices after the fact. Menu 4, the one composed entirely from the week's best deals, never gets generated because nobody asked for it.

The price-signal approach inverts the flow. You observe the market first, then construct a menu that rides the deals. The result isn't just cheaper than a random good menu — it's built from ingredients that are genuinely below their market norm this week, which means the savings are real rather than coincidental.

The total agent cost for this approach: two tool calls to probe the market, one to validate. Three calls total. Comparable to a single regular shopping query.

The prompt, all in one block

For copying into your assistant:

Act as a grocery optimizer. Plan a full week of dinners for my household.

1. Probe the market. Run two broad queries through offerhopper.ai:
   - Proteins and staples: Hähnchen, Hackfleisch, Putenfleisch, Lachs,
     Thunfisch, Eier, Joghurt, Quark
   - Carbs and vegetables: Nudeln, Reis, Kartoffeln, Linsen, Couscous,
     Tomaten, Paprika, Zucchini, Spinat, Brokkoli, Möhren, Zwiebeln,
     Äpfel, Bananen, Butter, Brot

2. Extract price signals. For every item, calculate a value score:
   1 - (price / market_average). Sort by value score descending.
   Items above 0.25 are strong deals. Items below 0.10 are no-deal.

3. Compose a weekly menu. Plan 7 dinners that lean heavily on the
   strong-deal items, use mid-deal items as supporting ingredients,
   and avoid no-deal items where possible. Ensure variety: no protein
   repeated more than 3 times, different carb bases across the week.

4. Build the shopping list in German with quantities sized for my
   household. Run it through offerhopper.ai for the final route and
   cost.

5. Report: the weekly menu, the shopping list with route, total
   all-in cost (headline + in-store time), and which deals drove
   the biggest savings. Show the value score table so I can see
   what was cheap this week.

Hand the Whole Setup to Your Agent

Everything above can be done by hand. It doesn't have to be.

If your assistant can read a URL and call the connector, hand it this page and let it run the setup, interviewing you for whatever it needs. That's a better use of the guide than reading it start to finish, and it produces a config built from your actual results rather than from the examples.

Connect the MCP server first, then paste this:

Read https://offerhopper.ai/blog/en/ai-grocery-shopping-assistant and set up my
grocery automation end to end.

Work in this order and don't skip ahead:

1. Confirm you can see the offerhopper.ai tool. If you can't, stop and
   tell me how to connect it before doing anything else.

2. Interview me for everything the guide's context block needs and I
   haven't already told you: my PLZ or town, household size, transport,
   hourly time value, dietary constraints, how often I shop, whether
   drugstore items are in scope, and my two lists. Ask in small batches
   rather than all at once. If a value I give differs from the guide's
   default, tell me what it changes about the advice I'll get.

3. Draft my fresh basket and stock-up list in German, naming specific
   product intent rather than categories, as the guide describes. Show
   me both lists and let me correct them before you run anything.

4. Run the fresh basket once. Report the verdict, the travel overhead,
   the headline cost, and the in-store time cost as a separate figure.

5. Audit that run properly. For every item, compare selected_product
   against what I actually asked for. Flag intent mismatches, synthetic
   prices with null relevance, and anything priced above market_average.
   Do not flag a broad word that resolved to a good-value pick, or a
   pack size that beat the unit I counted in; both are correct. Propose
   better wording for whatever is genuinely wrong and re-run to confirm
   the fix landed. Don't skip this step because the confidence score
   looks good.

6. Write my final context block using the values I confirmed. Tell me
   where to put it: custom instructions if I'm only going to chat, a
   skill if I'm going to schedule.

7. Tell me exactly how to create the weekly stock-up task on my
   platform, and give me the task prompt and the starting state file to
   go with it. Do not claim to have scheduled anything you cannot
   actually schedule from where you are.

Ask me when you're unsure rather than assuming. I'd rather answer three
questions than fix a wrong basket.

What to expect

Step 2 should feel like a short interview, not a form. A good agent asks about six things and explains the consequences of at least one of your answers.

Step 5 is the one that gets skipped, and it's the one that pays. A first-draft basket almost always contains one item the matcher read differently than you did. If your agent reports a clean run with nothing flagged on the first attempt, ask it to list every selected_product explicitly before you believe it.

Step 7 is where agents tend to overreach. Scheduling lives in specific places, and a chat session usually can't create a recurring task on its own. An agent that tells you it has scheduled something, when it has only written you a prompt, has left you with an automation that will never fire.


Frequently Asked Questions

How do I connect offerhopper.ai to my AI assistant?

Server URL https://mcp.offerhopper.ai/mcp, no authentication, on Claude or ChatGPT. On OpenClaw, openclaw plugins install clawhub:offerhopper.ai. Full walkthrough in the MCP Developer Setup Guide.

Which stores and prices are actually covered?

Special offers are covered across all the major German grocers and drugstores: Aldi Nord and Süd, Lidl, REWE, Edeka, Penny, Norma, Netto, dm, Rossmann and Müller. Live everyday shelf prices are available for Aldi, REWE, dm, Rossmann and Müller only. That's a limit of what those chains publish, not a design choice.

It has a routing consequence worth understanding. An offers-only store is hard to send you to for a full basket, because everything you asked for beyond its promotions has to be estimated there. The cheap item is real, but a destination is chosen for the whole list.

Does offerhopper.ai store my location or shopping habits?

No. There are no accounts, no stored location data, and searches are discarded after execution. Your context lives inside your own assistant, under your control.

Why does it usually send me to one store?

A second stop costs a fixed 10 minutes of shopping time plus the travel leg. Per-item price differences rarely cover that. Splits do happen when a category is genuinely cheaper or unavailable elsewhere, and when they do, they're worth taking seriously rather than optimising away.

Can it handle specific brands?

Yes, and precision helps. Swirl Müllbeutel 35L matched exactly. The more of the intent you specify, the less the matcher has to guess.

What if I shop every few days rather than weekly?

Split it. Keep the fresh basket manual and sized for two or three days, and schedule only the stock-up check weekly. Promotion cycles are weekly regardless of how often you shop, so there's nothing to gain from checking offers more often than that.

Does it work outside Germany?

No. German supermarkets, discounters and drugstores only. A German 5-digit PLZ or city name is required.

Can I use Google Gemini?

Gemini currently restricts custom MCP integration to Vertex AI and Spark accounts in select regions, mostly US paid developer tiers. For consumer use in Europe, Claude, ChatGPT and OpenClaw are the workable options.


If you want the arithmetic underneath any of this, the route optimization algorithm post covers the cost function, how offerhopper.ai understands your shopping list covers unit handling and quantity inference, and which pack is actually cheaper explains how pack sizes get compared when the unit price doesn't help.

offerhopper.ai

About offerhopper.ai

The AI-driven shopping route planner for expats and locals in Germany.