Skip to main content
Glama
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
упереться в потолок практически невозможно.