Skip to main content
Glama
thijserven

dutch-supermarkets-mcp

by thijserven
README.md
# Dutch Supermarkets MCP

TypeScript MCP server for Dutch supermarket and drugstore prices, promotions, shopping lists, meal planning, price alerts, routes, and budgets.

## MCP tools

| Tool                     | Purpose                                             |
| ------------------------ | --------------------------------------------------- |
| `search_products`        | Search supermarket and drugstore products           |
| `compare_prices`         | Compare the cheapest matching product per store     |
| `optimize_shopping_list` | Split a list across the cheapest stores             |
| `list_supermarkets`      | List stores and product counts                      |
| `list_drugstores`        | List drugstores and active promotion counts         |
| `list_promotions`        | Search current promotions by store/category         |
| `search_recipes`         | Search recipes by text, category, diet, or duration |
| `plan_meals`             | Generate a meal plan and priced shopping list       |
| `get_price_history`      | Show recent price ranges                            |
| `create_price_alert`     | Create a price or promotion alert                   |
| `check_price_alerts`     | Check active alerts against current data            |
| `save_shopping_list`     | Save a shopping list                                |
| `load_shopping_list`     | Re-price a saved list                               |
| `list_shopping_lists`    | List saved shopping lists                           |
| `get_purchase_advice`    | Recommend buying now or waiting                     |
| `find_stores`            | Find nearby stored locations                        |
| `plan_store_route`       | Group cheapest products into store stops            |
| `set_budget`             | Set the current weekly budget                       |
| `check_budget`           | Price a list against a budget                       |
| `get_saving_tips`        | Find promotion savings                              |

Tool names, input fields, descriptions, and responses are English. Dutch terms are retained only in synchronized source values and matching keywords required by Dutch retailer data sources.

## Docker quick start

```sh
cp .env.example .env
# Replace DB_PASSWORD and HTTP_AUTH_TOKEN with random values.
docker compose up -d --build
```

The stack contains:

- `db`: PostgreSQL 16 with `docker/init.sql`
- `mcp-server`: authenticated Streamable HTTP at `http://localhost:8000/mcp`
- `scheduler`: initial sync followed by 12-hour product/promotion/recipe syncs

The MCP and scheduler initialize the PostgreSQL schema themselves from the schema bundled in the image. A deployment consuming the published image does not need to clone this repository or mount `docker/init.sql`.

## GitHub Container Registry deployment

Every push to `main` publishes two image tags through `.github/workflows/publish-container.yml`:

```text
ghcr.io/thijserven/dutch-supermarkets-mcp:latest
ghcr.io/thijserven/dutch-supermarkets-mcp:<full-commit-sha>
```

```yaml
services:
  dutch-supermarkets-db:
    image: postgres:16-alpine
    restart: unless-stopped
    environment:
      POSTGRES_DB: dutch_supermarkets
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: ${DUTCH_SUPERMARKETS_DB_PASSWORD}
    volumes:
      - dutch-supermarkets-data:/var/lib/postgresql/data
    healthcheck:
      test: ['CMD-SHELL', 'pg_isready -U postgres -d dutch_supermarkets']
      interval: 5s
      timeout: 5s
      retries: 10

  dutch-supermarkets-mcp:
    image: ghcr.io/thijserven/dutch-supermarkets-mcp:<full-commit-sha>
    restart: unless-stopped
    depends_on:
      dutch-supermarkets-db:
        condition: service_healthy
    environment: &dutch-supermarkets-env
      DB_HOST: dutch-supermarkets-db
      DB_PORT: 5432
      DB_NAME: dutch_supermarkets
      DB_USER: postgres
      DB_PASSWORD: ${DUTCH_SUPERMARKETS_DB_PASSWORD}
      ENABLE_HTTP_SERVER: 'true'
      HTTP_HOST: '0.0.0.0'
      HTTP_PORT: 8000
      HTTP_AUTH_TOKEN: ${DUTCH_SUPERMARKETS_MCP_AUTH_TOKEN}

  dutch-supermarkets-scheduler:
    image: ghcr.io/thijserven/dutch-supermarkets-mcp:<full-commit-sha>
    restart: unless-stopped
    depends_on:
      dutch-supermarkets-mcp:
        condition: service_healthy
    environment:
      <<: *dutch-supermarkets-env
      SYNC_INTERVAL_MS: 43200000
    command: ['bin/scheduler.js']
    healthcheck:
      disable: true

volumes:
  dutch-supermarkets-data:
```

Check health:

```sh
curl http://localhost:8000/health
```

Connect an MCP client using either header:

```text
Authorization: Bearer <HTTP_AUTH_TOKEN>
x-mcp-token: <HTTP_AUTH_TOKEN>
```

Example Hermes MCP configuration:

```yaml
mcp_servers:
  dutch_supermarkets:
    url: http://mcp-server:8000/mcp
    headers:
      Authorization: Bearer ${HTTP_AUTH_TOKEN}
```

## Local stdio setup

Requirements: Node.js 20+ and a reachable PostgreSQL database initialized with `docker/init.sql`.

```sh
npm ci
npm run build
DB_HOST=127.0.0.1 DB_PORT=5432 DB_PASSWORD=... node bin/mcp-server.js
```

`ENABLE_HTTP_SERVER` defaults to `false`, so the CLI uses stdio. An MCP client configuration can invoke it directly:

```json
{
  "mcpServers": {
    "dutch-supermarkets": {
      "command": "node",
      "args": ["/absolute/path/to/dutch-supermarkets-mcp/bin/mcp-server.js"],
      "env": {
        "DB_HOST": "127.0.0.1",
        "DB_PORT": "5432",
        "DB_NAME": "dutch_supermarkets",
        "DB_USER": "postgres",
        "DB_PASSWORD": "..."
      }
    }
  }
}
```

## Configuration

| Variable                |              Default | Description                              |
| ----------------------- | -------------------: | ---------------------------------------- |
| `DB_HOST`               |          `127.0.0.1` | PostgreSQL host                          |
| `DB_PORT`               |               `5432` | PostgreSQL port                          |
| `DB_NAME`               | `dutch_supermarkets` | Database name                            |
| `DB_USER`               |           `postgres` | Database user                            |
| `DB_PASSWORD`           |                empty | Database password                        |
| `ENABLE_HTTP_SERVER`    |              `false` | Use Streamable HTTP instead of stdio     |
| `HTTP_HOST`             |            `0.0.0.0` | HTTP bind host                           |
| `HTTP_PORT`             |               `8000` | HTTP bind port                           |
| `HTTP_AUTH_TOKEN`       |                unset | Optional HTTP bearer/custom-header token |
| `HTTP_AUTH_HEADER_NAME` |        `x-mcp-token` | Custom token header                      |

For exposed or shared HTTP deployments, always set `HTTP_AUTH_TOKEN` and place the server behind TLS. The Docker Compose stack requires the token.

## Data synchronization

```sh
npm run sync -- prices
npm run sync -- promotions
npm run sync -- recipes
npm run sync -- all
```

Sources:

- Product prices: `checkjebon.nl/data/supermarkets.json`
- Promotions: public Folderz category pages
- Recipes: bundled Dutch recipes plus TheMealDB

These are third-party sources and can change or rate-limit requests. The sync commands fail visibly on source/database errors; existing database data remains available to the MCP server.

## Development

```sh
npm test
npm run lint
npm run typecheck
npm run build
```

The test suite covers tool parity, Zod validation, parameterized SQL behavior, transport authentication, MCP protocol calls, geographic calculations, and promotion parsing.

## License

MIT