Combo Engine MCP Server
Combo Engine MCP Server
Расчёт выгодных комбо по сетям доставки Воронежа. Это MCP-сервер + библиотека парсеров: подбирает набор еды под бюджет, сравнивает сети по выгодности, следит за ценами и меню.
Все зависимости и браузер Playwright изолированы внутри .venv\ — системное
окружение не затрагивается.
Смысл и сценарии каждой команды — в COMMANDS.md.
Сети
7 служб доставки Воронежа (kind=combo): Ла Пицца, Пицца Куба, Ниндзя Фуд, Сушитайм, Суши Даром, Антисуши, Додо Пицца. Дополнительно подключены продуктовые магазины (kind=store): Магнит (включён — поиск товаров/категории), Пятёрочка (выключена, анти-бот).
Как это работает
Сервер получает меню сетей (парсинг сайтов/API, с кэшированием и защитой от сбоев — «stale-if-error»).
По запросу строит комбо под бюджет: по умолчанию 3 варианта — Оптимум (вес + вкус, 1 напиток) → Без повторов → Макс. вес.
Поддерживает категории, промо-скидки, сравнение сетей, мониторинг цен и меню.
Комбо/меню/сравнение работают только по сетям доставки; магазины — отдельные store-тулы (поиск товаров и категории через нативный API).
Структура
├── combo_mcp\ # пакет MCP-сервера
│ ├── server.py # точка входа (stdio)
│ ├── config.py # конфиг сетей (config\chains_config.json)
│ ├── cache.py # дисковый кэш + stale-if-error
│ ├── http_client.py # requests-обёртка (UA, ретраи, куки)
│ ├── playwright_client.py # ленивый браузер (фолбэк dodo)
│ ├── weights.py # справочник расчётных весов (config\estimated_weights.json)
│ ├── engines\ # dp.py (комбо), taste.py (вкусность), drinks.py (напитки)
│ ├── chains\ # 1 файл = 1 сеть (@chain("id"))
│ └── tools\ # 1 файл = 1 инструмент MCP
├── scripts\ # selftest.py, autotest.py, smoke_test.py, gen_expected.py
├── tests\expected.json # эталоны для autotest
├── config\chains_config.json
├── config\estimated_weights.json # веса позиций без веса на сайте (с источником)
├── cache\ # создаётся автоматически
├── COMMANDS.md # для чего нужна каждая команда (смысл и сценарии)
└── archive\ # старые файлы + референсы разведки (recon\)Запуск
# MCP-сервер (регистрируется opencode через opencode.json)
.\.venv\Scripts\python.exe combo_mcp\server.py
# Selftest: все сети + эталоны Ла Пиццы (3000₽ → 4400/2950, 4850/3000, 5100/2800)
.\.venv\Scripts\python.exe scripts\selftest.py
# Автотесты: эталоны комбо + инварианты + контрольные блюда + health_check
.\.venv\Scripts\python.exe scripts\autotest.py
# Smoke-тест реального MCP-протокола (15 инструментов)
.\.venv\Scripts\python.exe scripts\smoke_test.py
# Очистка кэша
.\.venv\Scripts\python.exe scripts\clear_cache.pyИнструменты MCP (15)
Подбор и сравнение
best_combo(chain_id, budget, variations=3, refresh=, categories=, promos=, sort_by=)варианты комбо под бюджет.по умолчанию 3 вариации в порядке: Оптимум (максимум веса еды в бюджете
вкус, напиток оптимизируется внутри подбора) → Без повторов (макс. вес, каждый продукт по 1 шт) → Макс. вес (чистый максимум веса); при
variations>3— ещё доп. стратегии
1 напиток в каждой вариации (если есть напитки с весом, влезающие в бюджет)
chain_id— одна сеть (обычный режим), пустая строка («все сети») или список через запятую («dodo, ninja_food») — сквозной топ-N: кандидаты сетей сортируются метрикой и отбирается топvariations(mode=all/multi)sort_by=—price_per_100g(по умолчанию),weight,price(только в cross-chain режиме)categories=— фильтр по категориям:pizza/rolls/sushi/sets/combo/noodles/ snacks/desserts/drinks/sauces/other+ русские слова («пицца», «напитки»…).combo— наборы/комбо-предложения,sets— чистые ролл-сеты; напитки — только если группаdrinksв спискеpromos=— применить промо-скидки:order/pickup/allrefresh=true— реальный прогон парсера, иначе ответ по кэшу
compare(budget, categories=)— лучшее комбо каждой сети на бюджет, отсортировано по выгодности (₽/100г).
Меню и контент
parse_menu(chain_id, category=, min_weight=, sort_by=, limit=, refresh=)меню сети с фильтрами и сортировкой (limit не более 500).
Контроль цен и меню
check_price(chain_id, item_name, expected_price=)— свежая проверка цены позиции (реальный парсинг).diff_menu(chain_id)— изменения меню с прошлой загрузки.chain_info(chain_id, refresh=)— доставка, акции, лояльность сети.
Состояние и качество данных
list_chains(refresh=)— сети: id, название, город,available, описание.status()— конфиг, возраст кэша, ошибки.verify_chain(chain_id)— качество данных сети: веса, дубликаты, аномалии.check_config()— валидацияchains_config.json.health_check(refresh=False)— HTTP + парсинг + кол-во позиций по всем сетям (по кэшу или live).
Служебные
help(action=, command=)— справочник команд: список с пагинацией (/help next,/help back), детали одной команды (/help best_combo).favorites(action=, chain_id=, label=, items=, query=)— избранное: сохранить (add), показать (list), удалить (remove), очистить (clear). Хранение:cache/favorites.json.
Магазины (kind=store)
store_search(query, store=, min_price=, max_price=, in_stock=, sort=, limit=)— поиск товаров по магазинам (живой нативный API, не меню-кэш).store_categories(store=)— серверное дерево категорий магазинов. Толькоkind=store; рестораны — ошибка. Подробности — в разделе «Магазины».
Параметры и защита от дурака
Числовые параметры передаются строками и валидируются. Капы: budget ≤ 100000,
variations ≤ 50, limit ≤ 500 — иначе явная ошибка {"error": ...}.
Нераспознанные categories/sort_by/chain_id — ошибка с перечнем доступных
значений, а не молча пустой ответ. Мусор в числовых параметрах (abc, 0,
отрицательные) не роняет сервер — возвращается корректная ошибка.
Автотесты
tests\expected.json — фиксированные эталоны (комбо по сетям/бюджетам,
контрольные блюда). Без record-режима: scripts\autotest.py сверяет фактический
результат с эталоном, расхождение → FAIL + дифф. Эталон обновляется только
осознанным коммитом.
Сравнение комбо: вариант 1 (оптимум) сверяется по точному составу, а варианты 2+ — с допуском ±5% на вес и цену: меню сетей регулярно меняются, и состав «добивок» (соусы, маффины, имбирь, палочки) в вариантах 2+ легко сдвигается без потери качества. Так автотест ловит реальные регрессии оптимума, но не спамит ложными падениями из-за дрейфа меню в не-оптимальных вариантах.
CI (GitHub Actions)
.github\workflows\ci.yml— на каждый push: компиляция + быстрый smoke без сети (сервер стартует, 15 инструментов зарегистрированы, JSON-ответы). Запуск вручную:scripts\ci_smoke.py..github\workflows\nightly.yml— ежедневно в 06:00 UTC: живой парсинг всех сетей (health_check refresh=true) + полный автотест (30 блоков). Это мониторинг парсеров: упавшая сеть или регрессия → красный статус + артефакт с отчётом (cache/).
Магазины (kind=store)
Магазины — отдельный мир: комбо/меню/сравнение (parse_menu/best_combo/compare) для них не работают, их обслуживают два store-тула:
store_search(query, store=, min_price=, max_price=, in_stock=, sort=, limit=)— поиск товаров через нативный серверный поиск магазина (parser.search): свежие цены и наличие (не меню-кэш).store=""— все включённые магазины, или список через запятую. Матчинг —combo_mcp/engines/textmatch.py(регистр/ё→е не важны, Левенштейн ≤1 для слов ≥5 симв., бонус за категорию). Сортировка:relevance(default) |price_asc|price_desc. В ответеscore,chain_id,stores_errors(ошибка одного магазина не роняет ответ).store_categories(store=)— серверное дерево категорий магазина (parser.get_categories),store=""— все включённые.magnit — включён. API
webgate.magnit.ru(requests, без браузера): категории/webgate/v3/categories/store/992301, товары/поискPOST /webgate/v2/goods/search(цены — рубли, вес из weight/имени,weight_source=name). TTL кэша меню — неделя (menu_ttl_minutes: 10080).pyaterochka — выключен (
enabled: false). API5d.5ka.ruзакрыт анти-ботом (капча вне браузера); парсер написан на Playwright, но headless стабильно получает капчу → явная ошибка «магазин отключён (enabled=false)».Для обоих магазинов работают
store_search/store_categories(серверный путь) и общиеstatus/health_check/check_config. Рестораны (kind=combo) в store-тулах — ошибка.
Конфиг сетей
config\chains_config.json — per-chain: url, enabled, ttl_minutes, headers,
cookies. Смена URL/куки не требует правки кода.
Добавление новой сети
Скопировать
combo_mcp\chains\_template.py→combo_mcp\chains\my_chain.py(или проще:python scripts\new_chain.py my_chain "Моя Сеть" https://example.com)Реализовать метод
parse()(возвращает список позиций:{name, weight_g, price_rub, is_from_price, description, category, product_url, in_stock, extra})Заполнить
category_map— маппинг категорий меню → группы комбо (pizza/rolls/sushi/sets/noodles/snacks/desserts/drinks/sauces/other)Указать
urlвchains_config.json(генератор делает это сам)
Больше ничего: парсеры регистрируются автоматически (pkgutil), метаданные сети
(id/name/city/url/description) берутся из класса парсера, категории — из его
category_map. По желанию: parse_extra() (доставка/акции), веса в
config\estimated_weights.json, переводы в config\translations.json.
Добавление нового инструмента
Положить файл с функцией-хендлером в combo_mcp\tools\ и зарегистрировать
декоратором (см. существующие инструменты) — server.py не трогается.
Правила данных
Вес позиции: с сайта/API (
weight_sourcesite), из названия размера (size_name, pizza_kuba), из справочникаconfig\estimated_weights.json(reference, полеsource— откуда взят вес) или отсутствует (none).Позиции без веса исключаются из расчёта комбо (мерч, палочки — намеренно).
Недоступные сети честно помечаются в
list_chains/compareс причиной.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/1artik1/ComboPizzaSushiMCP'
If you have feedback or need assistance with the MCP directory API, please join our Discord server