[2026-09-26] eagle: personal/plans/balda-mm-phase4-agents.md personal/plans/tagwriter-container.md personal/projects/personal-os/obsidian-mcp-setup.md personal/tech/balda-executor-mcp.md personal/tech/obsidian-mcp-ecosystem.md
This commit is contained in:
1 parent
9e7a888f94
commit
270c4b7c52
5 files changed
+326
-11
No files matched your search
@@ -14,8 +14,9 @@
|
||||
| 2 | Мост FastAPI :8790 | ✅ работает под launchd |
|
||||
| 3 | Сеть Kraken → Mac | ✅ **LAN напрямую**, туннель не нужен |
|
||||
| 4 | Секция «Agents» в Dashboard | ✅ активирована и проверена |
|
||||
| 5 | Связка Балда → мост | 🟡 **связка работает**, конфиг Балды ждёт переключения на `type: http` |
|
||||
| 6 | Приёмка от аккаунта Alex | 🔴 блокер: авторизация CLI |
|
||||
| 5 | Связка Балда → мост | 🟡 **связка работает**; конфиг Балды ждёт переключения на `type: http` — одним заходом с Obsidian |
|
||||
| 6 | Obsidian MCP для Балды | 🟡 контейнер `obsidian-mcp` **поднят на Kraken**, в конфиг Балды ещё не прописан |
|
||||
| 7 | Приёмка от аккаунта Alex | 🔴 блокер: авторизация CLI |
|
||||
|
||||
---
|
||||
|
||||
@@ -266,10 +267,46 @@ lsof -nP -iTCP:8790 -sTCP:LISTEN
|
||||
1. **Переключить конфиг Балды на HTTP-MCP** — сейчас там stdio-вариант с `docker run`,
|
||||
который не может работать (нет `docker.sock` и нет shell в образе Балды).
|
||||
Нужен `type: http` → `http://balda-executor-mcp:8890/mcp`. См. [[personal/tech/balda-executor-mcp]] §7.
|
||||
2. Проверить, что инструменты видны в сессии Балды (после перезапуска).
|
||||
3. Логин CLI (Alex вручную) — без него сквозная приёмка невозможна.
|
||||
4. Cloudflare Tunnel — **не нужен** на текущей схеме; держать как резерв, если Mac
|
||||
уедет из LAN.
|
||||
2. **Прописать Obsidian в конфиг Балды** — контейнер `obsidian-mcp` на Kraken уже поднят,
|
||||
но `runtime.mcp_servers.obsidian` ещё не добавлен. Делается тем же заходом, что п. 1:
|
||||
```yaml
|
||||
runtime:
|
||||
mcp_servers:
|
||||
obsidian:
|
||||
type: http
|
||||
url: "http://obsidian-mcp:3101/sse"
|
||||
balda:
|
||||
mcp_servers:
|
||||
- executor-mcp
|
||||
- obsidian
|
||||
```
|
||||
См. [[personal/projects/personal-os/obsidian-mcp-setup]] §«obsidian-mcp на Kraken».
|
||||
3. Проверить, что инструменты видны в сессии Балды (после перезапуска) — оба сервера.
|
||||
4. Логин CLI (Alex вручную) — без него сквозная приёмка невозможна.
|
||||
5. Cloudflare Tunnel — **не нужен** на текущей схеме; держать как резерв, если Mac
|
||||
уедет из LAN. ⚠️ Alex отдельно настаивал, что постоянный конфиг должен ходить через
|
||||
туннель, а не по IP — при первой возможности перевести на `agents-bridge.qentra.top`
|
||||
(нужны 2 маршрута в CF Zero Trust + Access Application, заводит Alex).
|
||||
|
||||
## 9a. 🔴 Obsidian на Kraken — существовал ДО этой сессии
|
||||
|
||||
Важное исправление ошибочного вывода. Я дважды сказал Alex, что Obsidian MCP
|
||||
на Kraken нет / есть только на Mac. **Оба утверждения неверны.**
|
||||
|
||||
**Почему ошибся:** искал через `docker ps --filter name=obsidian` — контейнера нет.
|
||||
Но на Kraken Obsidian MCP запущен **не контейнером**, а как stdio-процесс внутри
|
||||
агента Hermes:
|
||||
|
||||
```
|
||||
mcpvault /vault (PID 1168741, UID 10000)
|
||||
└─ родитель PID 5039 = hermes gateway (контейнер hermes-kraken)
|
||||
```
|
||||
|
||||
Конфиг — `/opt/data/config.yaml` **внутри** `hermes-kraken`. Волт на Kraken реальный:
|
||||
`/home/kraken/obsidian` → `/srv/…-49e8f586…/obsidian` (с `personal/`, `family/`, `.git/`).
|
||||
|
||||
📌 **Правило поиска: `docker ps` не является доказательством отсутствия сервиса.**
|
||||
Сначала `ps aux | grep <name>`, потом порты (`ss -tlnp`), и только потом контейнеры.
|
||||
|
||||
## 10. Шаг 5 — связка Балда → мост (что сделано 2026-09-26)
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
---
|
||||
created: '2026-09-26'
|
||||
namespace: personal
|
||||
related:
|
||||
- '[[personal/plans/music-tags-wave38-fixes]]'
|
||||
- '[[personal/tech/music-tags-repair-truenas]]'
|
||||
status: plan
|
||||
tags:
|
||||
- music
|
||||
- truenas
|
||||
- docker
|
||||
- id3
|
||||
- plan
|
||||
title: tagwriter — контейнер применения тегов MP3
|
||||
type: plan
|
||||
updated: '2026-09-26'
|
||||
---
|
||||
# tagwriter — контейнер применения тегов MP3 (TrueNAS)
|
||||
|
||||
_Создан: 2026-09-26. **Статус: ПЛАН → СБОРКА.** Основание: [[personal/plans/music-tags-wave38-fixes]]._
|
||||
|
||||
## Задача
|
||||
|
||||
Применить `tag-fix-plan.csv` (7905 строк) к MP3 на `/storage/Music`, **без пересжатия аудио**,
|
||||
в **UTF-8/UTF-16**. Alex: «в контейнер». Чужие контейнеры (transmission/immich/jellyfin) **НЕ трогаем**.
|
||||
|
||||
## Разведка окружения (факты, 2026-09-26)
|
||||
|
||||
| Что | Факт |
|
||||
|---|---|
|
||||
| `/storage/Music` | 48 ГБ, владелец `truenas_admin` (uid 950) |
|
||||
| Свободно на `/mnt/RED_2TB` | 960 ГБ (бэкап влезет) |
|
||||
| python3 на хосте NAS | 3.11.9, **нет pip, нет ensurepip, нет venv** |
|
||||
| mutagen | **нигде** (ни на хосте, ни в контейнерах) |
|
||||
| ffmpeg | только `immich-server` (музыки не видит) |
|
||||
| `/storage/Music` видят | `jellyfin`, `radarr`, `sonarr`, `transmission` |
|
||||
| Docker Hub | доступен (`python:3.12-alpine` скачался) |
|
||||
| docker compose | v2.29.1 |
|
||||
| `media_net` | есть |
|
||||
|
||||
**Вывод:** пишем теги своим контейнером. mutagen ставится в образ при сборке (pip внутри образа есть).
|
||||
|
||||
## Решение: контейнер `tagwriter`
|
||||
|
||||
- **Путь проекта:** `/mnt/RED_2TB/docker/tagwriter/`
|
||||
- **Образ:** `python:3.12-alpine` + `pip install mutagen`
|
||||
- **Пользователь:** uid/gid `950:950` (`user: "950:950"`) — владелец Music
|
||||
- **Монтирование:** `/mnt/RED_2TB/storage/Music` → `/music` (rw)
|
||||
- **Теги:** ID3v2.3, кодировка `encoding=1` (UTF-16), аудио НЕ пересжимается (mutagen правит только тег-блок)
|
||||
|
||||
## Почему не чужой контейнер
|
||||
|
||||
`python3` есть только в `transmission`, но он чужой сервис — Alex запретил. Свой контейнер
|
||||
изолирован: не влияет на торренты/медиасервисы, снимается без следа.
|
||||
|
||||
## Скрипты
|
||||
|
||||
| Файл | Назначение |
|
||||
|---|---|
|
||||
| `apply_tags_truenas.py` | `--backup` → копия всех файлов к изменению в `/music/backup/`; `--dry-run` → печать плана; `--apply` → запись тегов |
|
||||
| `docker-compose.yml` | проект контейнера |
|
||||
| `Dockerfile` | `python:3.12-alpine` + mutagen |
|
||||
| `build_and_run.sh` | сборка + запуск |
|
||||
|
||||
## Порядок работ
|
||||
|
||||
1. ✅ **Бэкап** — `--backup` копирует 7905 файлов (48 ГБ → 960 ГБ свободно) в `/music/backup/`.
|
||||
2. ✅ **Dry-run** — печать плана, 0 записей. Приёмка Alex.
|
||||
3. ✅ **Apply** — запись тегов (по «go»).
|
||||
4. ✅ **Приёмка** — выборочная проверка тегов (`mutagen` read-back) + глазами в Jellyfin/webdav.
|
||||
|
||||
## Питфоллы (предвиденные)
|
||||
|
||||
- 🔴 Имена файлов на диске **НЕ в UTF-8** (mojibake в `ls`) → работать только python-скриптом (`os.listdir`), NFC-нормализация.
|
||||
- 🔴 Пути в CSV контейнерные (`/storage/Music/...`) → мапить в `/music/...`.
|
||||
- 🔴 mutagen сохраняет тег-блок; **аудио-поток не трогается** — пересжатия нет by design.
|
||||
- ⚠️ uid: контейнер под 950, иначе не сможет писать в Music.
|
||||
- ⚠️ Бэкап 48 ГБ — проверить `df` до и после.
|
||||
@@ -60,6 +60,115 @@ mcp_servers:
|
||||
|
||||
Workaround: использовать Hermes `patch()` (fuzzy matching, 9 стратегий) для сложных строк. `mcp_obsidian_patch_note` — только для простого текста без спецсимволов.
|
||||
|
||||
## Fact: Obsidian MCP на Kraken — есть, и это stdio-процесс Hermes
|
||||
|
||||
Проверено 2026-09-26. На Kraken MCP-сервер **не является отдельным контейнером** —
|
||||
его спавнит агент Hermes как дочерний процесс. Первый поиск «нет контейнера →
|
||||
нет MCP» был **ошибочным**: искать надо было процессы, а не `docker ps`.
|
||||
|
||||
```
|
||||
# на хосте Kraken
|
||||
ps aux | grep mcpvault
|
||||
→ UID 10000 1168741 ... node /opt/data/.local/bin/mcpvault /vault
|
||||
PPID 5039 → /opt/hermes/.venv/bin/hermes gateway (контейнер hermes-kraken)
|
||||
```
|
||||
|
||||
Конфиг (`/opt/data/config.yaml` **внутри** контейнера `hermes-kraken`):
|
||||
|
||||
```yaml
|
||||
mcp_servers:
|
||||
obsidian:
|
||||
command: /opt/data/.local/bin/mcpvault
|
||||
args:
|
||||
- /vault
|
||||
```
|
||||
|
||||
`mcpvault` там — симлинк на npm-пакет: `/opt/data/.local/lib/node_modules/@bitbonsai/mcpvault/dist/server.js`.
|
||||
|
||||
### Волт на Kraken — реальный, не пустой
|
||||
|
||||
```
|
||||
/home/kraken/obsidian -> /srv/dev-disk-by-uuid-49e8f586-…/obsidian
|
||||
(тот же HDD, что и docker-data)
|
||||
```
|
||||
|
||||
Внутри: `personal/`, `family/`, `.obsidian/`, `.git/`, `_index.md` — полноценная
|
||||
git-реплика волта Mac. То есть **волт существует в обоих местах**, а не только на Mac.
|
||||
|
||||
### Итог: два независимых Obsidian MCP
|
||||
|
||||
| Где | Как | Транспорт | Порт |
|
||||
|---|---|---|---|
|
||||
| Mac | контейнер `obsidian-mcp` (`mcp-proxy` → `mcpvault`) | SSE | `:3101`, публикуется на `0.0.0.0` |
|
||||
| Kraken | `mcpvault` как дочерний процесс `hermes-kraken` | **stdio** | нет |
|
||||
| Kraken (для Балды) | контейнер `obsidian-mcp` — **создан 2026-09-26** | SSE | `:3101`, только внутри `mattermost_mm-net` |
|
||||
|
||||
📌 `mcpvault` **не умеет HTTP** — только stdio (`--help` подтверждает: только `[vault-path]`).
|
||||
Чтобы отдать его по сети, нужен `mcp-proxy` (обёртка stdio → SSE). Именно так сделано
|
||||
на Mac и повторено на Kraken.
|
||||
|
||||
---
|
||||
|
||||
## Fact: obsidian-mcp на Kraken для Балды (создан 2026-09-26)
|
||||
|
||||
Собрано для шага 5 [[personal/plans/balda-mm-phase4-agents]]: Балде нужен доступ к
|
||||
волту, чтобы читать документацию при формировании ТЗ.
|
||||
|
||||
**Точная копия рабочей схемы с Mac** — пара `mcp-proxy` + `mcpvault`:
|
||||
|
||||
| Что | Путь / значение |
|
||||
|---|---|
|
||||
| Проект (Mac) | `~/Developer/obsidian-mcp-kraken/` |
|
||||
| Dockerfile | `node:24-bookworm` + `@bitbonsai/mcpvault@0.11.0` + `mcp-proxy@6.4.4` |
|
||||
| Образ | `obsidian-mcp-image:0.1.0` (загружен на Kraken) |
|
||||
| Compose (Kraken) | `/home/kraken/obsidian-mcp/docker-compose.yml` |
|
||||
| Контейнер | `obsidian-mcp`, сеть `mattermost_mm-net` |
|
||||
| Волт | `/srv/dev-disk-by-uuid-49e8f586-…/obsidian` → `/vault` **`:ro`** |
|
||||
| Команда | `mcp-proxy --port 3101 -- mcpvault /vault` |
|
||||
|
||||
🔴 **Волт смонтирован только для чтения (`:ro`).** Балда читает документацию, но не
|
||||
правит её. Запись в волт через Балду — отдельное решение, требует апрува Alex.
|
||||
|
||||
```yaml
|
||||
services:
|
||||
obsidian-mcp:
|
||||
image: obsidian-mcp-image:0.1.0
|
||||
container_name: obsidian-mcp
|
||||
restart: unless-stopped
|
||||
command: ["--port", "3101", "--", "mcpvault", "/vault"]
|
||||
volumes:
|
||||
- /srv/dev-disk-by-uuid-49e8f586-…/obsidian:/vault:ro
|
||||
networks:
|
||||
- mm-net # mattermost_mm-net, external
|
||||
```
|
||||
|
||||
### Питфоллы, пройденные при сборке
|
||||
|
||||
| # | Питфолл | Симптом | Фикс |
|
||||
|---|---|---|---|
|
||||
| 1 | `--host` у `mcp-proxy` **нет** | контейнер падает на разборе аргументов | только `--port N -- <cmd> <args>`; слушает `0.0.0.0` по умолчанию |
|
||||
| 2 | Точка монтирования должна существовать в образе | `mkdir -p /vault` в Dockerfile + `WORKDIR /vault` | иначе mcpvault стартует не в том каталоге |
|
||||
|
||||
### Проверка
|
||||
|
||||
```bash
|
||||
# /sse отвечает 200 (и локально, и из docker-сети Kraken)
|
||||
curl -s -o /dev/null -w "%{http_code}\n" http://obsidian-mcp:3101/sse
|
||||
|
||||
# конфиг Балды (ещё НЕ применён — см. ниже)
|
||||
runtime:
|
||||
mcp_servers:
|
||||
obsidian:
|
||||
type: http
|
||||
url: "http://obsidian-mcp:3101/sse"
|
||||
```
|
||||
|
||||
⚠️ **Не применено:** конфиг Балды (`config.yaml` на Kraken) пока содержит **только**
|
||||
`executor-mcp`. Obsidian туда ещё не прописан — ждёт переключения `executor-mcp`
|
||||
на `type: http` (делается одним заходом).
|
||||
|
||||
---
|
||||
|
||||
## Эволюция Obsidian MCP у нас
|
||||
|
||||
| Период | Что было | Проблемы |
|
||||
|
||||
@@ -171,12 +171,78 @@ docker run --rm --network mattermost_mm-net curlimages/curl:latest \
|
||||
|
||||
---
|
||||
|
||||
## 10a. 🔴 Питфолл: `from __future__ import annotations` ломает FastAPI-эндпоинт
|
||||
|
||||
Симптом: `initialize` возвращал **422 `{"type":"missing","loc":["query","request"],"msg":"Field required"}`**,
|
||||
и `mcp-session-id` в ответе отсутствовал — значит handshake не начинался вовсе.
|
||||
|
||||
**Причина:** модуль объявлен с `from __future__ import annotations`, поэтому аннотация
|
||||
`async def mcp(request: Request)` превращается в **строку** и FastAPI не распознаёт
|
||||
её как объект запроса → считает `request` обычным query-параметром.
|
||||
|
||||
**Фикс:** убрать `from __future__ import annotations` из модуля, где объявлены
|
||||
FastAPI-роуты. Никакие обходы (`request: Request = None`, ручная проверка `isinstance`,
|
||||
импорт `Request as FastAPIRequest`) **не помогают** — только удаление строки.
|
||||
|
||||
📌 Правило: файл с FastAPI-эндпоинтами не должен использовать отложенные аннотации.
|
||||
Если `from __future__ import annotations` нужен для остального кода — выносить роуты
|
||||
в отдельный модуль.
|
||||
|
||||
## 10b. 🔴 Питфолл: `command:` в compose не переопределяет ENTRYPOINT
|
||||
|
||||
Образ имеет `ENTRYPOINT ["python3", "/app/server.py"]`. В compose было
|
||||
`command: ["python3", "/app/server.py", "--http", ...]` → аргументы **добавляются**
|
||||
к ENTRYPOINT, и получается `python3 /app/server.py python3 /app/server.py --http ...`:
|
||||
|
||||
```
|
||||
server.py: error: unrecognized arguments: python3 /app/server.py
|
||||
```
|
||||
|
||||
**Фикс:** в `command:` указывать **только флаги**, без повторения ENTRYPOINT:
|
||||
|
||||
```yaml
|
||||
command: ["--http", "--host", "0.0.0.0", "--port", "8890"]
|
||||
```
|
||||
|
||||
Симптом коварен: контейнер уходит в `Restarting (2)`, а в логах — usage от argparse.
|
||||
|
||||
## 10c. Питфолл: `uvicorn`/`fastapi` нужны в образе для `--http`
|
||||
|
||||
Образ изначально ставил только `httpx` (для stdio-режима). Запуск с `--http` падал:
|
||||
`ModuleNotFoundError: No module named 'uvicorn'`. Оба режима живут в одном файле,
|
||||
поэтому `requirements.txt` обязан содержать `httpx`, `uvicorn` **и** `fastapi`.
|
||||
|
||||
## 10d. Питфолл: точка монтирования отсутствует в docker-сети
|
||||
|
||||
`http://balda-executor-mcp:8890` резолвится только внутри `mattermost_mm-net`.
|
||||
Проверка извне сети даёт «мост недоступен» с `httpx.HTTPError`. Прогонять пробу
|
||||
через `docker run --network mattermost_mm-net curlimages/curl:latest`.
|
||||
|
||||
⚠️ При ручной пробе через `docker run -e BRIDGE_SECRET=...` редакция секретов Hermes
|
||||
может подставить `***` (3 символа) → мост вернёт 401, а `r.json()` упадёт с
|
||||
`JSONDecodeError: Expecting value: line 1 column 1`. Передавать секрет через
|
||||
`--env-file`, а не через `-e` в командной строке.
|
||||
|
||||
---
|
||||
|
||||
## 11. Что осталось
|
||||
|
||||
1. **Переключить `runtime.mcp_servers.executor-mcp` на `type: http`** (сейчас там stdio-вариант с `docker run`).
|
||||
2. Перезапустить Балду, убедиться, что инструменты видны в сессии.
|
||||
3. Сквозная приёмка: `executor_run` из чата → задача на Mac → результат.
|
||||
4. Логин CLI внутри `balda-executor` — делает **Alex вручную**.
|
||||
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 вручную**.
|
||||
|
||||
## 12. Версии образа
|
||||
|
||||
| Тег | Что | Замечание |
|
||||
|---|---|---|
|
||||
| `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'`.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -15,12 +15,37 @@
|
||||
### 1. @bitbonsai/mcpvault (наш текущий) — filesystem
|
||||
|
||||
- **npm:** `@bitbonsai/mcpvault` (бывший `obsidian-mcp`, переименован март 2026 из-за trademark)
|
||||
- **Версия:** 0.12.1 (июнь 2026)
|
||||
- **Версия:** 0.12.1 (июнь 2026); на Kraken стоит **0.11.0**
|
||||
- **Язык:** TypeScript
|
||||
- **Транспорт:** stdio только
|
||||
- **Транспорт:** **stdio только** — HTTP-режима нет (проверено: `mcpvault --help`
|
||||
принимает единственный аргумент `[vault-path]`, флагов порта/хоста не существует)
|
||||
- **Инструменты:** 14: read_note, write_note, patch_note, list_directory, delete_note, search_notes, move_note, move_file, read_multiple_notes, update_frontmatter, get_notes_info, get_frontmatter, manage_tags, get_vault_stats, list_all_tags
|
||||
- **Проблема:** exact string match в patch_note (`fullContent.split(oldString).length - 1`), без fuzzy, без trim, без нормализации whitespace
|
||||
|
||||
#### Как отдать mcpvault по сети (проверено на Mac и Kraken)
|
||||
|
||||
Поскольку HTTP-режима нет, для сетевого доступа нужна обёртка **`mcp-proxy`**
|
||||
(пакет `mcp-proxy@6.4.4`), которая поднимает SSE поверх stdio-сервера:
|
||||
|
||||
```yaml
|
||||
# docker-compose.yml — рабочая пара
|
||||
services:
|
||||
obsidian-mcp:
|
||||
image: obsidian-mcp-image:0.1.0 # node:24-bookworm + оба пакета
|
||||
command: ["--port", "3101", "--", "mcpvault", "/vault"]
|
||||
```
|
||||
|
||||
| Питфолл | Симптом | Фикс |
|
||||
|---|---|---|
|
||||
| У `mcp-proxy` **нет** флага `--host` | контейнер падает на разборе аргументов | только `--port N -- <cmd> <args>`; слушает `0.0.0.0` по умолчанию |
|
||||
| Точка монтирования должна существовать в образе | mcpvault стартует не в том каталоге | `RUN mkdir -p /vault` + `WORKDIR /vault` в Dockerfile |
|
||||
|
||||
Проверка живости: `curl -o /dev/null -w "%{http_code}" http://<host>:3101/sse` → **200**.
|
||||
Endpoint для MCP-клиента — `/sse` (не `/mcp`; `/mcp` отдаёт 400).
|
||||
|
||||
📌 Для Балды на Kraken волт монтируется **`:ro`** — агент читает документацию,
|
||||
но не правит её. Запись в волт через агента — отдельное решение.
|
||||
|
||||
### 2. cyanheads/obsidian-mcp-server — REST API, самый популярный по загрузкам
|
||||
|
||||
- **npm:** `obsidian-mcp-server`
|
||||
|
||||
Reference in new issue
Block a user