grocery-delivery-mcp

Ask your AI assistant "is oat milk in stock for delivery?" — and get a real answer, with today's price.
This is an MCP server, CLI, and library for mein-aldi.de, ALDI Süd's grocery delivery platform. It lets a language model (or you, from a terminal) search the catalog, check price and availability, and browse categories.
Under the hood the catalog talks to the public api.mein-aldi.de JSON API — no login, no account, no scraping tricks. Search, price, and availability are all publicly readable. Every request is rate-limited, retried, and circuit-broken to stay polite to an undocumented API, and responses are cached (in memory and on disk) so repeat questions are instant. There's also an opt-in cart prefill for your own account — the one part that logs in.
Unofficial and unaffiliated with ALDI. It uses an undocumented API that may change or break without notice. Prices and availability are guidance, not a real-time inventory guarantee. See COMPLIANCE.md for exactly how it accesses the API responsibly — robots.txt, terms, and rate-limit posture.
Add it to your LLM
No install, no API key, no configuration. Point any MCP client at the package and start asking.
Claude Code — one command:
claude mcp add grocery-delivery -- npx -y grocery-delivery-mcp
Claude Desktop, Cursor, or any other MCP client — add this to your MCP config (e.g. claude_desktop_config.json):
{
"mcpServers": {
"grocery-delivery": {
"command": "npx",
"args": ["-y", "grocery-delivery-mcp"]
}
}
}
Restart the client and ask: "Is oat milk in stock for delivery on ALDI, and what does it cost?" That's it. 🎉
Or find it in the registry. It's published in the official MCP Registry as io.github.RayNCooper/grocery-delivery-mcp, so registry-aware clients can discover and install it by name.
Prefer the terminal or want to embed it in your own code? See the Quickstart.
Contents
What you can do with it
Connected to an MCP client like Claude Desktop, your assistant can answer questions like:
- "Is bio milk available for delivery right now, and what does it cost?"
- "Find me the five cheapest options for pasta."
- "What's on sale at ALDI right now?"
- "How much fat and sugar are in these chips, and what are the allergens?"
- "What product categories are available?"
Straight from a terminal, the same thing:
grocery-delivery search "milch" --limit 5
grocery-delivery check "bio bananen"
grocery-delivery offers --sort price_asc
grocery-delivery product 434365
grocery-delivery categories --depth 2
Or from your own code, as a library — see Ways to use it.
Quickstart
Wiring it into an LLM is the one-liner above. This is the terminal / CLI path. For the read-only catalog (steps 1–3) you only need Node ≥ 22 — nothing to clone, build, or configure; the store is baked in (see Limitations). Cart prefill (step 4) is the one exception: it's authenticated and runs from a clone.
1. Install
npm install -g grocery-delivery-mcp
That puts the grocery-delivery command (and the grocery-delivery-mcp server binary) on your PATH.
2. Check it works
grocery-delivery doctor
doctor is a connectivity check — it exits non-zero if the API returns nothing, so scripts can gate on it.
3. Use it
grocery-delivery search "kaffee" --limit 5
grocery-delivery check "bio bananen"
grocery-delivery offers --sort price_asc --limit 10
grocery-delivery product 434365
grocery-delivery categories --depth 2
4. Fill your own cart
Your assistant can also prefill your own mein-aldi.de cart — via two tools: connect_aldi_account (opens ALDI's login in a browser, once) and prefill_cart (adds items). You still check out, pay, and pick a slot yourself; it never places an order.
Run the server from a clone (the cart tools aren't in the published npm build yet), plus a browser engine for the login:
git clone https://github.com/RayNCooper/grocery-delivery-mcp.git
cd grocery-delivery-mcp && pnpm install && pnpm build
npx playwright install chromium
Point your MCP client at it — no session file, no extra config:
{
"mcpServers": {
"grocery-delivery": {
"command": "pnpm",
"args": ["-C", "/abs/path/to/grocery-delivery-mcp", "start"]
}
}
}
Then just chat: "Add 2× oat milk, bananas, and coffee to my ALDI cart." The first time, the assistant offers to connect — a browser window opens on the ALDI login, you sign in once, and it's remembered (and refreshed automatically after). Your cart fills; open mein-aldi.de to finish. Details + compliance scope: docs/CART-PREFILL.md.
See Ways to use it for the full command list, the HTTP transport, and the library API.
Ways to use it
One shared core, consumed three ways.
Local MCP server (stdio)
For Claude Desktop, Cursor, and other desktop MCP clients. Add it to your LLM shows the npx config, which needs no install and is the recommended setup. If you've installed the package globally, you can run the server directly instead:
grocery-delivery-mcp
One-click install: a prebuilt MCPB bundle (.mcpb) installs into Claude Desktop with a double-click — no configuration — built from manifest.json and attached to releases.
Remote MCP server (Streamable HTTP)
For networked or hosted use.
grocery-delivery serve --http
Point a Streamable-HTTP MCP client at http://127.0.0.1:3000/mcp. There's also a GET /health endpoint, and sessions are keyed by the Mcp-Session-Id header.
Self-host with Docker: docker compose up (see docker-compose.yml and Dockerfile) runs your own single-tenant instance on your own IP. It's deliberately not a shared multi-user service; COMPLIANCE.md explains why.
A note on security: the server binds to 127.0.0.1 by default and enforces DNS-rebinding protection (allowedHosts). It has no built-in auth — if you expose it beyond localhost (ALDI_HTTP_HOST=0.0.0.0), put it behind a reverse proxy that adds auth and TLS.
CLI & library
Install globally (npm install -g grocery-delivery-mcp) for the grocery-delivery command, or run any command ad-hoc with npx -p grocery-delivery-mcp grocery-delivery <cmd>. Every command supports --json for machine-readable output and --force-refresh to bypass the cache.
grocery-delivery search "milch" --limit 5
grocery-delivery search "joghurt" --diet Vegan --sort price_asc
grocery-delivery search "wasser" --limit 10 --offset 10
grocery-delivery stock 229396 000000000000228798
grocery-delivery check "bio bananen"
grocery-delivery product 434365
grocery-delivery offers --sort price_asc --limit 10
grocery-delivery categories --depth 2
grocery-delivery browse 19000000 --limit 20 --brand MILSANI
grocery-delivery status
grocery-delivery doctor
grocery-delivery <cmd> --json
search and browse accept --sort (relevance|price_asc|price_desc|name_asc|name_desc), server-side --offset paging, and the facet filters --label (Bio|Neu|Aktion), --diet, --brand, and --country. Text and --json output both include the available facets (brands, diet types, labels, with counts) so you can see how to refine.
Use the same core from your own code — npm install grocery-delivery-mcp, then:
import { createService, loadConfig } from "grocery-delivery-mcp";
const service = createService(loadConfig(process.env));
const result = await service.search({ query: "milch", limit: 5 });
console.log(result.products, result.cacheState);
Tools & resources
The MCP server exposes these tools:
search_products | Free-text search → products + facets; label/diet/brand/country filters, sort, paging |
get_products | Exact lookup by SKU(s) — "check stock" for known items |
get_product_details | Full detail by SKU: nutrition, ingredients, allergens, storage, origin |
check_availability | One-shot "is X available right now?" (search → top match → live check) |
list_offers | Current promotional products ("Aktion") — the "what's on sale" list |
list_categories | Browse the category tree (with numeric keys) |
browse_category | List products in a category — same filters, sort, and paging as search |
get_delivery_slots | Delivery time-slots — experimental (see Limitations) |
get_service_status | Diagnostics: cache, breaker, rate-limit — plus store metadata from /v1/stores |
connect_aldi_account | Connect your ALDI account (opt-in) — opens ALDI's login in a browser so you sign in once; remembers the session for cart prefill — see Fill your own cart |
prefill_cart | Prefill your own cart (authenticated, opt-in) — add products by name/SKU; you still check out. Prompts to connect first if needed |
And these resources: aldi://categories (the full tree), aldi://product/{sku} (price & availability), aldi://product-details/{sku} (full detail), and aldi://offers (current deals).
Every returned product carries the usual price and availability plus depositDisplay (Pfand), sizeDisplay (pack size), labels (badges like Bio / Aktion), and deepLink (the mein-aldi.de product page). Every result carries a cacheState (fresh / stale / miss / bypass / degraded) and a fetchedAt timestamp, so a model always knows how fresh a price is. Pass force_refresh: true (or --force-refresh) when you need an authoritative, up-to-the-second read.
Configuration
Everything is set through environment variables, and every default is sensible — out of the box, there's nothing you need to set. The full list with defaults lives in .env.example. The ones you're most likely to touch:
ALDI_SERVICE_TYPE | delivery | delivery (verified), pickup, instore |
ALDI_CACHE_DIR | OS cache dir | Where the on-disk cache lives |
ALDI_CACHE_DISABLED | false | Turn the cache off |
ALDI_TTL_PRODUCTS_MS | 300000 | Price/availability freshness (5 min) |
ALDI_TTL_SEARCH_MS | 600000 | Search freshness (10 min) |
ALDI_TTL_CATEGORY_MS | 86400000 | Category tree freshness (24 h) |
ALDI_RATE_MIN_INTERVAL_MS | 250 | Min spacing between requests (~4 rps) |
ALDI_HTTP_HOST / ALDI_HTTP_PORT | 127.0.0.1 / 3000 | HTTP transport bind |
ALDI_LOG_LEVEL | info | silent/error/warn/info/debug (stderr) |
How it stays polite and fast
It treats an undocumented, unowned API with care, and caches aggressively so it rarely has to ask twice:
- Rate limiting — a minimum interval plus jitter between request starts, with capped concurrency.
- Retries — exponential backoff (equal jitter) on
429/5xx/network/timeout, honoring Retry-After; fails fast on other 4xx.
- Single-flight — identical concurrent requests share one underlying fetch.
- Circuit breaker — after repeated failures it backs off, and tools degrade to best-effort/empty results (
degraded: true) instead of erroring.
- Cache — memory + disk, per-resource TTLs, and stale-while-revalidate (serve stale instantly, refresh in the background). Delivery slots are never served stale.
Limitations
- Delivery slots (
get_delivery_slots) aren't implemented yet — no public endpoint is confirmed. The tool returns supported: false with a reason. See scripts/slots-probe.mjs and Development.
- Account features are limited to cart prefill (opt-in). The catalog side is read-only and never logs in. A separate, opt-in flow prefills your own cart — you authenticate once, and it never checks out, pays, or places an order (see Fill your own cart). Checkout, payment, and order history stay out of scope. COMPLIANCE.md covers the access posture.
- One store, baked in. mein-aldi.de delivery is a single-store pilot: the API serves exactly one live merchant (
ADG045_1), identical for every address (the postal code is ignored, and no other store resolves). So there's nothing to select — the service point is hardcoded and there's no ALDI_SERVICE_POINT setting. If ALDI expands delivery to multiple stores, this becomes configurable again.
Development
Working on the project itself? Build from source — this needs pnpm:
git clone https://github.com/RayNCooper/grocery-delivery-mcp.git
cd grocery-delivery-mcp
pnpm install
pnpm build
Then the usual loop:
pnpm dev
pnpm dev:http
pnpm typecheck
pnpm test
pnpm test:integration
pnpm lint
pnpm format
node scripts/slots-probe.mjs
Integration tests hit the real API and are gated behind ALDI_INTEGRATION=1 — keep them out of hot CI loops to avoid hammering ALDI. Architecture notes live in docs/ARCHITECTURE.md, and contributions are welcome — see CONTRIBUTING.md.
Releasing
Releases are driven by the VERSION file. Bump it, and merging to main triggers .github/workflows/publish.yml, which stamps that version into package.json and server.json, publishes the package to npm, and publishes the entry to the MCP Registry via mcp-publisher. Both artifacts always match VERSION, so they can't drift.
echo "0.2.0" > VERSION
git commit -am "release: v0.2.0" && git push
The job is idempotent — if the version is already on npm it skips cleanly, so re-runs stay green. No secrets are required: npm uses trusted publishing (OIDC) and the MCP Registry uses GitHub OIDC — both ride the workflow's id-token: write permission, and npm provenance is generated automatically. The one prerequisite is that the npm trusted publisher is configured on npmjs.com to point at this repo and the publish.yml workflow.
License
GPL-3.0-or-later. See LICENSE.