grocery-delivery-mcp
v0.2.0
Published
MCP server + CLI for mein-aldi.de (ALDI Süd grocery delivery): product search, price & availability, categories. Public API, cached and rate-limited. Unofficial, unaffiliated.
Maintainers
Readme
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-mcpClaude 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
- Quickstart — including filling your own cart
- Ways to use it
- Tools & resources
- Configuration
- How it stays polite and fast
- Limitations
- Development
- License
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 # search the catalog
grocery-delivery check "bio bananen" # is the top match in stock?
grocery-delivery offers --sort price_asc # current deals, cheapest first
grocery-delivery product 434365 # full detail: nutrition, allergens
grocery-delivery categories --depth 2 # browse the category treeOr 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-mcpThat puts the grocery-delivery command (and the grocery-delivery-mcp server binary) on your PATH.
2. Check it works
grocery-delivery doctor
# Checking https://api.mein-aldi.de (service point ADG045_1) ...
# ✓ categories: 32 roots
# ✓ search "milch": 25 hits
# ✓ The mein-aldi.de API is live and serving data — you're good to go.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 # cheapest current deals
grocery-delivery product 434365 # nutrition, ingredients, …
grocery-delivery categories --depth 24. 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 # the browser used for the ALDI loginPoint 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 # the MCP server (stdio) — same as: grocery-delivery serveOne-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
# → HTTP server listening on http://127.0.0.1:3000/mcpPoint 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 # filter + sort
grocery-delivery search "wasser" --limit 10 --offset 10 # page 2
grocery-delivery stock 229396 000000000000228798 # availability + price by SKU
grocery-delivery check "bio bananen" # is the top match available?
grocery-delivery product 434365 # full detail: nutrition, allergens
grocery-delivery offers --sort price_asc --limit 10 # current deals, cheapest first
grocery-delivery categories --depth 2
grocery-delivery browse 19000000 --limit 20 --brand MILSANI # filter a category
grocery-delivery status # cache / breaker / rate-limit
grocery-delivery doctor # health check (API reachable?)
grocery-delivery <cmd> --json # machine-readable outputsearch 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:
| Tool | What it does |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 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:
| Env | Default | Meaning |
| ----------------------------------- | -------------------- | ----------------------------------------------- |
| 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, honoringRetry-After; fails fast on other4xx. - 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 returnssupported: falsewith a reason. Seescripts/slots-probe.mjsand 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 noALDI_SERVICE_POINTsetting. 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 # compiles TypeScript → dist/Then the usual loop:
pnpm dev # tsx watch src/stdio.ts
pnpm dev:http # tsx watch src/http.ts
pnpm typecheck
pnpm test # unit tests (no network)
pnpm test:integration # live API, gated by ALDI_INTEGRATION=1, serial + rate-limited
pnpm lint
pnpm format
# Phase 2: discover the delivery-slots endpoint (read-only probe)
node scripts/slots-probe.mjsIntegration 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.
# from the /ship flow, or by hand:
echo "0.2.0" > VERSION # (also bump package.json / CHANGELOG)
git commit -am "release: v0.2.0" && git push # merge to main → publishThe 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.
