8.6 KiB
balda-executor-mcp — MCP-сервер делегирования задач (шаг 5 Фазы 4)
Создан 2026-09-26. Шаг 5 плана personal/plans/balda-mm-phase4-agents. Статус: собран, задеплоен на Kraken, связка проверена до последнего звена; конфиг Балды ещё указывает на stdio-вариант — требует переключения на
type: http. Связка: Балда (Kraken) → MCP → мост (Mac :8790) → контейнер-исполнитель.
1. Зачем отдельный MCP-сервер
Балда не может сама запустить CLI на Mac: Mac за NAT, а runtime.mcp_servers —
это либо stdio-процесс внутри контейнера Балды, либо HTTP по сети.
Схема выбрана такая:
Балда (Kraken, контейнер balda-mm)
│ runtime.mcp_servers.executor-mcp
▼
MCP-сервер balda-executor-mcp (Kraken, контейнер в mattermost_mm-net) ← HTTP
│ X-Bridge-Secret
▼
мост agents-bridge (Mac, 192.168.1.57:8790) ← обычный HTTP по LAN
│ docker exec
▼
контейнер balda-executor (Mac) → CLI claude/codex
2. Расположение и файлы
| Что | Путь |
|---|---|
| Проект | ~/Developer/balda-executor-mcp/ |
| Сервер | server.py (stdio + HTTP в одном файле) |
| Образ | balda-executor-mcp:0.1.0 (python:3.12-slim-bookworm) |
| Dockerfile | ~/Developer/balda-executor-mcp/Dockerfile |
| Compose (Kraken) | /home/kraken/mcp/docker-compose.yml |
| Контейнер на Kraken | balda-executor-mcp (сеть mattermost_mm-net) |
| Порядковый порт | :8890 внутри docker-сети, наружу не публикуется |
3. Инструменты MCP
| Инструмент | Аргументы | Что делает |
|---|---|---|
executor_run |
prompt (обяз.), provider? (claude/codex), idempotency_key? |
ставит задачу, возвращает task_id немедленно |
executor_status |
task_id |
статус + stdout/stderr/exit_code |
executor_health |
— | живость моста и контейнера-исполнителя |
Ошибки возвращаются как isError: true с человеческим текстом:
- 429 → «Исполнитель занят (лимит одновременных задач)»
- 404 → «задача … не найдена»
- прочие → «мост вернул N: …»
4. Переменные окружения
| Ключ | Смысл |
|---|---|
BRIDGE_URL |
базовый URL моста (например http://192.168.1.57:8790) |
BRIDGE_SECRET |
общий секрет, уходит в X-Bridge-Secret |
BRIDGE_TIMEOUT |
таймаут обычных вызовов (по умолчанию 60 с) |
MCP_HOST / MCP_PORT |
адрес HTTP-режима (по умолчанию 127.0.0.1:8890) |
5. Два режима запуска
# stdio (Балда сама запускает процесс)
python3 server.py
# HTTP (отдельный контейнер, Балда ходит по URL)
python3 server.py --http --host 0.0.0.0 --port 8890
⚠️ Использовать надо HTTP-режим. Причины — §7.
6. Приёмка (2026-09-26) — что именно проверено
| Проверка | Результат |
|---|---|
stdio: initialize → tools/list |
3 инструмента возвращены ✅ |
stdio: tools/call health |
живые данные моста ✅ |
| stdio: 429 / 500 / 404 | корректные isError с текстом ✅ |
| образ: тот же handshake внутри контейнера | ✅ |
| образ на Kraken: загрузка | Loaded image: balda-executor-mcp:0.1.0 ✅ |
| Kraken → мост по LAN (curl из docker-сети) | status: ok ✅ |
MCP в docker-сети → executor_health через мост |
живые данные Mac ✅ |
контейнер balda-executor-mcp |
Up ✅ |
Тестовые скрипты: /tmp/test-mcp-server.sh (мок моста + stdio-проба),
/tmp/kraken-test-mcp.sh (ручной handshake из docker-сети Kraken).
7. 🔴 Питфолл: stdio-вариант нерабочий в этом окружении
Изначальный план был type: stdio + cmd: ["docker", "run", ...] — то есть Балда
сама запускает MCP-контейнер. Это не работает по двум причинам:
docker.sockв контейнер Балды не смонтирован. Проверено:docker inspect balda-mmдаёт только три тома (config/state/workspace) — сокета нет. Значитdocker runизнутри Балды невозможен.- В образе Балды нет shell.
docker exec balda-mm sh -c ...→OCI runtime exec failed: exec: "sh": executable file not found in $PATH(образ изscratch). Отладка и запуск подпроцессов через shell недоступны.
Решение: MCP-сервер вынести в отдельный контейнер с HTTP-транспортом. Конфиг Балды тогда:
runtime:
mcp_servers:
executor-mcp:
type: http
url: "http://balda-executor-mcp:8890/mcp"
balda:
mcp_servers:
- executor-mcp
8. 🔴 Питфолл: --chmod=644 для файла, запускаемого не-root
Dockerfile изначально делал COPY server.py /app/server.py + USER mcpsvc → при
запуске: python3: can't open file '/app/server.py': [Errno 13] Permission denied.
Фикс: COPY --chmod=644 server.py /app/server.py. Не-root пользователь должен уметь
читать код, иначе контейнер падает ещё до handshake.
Симптом обманчив — выглядит как поломка образа, а не как права файла.
9. Питфолл: docker-compose на Mac, docker compose на Kraken
| Хост | Как вызывать |
|---|---|
| Mac (Colima) | docker-compose (v5.1.3, отдельный бинарь) |
| Kraken | docker compose (плагин v5.1.3); docker-compose → command not found |
Одна и та же версия, но на разных хостах разный способ вызова. Скрипты деплоя для двух хостов нельзя копировать без правки этой строки.
10. HTTP-transport: ручная проба (streamable)
docker run --rm --network mattermost_mm-net curlimages/curl:latest \
-s --max-time 10 -X POST http://balda-executor-mcp:8890/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":"t","version":"0"}}}' -i
⚠️ Заголовок Accept: text/event-stream обязателен — сервер отвечает SSE.
Без него — отказ. Ответ отдаётся как event: message\ndata: {...}.
GET /healthz у MCP-сервера отдаёт {"status":"ok","bridge_url":...,"secret_set":true}.
11. Что осталось
- Переключить
runtime.mcp_servers.executor-mcpнаtype: http(сейчас там stdio-вариант сdocker run). - Перезапустить Балду, убедиться, что инструменты видны в сессии.
- Сквозная приёмка:
executor_runиз чата → задача на Mac → результат. - Логин CLI внутри
balda-executor— делает Alex вручную.
Связанное
- personal/plans/balda-mm-phase4-agents — план Фазы 4 (шаг 5)
- personal/tech/agents-bridge — мост :8790, к которому ходит этот MCP
- personal/projects/balda/balda-kraken-mm-deploy — деплой Балды на Kraken (конфиг,
.env, сети) - personal/tech/macos-docker-virtiofs-mount-limits — ограничения macOS Docker
~/Developer/balda-executor-mcp— код- скилл
native-mcp— handshake streamable-http, питфоллы