vibe-agent-mcp
README.md
# vibe-agent-mcp
Автономный ИИ-агент для Agent API «Вайб-Маркетолог», живущий на тарифах
Claude Code (без отдельных платных API-токенов модели) — второе тестовое
задание для компании.
Первое тестовое (детерминированный линтер их генератора,
[ad-preflight](https://github.com/yagaMI-Reverse/ad-preflight)) принято на
90/100 и названо основателем «одной из самых сильных работ кампании»: он
нашёл, что 34% сгенерированных заголовков превышают лимит Яндекс.Директа
(58–59 знаков при лимите 56). Это тестовое — тот же линтер, но встроенный в
автономный цикл: сгенерировать → проверить → переформулировать →
остановиться, с MCP-сервером сверху, чтобы Claude Code мог сам дёргать API.
## Что это
- **`mcp_server/server.py`** — MCP-сервер, 8 инструментов поверх Agent API
(5 бесплатных read-only/локальных, 3 платных write).
- **`orchestrator/run_agent.py`** — автономный цикл с бюджетным гейтом:
генерирует объявления → прогоняет через preflight → **механически чинит
локально** то, что чинится обрезкой (превышение длины) → если остались
содержательные дефекты (непроверенные обещания), переформулирует `utp` и
повторяет → останавливается по чистоте выдачи или по бюджету/итерациям.
- **`vendor/ad-preflight`** — git submodule на первый тестовый проект;
чек-логика (`check_ad`, лимиты Директа, ст. 5 ФЗ «О рекламе», сверка
обещаний с лендингом) переиспользуется напрямую, не переписана.
- **[cosmetics-demo-shop](https://github.com/yagaMI-Reverse/cosmetics-demo-shop)** —
отдельный демо-магазин косметики (свой, не чужой реальный сайт), чтобы
проверить решение не только на лендинге самой платформы. [Живой сайт](https://yagami-reverse.github.io/cosmetics-demo-shop/).
## Важные поправки к брифу (найдено на живом API, не в документации)
1. **Нет бесплатного `/generate/estimate` для `direct/*`.** Он бесплатен
только для общей мультимедиа-фабрики (image/video/text/voice/music) и для
`brand_id`-полей. У `direct/ads-generate` (49₽), `direct/landing-audit`
(39₽), `direct/sitelinks` (29₽), `direct/forecast` (49₽) цена
**фиксированная** и известна заранее из `/capabilities.direct_tools`.
Бюджетный гейт в этом решении сравнивает потрачённое с этой таблицей цен,
а не делает pre-flight dry-run запрос перед каждым платным вызовом.
2. **`/api/agent/brands` реально работает** (0 брендов на аккаунте), но
`direct/ads-generate` принимает только `url`/`utp`, не `brand_id` — бренд
не пробрасывается в генератор напрямую.
3. **`/agent/list`, `/agent/message`, `safety-check`** недоступны этому
токену (`404 not_found`, не вопрос скоупа) — это Bitrix24-инбокс и риск-
оценка реальных кампаний Директа, по документации «предоставляются по
партнёрскому соглашению». Не используются в решении.
Подробности проверки — в `docs/boundaries.md`.
## Как запустить
```bash
git clone --recurse-submodules <URL этого репо>
cd vibe-agent-mcp
pip install -r requirements.txt
```
Токен: `VIBE_API_TOKEN` в окружении или файл `C:/Users/Ilay/vibe_token.txt`
(в репозиторий не коммитится, см. `.gitignore`). Пример — `.env.example`.
### Отладка без реальных денег
```bash
python orchestrator/run_agent.py --url https://vibemarketolog.ru --dry-run \
--max-iterations 3 --out runs/test/report.md
```
`--dry-run` вообще не бьёт в платное API — гоняет весь цикл (гейт, генерация,
preflight, переформулировка, отчёт) на `fixtures/raw_response_sample.json`
(реальный платный ответ с первого тестового). Это позволяет отладить всю
логику на 0₽ перед тем, как тратить реальный баланс.
### Реальный автономный прогон
```bash
python orchestrator/run_agent.py --url <лендинг> --max-iterations 3 --audit \
--out runs/<дата>/report.md
```
### Регистрация MCP-сервера в Claude Code
```bash
claude mcp add vibe -- python <абсолютный путь>/mcp_server/server.py
```
## MCP-инструменты (8)
| Инструмент | Стоимость | Что делает |
|---|---|---|
| `account_status` | бесплатно | баланс, дневной лимит и остаток |
| `price_table` | бесплатно | фиксированные цены `direct/*` из `/capabilities` |
| `list_brands` | бесплатно | список брендов/товаров аккаунта |
| `get_brand` | бесплатно | профиль одного бренда |
| `preflight_check` | бесплатно, локально | вердикты ГОДНО/ИСПРАВИМО/БРАК/ГАЛЛЮЦИНАЦИЯ по пакету объявлений |
| `generate_ads` | **49₽** | `direct/ads-generate` — пакет объявлений |
| `audit_landing` | **39₽** | `direct/landing-audit` — аудит лендинга |
| `generate_sitelinks` | **29₽** | `direct/sitelinks` — быстрые ссылки и уточнения (бонус, не в брифе) |
## Проверено: MCP-протокол end-to-end (01.08.2026)
`claude mcp add vibe -- python mcp_server/server.py` + `claude mcp list` →
`✔ Connected` — но это только health-check хендшейка. Сделал более строгую
проверку: поднял настоящий MCP-клиент (`mcp.client.stdio`, тот же протокол,
что использует сам Claude Code) и прогнал реальный цикл `initialize` →
`tools/list` → `tools/call`:
```
TOOLS: ['account_status', 'price_table', 'list_brands', 'get_brand',
'generate_ads', 'audit_landing', 'generate_sitelinks', 'preflight_check']
account_status -> {"balance": 72, ..., "daily_spend_remaining": 363, ...}
price_table -> {"direct/landing-audit": 39, "direct/ads-generate": 49, ...}
```
Все 8 инструментов видны по протоколу, вызов реально бьёт в живой API и
возвращает актуальные данные — не заглушку. Это подтверждает, что сервер
работает не только как импортируемый Python-модуль (как было проверено
раньше), а как настоящий MCP-сервер по stdio.
## Локальный фиксер длины — почему это сработало, а переформулировка нет
Прогон 31.07 (ниже) показал: просьба словами «заголовок ≤56 знаков» в `utp`
не работает. Вместо неё — механическая обрезка по границе слова для
объявлений, у которых **единственная** причина брака — превышение длины
(`orchestrator/run_agent.py:apply_local_fixes`). Ничего не генерируется
заново, ничего не оплачивается — просто пересчитывается вердикт после
обрезки. Результат на живых данных 01.08.2026 (лендинг
[cosmetics-demo-shop](https://github.com/yagaMI-Reverse/cosmetics-demo-shop)):
| Итерация | utp | стоимость | ГОДНО | ИСПРАВИМО | БРАК | ГАЛЛЮЦИНАЦИЯ | починено локально |
|---|---|---|---|---|---|---|---|
| 1 | (пусто) | 49₽ | 135 | 3 | 0 | 9 | **81** |
| 2 | «упоминай только реальные скидки/сроки/гарантии с лендинга» | 49₽ | 136 | 11 | 0 | 0 | 66 |
Итерация 1: из 147 объявлений фиксер локально почистил 81 (были бы БРАК из-за
длины) — остались только 9 содержательных ГАЛЛЮЦИНАЦИЯ (выдуманные
подробности, не обрезкой не почини). Итерация 2: переформулировка `utp`
для этого класса дефекта **сработала** — 0 галлюцинаций. Итог: **0 БРАК,
0 ГАЛЛЮЦИНАЦИЯ, 2 итерации вместо 3, 137₽ вместо безуспешных 186₽**, и
впервые цикл реально сходится к чистой выдаче, а не просто честно
останавливается по лимиту с браком на руках.
Полные материалы: [`runs/2026-07-31_cosmetics-demo/`](runs/2026-07-31_cosmetics-demo/).
Честно: обрезка по границе слова иногда обрывает мысль на полуслове
(«…нейросетей для фото баннеров и креативов в одном» вместо «…в одном
месте») — грамматически и по лимитам корректно, но не отредактировано
человеком. Для реального запуска кампании это годится как «прошло технический
контроль», не как финальная редактура текста.
## Результат первого живого прогона — 31.07.2026 (до фиксера)
Полные материалы: [`runs/2026-07-31_live/`](runs/2026-07-31_live/) —
[report.md](runs/2026-07-31_live/report.md),
[console_log.txt](runs/2026-07-31_live/console_log.txt) (полный stdout прогона).
Лендинг: `vibemarketolog.ru`. Бюджет: 3 генерации + 1 аудит (186₽ из 453₽
на балансе). Баланс до прогона — 453₽, после — 267₽ (проверено через
`/balance` независимо от лога прогона): списано ровно 186₽ = 3×49 + 39.
| Итерация | utp | стоимость | ГОДНО | ИСПРАВИМО | БРАК | ГАЛЛЮЦИНАЦИЯ |
|---|---|---|---|---|---|---|
| 1 | (пусто) | 49₽ | 45 | 12 | 90 | 0 |
| 2 | «каждый заголовок ≤56 знаков…» | 49₽ | 32 | 4 | 111 | 0 |
| 3 | «каждый заголовок ≤56 знаков…» | 49₽ | 15 | 0 | 153 | 0 |
Остановка: исчерпаны 3 итерации, брак остаётся (программа честно не
рапортует успех, которого не было).
**Честная находка**: переформулировка `utp` с текстовой инструкцией про длину
заголовка не снизила брак, а увеличила его — 90→111→153. Разбор причины и
вывод — в `docs/boundaries.md`, раздел 1. Это не баг цикла: гейт и критерий
остановки отработали корректно (не зациклились, не приукрасили результат),
проблема в самой стратегии «попроси генератор словами исправить формат» для
этого типа дефекта.
**Что сделано с этой находкой дальше**: не выброшена, а исправлена — см.
раздел выше «Локальный фиксер длины». Переформулировка `utp` осталась только
для содержательных дефектов (галлюцинации), для длины теперь механическая
обрезка. На новом прогоне это дало 0 БРАК за 2 итерации вместо честного
поражения за 3.
## Бонус: `direct/sitelinks` (29₽) — вне брифа, для полноты MCP-инструментов
Живой вызов на том же лендинге вернул 8 корректных sitelinks (title≤30,
description≤60) — сырой ответ: [`runs/2026-07-31_live/raw/sitelinks.json`](runs/2026-07-31_live/raw/sitelinks.json).
**Честно про этот вызов**: он оплачен дважды (58₽ вместо 29₽) — я тестировал
скрипт демо-вызова, он упал на отсутствующей папке `runs/…/raw/` уже ПОСЛЕ
успешного платного POST, я пересоздал папку и перезапустил, оплатив ещё раз.
Баланс упал 267₽ → 209₽ вместо ожидаемых 267₽ → 238₽. Вывод для реального
использования: любой скрипт с платным вызовом обязан гарантировать
существование выходной директории (или писать во временный файл) **до**
запроса, а не полагаться на то, что запись после него не упадёт. Мелочь, но
именно так на реальных деньгах теряются 29₽ по невнимательности — а не из-за
API.
## Границы решения
Полный честный список — [`docs/boundaries.md`](docs/boundaries.md): что
проверено вживую, что нет, где решение сломается и почему.
---
Илья Шаповалов · [ilya-proof.vercel.app](https://ilya-proof.vercel.app) ·
[github.com/yagaMI-Reverse](https://github.com/yagaMI-Reverse)
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues