Skip to main content
Glama
Raleose

dns-customer-mcp

by Raleose
README.md
# dns-customer-mcp

MCP-сервер для [DNS Shop](https://www.dns-shop.ru) (сторона покупателя): поиск, карточки товаров, отзывы, характеристики, каталоги, наличие по городу и список магазинов через [Model Context Protocol](https://modelcontextprotocol.io).

Репозиторий: `dns-customer-mcp`. Имя MCP-сервера и Docker-образа совпадает.

Работает в Cursor, Claude Desktop, VS Code и других MCP-клиентах.

## Зачем

У DNS нет публичного consumer API. Сайт защищён **Qrator** — анонимный `curl`/`requests` с датацентрового IP получает HTTP 401.

Этот сервер держит сессию anti-detect браузера [Camoufox](https://github.com/daijro/camoufox), один раз проходит JS-челлендж и дальше читает страницы через in-page `fetch` (часто с `X-Requested-With: XMLHttpRequest` → `{ html }`) или DOM-парсинг. Цены и наличие зависят от **города** сессии.

Архитектура: Node MCP (stdio) → Python Camoufox.

## Инструменты

| Tool | Назначение |
|---|---|
| `dns_search` | Поиск товаров |
| `dns_search_multi` | Пакетный поиск (1–10 запросов) |
| `dns_product_details` | Карточка (+ location) |
| `dns_products_batch` | Пакет 1–40 карточек |
| `dns_product_reviews` | Отзывы (`/product/opinion/`) |
| `dns_product_specs` | Характеристики |
| `dns_product_variants` | Конфигурации на карточке |
| `dns_product_availability` | Наличие по городу/магазинам |
| `dns_list_stores` | Магазины города |
| `dns_related_products` | Аналоги / аксессуары |
| `dns_compare_products` | Сравнение 2–8 товаров |
| `dns_category_browse` | Товары в категории |
| `dns_list_categories` | Живое дерево категорий |
| `dns_brand_catalog` | Витрина / поиск по бренду |
| `dns_search_filters` | Фильтры поиска |
| `dns_get_location` | Текущий город сессии |
| `dns_search_cities` | Поиск городов |
| `dns_set_city` | Сменить город |

Seller/offers маркетплейса нет — DNS один продавец. Q&A не реализован.

### Город

По умолчанию анонимная сессия ≈ **Москва** (cookie `current_path` / `city_path`).

```
dns_get_location
dns_search_cities({ query: "Каз" })
dns_set_city({ city: "Казань" })
dns_product_availability({ product: id })
```

### Идентификаторы

- **Товар:** 16-hex id (`b7a1667f9b19ed20`) или URL `https://www.dns-shop.ru/product/{id}/`
- **Категория:** slug из `dns_list_categories` — не выдумывать путь

## Быстрый старт

```bash
git clone <repo> dns-customer-mcp
cd dns-customer-mcp
docker build -t dns-customer-mcp:latest .
```

### MCP-конфиг (Docker)

Скопируйте [docs/mcp.example.json](docs/mcp.example.json) в конфиг клиента или используйте [.cursor/mcp.json](.cursor/mcp.json):

```json
{
  "mcpServers": {
    "dns-customer": {
      "command": "docker",
      "args": [
        "run", "-i", "--rm", "--init",
        "--stop-timeout=10",
        "--shm-size=1g",
        "--label", "dns-customer-mcp=1",
        "dns-customer-mcp:latest"
      ]
    }
  }
}
```

`--shm-size=1g` обязателен для браузера. Контейнер живёт пока открыт MCP-сеанс (`--rm` убирает его после выхода).

### Локальный запуск (без Docker)

```bash
npm install
pip install -r python/requirements.txt
python -m camoufox fetch
npm start
```

Windows: задайте `DNS_PYTHON=python` при необходимости.

## Переменные окружения

| Var | Default | Purpose |
|---|---|---|
| `DNS_HEADLESS` | `true` | Camoufox headless |
| `DNS_GEOIP` | `false` | Camoufox geoip |
| `DNS_CHALLENGE_WAIT_MS` | `25000` | Ожидание Qrator |
| `DNS_NAV_TIMEOUT_MS` | `90000` | Таймаут навигации |
| `DNS_PACE_MS` | `600` | Пауза между запросами |
| `DNS_IDLE_TIMEOUT_MS` | `1800000` | Idle браузера (Python; выровнен с Node) |
| `DNS_EXIT_IDLE_MS` | `1800000` | Idle Node → exit (Docker `--rm`) |
| `DNS_PYTHON` | `python3` | Интерпретатор bridge |
| `DNS_WARMUP` | on | Background warmup (`0` = off) |
| `DNS_ORIGIN` | `https://www.dns-shop.ru` | Базовый URL сайта |
| `DNS_RESTAPI` | `https://restapi.dns-shop.ru` | REST API отзывов |
| `DNS_ALLOWED_HOSTS` | `www.dns-shop.ru,...` | Allowlist для fetch_url (SSRF) |
| `DNS_TOOL_TIMEOUT_MS` | `55000` | Таймаут MCP tool по умолчанию |
| `DNS_TOOL_CONCURRENCY` | `1` | Параллельные MCP handlers (очередь вне timeout) |
| `DNS_TOOL_SESSION_RETRIES` | `1` | Auto-retry SESSION_DROP + warmup |
| `DNS_RATE_LIMIT_PER_MIN` | `20` | Token bucket на tool calls (`0` = off) |
| `DNS_BRIDGE_RETRY_MAX` | `3` | Перезапуски Python bridge |
| `DNS_SEARCH_TIMEOUT_MS` | `180000` | Таймаут dns_search |
| `DNS_META_TTL_MS` | `300000` | TTL кэша product meta |
| `HTTP_PROXY` / `HTTPS_PROXY` | — | Прокси (RU IP при бане Qrator) |

## Разработка

```bash
npm test              # node:test — парсеры + config allowlist
npm run test:parse    # alias для npm test
npm run smoke:warmup    # Camoufox + Qrator
npm run discover        # снять XHR/DOM после warmup
npm run smoke           # search → details → specs → reviews
npm run smoke:location  # города (+ --set для смены)
```

Маршруты зафиксированы в [`src/dns/`](src/dns/) по live discovery (Aug 2026):

- Cookie `current_path`: `{ city: UUID, cityName }` (Москва = `30b7c1f3-03fb-11dc-95ee-00151716f9f5`)
- `/catalog/` + XHR → JSON `{ html, data, … }`
- `/search/` — in-page fetch (XHR часто 302); **цены** через `POST /ajax-state/product-buy/`
- Карточка товара — Vue SPA (hydrate wait) + meta/og fallback
- `REST_API_URL` из Pinia: `https://restapi.dns-shop.ru` (снаружи за Qrator)
- `/product/characteristics/{id}/` может 404 — specs с карточки после hydrate

### Парсер

- Плитка: точный класс `.catalog-product`
- Цена в SSR-HTML пустая — догрузка через AjaxState; в DOM — `.product-buy__price` (не рассрочка «от N ₽/мес»)
- При срыве селекторов — `parser_drift: true`, не `price: 0`

## Разработка

### Тесты и CI

```bash
npm test              # Node unit tests (parse, errors, bridge protocol, config parity)
npm run test:python   # pytest (config, locations, shared config parity)
npm run test:all      # оба набора
npm run lint          # ESLint (JS)
npm run lint:python   # Ruff (Python) — pip install -r python/requirements-dev.txt
npm run build:config  # проверка parity shared/config.json ↔ runtime
npm run build:selectors  # генерация python/dns_selectors.py из src/selectors.js
```

GitHub Actions (`.github/workflows/test.yml`): `npm test`, `pytest`, `docker build` на каждый push/PR.

Smoke-тесты (живой Camoufox + сеть):

```bash
npm run smoke
npm run smoke:warmup
npm run smoke:location
npm run discover
```

### Discovery-скрипты

Одноразовые скрипты для reverse-engineering HTML/API DNS лежат в [`tools/discovery/`](tools/discovery/) (не входят в CI). Основные smoke/discover — в [`scripts/`](scripts/).

Протокол Node↔Python: [`docs/bridge-protocol.md`](docs/bridge-protocol.md).

### Переменные окружения (доп.)

| Переменная | По умолчанию | Назначение |
|---|---|---|
| `DNS_RATE_LIMIT_PER_MIN` | `20` | Token bucket на MCP tool calls (`0` = off) |
| `DNS_TOOL_CONCURRENCY` | `1` | Сериализация MCP handlers (таймаут не тикает в очереди) |
| `DNS_WARMUP` | on | Фоновый прогрев Camoufox при старте (`0` = off) |

## Ограничения

- Только публичный каталог (не корзина / профиль / заказы)
- Без CDP к локальному Chrome и без Parse/Apify
- При бане Qrator — RU residential proxy через `HTTPS_PROXY`

## Лицензия

MIT

TDQS

A3.7/5.0

Scored across 18 tools

Disambiguation4/5

Most tools map cleanly to separate resources or actions—search, product details, specs, reviews, variants, availability, stores, categories, brands, and city state—and the descriptions call out intended use cases. A few pairs require careful reading, especially product_details vs products_batch and product_availability vs list_stores, but the boundaries are drawable.

Naming Consistency3/5

All names share the dns_ prefix and snake_case, but the internal convention is mixed: verb-first names like list_stores and get_location coexist with noun-first names like product_details and category_browse, plus adjective-noun forms like related_products. There is also singular/plural inconsistency between product_details and products_batch, making the scheme readable but not uniform.

Tool Count3/5

18 tools sits within the 16–25 range that starts to feel heavy for a single server, even though the domain is fairly broad. The count is somewhat justified by purpose-built batching tools like search_multi, products_batch, and compare_products, but several product-data tools could potentially be consolidated.

Completeness4/5

The read-only shopping surface is nearly comprehensive: city selection, search, category and brand browsing, product cards, specs, reviews, variants, availability, stores, related products, and comparison are all covered. The main gap is that dns_search_filters exposes filters but no tool clearly documents how to apply those filters back into a search or category query.

Maintenance

ActivityMaintained
ResponsivenessNo issues