dodo-mcp
by aipapa-md
README.md
# dodo-mcp
MCP-сервер, который позволяет AI-агенту заказывать пиццу из чата — в Claude,
ChatGPT или любом клиенте с поддержкой MCP.
> **Дисклеймер.** Исследовательский концепт. Проект **не аффилирован** с Dodo Brands,
> Додо Пиццей и Drinkit, не одобрен и не поддерживается ими. Публичного клиентского
> ordering API у Додо нет — модель данных реконструирована наблюдением за собственным
> веб-заказом в браузере. Сервер работает **только с вашим аккаунтом на вашей машине**:
> никакие данные никуда не отправляются, чужие сессии нигде не хранятся.
> Используя проект, вы действуете на свой страх и риск и сами отвечаете за
> соблюдение условий использования сервисов Додо.
## Зачем это
Не «заказать через чат» — это и на сайте две минуты. Ценность в том, что агент
понимает **«как обычно, но большую»** и снимает боль многошаговой кастомизации:
```
▸ Закажи пепперони на тонком, добавь моцареллу
Пепперони · 30 см, тонкое · + моцарелла — 4 600 ₸ (1214 ккал)
▸ Ту же, но большую, и без ананасов
Пепперони · 35 см, тонкое · + моцарелла — 5 700 ₸
⚠️ нельзя убрать «ананасы»: их нет в составе. Убрать можно: пепперони из цыпленка
▸ Нас четверо, уложись в 12000
Пепперони фреш 35 см + Ветчина и грибы 35 см — 8 740 ₸, ~950 ккал на человека
```
## Установка
```bash
git clone <repo> dodo-mcp && cd dodo-mcp
npm install
npm run build
npm run doctor # проверит Node, Chrome, сборку и подскажет, чего не хватает
```
Нужны **Node.js 20+** и **Chrome**. Для доступа по сети — ещё `cloudflared`.
Посмотреть, как это работает, можно сразу и без всякой настройки:
```bash
npm run demo # проигрывает сценарий на настоящем меню, заказов не создаёт
```
## Два режима
| Режим | Что делает |
|---|---|
| `mock` | Настоящее меню из репозитория, корзина в памяти, заказ имитируется. Безопасно для демо |
| `live` | Реальные вызовы Додо через ваш Chrome. Заказ настоящий |
Для `live` нужно один раз войти в аккаунт:
```bash
node scripts/live-check.mjs
```
Откроется отдельное окно Chrome с профилем `~/.dodo-mcp/chrome-profile` — введите
телефон и код из SMS. Автоматизировать вход нельзя: там Yandex SmartCaptcha, и
обходить её мы не собираемся. Дальше сессия живёт в профиле.
## Подключение
### Claude Code
В репозитории есть `.mcp.json` — просто запустите `claude` из этой папки.
### Claude Desktop
В `~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"dodo": {
"command": "/opt/homebrew/bin/node",
"args": ["/полный/путь/к/dodo-mcp/dist/index.js"],
"env": { "DODO_MCP_MODE": "live", "DODO_MCP_LOCALITY": "almaty",
"DODO_MCP_ORIGIN": "https://dodopizza.kz" }
}
}
}
```
Путь к `node` — абсолютный: приложение запускает процесс без shell-окружения.
### ChatGPT и другие клиенты по сети
```bash
npm run serve:remote
```
Скрипт поднимет туннель и напечатает адрес вида
`https://xxx.trycloudflare.com/mcp`. В ChatGPT: **Настройки → Плагины →
Режим разработчика**, затем создать коннектор с этим адресом.
**Ноутбук должен быть включён**: Chrome с вашей сессией живёт локально, туннель
ведёт на него. Это не облачный сервис.
## Безопасность
Сервер даёт доступ к вашему аккаунту Додо — то есть к возможности потратить ваши
деньги. Поэтому защита многослойная.
**Сервер слушает только localhost.** Из локальной сети к нему не подключиться,
наружу ведёт исключительно туннель.
**OAuth 2.1 с PKCE** — способ по умолчанию (`DODO_MCP_AUTH=oauth`), проверен
с живым коннектором ChatGPT. Клиент регистрируется сам (RFC 7591), а доступ
подтверждаете вы: при запросе авторизации в терминале сервера печатается
шестизначный код, и без него токен не выдаётся.
У доступа есть срок жизни (час, с обновлением), его можно отозвать:
```bash
curl -X POST http://127.0.0.1:8787/oauth/revoke
```
**Статический токен** (`DODO_MCP_AUTH=token`) — запасной вариант для клиентов,
которые не умеют OAuth. Секрет кладётся прямо в адрес `/mcp/<токен>`. Работает,
но такой ключ вечен и утекает вместе со скриншотом — используйте, только если
иначе никак.
**Ограничители ущерба** — на случай, если доступ всё же утёк:
| Переменная | По умолчанию | Что делает |
|---|---|---|
| `DODO_MCP_MAX_ORDER` | 20 000 ₸ | потолок суммы одного заказа |
| `DODO_MCP_MAX_ORDERS_PER_DAY` | 5 | сколько заказов в сутки |
**Подтверждение человеком.** Оформление, отмена и отзыв требуют явного
согласия: агент обязан показать итог и дождаться «да».
**Логи** пишут каждое обращение с IP, включая отказы:
```
[dodo-mcp] ⛔ 401 POST /mcp от 203.0.113.77
[dodo-mcp] tools/call place_order → 200 (2426 мс) OAuth: ChatGPT от 74.161.200.110
```
## Инструменты
| Инструмент | Назначение |
|---|---|
| `suggest_addresses` | Варианты адреса по неточному запросу |
| `find_stores` | Пиццерия по адресу: часы, время доставки, самовывоз |
| `browse_menu` | Меню с осями кастомизации и фильтрами: калории, цена, без мяса, не острое |
| `plan_order` | Подбор набора: «на компанию из N», «в бюджет», «до N ккал на человека» |
| `compose_order` | Пожелания → валидная конфигурация: подбор SKU, проверка допустимости, цена |
| `estimate` | Итог: сумма, доставка, время, бонусы, калорийность |
| `place_order` | Оформление с оплатой при получении. Требует подтверждения |
| `order_status` | Активные заказы: стадия, время, курьер, оплата и сдача |
| `cancel_order` | Отмена, пока она доступна |
| `rate_order` | Оценка 1–5 и отзыв |
| `my_usual` | Прошлые заказы и «как обычно» |
| `clear_cart` | Очистить корзину |
| `auth_status` | Выполнен ли вход в аккаунт и где сейчас заказываем |
| `start_here` | Сценарии работы и текущее состояние — с этого начинает агент |
| `list_countries` | Страны Додо с числом пиццерий |
| `list_cities` | Города страны, где есть доставка |
| `set_location` | Переключить страну и город |
Инструменты спроектированы под **намерения агента**, а не как обёртки над
эндпоинтами: `compose_order` за один вызов делает то, на что в реальном API
уходит цепочка из четырёх шагов, и объясняет отказы вместо молчания.
## Города и страны
Сервер работает во всех странах, где есть Додо Пицца, — **27 стран, 1519
пиццерий**. Переключение делается из чата, перезапуск не нужен:
```
— Я на неделю в Варшаве, что там в меню?
→ set_location(city: "Warszawa", country: "pl")
Теперь заказываем: Warszawa, Польша. Меню: 164 позиции, валюта zł, язык pl-PL.
```
Что при этом происходит само:
- **домен** — почти везде `dodopizza.<код страны>`, но четыре исключения
(`dodopizza.com.tr`, `dodopizza.com.cy`, `dodopizza.co.id`, `dodo.mn`)
зашиты в справочнике;
- **язык меню** — берётся со страницы самой страны (`link rel=alternate`):
русский, если страна его поддерживает, иначе родной. Выдумывать коды культур
нельзя — на неизвестной культуре меню отвечает 400;
- **валюта** — из отформатированных сумм живой корзины, а не из таблицы;
- **город** — из списка доставки этой страны. Если запрос подходит под
несколько городов («моск» → Москва и Московский), сервер не выбирает
молча, а спрашивает.
Стартовая локация задаётся в `.env` (`DODO_MCP_ORIGIN`, `DODO_MCP_LOCALITY`).
Меню в `fixtures/` снято для Алматы и используется только в `mock`.
Оговорка: фильтры «без мяса» и «не острое» опознают состав по словарю
ингредиентов. Он покрывает русский и основные латинские языки Додо, но не все
27 стран — вне русскоязычных стран сервер сам предупреждает, что проверять
состав надо глазами.
## Как это устроено
Подробная карта реального API — в [`docs/recon/endpoints.md`](docs/recon/endpoints.md).
Несколько находок, без которых ничего бы не работало:
**Тесто — не свойство пиццы, а отдельный товар.** «Пепперони 30 см тонкое» и
«Пепперони 30 см традиционная» — два разных SKU. Топинги — третья ось, у каждого
свой идентификатор и цена, зависящая от размера.
**Калории даны на 100 г, а не на порцию.** Пепперони 30 см — не 269 ккал,
а 269 × 580/100 = 1561 ккал. Сервер пересчитывает на порцию, иначе фильтр
«до 1000 ккал» пропускал бы целую большую пиццу.
**Корзина не работает без выбранного адреса**, а строка корзины адресуется
не идентификатором, а полной конфигурацией — у неё есть `cartLineId`, но
`DELETE` по нему отвечает 400.
**Позиция может вернуться с кодом 200 и не попасть в корзину** — так выглядит
стоп-лист. Поэтому сервер сверяет состав и предупреждает, а не рапортует об успехе.
**Сумма сдачи передаётся через комментарий к заказу** — отдельного поля у неё нет.
**Запросы идут изнутри страницы** dodopizza.kz, а не отдельным HTTP-клиентом:
сайт прогоняет antifraud-фингерпринт на каждом действии, и вызовы мимо браузера
этих сигналов не несут. Мы ничего не подделываем — это тот же контекст, что и
при ручном заказе.
## Ограничения
- **Только оплата при получении** — наличными или картой курьеру. Онлайн-оплата
не поддерживается намеренно: там форма карты на стороне шлюза и 3DS от банка.
- **Один аккаунт на установку.** Сервер работает с вашей сессией; мультитенантности
нет и не планируется — хранить чужие сессии Додо мы не будем.
- **Приватный API может измениться в любой день.** Это не публичный контракт,
и никаких гарантий у него нет.
- **Чата с поддержкой нет**: на сайте сторонний Intercom, API у него своё.
## Разработка
```bash
npm run dev # пересборка на лету
npm run demo # демо-сценарий
node scripts/smoke.mjs # прогон инструментов через настоящий MCP-клиент
node scripts/scenarios.mjs # сценарии: компания, калории, диета
node scripts/oauth-test.mjs # полный цикл OAuth
```
Структура:
```
docs/recon/endpoints.md карта реального ordering API
fixtures/ меню Алматы для mock-режима
src/menu/ загрузка меню, индекс, оси кастомизации
src/domain/ сборка и валидация заказа, подбор, память
src/backend/ mock / live, управление браузером
src/oauth.ts OAuth 2.1 для remote-доступа
src/index.ts stdio-транспорт
src/http.ts HTTP-транспорт
```
## Лицензия
MIT
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues