Skip to main content
Glama
henryjcee

sainsburys-mcp

by henryjcee
README.md
# sainsburys-cli

Build a [Sainsbury's Groceries](https://www.sainsburys.co.uk/gol-ui/) basket
from a plain-text shopping list, from the command line.

It talks directly to Sainsbury's internal Groceries API and authenticates by
**borrowing the session from a browser you're already logged in to** — so there's
no password to store and no browser being driven around. It **never places an
order**; it only fills the basket, ready for you to review and check out yourself.

> ⚠️ This uses an undocumented, unofficial API. It can break if Sainsbury's
> changes their site, and it's for personal use with your own account only.

## Install

```fish
cd ~/code/sainsburys_cli
python3 -m venv .venv
.venv/bin/pip install -e .
```

This gives you a `sainsburys` command (at `.venv/bin/sainsburys`).

## First-time setup: log in

1. Open your normal browser and log in at <https://www.sainsburys.co.uk>.
2. Leave that session logged in.
3. Check it works:

   ```fish
   .venv/bin/sainsburys whoami
   ```

   You should see your name/email. The CLI reads the Sainsbury's cookies from
   your browser automatically. If you have several browsers, point it at one:

   ```fish
   .venv/bin/sainsburys --browser firefox whoami
   ```

When your session later expires, just reload sainsburys.co.uk in the browser and
run any command with `--refresh`.

## Build a basket from a list

Write a shopping list, one item per line:

```text
# weekly shop
2 x semi skimmed milk
free range eggs x2
wholemeal bread
600g chicken breast
bananas
```

Quantities can go at the start (`2 x milk`, `3 bananas`) or end (`milk x2`).
Blank lines and `#` comments are ignored.

**Preview first** (nothing is added):

```fish
.venv/bin/sainsburys add-list shopping.txt
```

You'll get a per-line report: ✓ confident matches, `?` low-confidence or
out-of-stock (with alternatives shown), and ✗ no-match. When it looks right,
**commit**:

```fish
.venv/bin/sainsburys add-list shopping.txt --commit
```

Only confident, in-stock matches are added; anything flagged for review is left
for you to add manually (e.g. with `search` + `add`). Then open the basket on
the website to review and check out.

## Other commands

```fish
.venv/bin/sainsburys search "oat milk"        # find products + their ids
.venv/bin/sainsburys add 7977681 --qty 2      # add one product by id
.venv/bin/sainsburys basket                   # show the current basket (with item ids)
.venv/bin/sainsburys remove "oat milk"        # remove an item by name (or [item ...] id)
```

`remove` matches on the product name; if more than one basket item matches
equally (e.g. `remove "milk"` with two milks) it lists them and does nothing, so
you can re-run with the specific `[item ...]` id or `--all`.

Add `--json` to any command for machine-readable output (handy when Claude is
driving it).

## Using it with Claude Desktop

Claude Desktop drives this through a bundled **MCP server** (`sainsburys-mcp`) so
your partner can just chat — "add milk, eggs and bread to my Sainsbury's basket"
— with no terminal. See **[docs/CLAUDE_DESKTOP.md](docs/CLAUDE_DESKTOP.md)** for
the one-time setup. (In Claude Code, use the CLI directly as below.)

## Using it with Claude (CLI)

You don't have to pre-format anything. Paste a messy list into Claude — prose,
a copied spreadsheet, mixed units ("6 pints semi-skimmed, a dozen eggs, loaf of
wholemeal") — and ask it to build the basket. Claude interprets the list itself,
then uses `search --json` to find each product and `add` to put it in the
basket, **stopping to ask you** whenever an item is ambiguous (which size? which
brand? the usual one's out of stock — swap it?). It confirms the basket at the
end; you review and check out on the website. See `CLAUDE.md` for the exact
protocol Claude follows.

## If reading browser cookies doesn't work

Some locked-down work machines (or Chrome on recent Windows) block reading the
cookie store. Fallback: copy the request cookies from your browser's dev tools
(Network tab → any `groceries-api` request → Cookie header, and the
`wcauthtoken` request header) and set them as env vars:

```fish
set -x SAINSBURYS_COOKIE "WC_AUTHENTICATION_123=...; JSESSIONID=..."
set -x SAINSBURYS_WCAUTHTOKEN "..."
```

## Running the tests

```fish
.venv/bin/pip install pytest
.venv/bin/python -m pytest
```

The tests cover the list-parsing and product-matching logic (which run offline);
the API calls themselves need a live logged-in session to exercise.

Maintenance

ActivitySlowing
ResponsivenessNo issues