5.6 KiB
namespace, tags, last_updated, confidence
| namespace | tags | last_updated | confidence | ||||
|---|---|---|---|---|---|---|---|
| work |
|
2026-05-09 | 1.0 |
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 5000ms → child.kill() → startChild().
Как работает
Hermes (stdin) → wrapper → obsidian-mcp (child)
↑ auto-restart при краше (до 10 раз)
Состояния:
ready = false— child стартует, все tool-вызовы в очередьready = true— handshake завершён, очередь флашится, всё проходит напрямую
Restart логика:
- Child упал →
ready = false,restarts++ - Новый child спавнится
- Wrapper реплеит сохранённый
initialize→ ждёт ответа сserverInfo - Отправляет
notifications/initialized(не форвардит Hermes — он не просил) 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.