- TypeScript 90.3%
- JavaScript 6.6%
- CSS 1.5%
- HTML 1.4%
- Dockerfile 0.2%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
workflow: .forgejo/workflows, Forgejo actions и docker CLI вместо docker actions runner: инструкция по Forgejo Runner и DinD вместо Gitea act_runner compose: EDA_MCP_IMAGE обязателен, без заглушки gitea.example.com |
||
| .forgejo/workflows | ||
| .kilo/skill/yandex-eda | ||
| deploy | ||
| src | ||
| web | ||
| .dockerignore | ||
| .env.example | ||
| .gitignore | ||
| AGENTS.md | ||
| compose.yaml | ||
| Dockerfile | ||
| eda.config.example.json | ||
| kilo.json | ||
| package-lock.json | ||
| package.json | ||
| README.md | ||
| tsconfig.json | ||
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:
- Перезапустите сессию/примените конфиг (MCP-серверы читаются при старте).
- Откройте
/mcps— должен быть включёнyandex-eda. - Попросите агента вызвать
eda_status, затемeda_search_products {query:"молоко", limit:3}. - Проверка изменений:
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) Выйти
-
Импорт. Копирует cookies и ключ шифрования (
Local State) из установленного Chrome/Edge в.eda/profile. Оригинальный профиль не меняется, пароль вводить не нужно. Браузер-источник должен быть полностью закрыт (он блокирует cookie DB); команда ждёт до 60 секунд. -
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 неполные/просроченные. -
Окно браузера.
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 нет, поэтому эндпоинты задаются вручную:
- Откройте eda.yandex.ru в DevTools → Network.
- Найдите запросы поиска/корзины (обычно
api.eda.yandex.ru). - ПКМ → Copy → Copy as cURL; выделите path.
- Заполните
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:
- Settings → Registries и Settings → Git: добавьте
forgejo.example.comи PAT с правом читать пакеты и репозиторий. Для self-signed Forgejo задайте в DockhandNODE_EXTRA_CA_CERTSс CA-сертификатом. - Stacks → New Git stack: репозиторий, ветка
main, compose filecompose.yaml, context directory.. - 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для этого не нужен. - Deploy options: включите Re-pull images и Force redeployment. CI пушит
:latest, и без этих опций webhook может не пересоздать контейнер. Build images on deploy не включайте — образ уже собран и протестирован CI. - Скопируйте webhook URL стека (
.../api/git/stacks/<id>/webhook) в переменную ForgejoDOCKHAND_WEBHOOK_URL, а его секрет — вDOCKHAND_WEBHOOK_SECRET. - Сеть
agentgatewayдолжна существовать на Docker-хосте: создайте её один раз или объявите в стеке agentgateway с фиксированнымname. Volumeyandex-eda-mcp_eda-dataпри передеплое не удаляйте — в нём сессия Яндекса.
Dockhand должен вести именно Git-стек: compose ссылается на deploy/eda.config.container.json, а клон репозитория содержит и его, и Dockerfile. Для local stack этот файл придётся монтировать отдельно.
Docker Compose без Dockhand
- Скопируйте
.env.example→.env, задайтеMCP_AUTH_TOKENи образ из Forgejo Registry. - Создайте общую сеть:
docker network create agentgateway. docker compose pull eda-mcp && docker compose up -d eda-mcp; порт 8787 наружу не публикуется. Compose сам читает.envдля подстановок.- Подключите
deploy/agentgateway.yamlк agentgateway; токенEDA_MCP_TOKENдолжен совпадать сMCP_AUTH_TOKEN. - Если 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/gid1000(пользователь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. Если инструмент пишет «не нашёл кнопку/адрес/строку»:
- Вызовите
eda_probe_page(home,address,cart,search,checkout,product) илиnpm.cmd run probe -- checkout. - Откройте
.eda/probes/*.json: тамdata-testid,aria-label, тексты и классы. - Переопределите нужные ключи в
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