Files
obsidian-vault/personal/tech/balda-executor-mcp.md
T

8.6 KiB
Executable File
Raw Blame History

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-контейнер. Это не работает по двум причинам:

  1. docker.sock в контейнер Балды не смонтирован. Проверено: docker inspect balda-mm даёт только три тома (config/state/workspace) — сокета нет. Значит docker run изнутри Балды невозможен.
  2. В образе Балды нет 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. Что осталось

  1. Переключить runtime.mcp_servers.executor-mcp на type: http (сейчас там stdio-вариант с docker run).
  2. Перезапустить Балду, убедиться, что инструменты видны в сессии.
  3. Сквозная приёмка: executor_run из чата → задача на Mac → результат.
  4. Логин CLI внутри balda-executor — делает Alex вручную.

Связанное