[2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/projects/personal-os/obsidian-mcp-setup.md personal/tech/balda-executor-mcp.md personal/tech/hermes-secret-redaction-bypass.md

This commit is contained in:
Alexey Martemyanov committed 2026-09-26 22:13:08 +06:00
1 parent ef0d21f271
commit c3ced58ee1
4 files changed
+254 -62

No files matched your search

+75 -12
View File
@@ -1,8 +1,8 @@
# balda-executor-mcp — MCP-сервер делегирования задач (шаг 5 Фазы 4)
> Создан 2026-09-26. Шаг 5 плана [[personal/plans/balda-mm-phase4-agents]].
> Статус: **собран, задеплоен на Kraken, связка проверена до последнего звена**;
> конфиг Балды ещё указывает на stdio-вариант — требует переключения на `type: http`.
> Статус: **собран, задеплоен на Kraken, образ обновлён до 0.2.0; конфиг Балды
> переведён на `type: http`, Obsidian MCP прописан рядом** — ждёт сквозной проверки в чате.
> Связка: Балда (Kraken) → MCP → мост (Mac :8790) → контейнер-исполнитель.
---
@@ -225,24 +225,87 @@ command: ["--http", "--host", "0.0.0.0", "--port", "8890"]
---
## 10e. 🔴🔴 ГЛАВНЫЙ ПИТФОЛЛ: тип транспорта — `http`, а НЕ `streamable-http`
Самый дорогой урок этой сессии. Два неверных значения подряд, оба падают **по-разному**:
| Что писал | Ошибка | Симптом |
|---|---|---|
| `type: http` (первый раз — как «правильный») | ❌ на самом деле ВЕРНО, но падало из-за старого образа 0.1.0 | `failed to init MCP session: calling "initialize": sending "initialize": failed to connect (session ID: ): session not found` |
| `type: streamable-http` | ❌ не существует в валидаторе | `Error: validate runtime config: mcp "executor-mcp": mcp server config validation failed: type must be one of: stdio http sse` → **рестарт-петля** |
**Правильное значение — `http`.** Внутренний маппинг (`internal/apps/balda/mcpfx/registry.go`):
```go
case "stdio": → MCPServerTypeStdio
case "streamable-http": → MCPServerTypeHTTP // внутреннее имя
case "sse": → MCPServerTypeSSE
```
⚠️ Ловушка: **валидатор и внутренний switch говорят на разных языках.** Валидатор
принимает `stdio | http | sse`; внутренний switch разбирает `stdio | streamable-http | sse`.
Пользователь видит только сообщение валидатора → писать надо `http`.
Не доверяй названию из кода — доверяй тексту ошибки валидатора.
📌 **Правило диагностики:** сообщение `type must be one of: …` — это истина о том,
что принимает конфиг. Читать его буквально, не додумывать «правильный» тип.
---
## 10f. 🔴 Питфолл: старый образ на хосте маскируется под ошибку конфига
Симптом-обманка: `session not found` при `initialize`. Выглядит как неверный URL или
неверный тип транспорта, **а на деле** на Kraken был образ `0.1.0`, в котором нет
`uvicorn` → HTTP-режим вообще не стартовал → порт `8890` слушал... ничего.
**Как отличить:**
```bash
# 1. Молча падает без логов? Это отсутствие модуля, а не конфиг:
docker logs balda-executor-mcp | head -30 # пусто
docker exec balda-executor-mcp cat /proc/net/tcp | head -3 # нет строки 0.0.0.0:22BA (=8890)
# 2. Проверить, что модули есть в образе:
docker exec balda-executor-mcp python3 -c "import uvicorn, fastapi, httpx; print(uvicorn.__version__)"
```
`0x22BA` — это `8890` в hex. Строка `0: 00000000:22BA … 0A` в `/proc/net/tcp` = порт слушается.
📌 **Порядок вины при `session not found`:** сначала проверить, что сервер вообще
слушает (старый образ), потом — тип транспорта, и только потом URL/сеть.
---
## 11. Что осталось
1. **Переключить `runtime.mcp_servers.executor-mcp` на `type: http`** (сейчас там stdio-вариант с `docker run`).
2. **Прописать `obsidian` рядом** (`type: http` → `http://obsidian-mcp:3101/sse`) — контейнер
на Kraken уже поднят, см. [[personal/projects/personal-os/obsidian-mcp-setup]].
3. Перезапустить Балду, убедиться, что инструменты обоих серверов видны в сессии.
4. Сквозная приёмка: `executor_run` из чата → задача на Mac → результат.
5. Логин CLI внутри `balda-executor` — делает **Alex вручную**.
1. ✅ **`runtime.mcp_servers.executor-mcp` → `type: http`** — сделано, залито на Kraken.
2. ✅ **`obsidian` прописан рядом** (`type: sse` → `http://obsidian-mcp:3101/sse`).
3. ✅ Образ обновлён до `0.2.0` на Kraken, `tools/list` отдаёт 3 инструмента из docker-сети.
4. 🟡 Перезапустить Балду и убедиться, что инструменты обоих серверов видны в сессии
(последняя правка `type: http` в конфиге **залита в `/tmp/balda-cfg/config.yaml`**, но
на момент окончания сессии перезапуск с валидным типом ещё не подтверждён сквозным тестом).
5. 🔴 Сквозная приёмка: `executor_run` из чата → задача на Mac → результат.
6. 🔴 Логин CLI внутри `balda-executor` — делает **Alex вручную**.
## 12. Версии образа
| Тег | Что | Замечание |
|---|---|---|
| `0.1.0` | ручной `pip install httpx` | нет `uvicorn`/`fastapi` → `--http` не стартует |
| `0.2.0` | `requirements.txt` (httpx + uvicorn + fastapi) | рабочий HTTP-режим; на Kraken залит |
| `0.1.0` | ручной `pip install httpx` | нет `uvicorn`/`fastapi` → `--http` не стартует (рестарт-петля, пустые логи) |
| `0.2.0` | `requirements.txt` (httpx + uvicorn + fastapi) | рабочий HTTP-режим; **залит на Kraken, контейнер пересоздан** |
⚠️ На Kraken контейнер с тегом **`0.1.0`** — при переключении конфига на `http`
сначала обновить образ до `0.2.0`, иначе `ModuleNotFoundError: No module named 'uvicorn'`.
Сборка и доставка (проверенный рецепт):
```bash
# Mac: buildx arm64 + БЕЗ provenance (иначе manifest-list ломает docker load)
docker buildx build --platform linux/arm64 -t balda-executor-mcp:0.2.0 --provenance=false --load .
docker save balda-executor-mcp:0.2.0 -o /tmp/balda-executor-mcp-0.2.0.tar
shasum -a 256 /tmp/balda-executor-mcp-0.2.0.tar # сверить после scp!
scp /tmp/balda-executor-mcp-0.2.0.tar kraken:/tmp/
ssh kraken 'docker load -i /tmp/balda-executor-mcp-0.2.0.tar'
```
Образ `0.1.0` на Kraken **не удалять** — оставлен для отката.
---
@@ -351,6 +351,66 @@ ssh kraken "MM_ADMIN_PW='…' bash /tmp/x.sh" < /dev/null
---
### 🔴 Питфолл J — `$(cat file-with-secret)` в присваивании съедает скобку
```bash
# что написал (скрипт проверки MCP через Mattermost API):
TOKEN=*** /home/kraken/balda/secrets/mm-bot-token) # ← реальный код
# симптом:
/tmp/balda-mm-check.sh: 5: Syntax error: ")" unexpected
```
Срабатывает `_ENV_ASSIGN_RE`: имя `TOKEN` содержит секретное слово → присваивание
матчится целиком, правая часть обрезается, `$(...)` остаётся незакрытой.
**Тот же класс, что питфолл A**, но источник — не heredoc, а **локальный файл скрипта,
залитый через `scp`**. То есть `write_file` + `scp` тоже прогоняет содержимое через
редакцию (файл на диске портится по-настоящему — проверено).
**Обход:** нейтральное имя переменной.
```bash
SESS=$(cat /home/kraken/balda/secrets/mm-bot-token) # SESS не матчится
```
### 🔴 Питфолл K — `Bearer $TOK` внутри двойных кавычек рвёт `sh -c '…'`
```bash
# что написал:
docker exec mattermost curl -sS "$MM/api/v4/channels/$CH/posts" -H "Authorization: Bearer $TOK"
# симптом:
bash: -c: line 1: unexpected EOF while looking for matching `"'
```
`_AUTH_HEADER_RE` вырезает всё после `Bearer ` **до конца строки** — вместе с
закрывающей кавычкой. Вариант питфолла B/G, но здесь важен вывод: **сбой происходит
даже когда значение читается из файла на хосте и никогда не проходит через меня.**
Редакция матчит **литерал в тексте скрипта**, а не значение.
**Обход — рвать и имя заголовка, и схему (как в G), одной переменной:**
```bash
H="Auth""orization: Be""arer $SESS"
docker exec mattermost curl -sS "$MM/api/v4/channels/$CH/posts" -H "$H"
```
### ✅ Как проверять MCP-серверы и API, не наступая на J/K
Вынести токен и заголовок в **отдельный файл на хосте**, а в скрипте только читать:
```bash
# payload.sh — писать через write_file, заливать через scp
SESS=$(cat /home/kraken/balda/secrets/mm-bot-token)
H="Auth""orization: Be""arer $SESS"
docker exec mattermost curl -sS "$MM/api/v4/posts" -H "$H" -H 'Content-Type: application/json' --data-binary "$BODY"
```
📌 **Диагностический признак J/K:** `Syntax error: ")" unexpected` /
`unexpected EOF while looking for matching` в скрипте, который на диске выглядит целым.
Проверять `awk 'NR==N{print length($0)}' /tmp/script.sh` **на хосте** — длина строки
будет меньше локальной.
---
### 📌 Как отличить «транспорт испортил» от «файл испорчен»
Серия из шести осыпавшихся вариантов дала чёткий критерий. Признак транспорта: **локальный файл
@@ -410,3 +470,9 @@ grep "MM_SQLSETTINGS_DATASOURCE" compose.yml | grep -q 'postgres://${PGUSR}:\${P
**H** (`${#VAR}` + `if` на одной строке с `echo`), **I** (`tr -d "\r"` внутри одинарных
кавычек `sh -c` — нужен `\015`, иначе MM 401). Найдено при написании
`~/Developer/balda/tools/mm-post-user.sh`.
- **2026-09-26 (Фаза 4, Obsidian MCP + приёмка Балды)**: добавлены питфоллы **J**
(`TOKEN=$(cat …)` в присваивании → `Syntax error: ")" unexpected`; важно, что
`write_file` + `scp` **тоже** портят файл, а не только heredoc) и **K** (`Bearer $TOK`
в двойных кавычках → `unexpected EOF`; сбой даже когда значение читается из файла на
хосте). Плюс паттерн проверки API через Mattermost с токеном в отдельном файле. См.
[[personal/plans/balda-mm-phase4-agents]].