trundler
Summary: This MCP server exposes grocery-shopping tools for authentication, product discovery, store selection, cart management, and past-order review.
Sign in to a shopping provider and check whether the stored session is still authenticated (
login,check_login).Search products by keyword, fetch current specials, and browse departments/aisles with filters like in-stock-only and specials-only.
List stores and set the active store for providers with per-store pricing, such as New World.
Read the cart and add, update, or remove cart items by SKU, quantity, and
Each/Kgunit.List past orders, list frequently purchased past-order items, and fetch all items from a specific past order; these require login.
Most tools accept a
providerid, defaulting tocountdown; provider-specific capabilities and login requirements apply.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@trundler search for milk at countdown"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
A desktop app that lets you have a dialogue with an agent while it shops for you.
The agent's brain runs either locally (Ollama), in the cloud (Claude, API key), or
through the Claude Code CLI on your own machine (billed to your Claude subscription
rather than API tokens); whichever you pick,
all the actual grocery I/O happens on your own machine and residential connection
via trundler-mcp
(source), so there's no
bot-detection or hosting problem to solve. It's a normal npm dependency of this app —
you don't install it separately.
Cloud brain, local hands. Bot detection only cares about the requests hitting the grocery sites — those are made by trundler-mcp on your machine. The model that decides what to fetch can run wherever you like.
Architecture
Electron main (Node) Renderer (React)
├── TrundlerMcp ──stdio──► trundler-mcp ──► grocery sites (residential IP)
├── Agent loop (shared) ├── chat + streamed tokens
│ ├── OllamaBackend (local) ├── product grid (from tool results)
│ └── AnthropicBackend (API key) ├── live cart panel
├── Claude Code CLI ◄─http─ MCP proxy │
└── IPC + approval gate ◄──── approve/deny ──┤ approval modal (cart mutations)One shared agent loop (
src/main/agent/loop.ts) drives the conversation. It's parameterised by aBackend, so switching Ollama ↔ Claude is a dropdown, not a rewrite.The Claude Code brain is the exception: the CLI runs its own agent loop, so it bypasses
loop.ts. It gets the grocery tools back through a loopback MCP proxy (claudeCodeBridge.ts) that forwards every call into this process — so tool execution, the approval gate and the session log stay exactly where they are, and no secondtrundler-mcpis spawned.Tools come from MCP, so every brain inherits trundler's own product-listing instructions (letter labels, price-per-unit, cheapest-first).
Cart mutations require approval —
cart_add/cart_update/cart_removepop an approval modal before they run.Structured tool results become UI: product results render as a card grid; cart tools refresh the live cart panel.
Quantity and pricing unit are explicit: weight-sold items (loose produce, deli, butchery) are flagged
⚖ priced per kg, and every card, favourite and cart line carries a typeable quantity plus a kg / ea switch, so "1" is never ambiguous.Auth is first-class: a status chip shows whether you're signed in to Countdown, with Log in / Log out buttons in the top bar (no need to discover it via an error).
Related MCP server: Willys MCP Server
Prerequisites
Node ≥ 20 (dev has 22).
A brain — any one of these is enough:
Ollama running locally with a tool-capable model:
ollama pull llama3.1:8b # fast, good enough for dev ollama pull qwen3:14b # stronger tool use, slowerAn Anthropic API key, for the cloud Claude backend.
The Claude Code CLI, to run Claude on your existing subscription instead of API tokens:
npm i -g @anthropic-ai/claude-code claude # run once to sign in, then quitNo API key is involved — the CLI carries its own credentials. Settings → Models shows whether the app can find it.
That's it — the grocery data layer (@auckland-ai-collective/trundler-mcp) is a
dependency and installs automatically. No second repo to clone or build.
What the Claude Code brain is allowed to do
It is deliberately boxed in, since Claude Code can otherwise read files and run commands:
--tools ""removes every built-in tool, so there is no file, shell or web access.--strict-mcp-configmeans it sees only the grocery tools — none of your own MCP servers are loaded. (This matters: without it, a developer's connectors get pulled into every prompt — measured at 211k tokens of cache on a one-word reply.)--setting-sources ""and--disable-slash-commandskeep your Claude Code setup, skills andCLAUDE.mdout of a grocery session; it runs in an empty scratch directory.Cart changes still raise the app's own approval modal — the CLI never reaches the grocery layer directly, only the proxy.
Setup
npm install # also pulls trundler-mcp (no browser download; that's lazy — see below)Verify the plumbing (no GUI)
npm run smoke -- "find jasmine rice on special" # stdio round-trip via the MCP + Ollama
npm run libcheck # in-process (buildServer) round-tripBoth resolve the MCP from the installed package, run a real tool call, and print the result — use them to confirm your model works before launching the app.
Run the app
npm run dev # launches the Electron app with HMRBuild a distributable:
npm run build # compile main/preload/renderer into out/
npm run dist # + package a Windows installer (electron-builder)Signing in (Countdown)
Countdown/Woolworths needs a login for cart and order history. Two ways:
In the app: click Log in in the top bar — a real browser window opens; sign in there and the app captures the session. The first sign-in downloads a browser (~150 MB, one-time) — the app shows a banner while that happens.
From the CLI:
npx trundler login(the package ships atrundlerbin).
Log out (in the app) clears the stored session and forces re-authentication. New World and Pak'nSave need no login (anonymous, read-only).
Configuration
Brain (Ollama / Claude / Claude Code) and provider are quick switches in the top bar. The
⚙︎ drawer holds the rest — model, Ollama host, Claude API key, Claude Code CLI command,
MCP server path, and the
cart-approval and debug-logging toggles — and edits there apply only when you press
Save (Cancel discards). Defaults can also come from env vars — see
.env.example. Settings persist to config.json in the app's userData
directory.
MCP path: by default the app resolves the server from the installed
@auckland-ai-collective/trundler-mcp package automatically — no path to set. Override
with TRUNDLER_MCP_PATH (or the ⚙︎ field) only if you're pointing at a local checkout.
Providers
Provider | id | Cart | Notes |
Countdown |
| ✅ | Needs login — use the Log in button. |
New World |
| ❌ | Read-only; pick a store first (agent does this). |
Pak'nSave |
| ❌ | Read-only; per-store pricing. |
Debug logging / telemetry
Trundler writes a structured JSONL session log capturing the whole interaction:
the user's prompt, the model/backend in use, every MCP tool call and its result,
cart state, approvals, and errors. One file per app run in the app's
userData/logs/ directory (use the open logs button in the debug footer, or
shell reveal).
Off by default. Turn it on with the Debug logging toggle in ⚙︎ Settings; the footer line (with an open logs button) appears only while it's on.
--debugorTRUNDLER_DEBUG=1force it on regardless of the setting, so a user can capture and send you logs without touching Settings:Trundler.exe --debug
Each line is one JSON event, e.g.:
{"t":"2026-07-05T…","type":"user-message","text":"add A to cart","backend":"ollama","model":"llama3.1:8b","provider":"countdown"}
{"t":"2026-07-05T…","type":"mcp-call","name":"cart_add","args":{"sku":"601342","quantity":1}}
{"t":"2026-07-05T…","type":"mcp-result","name":"cart_add","ok":true,"provider":"countdown","data":{…}}
{"t":"2026-07-05T…","type":"cart-state","provider":"countdown","itemCount":1,"detailedItems":0,"total":null}The debug log is what let us pin down real bugs — e.g. detailedItems: 0 with a
non-zero itemCount was the fingerprint of a cart-detail mapping bug in trundler-mcp
(#1, now fixed).
The cart panel still tolerates sparse data defensively in case a provider returns it.
Project layout
src/
shared/types.ts shared types (domain types re-exported from the MCP package)
main/
index.ts window, IPC, orchestration, approval + auth gate
config.ts config + MCP path resolution (from the installed package)
mcpClient.ts trundler-mcp stdio client (spawn + reconnect)
logger.ts JSONL session logger
agent/
loop.ts shared tool loop
ollamaBackend.ts local model (streaming + tools)
anthropicBackend.ts Claude (streaming SSE + tools)
claudeCodeAgent.ts Claude Code CLI (spawn, containment, NDJSON stream)
claudeCodeBridge.ts loopback MCP proxy so the CLI reuses our tools/gate
system.ts system prompt (+ MCP instructions)
preload/index.ts contextBridge API
renderer/ React chat UI
scripts/smoke.mjs headless stdio MCP + Ollama check
scripts/lib-check.mjs headless in-process (buildServer) checkNotes / next steps
Only the Ollama path is exercised by the smoke test; the Claude path shares the same loop and is wired but needs an API key to try.
The Claude Code path has no smoke test — it needs the signed-in CLI, so it can't run in CI. It was validated by hand end-to-end: tool call → approval → cart write, the approval-denied path,
--resumecontinuity across messages, and cancel taking the CLI and its MCP proxy down together (no orphans).The app spawns the MCP as a subprocess with Electron's bundled Node (
ELECTRON_RUN_AS_NODE). The package also exposes a library entry (buildServer), so a packaged build can instead mount the MCP in-process (in-memory transport) to avoid subprocess/path issues — validated bynpm run libcheck.
License
MIT © 2026 Michael Wells <mike@aaic.nz> — see LICENSE.
An open-source project. Contributions welcome.
Available Tools
14 toolsbrowse_productsB
Browse products by department. Departments: fruit-veg, meat-poultry, fish-seafood, fridge-deli, bakery, frozen, pantry, beer-wine, drinks, health-body, household, baby-child, pet.
Inputs: department (required), aisle, pageSize, provider, +2 more.
cart_addC
Add an item to the cart.
Inputs: sku (required), unit, provider, quantity.
cart_getB
Get current cart contents and totals.
Inputs: provider.
cart_removeB
Remove an item from the cart.
Inputs: sku (required), unit, provider.
cart_updateC
Update the quantity of an item in the cart.
Inputs: sku (required), quantity (required), unit, provider.
check_loginA
Check whether the stored session for a provider is still authenticated.
Inputs: provider.
get_order_itemsC
Get all items from a specific past order. Requires login.
Inputs: orderId (required), provider.
get_specialsA
Get current specials/deals with automatic pagination.
Inputs: pageSize, provider, maxProducts.
list_past_order_itemsB
List products from past orders, sorted by purchase frequency. Requires login.
Inputs: page, sort, maxPages, provider.
list_past_ordersC
List past orders with dates, totals, and status. Requires login.
Inputs: filter, provider.
list_storesA
List a provider's stores (for providers with per-store pricing, e.g. New World). Optionally filter by name. Use set_store to choose one before searching.
Inputs: query, provider.
loginA
Open a browser window to sign in to a shopping provider. Complete the login in the window; the session is captured and stored locally. Run this once, or again when the session expires.
Inputs: provider.
search_productsC
Search for products by keyword.
Inputs: query (required), provider, inStockOnly, maxProducts, +1 more.
set_storeA
Select the active store for a provider with per-store pricing. Persisted for future calls. Get store ids from list_stores.
Inputs: storeId (required), provider.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
Loading tool schema changelog…
TDQS
Scored across 14 tools
Each tool has a clearly distinct purpose: browsing vs searching, separate cart operations, distinct login and order history tools. No overlap.
All tools follow a consistent snake_case verb_noun pattern (e.g., browse_products, cart_add, list_past_orders).
14 tools is well-scoped for a shopping service, covering browsing, cart management, login, and order history without being excessive.
Notably missing a place_order or checkout tool, which is critical for the shopping domain. Agents can build a cart but cannot complete a purchase, leaving a dead end.
Maintenance
Related MCP Connectors
Unlock the power of food transparency with our Open Food Facts MCP server. Easily look up any food
USDA FoodData Central nutrient lookup, FDA recall watch, EU FMCG labelling via remote MCP
An MCP server that provides read access to your cloud storage providers, bank accounts and more.
One MCP server for all web data. Scrape, search, crawl, and AI research in one install.
Related MCP Servers
- FlicenseAqualityAmaintenanceA 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.7524 npm42-
- AlicenseNot gradedqualityDmaintenanceAn MCP server for Sweden's largest grocery chain Willys. Enables controlling your shopping cart, browsing orders, searching products, and getting AI-powered recommendations from any MCP client.5MIT
- AlicenseNot gradedqualityCmaintenanceControl your grocery cart with AI! This MCP server enables LLMs (like Claude) to search past orders, find products, and manage your shopping cart across all Loblaws-owned grocery banners.11AGPL 3.0
- AlicenseNot gradedqualityDmaintenanceMCP server for grocery-related web automation using Playwright, enabling AI assistants to interact with grocery websites.ISC