Skip to main content
Glama

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 a Backend, 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 second trundler-mcp is 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_remove pop 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, slower
    • An 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 quit

      No 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-config means 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-commands keep your Claude Code setup, skills and CLAUDE.md out 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-trip

Both 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 HMR

Build 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 a trundler bin).

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

countdown

✅

Needs login — use the Log in button.

New World

newworld

❌

Read-only; pick a store first (agent does this).

Pak'nSave

paknsave

❌

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.

  • --debug or TRUNDLER_DEBUG=1 force 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) check

Notes / 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, --resume continuity 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 by npm run libcheck.

License

MIT © 2026 Michael Wells <mike@aaic.nz> — see LICENSE.

An open-source project. Contributions welcome.

Available Tools

14 tools
browse_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

    A3.5/5.0

    Scored across 14 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose: browsing vs searching, separate cart operations, distinct login and order history tools. No overlap.

    Naming Consistency5/5

    All tools follow a consistent snake_case verb_noun pattern (e.g., browse_products, cart_add, list_past_orders).

    Tool Count5/5

    14 tools is well-scoped for a shopping service, covering browsing, cart management, login, and order history without being excessive.

    Completeness2/5

    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

    ActivityMaintained
    ResponsivenessOutdated data

    Related MCP Connectors

    Related MCP Servers

    • F
      license
      A
      quality
      A
      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
      524 npm
      42
      -
    • A
      license
      Not graded
      quality
      C
      maintenance
      Control 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.
      11
      AGPL 3.0
    • A
      license
      Not graded
      quality
      D
      maintenance
      MCP server for grocery-related web automation using Playwright, enabling AI assistants to interact with grocery websites.
      ISC