Skip to main content
Glama
uskaritel

Czech DIY Retail MCP Server

by uskaritel
README.md
# Czech DIY Retail MCP Server (`czech-diy-mcp`)

Model Context Protocol (MCP) server for Czech DIY retail discovery. The server uses live current retailer pages for BAUHAUS CZ, HORNBACH CZ, OBI CZ and Baumax CZ.

### Release status

`1.0.0` is the production release. Live product search/detail is implemented for all four retailers; BAUHAUS and OBI expose live store stock, HORNBACH exposes qualitative store offers where present, and Baumax store stock is explicitly `NOT_SUPPORTED`.

---

## Česká dokumentace

`czech-diy-mcp` je read-only MCP server pro vyhledávání zboží v českých hobby marketech a stavebninách. Nabízí jednotné rozhraní pro BAUHAUS CZ, HORNBACH CZ, OBI CZ a Baumax CZ.

Server poskytuje:

- vyhledávání produktů, cen a kategorií;
- detail produktu včetně technických parametrů, EAN/GTIN a obrázků, pokud jsou dostupné;
- seznam prodejen;
- dostupnost produktu v konkrétní prodejně;
- porovnání cen mezi podporovanými prodejci.

Veřejné MCP nástroje jsou `search_products`, `get_product`, `list_stores`, `get_stock` a `compare_price`. Primární transport je Streamable HTTP na `/mcp`; kontrola dostupnosti aplikace je na `/healthz`.

### Rychlé spuštění

```bash
uv sync --extra dev
uv run python -m czech_diy_mcp --transport http --port 8000
```

Poté je MCP server dostupný na `http://localhost:8000/mcp` a health endpoint na `http://localhost:8000/healthz`.

### Docker

```bash
docker build -t czech-diy-mcp:1.0.0 .
docker run --rm -p 8000:8000 czech-diy-mcp:1.0.0
```

### Důležitá pravidla dostupnosti

Pokud prodejce neposkytne spolehlivý údaj o skladovosti, server vrací `UNKNOWN` nebo `NOT_SUPPORTED`. Chybějící množství se nikdy nepřevádí na nulu a server nevymýšlí stav skladu. Skladová data jsou pouze okamžitý údaj a mohou se mezi jednotlivými požadavky změnit.

Server neprovádí objednávky, nepřidává zboží do košíku, nepřihlašuje zákazníky, nezpracovává platby a neřeší CAPTCHA ani jiné interaktivní výzvy.

### Bezpečnost provozu

Server nepřijímá libovolné URL a používá pouze pevně definované domény prodejců. Přesto MCP endpoint nevystavujte přímo do veřejného internetu bez TLS, autentizace, rate limitu a firewallu. Podrobnosti jsou v [Operations Manual](docs/OPERATIONS.md) a [Security Policy](SECURITY.md).

## 1. Retailer Support Matrix

| Retailer | Priority | Search | Details | Stores | Stock | Quantity |
| :--- | :--- | :---: | :---: | :---: | :---: | :---: |
| **BAUHAUS CZ** | **P0** (Primary) | ✅ | ✅ | ✅ | ✅ | Exact numerical stock exposed (`qty`) |
| **HORNBACH CZ** | **P1** | ✅ live | ✅ live | ✅ live | ✅ live/unknown | JSON-LD stock offers where exposed |
| **OBI CZ** | **P2** | ✅ live | ✅ live | ✅ live | ✅ live | quantity/status parsed from the selected store page |
| **Baumax CZ** | **P3** | ✅ live | ✅ live | ✅ live | `NOT_SUPPORTED` | retailer exposes no reliable store stock API |
| **DEK** | **P4** | 🔮 Future | 🔮 Future | 🔮 Future | 🔮 Future | 🔮 Future |

---

## 2. MCP Tools

The server exposes five normalized MCP tools:

1. `search_products(query, retailer="all", max_price=None, max_results=20)`
   - Search all live-enabled retailers in production.
2. `get_product(retailer, product_id)`
   - Retrieve full product details, GTIN/EAN, specifications, manufacturer info, and retailer-provided in-store positions where available.
3. `list_stores(retailer, query=None)`
   - List physical store locations matching an optional query string (city, postcode, name).
4. `get_stock(retailer, product_id, store_id)`
   - Query store-specific physical stock availability with maximum 60s cache TTL. When exposed by the retailer, the result also contains `location.row` (řada), `location.section` (sekce), description and source timestamp.
5. `compare_price(query, retailers=None, max_results_per_retailer=10)`
   - Compare prices across all four currently enabled retailer adapters; results include per-retailer errors and do not invent unavailable stock.

---

## 3. Installation & Local Development

### Requirements
- Python 3.12+

### Setup
```bash
uv sync --extra dev
```

---

## 4. Operational Commands & Testing

### Run Tests with 100% Line & Branch Coverage
```bash
uv run pytest -m "not smoke and not container" --cov=czech_diy_mcp --cov-branch --cov-report=term-missing --cov-fail-under=100
```

### Run Live Retailer Smoke Suite
```bash
pytest -m smoke
```

### Code Formatting & Type Checking
```bash
uv run ruff check .
uv run ruff format --check .
uv run pyright
```

Mutation testing for critical parsing and comparison logic:

```bash
python scripts/run_mutation_audit.py
```

---

## 5. Running the MCP Server

### Streamable HTTP (Default)
```bash
python -m czech_diy_mcp --transport http --port 8000
```
- MCP endpoint: `POST /mcp`
- Health check: `GET /healthz`

### Stdio Transport
```bash
python -m czech_diy_mcp --transport stdio
```

---

## 6. Docker Deployment

### Build Image
```bash
docker build -t czech-diy-mcp .
```

### Run Container
```bash
docker run -d -p 8000:8000 --name czech-diy-mcp czech-diy-mcp
```

### Docker Compose
```bash
docker compose up -d
```

---

## 7. Configuration

Environment variables (or `.env` file):

| Variable | Default | Description |
| :--- | :--- | :--- |
| `MCP_HOST` | `0.0.0.0` | Bind host for HTTP server |
| `MCP_PORT` | `8000` | Bind port for HTTP server |
| `MCP_PATH` | `/mcp` | MCP endpoint path |
| `DIY_MAX_RESULTS` | `50` | Maximum search results per retailer |
| `DIY_HTTP_TIMEOUT` | `15.0` | HTTP request timeout in seconds |
| `DIY_CACHE_SEARCH_TTL` | `120` | Search cache TTL (seconds) |
| `DIY_CACHE_PRODUCT_TTL` | `600` | Product detail cache TTL (seconds) |
| `DIY_CACHE_STOCK_TTL` | `60` | Stock lookup cache TTL (seconds) |
| `DIY_CACHE_STORE_TTL` | `86400` | Store list cache TTL (seconds) |
| `DIY_MAX_CONCURRENT_PER_RETAILER` | `2` | Max concurrent requests per retailer |

---

## 8. Architecture

```text
                        MCP Client
                            │
                            ▼
                    ┌───────────────┐
                    │  MCP Server   │
                    │   server.py   │
                    └───────┬───────┘
                            │
                            ▼
                    ┌───────────────┐
                    │ Service Layer │
                    │ catalog.py    │
                    └───────┬───────┘
                            │
          ┌─────────────────┼──────────────────┐
          │                 │                  │
          ▼                 ▼                  ▼
      BAUHAUS           HORNBACH              OBI
      Adapter            Adapter            Adapter
```

---

## 9. License

[MIT License](LICENSE)

---

## 10. Documentation

Additional release documentation is maintained in `docs/`:

- [Architecture](docs/ARCHITECTURE.md) — application boundaries, adapters, data flow and normalized models.
- [Operations Manual](docs/OPERATIONS.md) — deployment, health checks, configuration, logs and incident handling.
- [Development and Testing](docs/DEVELOPMENT.md) — local workflow, fixtures, quality gates and live smoke tests.
- [Release Checklist](docs/RELEASE.md) — reproducible release procedure and acceptance criteria.

## 11. Data and availability policy

The server is read-only. It never places orders, adds products to baskets, authenticates users, solves challenges or accepts arbitrary URLs.

Retailer data is request-time data and may change between calls. `UNKNOWN` means that the retailer did not provide a reliable signal; it is never converted to `OUT_OF_STOCK`, and an absent quantity is never converted to zero. `fetched_at` is returned with normalized records.

BAUHAUS is the primary production adapter. HORNBACH and OBI provide live catalogue/store integrations; their stock precision depends on the signal exposed by the Czech site. Baumax store stock is intentionally `NOT_SUPPORTED` where no reliable upstream signal exists.

When BAUHAUS provides shelf placement, `get_stock` correlates the requested store's live warehouse code with the product detail response and returns the matching position, for example `location.row = "174"` and `location.section = "4"`. Missing placement data remains null; it is never copied from another store.

## 12. Release limitations

- Retailer pages can rate-limit or challenge automated requests. Such responses are surfaced as structured errors.
- Stock is inherently volatile and is cached for at most the configured stock TTL (60 seconds by default).
- Exact quantity is returned only when the retailer explicitly exposes it.
- The in-memory cache is process-local and is not a shared distributed cache.
- DEK is reserved for a future adapter.

TDQS

A3.6/5.0

Scored across 5 tools

Disambiguation5/5

Each tool targets a clearly distinct task: searching, fetching product details, listing stores, checking stock, and comparing prices. There is no meaningful overlap between these operations.

Naming Consistency5/5

All tool names follow the same snake_case verb_noun pattern, such as search_products, list_stores, and compare_price. The naming is predictable and easy to understand.

Tool Count5/5

Five tools is well-scoped for a retail-focused API covering search, product details, store locations, stock, and price comparison. Each tool serves a clear purpose without unnecessary bloat.

Completeness4/5

The tool set covers the core read-only workflows for a DIY retail assistant: discover products, inspect details, find stores, check availability, and compare prices. Minor gaps like browsing categories or fetching stock across all stores at once are not critical for the stated domain.

Maintenance

ActivityMaintained
ResponsivenessNo issues