routerai-image-mcp: MCP-сервер генерации картинок через RouterAI
- server.py: MCP-сервер (stdio + streamable HTTP), инструменты generate_image / list_image_models - два бэкенда: chat/completions (Gemini Image, понимает правку по референсу) и images/generations (FLUX, GPT-Image) - ключ RouterAI берётся из env или ~/.hermes/models.yaml автоматически - retry на 429/5xx, картинка сохраняется файлом (~/.hermes/image_cache/mcp-gen) - test_client.py / test_edit.py: проверка по протоколу, включая image-to-image - README: установка, настройка, подключение к Hermes/opencode, питфоллы
This commit is contained in:
@@ -0,0 +1,222 @@
|
||||
# routerai-image-mcp
|
||||
|
||||
MCP-сервер генерации и редактирования картинок через **RouterAI**
|
||||
(`https://routerai.ru/api/v1`). Даёт любой модели — в том числе той, что
|
||||
рисовать не умеет, но умеет ставить ТЗ другим — инструмент `generate_image`:
|
||||
передал текстовое описание, получил файл с картинкой.
|
||||
|
||||
## Зачем
|
||||
|
||||
Агентные модели (DeepSeek, Qwen, локальные) отлично формулируют ТЗ, но не
|
||||
генерируют изображения. Этот сервер закрывает разрыв: модель вызывает
|
||||
`generate_image(prompt=...)`, сервер ходит в модель генерации картинок
|
||||
(Nano Banana / Gemini Image, FLUX, GPT-Image), кладёт результат файлом и
|
||||
возвращает путь. Модель вставляет этот путь в ответ — клиент показывает картинку.
|
||||
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
LLM (agent) ──MCP──▶ routerai-image-mcp ──HTTPS──▶ routerai.ru/api/v1
|
||||
│
|
||||
└─▶ файл на диске (~/.hermes/image_cache/mcp-gen/)
|
||||
возвращает: "MEDIA:<путь>"
|
||||
```
|
||||
|
||||
Два бэкенда, выбираются автоматически по id модели:
|
||||
|
||||
| Backend | Что вызывается | Модели |
|
||||
|---|---|---|
|
||||
| `chat` | `POST /chat/completions` с `modalities: ["image","text"]` | Gemini Image / Nano Banana |
|
||||
| `images` | `POST /images/generations` | FLUX.2, GPT-Image |
|
||||
|
||||
`chat`-бэкенд дополнительно умеет **image-to-image**: если передать
|
||||
`reference_images`, они уходят в запрос как image_url-части, и модель
|
||||
редактирует присланную картинку (сохранив сюжет) — проверено на «поменяй фон».
|
||||
|
||||
## Требования
|
||||
|
||||
- Python 3.10+
|
||||
- Пакет `mcp` (`pip install mcp`) — в Hermes он уже есть:
|
||||
`/usr/local/lib/hermes-agent/venv/bin/python`
|
||||
- Ключ RouterAI (см. «Настройка»)
|
||||
- Сетевой доступ к `https://routerai.ru`
|
||||
|
||||
## Установка
|
||||
|
||||
```bash
|
||||
git clone https://git.binom.pw/subochev/routerai-image-mcp
|
||||
cd routerai-image-mcp
|
||||
|
||||
python3 -m venv .venv
|
||||
.venv/bin/pip install -r requirements.txt
|
||||
```
|
||||
|
||||
Либо без своего venv — использовать интерпретатор Hermes (там `mcp` уже стоит):
|
||||
|
||||
```bash
|
||||
/usr/local/lib/hermes-agent/venv/bin/python server.py
|
||||
```
|
||||
|
||||
## Настройка
|
||||
|
||||
Ключ ищется в таком порядке:
|
||||
|
||||
1. `ROUTERAI_API_KEY` (или `IMAGE_MCP_API_KEY`) в окружении;
|
||||
2. `~/.hermes/models.yaml` → `models.deepseek.api_key` (если у записи
|
||||
`base_url` содержит `routerai.ru`);
|
||||
3. любой подходящий `api_key` из `models.yaml`, где `base_url` = routerai.ru.
|
||||
|
||||
То есть на машине с Hermes, где `models.yaml` уже настроен, **ничего задавать
|
||||
не нужно** — ключ подхватится сам. Явный способ:
|
||||
|
||||
```bash
|
||||
export ROUTERAI_API_KEY='sk-...'
|
||||
```
|
||||
|
||||
Переменные окружения сервера:
|
||||
|
||||
| Переменная | Дефолт | Смысл |
|
||||
|---|---|---|
|
||||
| `ROUTERAI_API_KEY` | — | Ключ RouterAI (иначе берётся из `models.yaml`) |
|
||||
| `ROUTERAI_BASE_URL` | `https://routerai.ru/api/v1` | Базовый URL API |
|
||||
| `IMAGE_MCP_DEFAULT_MODEL` | `google/gemini-3.1-flash-image` | Модель по умолчанию |
|
||||
| `IMAGE_MCP_OUT_DIR` | `~/.hermes/image_cache/mcp-gen` | Куда складывать картинки |
|
||||
| `IMAGE_MCP_TIMEOUT` | `300` | Таймаут запроса, сек |
|
||||
| `MCP_TRANSPORT` | `stdio` | `stdio` или `http` |
|
||||
| `MCP_HOST` / `MCP_PORT` | `0.0.0.0` / `8790` | Только для `http` |
|
||||
|
||||
## Запуск
|
||||
|
||||
### stdio (обычный режим для локальных клиентов)
|
||||
|
||||
```bash
|
||||
python server.py
|
||||
```
|
||||
|
||||
### HTTP (когда сервер нужен по сети нескольким клиентам)
|
||||
|
||||
```bash
|
||||
MCP_TRANSPORT=http MCP_PORT=8790 python server.py
|
||||
```
|
||||
|
||||
Эндпоинт: `http://<host>:8790/mcp` (streamable HTTP). Проверка:
|
||||
|
||||
```bash
|
||||
curl -s -X POST http://127.0.0.1:8790/mcp \
|
||||
-H 'Content-Type: application/json' \
|
||||
-H 'Accept: application/json, text/event-stream' \
|
||||
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"probe","version":"1"}}}'
|
||||
# → event: message / data: {... "serverInfo":{"name":"routerai-image"}}
|
||||
```
|
||||
|
||||
## Подключение к Hermes
|
||||
|
||||
```bash
|
||||
hermes mcp add routerai-image \
|
||||
--command /usr/local/lib/hermes-agent/venv/bin/python \
|
||||
--args /opt/routerai-image-mcp/server.py
|
||||
```
|
||||
|
||||
На вопрос «Enable all N tools?» ответить `Y`. Проверка:
|
||||
|
||||
```bash
|
||||
hermes mcp test routerai-image
|
||||
# ✓ Connected, Tools discovered: 2 → generate_image, list_image_models
|
||||
```
|
||||
|
||||
⚠️ **Инструменты появляются только в новой сессии** — MCP-серверы
|
||||
подключаются при старте Hermes; в уже идущем диалоге их не будет.
|
||||
|
||||
Если сервер развёрнут на отдельной машине, вместо stdio удобнее HTTP:
|
||||
|
||||
```bash
|
||||
hermes mcp add routerai-image --url http://<host>:8790/mcp
|
||||
```
|
||||
|
||||
## Подключение к другим клиентам
|
||||
|
||||
opencode (`~/.config/opencode/config.json`):
|
||||
|
||||
```json
|
||||
{
|
||||
"mcp": {
|
||||
"routerai-image": {
|
||||
"type": "local",
|
||||
"command": ["/usr/local/lib/hermes-agent/venv/bin/python", "/opt/routerai-image-mcp/server.py"],
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Claude Desktop / прочие stdio-клиенты — тот же `command` + `args`.
|
||||
|
||||
## Инструменты
|
||||
|
||||
### `generate_image`
|
||||
|
||||
| Аргумент | Тип | Смысл |
|
||||
|---|---|---|
|
||||
| `prompt` | str | ТЗ: сюжет, стиль, свет, композиция, пропорции |
|
||||
| `model` | str | id модели RouterAI (дефолт `google/gemini-3.1-flash-image`) |
|
||||
| `reference_images` | list[str] | Пути к картинкам для правки/референса (image-to-image) |
|
||||
| `out_dir` | str | Куда сложить файл |
|
||||
| `filename` | str | Имя файла (расширение подставится само) |
|
||||
|
||||
Возвращает текст с абсолютным путём и строкой `MEDIA:<путь>`. **Чтобы клиент
|
||||
показал картинку, модель должна вставить эту строку `MEDIA:<путь>` целиком в
|
||||
свой ответ.**
|
||||
|
||||
### `list_image_models`
|
||||
|
||||
Печатает список поддерживаемых моделей, их бэкенды и дефолт.
|
||||
|
||||
## Модели и стоимость
|
||||
|
||||
| id | Что это | Цена (факт, за картинку) |
|
||||
|---|---|---|
|
||||
| `google/gemini-3.1-flash-image` | Nano Banana 2 — дефолт | ~7.5 ₽ |
|
||||
| `google/gemini-3.1-flash-lite-image` | Nano Banana 2 Lite — самый дешёвый | ~3.7 ₽ |
|
||||
| `google/gemini-2.5-flash-image` | Nano Banana (2.5) | ~4.3 ₽ |
|
||||
| `google/gemini-3-pro-image` | Nano Banana Pro | ~13 ₽ |
|
||||
| `black-forest-labs/flux.2-klein-4b` | FLUX.2 Klein — дешёвый, без правки | — |
|
||||
| `black-forest-labs/flux.2-pro` / `-max` / `-flex` | FLUX.2 Pro/Max/Flex | — |
|
||||
| `openai/gpt-image-1` / `-mini` | GPT Image | — |
|
||||
|
||||
Ключи без значения — считаются по своему тарифу, проверяйте на
|
||||
`GET https://routerai.ru/api/v1/models` (поле `pricing.image_output`).
|
||||
Gemini-Image дороже других, зато понимает сложные ТЗ и умеет правку по референсу.
|
||||
|
||||
## Проверка
|
||||
|
||||
```bash
|
||||
# список инструментов по протоколу + генерация
|
||||
/usr/local/lib/hermes-agent/venv/bin/python test_client.py --generate google/gemini-3.1-flash-lite-image
|
||||
|
||||
# правка существующей картинки по референсу
|
||||
/usr/local/lib/hermes-agent/venv/bin/python test_edit.py /путь/к/картинке.jpg
|
||||
```
|
||||
|
||||
Оба скрипта поднимают сервер по stdio и работают с ним как настоящий MCP-клиент.
|
||||
|
||||
## Питфоллы
|
||||
|
||||
- **`llm.binom.pw` может лежать (502), а `routerai.ru` — работать.** Сервер
|
||||
ходит напрямую в `routerai.ru` и не зависит от Bifrost/llm-proxy. Если у вас
|
||||
всё настроено через роутер и он упал — картинки всё равно будут.
|
||||
- **Путь к интерпретатору.** `python3` из системы может не иметь пакета `mcp`.
|
||||
В Hermes надёжнее указывать его venv: `/usr/local/lib/hermes-agent/venv/bin/python`.
|
||||
- **Картинки нужны модели, а не агенту.** Возвращается путь к файлу, а не
|
||||
base64 в контекст — контекст не забивается мегабайтами.
|
||||
- **Модели, отдающие картинки иначе.** `flux.2-*` и `gpt-image-*` не принимают
|
||||
`modalities` — для них используется `/images/generations`; определяется
|
||||
автоматически по таблице `MODELS` в начале `server.py`. Новая модель из
|
||||
другого семейства — дописать её туда.
|
||||
- **RouterAI отдаёт префикс-мусор перед JSON** на `/images/generations`
|
||||
(переводы строк и пробелы) — парсер ищет первый `{`.
|
||||
- **429/5xx** — сервер сам делает 3 повтора с нарастающей паузой; 403
|
||||
`model_blocked` означает, что модель не разрешена ключу на роутере.
|
||||
|
||||
## Лицензия
|
||||
|
||||
Apache License 2.0 — см. `LICENSE`.
|
||||
Reference in New Issue
Block a user