Files
obsidian-vault/personal/projects/personal-os/obsidian-mcp-wrapper.md
T
2026-05-29 12:03:23 +00:00

5.4 KiB
Executable File
Raw Blame History

obsidian-mcp-wrapper

Файл: ~/scripts/obsidian-mcp-wrapper.js
Назначение: прокси-обёртка над obsidian-mcp, решает четыре системных бага


Проблемы, которые решает

1. ZodError при инициализации (obsidian-mcp v1.0.6)

obsidian-mcp падал с ZodError сразу после запуска. Причина: Hermes отправляет
notifications/initialized с полем "id": null, а obsidian-mcp v1.0.6 использует
.strict() валидацию и не принимает лишние поля.

Fix: wrapper перехватывает все notification-сообщения (без result/error) с id === null
и удаляет поле id перед передачей в child.

2. Race condition при gateway restart

При рестарте Hermes gateway поднимает новый процесс obsidian-mcp-wrapper. Первые
параллельные tool-вызовы приходят пока child ещё инициализируется (~500ms) → они
тайм-аутились, circuit breaker открывался (3 фейла → 60s cooldown).

Fix: wrapper буферизует все tool-вызовы до завершения handshake
(initialize → ответ → notifications/initialized), потом флашит очередь.

3. Corrupted large payloads (UTF-8 chunk split)

При больших tool-вызовах (~200KB+) Node.js доставляет stdin в нескольких chunk-ах.
Старый код делал string split — JSON разрезался по байтам → UTF-8 multibyte символы
портились, JSON.parse падал, сообщение дропалось молча.

Симптом: edit_note с большим контентом тихо зависал (30s timeout), в логах:

[obsidian-wrapper] Non-JSON from Hermes (forwarding verbatim): {"jsonrpc": "2.0", "method": "tools/call", "id": 3, "params": {"name": "edit-not

Fix: stdin и stdout читаются через Buffer.concat + Buffer.slice на 0x0a.
Строка собирается полностью до передачи в JSON.parse.

4. Per-call watchdog (зависший child)

Если child не ответил на tools/call / tools/list / resources/* за 5s
watchdog убивает процесс. После авторестарта call автоматически уходит в голову
очереди и ретраится.

Fix: armWatchdog(callLine)setTimeout 5000mschild.kill()startChild().


Как работает

Hermes (stdin) → wrapper → obsidian-mcp (child)
                              ↑ auto-restart при краше (до 10 раз)

Состояния:

  • ready = false — child стартует, все tool-вызовы в очередь
  • ready = true — handshake завершён, очередь флашится, всё проходит напрямую

Restart логика:

  1. Child упал → ready = false, restarts++
  2. Новый child спавнится
  3. Wrapper реплеит сохранённый initialize → ждёт ответа с serverInfo
  4. Отправляет notifications/initialized (не форвардит Hermes — он не просил)
  5. ready = true → флаш очереди

Shutdown:
На stdin EOF (Hermes закрыл процесс) — child убивается без авторестарта, wrapper выходит чисто.

Логи (все в stderr с timestamp):

[obsidian-wrapper] 2026-05-09T12:00:00.000Z Spawning obsidian-mcp (vault=/Users/admin/obsidian)
[obsidian-wrapper] 2026-05-09T12:00:00.500Z Hermes → child (handshake complete): notifications/initialized
[obsidian-wrapper] 2026-05-09T12:00:00.501Z Child ready — flushing queue (3 items)
[obsidian-wrapper] 2026-05-09T12:00:01.200Z Stripped id:null from notification: notifications/initialized
[obsidian-wrapper] 2026-05-09T12:00:06.000Z WATCHDOG: child did not respond in 5000ms for tools/call #7 — killing and restarting

Конфиг Hermes

~/.hermes/config.yaml:

obsidian:
  command: node
  args: [/Users/admin/scripts/obsidian-mcp-wrapper.js]

Wrapper сам вызывает /opt/homebrew/bin/obsidian-mcp /Users/admin/obsidian.


Производительность

  • Инициализация: ~587ms (без ZodError)
  • Overhead wrapper: negligible (pure Node.js child_process, нет npm-зависимостей)
  • MAX_RETRY: 10
  • CALL_TIMEOUT_MS: 5000ms (watchdog)

История

2026-05-09 (1) — создан после диагностики 58 ошибок obsidian/... call failed в логах Hermes.
Корневая причина — ZodError + race condition при старте. Wrapper написан вместо патча
исходников obsidian-mcp (патч не нужен, wrapper чище и не ломается при обновлении пакета).

2026-05-09 (2) — фикс large payload: Buffer-based line splitting вместо string split
(edit_note с большим контентом молча дропался). Добавлен per-call watchdog (5s timeout → kill & retry).
MAX_RETRY повышен с 5 до 10.