No description
  • TypeScript 90.3%
  • JavaScript 6.6%
  • CSS 1.5%
  • HTML 1.4%
  • Dockerfile 0.2%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Aleksandr Surzhko 8848482f01
All checks were successful
ci / check (push) Successful in 54s
ci / image (push) Successful in 2m54s
ci / deploy (push) Successful in 1s
fix: перевести CI и деплой на Forgejo
workflow: .forgejo/workflows, Forgejo actions и docker CLI вместо docker actions

runner: инструкция по Forgejo Runner и DinD вместо Gitea act_runner

compose: EDA_MCP_IMAGE обязателен, без заглушки gitea.example.com
2026-11-15 04:26:24 +05:00
.forgejo/workflows fix: перевести CI и деплой на Forgejo 2026-11-15 04:26:24 +05:00
.kilo/skill/yandex-eda feat: искать по меню заведения и учитывать состав в предпочтениях 2026-11-15 01:48:39 +05:00
deploy feat: Streamable HTTP MCP и деплой в Proxmox 2026-11-15 03:18:03 +05:00
src feat: Streamable HTTP MCP и деплой в Proxmox 2026-11-15 03:18:03 +05:00
web feat: soft/hard блок-листы и редактирование предпочтений в веб-панели 2026-11-15 00:23:50 +05:00
.dockerignore feat: Streamable HTTP MCP и деплой в Proxmox 2026-11-15 03:18:03 +05:00
.env.example fix: перевести CI и деплой на Forgejo 2026-11-15 04:26:24 +05:00
.gitignore feat: собирать корзину по рецепту одним вызовом через cart API 2026-11-14 21:45:58 +05:00
AGENTS.md fix: перевести CI и деплой на Forgejo 2026-11-15 04:26:24 +05:00
compose.yaml fix: перевести CI и деплой на Forgejo 2026-11-15 04:26:24 +05:00
Dockerfile feat: Streamable HTTP MCP и деплой в Proxmox 2026-11-15 03:18:03 +05:00
eda.config.example.json feat: soft/hard блок-листы и редактирование предпочтений в веб-панели 2026-11-15 00:23:50 +05:00
kilo.json feat: MCP-сервер и веб-панель для Яндекс.Еды 2026-11-13 20:58:12 +05:00
package-lock.json feat: MCP-сервер и веб-панель для Яндекс.Еды 2026-11-13 20:58:12 +05:00
package.json feat: тайминги MCP по фазам и просмотр в веб-панели 2026-11-14 03:37:05 +05:00
README.md fix: перевести CI и деплой на Forgejo 2026-11-15 04:26:24 +05:00
tsconfig.json feat: MCP-сервер и веб-панель для Яндекс.Еды 2026-11-13 20:58:12 +05:00

yandex-eda-mcp

MCP-сервер и скилл для LLM: поиск товаров в Яндекс.Еде, управление корзиной, адрес доставки и подготовка оформления до экрана оплаты. Финальное подтверждение и оплату выполняет только человек.

LLM (Kilo / Claude Desktop / Cursor)
        │ MCP (stdio)
        ▼
yandex-eda-mcp ── Playwright Core ── Chrome/Edge (профиль .eda/profile)
        │                                   │
        └── (опционально) HTTP API ──────────┘
             cookies из .eda/cookies.json

Что умеет

Инструмент Действие
eda_status Диагностика: конфиг, cookies, браузер. Read-only
eda_preferences Действующие списки preferences (stores/brands/products: prefer/softBlock/hardBlock). Read-only
eda_login Импорт из Chrome/Edge, ручные cookies или окно входа (method)
eda_set_address Адрес доставки через диалог «Куда доставить заказ?»
eda_list_addresses Текущий адрес, confirmed, квартира/подъезд/этаж/комментарий, сохранённые адреса
eda_list_slots Слоты доставки: время, цена, бесплатно, скидка, минуты
eda_search_products Поиск: цена, скидка, объём, рейтинг, магазин; фильтры store/storeId, maxDeliveryTime, onlyPromo, minRating, ignorePreferences
eda_add_to_cart Добавить товар: mode=set (идемпотентно) или increment; health-check и retry, affectedCart
eda_build_recipe_cart Собрать корзину по списку ингредиентов: один магазин по покрытию, dryRun
eda_get_cart Магазины, сумма, реальные позиции с id, ценой/ед., количеством и весом
eda_set_cart_quantity Изменить количество/удалить позицию; dryRun
eda_clear_cart Очистить всё или только store; confirm, dryRun
eda_remove_store_cart Удалить корзину одного магазина; confirm, dryRun
eda_apply_promo Применить промокод (API корзины, fallback /checkout; не оплата)
eda_get_benefits Яндекс Плюс, кэшбэк, применённый промокод, итог
eda_list_orders История заказов: id, дата, сумма, статус
eda_get_order_status Статус заказа после оплаты
eda_repeat_order Повторить прошлый заказ; dryRun
eda_prepare_checkout Адрес, слот, комментарий, скриншот (image content) и передача экрана оплаты (handoff); оплату не нажимает
eda_probe_page Дамп разметки для починки селекторов

Где возможно, инструменты сначала идут в реальный внутренний JSON API сайта из контекста авторизованной вкладки (source: "api"). Если API вернул ошибку/пустой ответ, автоматически используется DOM (source: "browser").

Важно: даже API-вызовы выполняются через page.evaluate(fetch(...)) внутри вкладки eda.yandex.ru, чтобы использовать её cookies, гео и антибот-контекст. Поэтому браузер может запускаться и для API-инструментов; колонка «Основной путь» показывает источник данных, а не отсутствие браузера.

API или браузер

Операция Инструмент Основной путь Fallback
Поиск товаров eda_search_products API: POST /eats/v1/full-text-search/v1/search; поиск в магазине — POST /eats/v1/full-text-search/retail/places/search DOM-карточки product-card-v2-root
Адреса eda_list_addresses API: POST /eats/v1/eats-addresses/v2/list; текущий адрес — из шапки/eda_web нижний лист адресов и подсказки
Яндекс Плюс eda_get_benefits API: GET /eats/v1/eats-plus/v1/status; суммы — из cart API текст /checkout
История заказов eda_list_orders API: POST /eats/v1/orders-info/v1/orders DOM-история заказов
Статус заказа eda_get_order_status API: orders + POST /eats/v1/orders-info/v1/desktop/order-details клик по заказу в истории
Позиции и суммы корзины eda_get_cart API: POST /eats/v1/cart/v2/multi-carts + full-carts мини-корзина//checkout для проверки доступности
Промокод eda_apply_promo API: POST /api/v2/cart/promocode (только при одной корзине) ввод промокода на /checkout
Добавление товара eda_add_to_cart Браузер: карточка товара + DOM health-check —
Количество/удаление позиции eda_set_cart_quantity Браузер: DOM корзины —
Очистка корзины eda_clear_cart, eda_remove_store_cart Браузер: мини-корзина —
Смена адреса eda_set_address Браузер: диалог «Куда доставить заказ?» —
Слоты доставки eda_list_slots Браузер: диалог слотов; deliveryFee — из cart API —
Подготовка оформления eda_prepare_checkout Браузер: /checkout, адрес/слот/комментарий/скриншот —
Повтор заказа eda_repeat_order Браузер: история заказов + «Повторить» —
Логин, диагностика, дамп разметки eda_login, eda_status, eda_probe_page Браузер, профиль и локальные файлы —

Для eda_get_cart браузер используется всегда: кроме cart API он читает мини-корзину, проверяет логин и открывает /checkout, чтобы вернуть checkoutAvailable/checkoutReason. API даёт позиции и суммы; DOM — состояние оформления.

Оплату и финальное подтверждение заказа по-прежнему выполняет только человек.

Скилл для Kilo: .kilo/skill/yandex-eda/SKILL.md.

Веб-панель

npm.cmd run web

Откроется http://127.0.0.1:8787/ (порт меняется через EDA_WEB_PORT, --no-open — не открывать браузер).

Панель закрывает весь сценарий в одном окне:

  • LLM — Base URL, API key и модель. Подходит любой OpenAI-совместимый провайдер (OpenAI, OpenRouter, локальный vLLM/Ollama с /v1). LLM может быть удалённой.
  • MCP — local (stdio: node dist/index.js на этой машине) или remote (Streamable HTTP URL + headers). Кнопка «Проверить MCP» показывает инструменты.
  • Яндекс.Еда — вставить Cookie header, импортировать из Chrome/Edge, проверить сессию. Те же операции, что npm.cmd run login.
  • Агент — режим экрана оплаты (window на этом ПК или url для устройства) и лимит шагов.
  • Предпочтения — списки prefer/softBlock/hardBlock для магазинов, брендов сети и товаров; soft и hard работают одновременно, правки сохраняются в eda.config.json и применяются к поиску и сборке корзины без перезапуска.
  • Чат — «Закажи молоко 3,2% и хлеб». Агент сам вызывает eda_search_products, eda_add_to_cart, eda_get_cart, затем eda_prepare_checkout.

На оформлении в чате появляется карточка: магазин, позиции, сумма, адрес, слот, скриншот и кнопка экрана оплаты. При handoff=window сервер сразу открывает реальное окно Chrome на машине MCP. Кнопку «Оплатить» нажимает человек; сервер её не трогает.

Панель слушает только 127.0.0.1. API key и cookies лежат в .eda/web-settings.json и .eda/cookies.json в открытом виде — не выставляйте порт наружу и не коммитьте .eda/.

Как проверить, что MCP работает

npm.cmd run build
npm.cmd run check        # stdio-подключение, 20 инструментов, eda_status
npm.cmd run check:live   # + живой поиск и чтение корзины (нужен Chrome/Edge)

check запускает ровно тот же node dist/index.js, который прописан в kilo.json, и проверяет протокол, регистрацию инструментов и ответ eda_status. check:live дополнительно ищет «молоко» и читает корзину. Если другой экземпляр MCP уже использует .eda/profile, живой тест может упереться в блокировку профиля — остановите его или запускайте check:live до подключения Kilo.

В Kilo:

  1. Перезапустите сессию/примените конфиг (MCP-серверы читаются при старте).
  2. Откройте /mcps — должен быть включён yandex-eda.
  3. Попросите агента вызвать eda_status, затем eda_search_products {query:"молоко", limit:3}.
  4. Проверка изменений: eda_add_to_cart по ссылке из поиска → eda_get_cart → eda_clear_cart {confirm:true}.

eda_status теперь делает live-проверку сессии (может занять пару секунд): auth.verifiedSession=true — вход подтверждён, false — cookies есть, но сессия не принята.

Полный сценарий: поиск → добавление → проверка корзины → eda_prepare_checkout (только после входа; кнопку «Оплатить» нажимает человек).

Кто показывает экран оплаты

Браузер всегда запускается на машине, где работает MCP-сервер, а не там, где LLM:

LLM (облако/другой ПК) → клиент Kilo (ваш ПК) → stdio → MCP-сервер → Chrome/Edge
                                                          ^ здесь и открывается окно

Модель может быть удалённой — это не важно: kilo.json с "type": "local" запускает MCP-сервер на том же ПК, где клиент. Рекомендуемая схема — клиент, MCP и браузер на компьютере человека; LLM пусть будет хоть в облаке.

eda_prepare_checkout готовит /checkout и затем передаёт экран оплаты по режиму handoff:

Режим Что происходит Когда использовать
window (по умолчанию) Закрывает Playwright, запускает реальное окно Chrome/Edge с профилем .eda/profile на URL оформления на хосте MCP MCP на вашем ПК; или есть RDP/VNC к хосту
url Не открывает окно, возвращает handoff.url и инструкцию MCP на удалённом сервере/без GUI, а человек — за своим устройством
none Только скриншот Headless-сервер, отладка

Для window в окне на хосте MCP пользователь проверяет заказ и сам нажимает «Оплатить». Окно detached и не закрывается вместе с MCP, но блокирует профиль .eda/profile: пока оно открыто, другие браузерные инструменты вернут ошибку «Профиль занят». После оплаты окно нужно закрыть.

Для url человек открывает handoff.url на своём устройстве под тем же аккаунтом Яндекса: корзина хранится на сервере аккаунта и синхронизируется. Но адрес и слот выбираются в профиле MCP, поэтому на другом устройстве их, возможно, придётся выбрать заново. Ссылка не содержит авторизации — без входа в тот же аккаунт она покажет пустую корзину.

Режим по умолчанию задаётся в eda.config.json → browser.handoff, переопределяется параметром инструмента. Сервер никогда не нажимает оплату и не вводит платёжные данные.

Требования

  • Node.js 20+ (проверено на 24).
  • Google Chrome или Microsoft Edge (используется системный браузер, без скачивания Chromium).
  • Аккаунт Яндекс.Еды для оформления. Поиск, корзина и адрес работают и без входа, но checkout требует авторизации.

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

npm.cmd install
npm.cmd run build

# один раз: выбрать способ входа (импорт / cookies вручную / окно браузера)
npm.cmd run login

npm.cmd — потому что PowerShell блокирует npm.ps1 (Execution Policy).

Способы входа

npm.cmd run login показывает меню:

1) Импортировать cookies из Chrome/Edge (закройте браузер)
2) Вставить cookies вручную (Cookie header из DevTools)
3) Открыть браузер и войти в аккаунт
0) Выйти
  1. Импорт. Копирует cookies и ключ шифрования (Local State) из установленного Chrome/Edge в .eda/profile. Оригинальный профиль не меняется, пароль вводить не нужно. Браузер-источник должен быть полностью закрыт (он блокирует cookie DB); команда ждёт до 60 секунд.

  2. Cookies вручную. Если импорт не работает (App-Bound Encryption) или браузер нельзя закрыть:

    • откройте https://eda.yandex.ru там, где вы авторизованы;
    • F12 → Network → обновите страницу;
    • выберите любой запрос к eda.yandex.ru → Request Headers → Cookie;
    • скопируйте значение целиком (можно сохранить в файл одной строкой).
    npm.cmd run login -- --cookie-file cookies.txt
    # или вставить в интерактивном меню, или одной строкой:
    npm.cmd run login -- --cookie "Session_id=...; yandexuid=...; i=..."
    

    Сервер добавит cookies в .eda/profile, откроет сайт и проверит, что пропала кнопка «Войти». Если проверка не прошла — cookies неполные/просроченные.

  3. Окно браузера. npm.cmd run login -- --manual открывает отдельный профиль; вход выполняется руками один раз.

Флаги:

npm.cmd run login -- --from-chrome          # только импорт из Chrome
npm.cmd run login -- --from-edge            # только импорт из Edge
npm.cmd run login -- --profile "Profile 1"  # другой профиль
npm.cmd run login -- --manual               # войти в окне
npm.cmd run login -- --cookie-file file     # Cookie header из файла
npm.cmd run login -- --cookie "..."         # Cookie header строкой
npm.cmd run login -- --refresh              # повторный импорт/вход, даже если сессия есть
npm.cmd run login -- --import-only          # только импорт, без окна (exit 1 при неудаче)

Cookie даёт полный доступ к аккаунту. Не коммитьте .eda/, не пересылайте cookies и не выводите их в чат без явного согласия пользователя.

Подключение к Kilo

Проектный kilo.json уже содержит MCP-сервер:

{
  "mcp": {
    "yandex-eda": {
      "type": "local",
      "command": ["node", "dist/index.js"],
      "enabled": true,
      "timeout": 600000
    }
  }
}

После правок исходников пересоберите npm.cmd run build и перезапустите сессию.

Другие клиенты

Claude Desktop (claude_desktop_config.json):

{
  "mcpServers": {
    "yandex-eda": {
      "command": "node",
      "args": ["D:/dev/yandex-llm/dist/index.js"],
      "env": { "EDA_HOME": "D:/dev/yandex-llm" }
    }
  }
}

Конфигурация

Скопируйте eda.config.example.json → eda.config.json (файл в .gitignore).

{
  "baseUrl": "https://eda.yandex.ru",
  "dataDir": ".eda",
  "browser": {
    "headless": true,
    "channel": "auto",
    "importFrom": "auto",
    "importProfile": "Default",
    "handoff": "window",
    "timeoutMs": 30000
  },
  "urls": {
    "search": "/search?hideSelector=true&query={query}",
    "cart": "/",
    "checkout": "/checkout",
    "addresses": "/",
    "product": "/search?hideSelector=true&item={id}"
  },
  "limits": { "maxProducts": 20, "actionDelayMs": 350 },
  "api": { "enabled": false, "endpoints": {} },
  "preferences": {
    "stores": { "prefer": [], "softBlock": [], "hardBlock": [] },
    "brands": { "prefer": [], "softBlock": [], "hardBlock": [] },
    "products": { "prefer": [], "softBlock": [], "hardBlock": [] }
  }
}

preferences — личные списки: prefer поднимает в выдаче, softBlock понижает, hardBlock исключает; soft и hard работают одновременно, block сильнее prefer. Пример заполнения:

{
  "preferences": {
    "stores": { "prefer": ["Перекрёсток"], "softBlock": ["Магнит"], "hardBlock": ["Лента"] },
    "brands": { "prefer": [], "softBlock": [], "hardBlock": [] },
    "products": { "prefer": ["Простоквашино"], "softBlock": ["пальмовое масло"], "hardBlock": ["энергетик"] }
  }
}

brands — brand slug сети (brandSlug из поиска), а не бренд производителя; производители задаются в products по названию. Списки учитывают eda_search_products и eda_build_recipe_cart; редактировать их можно в веб-панели (npm run web → «Предпочтения») или прямо в конфиге, проверить — через eda_preferences, обойти разово — ignorePreferences:true. preferences перечитываются при каждом вызове, поэтому изменения применяются без перезапуска; остальные секции конфига читаются при старте. Устаревшие mode + block поддерживаются: записи block попадают в soft или hard по mode, а при сохранении из панели переносятся в softBlock/hardBlock.

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

Переменная Значение
EDA_HOME Корень проекта с package.json, если cwd другой
EDA_CONFIG Путь к другому конфигу
EDA_COOKIE Готовый Cookie:-заголовок (перекрывает cookies.json)
EDA_API_ENABLED=1 Включить HTTP-режим
EDA_HEADLESS=0 Показывать браузер
EDA_IMPORT_FROM auto / none / chrome / msedge
EDA_IMPORT_PROFILE Имя профиля Chrome/Edge (Default, Profile 1)
EDA_DEBUG=1 Подробные логи в stderr
EDA_LOGIN_TIMEOUT Таймаут npm run login, секунды
MCP_TRANSPORT stdio (по умолчанию) или http для Streamable HTTP
MCP_HTTP_HOST / MCP_HTTP_PORT Адрес HTTP-сервера (по умолчанию 127.0.0.1:8787)
MCP_HTTP_PATH Путь MCP-эндпоинта (по умолчанию /mcp)
MCP_AUTH_TOKEN Bearer-токен для /mcp; обязателен при MCP_HTTP_HOST не на loopback
MCP_SESSION_IDLE_MS Закрывать неактивные MCP-сессии, по умолчанию 30 минут, 0 — не закрывать
MCP_MAX_SESSIONS Лимит одновременных MCP-сессий, по умолчанию 50

Данные и безопасность

Всё состояние — в .eda/ (в .gitignore):

.eda/profile/        persistent-профиль Chrome
.eda/cookies.json    cookies для HTTP-режима
.eda/state.json      Playwright storageState
.eda/screenshots/    скриншоты действий
.eda/probes/         дампы разметки
  • Cookies и профиль дают полный доступ к аккаунту. Не коммитьте .eda/, не передавайте его третьим лицам.
  • Сервер не содержит инструментов оплаты, ввода карты и подтверждения заказа. eda_prepare_checkout останавливается на экране оформления.
  • eda_clear_cart / eda_remove_store_cart / eda_set_cart_quantity требуют confirm=true для изменений и поддерживают dryRun=true; скилл обязывает спросить пользователя.
  • Скриншоты возвращаются путём в JSON и MCP-блоком image (base64 PNG), чтобы LLM видел экран.

Ограничения Яндекс.Еды

  • Нет публичного API для покупателя. Сервер автоматизирует веб-интерфейс; разметка может меняться.
  • Антибот и капча. При капче вызовите eda_login и пройдите проверку вручную; headless-режим повышает риск.
  • Одна корзина — один магазин. Товары из другого магазина могут создать отдельную корзину/заменить текущую.
  • Позиции корзины читаются через те же same-origin web-api запросы, что использует сайт (/eats/v1/cart/v2/multi-carts + full-carts), даже если /checkout недоступен. Для гостя доступна только сводка мини-корзины.
  • Checkout требует логина: без сессии Яндекс редиректит на passport.yandex.ru. Если магазин не доставляет на адрес, /checkout редиректит с order_not_available; eda_get_cart вернёт checkoutAvailable=false, checkoutReason и checkoutAddress.
  • История заказов — карточки-вкладки без ссылок; используйте index из eda_list_orders для статуса и повтора.
  • Автоматизация может нарушать пользовательское соглашение сервиса. Используйте на свой риск и без массовых/партнёрских сценариев.

HTTP-режим (опционально)

Чтение можно ускорить внутренними эндпоинтами. Официального API нет, поэтому эндпоинты задаются вручную:

  1. Откройте eda.yandex.ru в DevTools → Network.
  2. Найдите запросы поиска/корзины (обычно api.eda.yandex.ru).
  3. ПКМ → Copy → Copy as cURL; выделите path.
  4. Заполните eda.config.json:
{
  "api": {
    "enabled": true,
    "baseUrl": "https://api.eda.yandex.ru",
    "endpoints": {
      "search": "/api/v1/search?text={query}",
      "cart": "/eats/v1/cart/v2/full-carts?placeSlug={store}",
      "addresses": "/api/v1/addresses"
    },
    "methods": { "cart": "POST" },
    "bodies": { "cart": {} }
  }
}

Подстановки: {query}, {id} и любые другие ключи, которые передаёт код. api.methods задаёт HTTP-метод (по умолчанию GET), api.bodies — JSON-тело для POST/PUT. Cookies берутся из .eda/cookies.json после eda_login. При 401/403 обновите вход. Позиции корзины при этом читаются и без HTTP-режима — через same-origin API внутри браузера.

Инструменты используют API, если операция настроена; иначе падают обратно в браузер. Добавление в корзину и checkout всегда идут через браузер.

MCP по HTTP и деплой в Proxmox

Тот же сервер можно запустить как Streamable HTTP MCP для agentgateway. Транспорт переключается MCP_TRANSPORT=http; stdio остаётся для локального Kilo.

npm.cmd run build
$env:MCP_TRANSPORT="http"; $env:MCP_AUTH_TOKEN="dev-token"; node dist/index.js
# GET http://127.0.0.1:8787/healthz
# MCP: http://127.0.0.1:8787/mcp (Authorization: Bearer dev-token)

В HTTP-режиме eda_prepare_checkout всегда отдаёт handoff=url: контейнер без GUI не может открыть окно на хосте. Оплата по-прежнему не нажимается.

Dockhand (Git-стек)

Dockhand деплоит стек из этого репозитория. В Dockhand:

  1. Settings → Registries и Settings → Git: добавьте forgejo.example.com и PAT с правом читать пакеты и репозиторий. Для self-signed Forgejo задайте в Dockhand NODE_EXTRA_CA_CERTS с CA-сертификатом.
  2. Stacks → New Git stack: репозиторий, ветка main, compose file compose.yaml, context directory ..
  3. Environment overrides: EDA_MCP_IMAGE (образ из Forgejo Registry) и MCP_AUTH_TOKEN (секрет); при необходимости MCP_HTTP_HOST, MCP_HTTP_PORT, MCP_SESSION_IDLE_MS, MCP_MAX_SESSIONS. Они подставляются в ${...} и попадают в контейнер через явный environment: в compose.yaml — env_file: .env для этого не нужен.
  4. Deploy options: включите Re-pull images и Force redeployment. CI пушит :latest, и без этих опций webhook может не пересоздать контейнер. Build images on deploy не включайте — образ уже собран и протестирован CI.
  5. Скопируйте webhook URL стека (.../api/git/stacks/<id>/webhook) в переменную Forgejo DOCKHAND_WEBHOOK_URL, а его секрет — в DOCKHAND_WEBHOOK_SECRET.
  6. Сеть agentgateway должна существовать на Docker-хосте: создайте её один раз или объявите в стеке agentgateway с фиксированным name. Volume yandex-eda-mcp_eda-data при передеплое не удаляйте — в нём сессия Яндекса.

Dockhand должен вести именно Git-стек: compose ссылается на deploy/eda.config.container.json, а клон репозитория содержит и его, и Dockerfile. Для local stack этот файл придётся монтировать отдельно.

Docker Compose без Dockhand

  1. Скопируйте .env.example → .env, задайте MCP_AUTH_TOKEN и образ из Forgejo Registry.
  2. Создайте общую сеть: docker network create agentgateway.
  3. docker compose pull eda-mcp && docker compose up -d eda-mcp; порт 8787 наружу не публикуется. Compose сам читает .env для подстановок.
  4. Подключите deploy/agentgateway.yaml к agentgateway; токен EDA_MCP_TOKEN должен совпадать с MCP_AUTH_TOKEN.
  5. Если agentgateway — бинарь на хосте, а не контейнер, раскомментируйте ports в compose и укажите в его конфиге http://127.0.0.1:8787/mcp.

Сессия Яндекса в контейнере

Профиль .eda/profile с Windows не переносится (cookies зашифрованы ОС). Один раз выполните вход в том же volume. Остановите стек в Dockhand (или docker compose stop eda-mcp), затем на Docker-хосте:

docker volume ls   # yandex-eda-mcp_eda-data
docker run --rm `
  -v yandex-eda-mcp_eda-data:/app/.eda `
  -v "${PWD}/cookies.txt:/run/secrets/cookies:ro" `
  forgejo.example.com/home/yandex-eda-mcp:latest `
  node dist/scripts/login.js --cookie-file /run/secrets/cookies

cookies.txt — полный Cookie:-заголовок с eda.yandex.ru. После успешного входа сервер сохранит .eda/state.json и будет восстанавливать сессию при старте. Не передавайте cookies через MCP: они попадут в чат и логи gateway.

Forgejo Actions и runner

.forgejo/workflows/ci.yaml проверяет typecheck/test/build и собирает образ в Forgejo Registry. Нужны переменная репозитория REGISTRY (например, forgejo.example.com), секреты REGISTRY_USER / REGISTRY_TOKEN (PAT с правом write:package), а для деплоя — DOCKHAND_WEBHOOK_URL (переменная) и DOCKHAND_WEBHOOK_SECRET (секрет). Deploy-job после сборки образа вызывает webhook Dockhand; если URL не задан, job пропускается. Включённый в Dockhand auto-sync на push может опередить сборку образа — надёжнее выключить его и деплоить только webhook'ом из CI.

В Forgejo нет hosted-раннеров: workflow будет висеть, пока не зарегистрирован Forgejo Runner (не Gitea act_runner). В Forgejo открой Site Administration → Actions → Runners (или настройки репозитория) → Create new runner и получи uuid + token.

docker run --rm data.forgejo.org/forgejo/runner:13 \
  forgejo-runner generate-config > data/runner-config.yml

Заполните в runner-config.yml:

server:
  connections:
    forgejo:
      url: https://forgejo.example.com/
      uuid: <uuid из Forgejo>
      token: <token из Forgejo>

runner:
  labels:
    - ubuntu-latest:docker://ghcr.io/catthehacker/ubuntu:act-22.04
  envs:
    DOCKER_HOST: tcp://docker-in-docker:2375

container:
  docker_host: tcp://docker-in-docker:2375
  options: '--add-host=docker-in-docker:host-gateway'

ubuntu-latest — единственная метка, нужная workflow. Образ catthehacker содержит Docker CLI и Node; сборка идёт в DinD, хостовый docker.sock не нужен. Запустить runner можно отдельным local stack в Dockhand:

services:
  docker-in-docker:
    image: docker:dind
    privileged: true
    restart: unless-stopped
    command: ["dockerd", "-H", "tcp://0.0.0.0:2375", "--tls=false"]
    volumes:
      - dind-data:/var/lib/docker

  runner:
    image: data.forgejo.org/forgejo/runner:13
    restart: unless-stopped
    depends_on: [docker-in-docker]
    environment:
      DOCKER_HOST: tcp://docker-in-docker:2375
    volumes:
      - ./data:/data
    command: forgejo-runner daemon --config /data/runner-config.yml

volumes:
  dind-data:

./data должен быть доступен uid/gid 1000; runner-config.yml содержит UUID/token, поэтому не коммитьте его. Версия runner должна соответствовать Forgejo (для v17 — runner 13, для LTS смотрите свою ветку документации). DinD — privileged-контейнер: запускайте на нём только доверенные репозитории и защитите main. Внутренний Docker должен резолвить Forgejo Registry и доверять его сертификату; для self-signed CA монтируется внутрь DinD. Deploy-job дергает Dockhand из job-контейнера, поэтому DOCKHAND_WEBHOOK_URL должен быть LAN/Tailscale-адресом, а не 127.0.0.1.

Ограничения

  • Профиль .eda/profile — один владелец, поэтому запускайте ровно один контейнер (replicas: 1). Все клиенты делят один аккаунт, адрес и корзину. При bind-mount каталога .eda вместо named volume дайте права uid/gid 1000 (пользователь pwuser в образе).
  • Headless-браузер на датацентровом IP может получить капчу. Если egress Proxmox не домашний, проверьте поиск до CI/CD; иначе держите браузер на домашней машине, а agentgateway подключайте к ней по приватной сети/Tailscale.
  • Дайте agentgateway входную аутентификацию (API key/JWT) и rate limit: инструменты меняют корзину и готовят checkout. При необходимости ограничьте набор инструментов политикой agentgateway (CEL/mcpAuthorization), например уберите eda_clear_cart и eda_prepare_checkout для недоверенных клиентов.
  • Dockhand — админ-интерфейс с доступом к Docker-сокету; держите его в приватной сети/VPN. MCP_AUTH_TOKEN храните как секрет Dockhand, не в Git.

Обслуживание селекторов

Разметка проверена в ноябре 2026. Если инструмент пишет «не нашёл кнопку/адрес/строку»:

  1. Вызовите eda_probe_page (home, address, cart, search, checkout, product) или npm.cmd run probe -- checkout.
  2. Откройте .eda/probes/*.json: там data-testid, aria-label, тексты и классы.
  3. Переопределите нужные ключи в eda.selectors.json (мержится с дефолтами):
{
  "addToCart": ["[data-testid=\"product-full-card-add-to-cart\"]"],
  "paymentButton": ["button:has-text(\"Оплатить\")"]
}

Список ключей — в src/eda/selectors.ts.

Разработка

npm.cmd run typecheck   # tsc --noEmit
npm.cmd run build       # dist/
npm.cmd test            # node:test: safety + buildUrl
npm.cmd run dev         # MCP-сервер через tsx
npm.cmd run probe -- search "молоко"

Структура:

src/index.ts            MCP stdio entrypoint
src/tools.ts            регистрация инструментов
src/config.ts           конфиг + URL-шаблоны
src/eda/browser.ts      Playwright: поиск, корзина, адрес, checkout
src/eda/api.ts          HTTP-адаптер (опционально)
src/eda/selectors.ts    селекторы + оверрайды
src/eda/safety.ts       запрет оплаты/подтверждения заказа
src/scripts/            login.ts, probe.ts