ya-direct-mcp
by dmburov
README.md
# ya-direct-mcp
MCP-сервер для Яндекс.Директ API v5. Управление кампаниями, группами, объявлениями,
ключевыми фразами и выгрузка статистики — через инструменты, доступные Claude.
Транспорт — Streamable HTTP. Рассчитан на деплой в Railway.
Поддерживаются текстово-графические кампании (`TEXT_CAMPAIGN`).
---
## 1. Что внутри
**Кампании:** `list_campaigns`, `get_campaign`, `create_campaign`, `update_campaign`,
`suspend_campaign`, `resume_campaign`, `archive_campaign`, `delete_campaign`
**Группы:** `list_adgroups`, `create_adgroup`, `update_adgroup`, `delete_adgroup`
**Объявления:** `list_ads`, `create_ad`, `update_ad`, `moderate_ad`, `suspend_ad`,
`resume_ad`, `delete_ad`
**Ключевые фразы:** `list_keywords`, `add_keywords`, `update_keyword_bids`,
`suspend_keywords`, `resume_keywords`, `delete_keywords`
**Статистика:** `get_report`, `get_account_balance`
Все инструменты принимают необязательный параметр `client_login`. Если он не передан,
используется логин из переменной окружения `YD_LOGIN`. Это позволяет обслуживать
несколько рекламодателей одним сервером.
---
## 2. Переменные окружения
| Переменная | Обязательна | Назначение |
|---|---|---|
| `YD_TOKEN` | да | OAuth-токен Яндекс.Директа |
| `YD_LOGIN` | да | Логин рекламодателя по умолчанию |
| `MCP_SECRET` | да | Собственный секрет сервера для защиты эндпоинта. **Не** токен Яндекса |
| `PORT` | нет | Railway подставляет автоматически |
| `LOG_LEVEL` | нет | `INFO` по умолчанию |
| `YD_REPORT_TIMEOUT` | нет | Максимум ожидания отчёта, сек. По умолчанию 120 |
Сгенерировать `MCP_SECRET`:
```bash
python -c "import secrets; print(secrets.token_urlsafe(32))"
```
---
## 3. Деплой в Railway
1. **New Project → Deploy from GitHub repo** → выбрать `ya-direct-mcp`.
2. **Variables** → добавить `YD_TOKEN`, `YD_LOGIN`, `MCP_SECRET`.
Переменную `PORT` не задавать — Railway проставит сам.
3. **Settings → Networking → Generate Domain**. Появится адрес вида
`ya-direct-mcp-production.up.railway.app`.
4. Дождаться сборки. Проверить готовность:
```bash
curl https://ВАШ-ДОМЕН.up.railway.app/health
# ожидается: ok
```
Nixpacks определит Python по `requirements.txt` и запустит команду из `railway.json`.
---
## 4. Подключение к Claude
Адрес MCP-эндпоинта включает секрет:
```
https://ВАШ-ДОМЕН.up.railway.app/mcp/ВАШ_MCP_SECRET
```
**Настройки → Connectors → Add custom connector** → вставить этот URL → **Add**.
Затем в чате: кнопка **+** → **Connectors** → включить `yandex-direct`.
Сервер принимает авторизацию двумя способами:
- **секрет в пути URL** (как выше) — работает всегда;
- **заголовок** `Authorization: Bearer <MCP_SECRET>` — если в вашем интерфейсе
доступна секция «Request headers» (функция в бете и раскатывается постепенно).
Достаточно любого из двух.
---
## 5. Локальный запуск
```bash
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # заполнить своими значениями
export $(grep -v '^#' .env | xargs)
python src/server.py
# → http://localhost:8000/mcp/<MCP_SECRET>
```
---
## 6. Безопасность
- Эндпоинт открыт в интернет. Единственная защита — `MCP_SECRET`.
Утечка секрета = полный доступ к рекламному аккаунту, включая удаление кампаний.
- Секрет хранится только в переменных Railway. В репозиторий не коммитится.
- При компрометации: сменить `MCP_SECRET` в Railway (сервис перезапустится сам),
затем пересоздать коннектор в Claude с новым URL.
- `delete_campaign` защищён предохранителем: требует точного совпадения имени
кампании с переданным значением `confirm_name`. Остальные `delete_*`
предохранителя не имеют — удаление необратимо.
- OAuth-токен Директа живёт только в окружении сервера. Claude его не видит.
---
## 7. Денежные значения
API Директа принимает деньги в **микроединицах**: 1 ₽ = 1 000 000.
Инструменты этого сервера конвертируют автоматически — в `daily_budget_amount`,
`bid` и `context_bid` передавайте рубли.
**Исключение:** объекты стратегий (`strategy_search`, `strategy_network`) передаются
в API как есть, поэтому суммы внутри них указывайте **в микроединицах**.
---
## 8. Пример: смена целевой цены конверсии
```
strategy_search = {"BiddingStrategyType": "SERVING_OFF"}
strategy_network = {
"BiddingStrategyType": "NETWORK_DEFAULT",
"NetworkDefault": {
"BiddingStrategyType": "AVERAGE_CPA",
"AverageCpa": {
"AverageCpa": 1000000000, // 1000 ₽ в микроединицах
"GoalId": 503496561
}
}
}
```
При смене стратегии нужно передавать **оба** поля — API требует объект
`BiddingStrategy` целиком.
---
## 9. Расход баллов
Лимит суточных баллов приходит в заголовке `units` каждого ответа и пишется в логи.
Чтение расходует единицы баллов, отчёты — десятки. При суточном лимите ~996 000
упереться в потолок практически невозможно.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues