Skip to main content
Glama
donadelicc

kundeavis-mcp

by donadelicc

kundeavis-mcp

An MCP server that makes Norwegian grocery kundeaviser (weekly store flyers) queryable by an AI agent, so it can plan cheap meals from what's actually on offer this week.

Roughly 1,800 offers a week across 16 chains — KIWI, REMA 1000, MENY, Extra, SPAR, Coop Mega/Prix, Bunnpris, Joker, Obs, Matkroken, Nærbutikken, Jacobs, Europris, Gigaboks, Holdbart — normalised to comparable unit prices and tagged with canonical ingredients.

you: plan three cheap dinners for this week
agent: [cheapest_by_category category=meat]
       → sausage 55 kr/kg at Obs, pork chop 69.90 kr/kg at Obs, pork neck 89.90 kr/kg at Coop Prix

Install

Requires Node 22.5+ (it uses the built-in node:sqlite, so there is no native module to compile).

Not on npm yet — install from the repo. dist/ is built during install:

npm i -g github:donadelicc/kundeavis-mcp
{
  "mcpServers": {
    "kundeavis": { "command": "kundeavis-mcp" }
  }
}

The first query fetches the current flyers automatically (about 45 HTTP requests, a few seconds) into ~/.kundeavis-mcp/offers.db. Nothing else to set up.

Each machine keeps its own database and its own archive. Two agents on two machines start with two separate histories — see below if you want them to share one.

Related MCP server: Swissgroceries MCP

Tools

Tool

What it answers

cheapest_by_category

"What protein is cheap this week?" Cheapest offer per ingredient, ranked by unit price. The main meal-planning entry point.

search_offers

Free-text plus filters on ingredient, category, chain, unit-price basis, discount.

compare_ingredient

One ingredient priced across every chain, against a baseline from earlier weeks.

price_history

Cheapest unit price per week per chain, to judge whether a "tilbud" is genuinely good.

find_stores

Grocery stores near a coordinate. Offers are chain-national, so this answers which shop to go to.

list_chains

Coverage per chain: offers, how many have a usable unit price, how many are tagged.

sync

Force a refresh. Runs automatically when nothing valid is cached.

There is no history upstream — capture it or lose it

This is the single most important thing to know before relying on this tool.

Offers vanish at their own run_till, with no grace period. Watched live across a week rollover: at two minutes past expiry, a KIWI "Uke 30" catalog served 12 of the 79 offers it had served minutes earlier — and all 12 survivors were two-week offers running to 2 August. Everything scoped to the week itself was already unreachable. /offers serves only what is valid at request time.

And old catalogs are deleted outright. Verified against real expired IDs recovered from archived mattilbud.no HTML: a KIWI catalog from May 2024 and three Danish ones from 2020 all return CATALOG_NOT_FOUND, byte-identical to the response for an ID that never existed. (A just-expired catalog's record lingers a while — it still answered 200, still claiming offer_count: 85 — but the offers behind it were already gone, which is the only part that matters.)

There is no archive endpoint and no backfill. offset past the end returns [], and date or ordering parameters are accepted but silently ignored.

Two traps worth knowing:

  • offers?catalog_id=<expired> returns HTTP 200 with an empty array, not an error. A backfill attempt looks like it worked and stores nothing.

  • offer_count overstates what /offers will ever return — MENY's "Uke 31" claims 151 and serves 70, before and after the flyer activates. It is not a timing artefact and not a pagination bug; the reachable set is simply smaller. Reported as a shortfall.

What the listing gives you is lookahead, not lookback: next week's flyer appears before it activates, but a week drops off the moment it ends. Once run_till passes there is nothing to go back for. Miss a week and it is gone permanently. So:

kundeavis-mcp schedule --install   # twice daily, macOS launchd

launchd rather than cron specifically because a job missed while the Mac was asleep runs on the next wake; cron just skips it, and a skipped Saturday is an unrecoverable week. Runs pass --if-stale, which decides locally — most fire and exit having made zero HTTP requests.

What to back up

Three tiers, and only the middle one matters:

~/.kundeavis-mcp/offers.db

derived cache

don't back up — rebuildable

~/.kundeavis-mcp/history/*.ndjson.gz

raw API payloads, immutable

this is the asset

a private git repo

off-machine copy

recommended

The archive stores the raw payloads, not normalized rows, because the unit-price parser is still improving — archiving today's output would freeze today's bugs into a history that can never be re-derived. kundeavis-mcp import replays snapshots through the current normalizer, so every future parser fix retroactively improves every week you have ever captured.

Snapshots are ~380 KB gzipped per sync, so a year is roughly 20–40 MB. They are named to the second and never overwritten: the API does not serve a stable set (two syncs 35 minutes apart returned 1,786 and 1,782 offers, four present only in the first), so collapsing them would quietly drop offers. Replay unions snapshots, and conflicts resolve to the best-evidenced copy rather than the last one written.

Back it up somewhere private. Please don't commit offer data to a public repo — it isn't ours to redistribute, which is the whole basis on which this project queries the API client-side.

kundeavis-mcp history    # what you have captured
kundeavis-mcp import     # rebuild the database from it, no network

Unit prices: read the confidence field

Comparing groceries means comparing unit prices, and flyer data makes that easy to get wrong. KIWI's "HAMBURGER 79,90 / 4 x 100 g" is 199.75 kr/kg, not 799 — the pack total is pieces × size × SI factor, and ignoring pieces is wrong by 4×.

Descriptions often repeat the retailer's own printed unit price, so every computed value is cross-checked against it. Each offer carries the outcome:

unit_price_confidence

Meaning

verified

Our value matches the kr/kg or kr/l figure printed in the flyer.

computed

Derived from the pack data, but nothing comparable was printed — or the source is internally ambiguous (see notes).

unavailable

We declined to serve a number. unit_price is null.

On a live week: 98.3% of checkable offers verified (865 / 880), and 15 of 1,782 offers (0.8%) had their unit price withheld. Those are genuine source inconsistencies — Coop's member-vs-non-member pricing, a flyer quoting drained weight while the API reports gross, Tjek reporting pieces: 1 for a multipack. A withheld value is deliberate: a confidently wrong kr/kg is the worst thing a price-comparison tool can do.

Two more rules the tools follow:

  • Never mix bases. unit_price_basis is kg, l or pcs. A per-piece price is not comparable to kr/kg — "pr. pk" and "/STK" both mean pieces but one is a pack and the other an item — so pcs offers are excluded from cheapest-first rankings.

  • Two different discount numbers, never conflated. discount_pct is the retailer's own claim, from the flyer's pre-price, and is null for ~83% of offers. vs_baseline_pct is our inference from stored history. The second is never presented as the first.

"This week" means valid right now, not the current ISO week. Flyers change over on Saturday or Sunday, so those differ by a day or two every week.

Ingredient coverage

The upstream API returns no categories at all (category_ids is empty on every Norwegian grocery offer), and headings are retail shorthand — GULOST&CHEDDAR, NAKKEKOTELETT, REKER I LØSVEKT. So data/ingredients.yaml maps them to canonical ingredients, and it is the most useful thing to contribute to.

Currently ~57% of offers carry an ingredient tag. The untagged remainder is mostly brand-specific compound names and non-food. To help:

kundeavis-mcp headings          # ranked worklist of what isn't covered
# add aliases to data/ingredients.yaml
kundeavis-mcp retag             # re-tag locally, no network, prints the new coverage
node test/taxonomy-lint.mjs     # catches aliases that can never fire

Matching is whole-token, not prefix or substring. An alias matches when it equals the heading or appears in it delimited by spaces, so SVINEKOTELETT matches SVINEKOTELETT MED BEN but not SVINEKOTELETTER — Norwegian inflection is not handled for you, and you should enumerate the forms you expect. Æ, Ø and Å survive normalization; other diacritics are folded, so write ENTRECOTE. Add the ingredient word, not the brand.

Run the lint before opening a PR. A duplicate alias fails silently — the first ingredient to claim it wins and the second never fires — which is how RIBBE ended up meaning spareribs rather than juleribbe, and PINNEKJØTT sat on lamb despite selling at a multiple of fresh lamb's price per kg.

The taxonomy also carries seasonal vocabulary written ahead of the season (jul, påske, fårikål, grillsesong). Since expired flyers can't be re-fetched, waiting to observe RIBBE or PINNEKJØTT would mean poor coverage every December. Those entries are inert in July and cost nothing to carry; correct them against real headings when the season arrives.

An optional LLM fallback resolves unknown headings and appends them to data/learned.yaml, with low-confidence answers diverted to needs_review.yaml. It is off by default — a routine sync should not spend money unless you asked:

KUNDEAVIS_LLM=1 ANTHROPIC_API_KEY=... kundeavis-mcp sync

Data source

Offer data comes from the read endpoints of Tjek (formerly ShopGun / eTilbudsavis), the platform Norwegian retailers publish their flyers through and which sites like mattilbud.no are built on. Retailers submit hotspot metadata, so offers arrive already structured — there is no OCR here.

Please read this before depending on it. That API is undocumented, not published open data. It happens to serve these reads unauthenticated, but it advertises X-Api-Key/X-Token in its CORS headers, which suggests a permissive default rather than a public contract. It can change or close at any time.

Accordingly:

  • This project is not affiliated with, endorsed by, or connected to Tjek, mattilbud.no, or any retailer. All trademarks belong to their owners.

  • No offer data is redistributed. Every install queries upstream itself and caches locally. What ships in this repo is our own code and taxonomy.

  • The client is deliberately gentle: concurrency capped at 4, exponential backoff, an identifying User-Agent, and aggressive caching. A full national sync is ~45 requests. Please don't raise those limits.

MIT licensed — covering this project's code and the ingredient taxonomy, not upstream data.

Development

bun install
bun run build                      # tsc -> dist/
bun run test                       # taxonomy lint + archive round trip + unit prices
bun run validate:unitprice         # correctness gate: computed vs printed, live week
node test/taxonomy-lint.mjs        # alias collisions, categories, shape (no network)
KUNDEAVIS_HISTORY=./dev-history node test/archive-roundtrip.mjs   # replay + union
node test/mcp-smoke.mjs            # end-to-end MCP over stdio
KUNDEAVIS_DB=./dev.db KUNDEAVIS_HISTORY=./dev-history node dist/cli.js sync
KUNDEAVIS_DB=./dev.db node dist/cli.js stats

test:archive needs a snapshot to work from, so run a sync first.

bun is fine for dev, but the server itself runs on Node — Bun does not implement node:sqlite.

Env var

Default

KUNDEAVIS_DB

~/.kundeavis-mcp/offers.db

KUNDEAVIS_HISTORY

~/.kundeavis-mcp/history

KUNDEAVIS_DATA

the packaged data/ directory

KUNDEAVIS_LLM

unset (LLM fallback off)

ANTHROPIC_API_KEY

— required only with KUNDEAVIS_LLM=1

Known limitations

  • Offers are chain-national. The API exposes no per-store pricing (store_id is empty on every offer), so a regional price difference is invisible. find_stores is a separate lookup.

  • Regional catalog duplicates are collapsed, not merged. Chains publish one catalog per region — Holdbart ships 26 with identical labels — and we keep the richest variant per (chain, label, date range). Fetching all of them would triple our request count for near-identical data.

  • Catalog metadata overstates its own contents. A catalog claiming 80 offers may only expose 73 at any offset. Reported as a shortfall, not treated as an error.

  • Price history starts when you do, and cannot be backfilled. See above. Baselines only cover weeks this installation captured while they were live.

  • The baseline is built from other weeks' offers, which are also discounts. vs_baseline_pct therefore compares a sale price against other sale prices and will understate how good a deal is. Accumulating more weeks does not remove that bias; only a shelf-price reference would. Read it as "cheap for a tilbud", not "cheap".

  • Long-running seasonal magazines carry no structured offers (Coop Mega's "Sommermat", Joker's "Den gode sommermaten"). The weekly flyers that matter for price hunting all do.

Available Tools

7 tools
cheapest_by_categoryCheapest ingredients this weekA

The primary meal-planning entry point: cheapest offers per ingredient, grouped and ranked by unit price. Only kg and l bases are included, because per-piece prices cannot be ranked against them. Omit category to compare across all food categories.

ParametersJSON Schema
NameRequiredDescriptionDefault
weekNo
basisNo
limitNo
categoryNo
per_ingredientNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden. It discloses the kg/l-only basis constraint and the reason (per-piece prices cannot be ranked), which is meaningful behavioral context. It partially covers limit behavior implicitly but doesn't discuss rate limits, auth, or grouping behavior in detail.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, all earning their place. The description front-loads the primary purpose, then adds the critical constraint and the category-omission tip in a compact, efficient structure. Zero wasted words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has 5 parameters, no annotations, and no output schema, so the description must carry a lot. It clearly states purpose, ranking basis, and category behavior. However, with no output schema and 0% schema coverage, some gap remains for week/limit/per_ingredient semantics and return-form behavior, making it slightly incomplete for a complex ranked-list tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate for the 5 undocumented parameters. It explains `category` (omitting compares across all categories) and clarifies the `basis` constraint (only kg/l). However, `week`, `limit`, and `per_ingredient` semantics are not explained beyond their schema definitions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states 'cheapest offers per ingredient, grouped and ranked by unit price' — specific verb (compare/rank), resource (ingredients), and scope. It distinguishes itself from siblings by describing the ranking-by-unit-price mechanism. Clearly the primary meal-planning entry point.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states when to use: 'primary meal-planning entry point'. Provides concrete exclusion: 'Only kg and l bases are included, because per-piece prices cannot be ranked against them.' Gives clear guidance on omitting category to compare across all categories. This differentiates it from search_offers and other siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

compare_ingredientCompare one ingredient across chainsA

Price one ingredient across every chain this week, with a baseline from earlier weeks. Two distinct fields: discount_pct is the retailer's OWN claim from the flyer's pre-price and is null for most offers; vs_baseline_pct is our inference from stored history. Do not present the second as the retailer's claim.

ParametersJSON Schema
NameRequiredDescriptionDefault
weekNo
ingredientYesCanonical ingredient key

TDQS

A4.1/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The description provides critical behavioral disclosure by distinguishing two data fields: `discount_pct` (retailer's own claim) vs `vs_baseline_pct` (the tool's inference). It explicitly warns not to present the second as the retailer's claim. With no annotations provided, the description carries the full burden, and it does this exceptionally well by warning about a subtle data provenance pitfall.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is compact and front-loaded with the core purpose ('Price one ingredient across every chain this week, with a baseline from earlier weeks'). It then adds essential field-disambiguation context. The warning clause is valuable but slightly wordy; could tighten 'is null for most offers' phrasing, but overall efficient.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With only 2 parameters, no output schema, and no annotations, the description covers the essential aspects: what the tool does, temporal scope, and the critical dual-field semantics with a correctness warning. It's complete for an agent to correctly select and use the tool for this purpose.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 50% (only `ingredient` has a description). The description meaningfully adds to parameter understanding by explaining the two computed fields (`discount_pct`, `vs_baseline_pct`) in the output, which relate to how results should be interpreted. However, it doesn't clarify `week` format/behavior beyond what the schema lacks.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses specific verbs ('Price', 'Compare') plus the resource ('ingredient') and clear scope ('across every chain this week', 'baseline from earlier weeks'). It distinguishes from siblings by emphasizing the cross-chain comparison aspect, though it doesn't explicitly name alternatives.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage context (weekly cross-chain pricing with historical baseline) but doesn't explicitly state when to use it vs price_history (which likely covers the historical angle) or cheapest_by_category (similar comparison but by category). No explicit exclusions or alternatives are named.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

find_storesFind nearby grocery storesA

Grocery stores near a coordinate, nearest first. Offers are chain-national — the source API carries no per-store pricing — so this answers 'which shop do I go to' for a chain whose offer you already picked.

ParametersJSON Schema
NameRequiredDescriptionDefault
latYes
lngYes
chainNo
limitNo
radius_mNo

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With NO annotations provided, the description carries the full burden of behavioral disclosure. It does some work: it clarifies that pricing is chain-national (not per-store), which is a meaningful limitation the agent should know. However, it doesn't disclose what the tool returns (list of store objects?), whether it's a read-only operation, rate limits, or behavior with no stores in range. For a read-oriented lookup tool with zero annotations, it adds value but could deepen the behavioral picture.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with zero waste. The first sentence states purpose and ordering; the second adds the key pricing limitation and the intended use case. Every word earns its place, and the tool name is not tautologically restated.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

This is a moderately simple tool with 5 params, no output schema, and no annotations. The description covers the core purpose and the main data limitation, but for a geospatial lookup with no output schema, the agent gains no information about the return shape (store names? distances? opening hours?) or what non-coordinate interactions look like (chain omitted behavior). Given the complexity (coordinate queries, radius, limit) and absence of output schema, additional description of the response would improve completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate for 5 undocumented parameters. The description helps marginally by implying the 'chain' param exists ('for a chain whose offer you already picked') and that coordinates are key. However, 'limit', 'radius_m', and the exact semantics of 'chain' (is it optional? what format?) are not explained in the prose, leaving the agent to infer from names and constraints alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb+resource ('Grocery stores near a coordinate'), and notably clarifies the scope ('nearest first', 'chain-national — no per-store pricing'). It distinguishes from siblings by explaining this answers 'which shop do I go to' rather than price comparison. However, it doesn't explicitly name sibling alternatives like search_offers or cheapest_by_category, so differentiation is implicit rather than explicit.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description articulates a clear use case: answering which store to visit for a chain whose offer is already selected, and explains the limitation (chain-national pricing, no per-store). This provides strong context for when to use it. However, it doesn't explicitly state when NOT to use it or name alternative tools for per-store pricing/price comparison scenarios, leaving exclusion guidance implicit.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_chainsList covered chainsB

Chains covered, with this week's offer counts and how many carry a usable unit price and an ingredient tag. Use it to see what coverage a meal plan can rely on.

ParametersJSON Schema
NameRequiredDescriptionDefault
weekNo

TDQS

B3.2/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden of behavioral disclosure. It reveals the output contains 'offer counts,' 'usable unit price' counts, and 'ingredient tag' counts, but doesn't disclose whether this is a read-only operation, how stale the data might be, what 'this week' means relative to the optional week parameter, or return format/pagination. For a seemingly non-destructive read tool with no annotation coverage, the description gives some content context but omits behavioral details.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, each earning its place: the first specifies content, the second gives usage purpose. No filler, no redundancy, appropriately front-loaded with the core purpose. This is a model of concise structuring.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given there's no output schema, no annotations, and one ambiguous optional parameter, the description should carry more burden. It touches on what the output contains (counts) but doesn't explain the week parameter semantics, how 'usable unit price' and 'ingredient tag' are defined, or how results might be ordered/filtered. For a coverage-overview tool alongside 6 siblings, more detail on how results relate to the broader toolset would improve completeness.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

There is one parameter (week) with 0% schema description coverage, and the description mentions 'this week's offer counts' which implies the week parameter affects the data window. However, the description doesn't clarify the expected format of the week value (ISO date, week number, date range) or what happens when it's omitted versus provided. The description adds modest meaning beyond the bare schema but leaves the week parameter's format and optionality semantics ambiguous.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a clear verb-resource pair ('List covered chains') and specifies the content scope: this week's offer counts, usable unit price counts, and ingredient tag counts. It distinguishes itself from siblings by the 'coverage' focus (which chains a meal plan can rely on), contrasting with siblings like search_offers or price_history. However, it doesn't explicitly name sibling alternatives or define what 'covered' precisely means against the set of available chains.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides a clear usage context ('Use it to see what coverage a meal plan can rely on'), implying it's a planning-level overview tool rather than a specific search. However, it doesn't give explicit when-to-use vs. when-not-to-use guidance or name alternatives like search_offers or cheapest_by_category for more targeted queries. No exclusions or explicit comparison to siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

price_historyUnit-price history for an ingredientA

Cheapest unit price per week per chain for one ingredient, so you can judge whether a 'tilbud' is genuinely good. History only goes back as far as this installation has been syncing.

ParametersJSON Schema
NameRequiredDescriptionDefault
chainNo
ingredientYes

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description carries the full burden of behavioral disclosure, and it does reasonably well: it discloses that history is limited to how far back this installation has been syncing, which is a genuine data-availability caveat. It also clarifies the aggregation (cheapest unit price per week), which is meaningful behavioral detail. It doesn't mention pagination, return format structure, or error behavior, but the key caveat is disclosed.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero waste. The first sentence states function and granularity; the second adds a critically useful data-availability caveat. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a relatively simple two-parameter tool with no output schema and no annotations, the description is largely complete: function, granularity, purpose, and data caveat are all present. Minor gaps exist around what the output columns/rows look like and whether chain is optional (schema shows not required), but these aren't critical for a read-only price-history lookup.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, so the description must compensate for both params. It explains the ingredient param contextually ('for one ingredient') and that chain is a filter ('per chain'), giving meaning beyond the bare schema. It doesn't explicitly spell out parameter formats, but the natural-language usage conveys the semantics adequately.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb+resource construct: it explains the tool returns the cheapest unit price per week per chain for one ingredient. It clearly distinguishes what it does from a generic 'price lookup' by specifying granularity (weekly, per chain, unit price), and its purpose (judging whether a tilbud is genuinely good). This clearly distinguishes it from siblings like search_offers and compare_ingredient.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives clear context on when to use: to judge whether a discounted price is genuinely good. It implies historical/reference comparison use. However, it doesn't explicitly mention when NOT to use it or name alternative tools (e.g., use compare_ingredient for direct comparisons), so it stops short of full 5.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_offersSearch grocery offersA

Search this week's Norwegian grocery offers. Free-text matches the product heading; filters narrow by ingredient, category, chain, unit-price basis and discount. Results are sorted cheapest-first by unit price. ALWAYS read unit_price together with unit_price_basis — a kr/kg and a kr/stk figure are not comparable — and treat unit_price_confidence='computed' as not cross-checked against the printed flyer.

ParametersJSON Schema
NameRequiredDescriptionDefault
weekNoISO week, e.g. '2026-W31'. Defaults to current.
basisNo
chainNoChain slug or name, e.g. 'kiwi' or 'REMA 1000'
limitNo
queryNoFree text, e.g. 'kyllingfilet' or 'laks'
categoryNo
ingredientNoCanonical ingredient key, e.g. 'chicken_breast'
priced_onlyNoDefault true. Set false to include offers whose unit price is withheld.
max_unit_priceNo
min_discount_pctNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden and does well: it discloses that unit prices are not always comparable across basis units (kr/kg vs kr/stk), that 'computed' confidence means unverified against the flyer, that results sort cheapest-first, and that priced_only defaults to true (hiding unpriced offers). This is meaningful behavioral context beyond the bare schema.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, zero filler. Every sentence adds distinct value: what it searches, how it sorts, and the two critical interpretation caveats. Front-loaded with the purpose, caveats appended as the essential reading guidance.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 10-parameter no-output-schema search tool, the description covers the key behavioral hazards (unit comparability, confidence status, sorting, pricing filter default). It doesn't enumerate each filter's semantics, but the critical correctness risks are addressed. With no output schema, the description adequately orients the agent on what to expect and how to interpret results.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 50%, with several parameters (basis, category, limit, priced_only, max_unit_price, min_discount_pct) lacking descriptions in the schema. The description partially compensates by explaining the basis-unit comparability caveat, but does not individually clarify basis, category enum semantics, or limit behavior. The most behaviorally critical caveat (unit_price_basis pairing) is covered in prose, adding real value.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Search') with a clear resource ('this week's Norwegian grocery offers'), and explains matching behavior. It distinguishes itself from siblings by explicitly noting free-text matching on product heading plus filtering, and its cheapest-first sorting.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explains the matching/filtering behavior and sorting. It gives clear guidance on interpreting unit_price with unit_price_basis and treating computed confidence as unchecked. It doesn't explicitly state when NOT to use it vs alternatives like cheapest_by_category or compare_ingredient, but the search-orientation is clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

syncRefresh offer dataA

Re-fetch the current flyers from the upstream API. Runs automatically when the current week is missing, so you rarely need to call it — use it to pick up a mid-week update.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the burden. It explains the refresh behavior (re-fetch from upstream API) and the auto-trigger condition (missing current week). It also implies a network/upstream dependency without spelling out failure modes, but for a zero-param sync tool this is reasonable context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two sentences, zero waste. Front-loaded with the primary action, then the pragmatic guidance about rarity of use and the mid-week use case.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a zero-param, no-output-schema tool, the description is complete: it states what it does, when it's needed, and when it isn't. It could mention rate-limit or cost implications but these are minor gaps for a simple refresh action.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has 0 parameters, so schema coverage is trivially 100%. The description explains there's nothing to configure, which meaningfully clarifies that the call is side-effect-only and requires no input.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states specific verb+resource ('Re-fetch current flyers from the upstream API'), distinguishing it from the sibling tools which are all query/search operations. It's unambiguous what this tool does.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Excellent guidance: explicitly states it runs automatically when the current week is missing, 'so you rarely need to call it', and tells when to use it ('pick up a mid-week update'). This contrasts clearly with the sibling search/query tools.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 7 tool updatesv0.1.0
    • First observedcheapest_by_category
    • First observedcompare_ingredient
    • First observedfind_stores
    • First observedlist_chains
    • First observedprice_history
    • First observedsearch_offers
    • First observedsync

TDQS

A4/5.0

Scored across 7 tools

Disambiguation4/5

Most tools have clearly distinct purposes: search/compare/history/cheapest all target prices but from different angles (single scan vs cross-chain vs cross-week vs ranked summary). cheapest_by_category and search_offers overlap somewhat (both surface cheap offers), though category vs free-text search differentiates them reasonably. price_history vs compare_ingredient also share some ground but split time-series from current-week cross-chain.

Naming Consistency4/5

Naming follows a consistent verb_noun pattern (search_offers, compare_ingredient, find_stores, list_chains, price_history). The two outliers are cheapest_by_category (adjective_noun preposition) and sync (single verb), which break the pattern but are still readable and not confusing.

Tool Count5/5

Seven tools is a well-scoped surface for a grocery-offer MCP server. Each covers a meaningful concern: discovery, ranking, cross-chain comparison, history, store lookup, chain coverage, and data refresh. No tool feels superfluous.

Completeness5/5

The domain is grocery-offer lookup and meal planning, and the surface covers it thoroughly: search, cheapest-ranking, cross-chain comparison, historical baseline, store proximity, chain coverage, and upstream sync. The only minor gap is no explicit per-season/weekly API refresh trigger beyond sync, but sync covers data freshness.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    A
    maintenance
    An MCP server for programmatically interacting with the Oda grocery shopping platform. It enables users to search for products, browse results, and manage their shopping cart contents through natural language.
    18
    -
  • A
    license
    A
    quality
    C
    maintenance
    A Model Context Protocol server for real-time Swiss grocery shopping that searches and compares products across 8 major Swiss retailers (Migros, Coop, Aldi, Denner, Lidl, Farmy, Volgshop, Otto’s), normalizes per-unit prices, surfaces promotions, computes optimal multi-store shopping plans, and works with any MCP-compatible client without API keys or accounts.
    7
    86 npm
    30
    AGPL 3.0
  • A
    license
    A
    quality
    A
    maintenance
    An MCP server for Nordic grocery shopping that finds deals across supermarkets in Denmark, Norway, Sweden, and Finland, plans weekly dinners around cheap ingredients, and generates shopping lists grouped by store.
    18
    27
    MIT