191 lines
8.6 KiB
Markdown
Executable File
191 lines
8.6 KiB
Markdown
Executable File
# 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. Два режима запуска
|
||
|
||
```bash
|
||
# 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-транспортом.
|
||
Конфиг Балды тогда:
|
||
|
||
```yaml
|
||
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)
|
||
|
||
```bash
|
||
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 вручную**.
|
||
|
||
---
|
||
|
||
## Связанное
|
||
|
||
- [[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, питфоллы
|