voracle — Obsidian vault 向けセマンティック検索 MCP/CLI ツールの設計と実装
Obsidian vault 向けセマンティック検索 MCP/CLI ツール voracle の設計と実装。ONNX embedding + ColBERT reranking、複数 vault root 対応、差分キャッシュ、LLM 会話取り込み(distil)までの一連の作業記録。
結論
Obsidian vault 向けセマンティック検索ツールMCP、CLI ツール voracle を設計、実装した。最終的なツールは以下の構成になった。
- MCP サーバー: stdio JSON-RPC 2.0、公開ツールは
searchとreadの2つ - CLI コマンド:
-ingest(インデックス構築)、-list/-rg(セマンティック検索)、-distil(外部 LLM 会話取り込み) - 複数 vault root:
--rootフラグまたはVORACLE_PATH環境変数(コロン区切り)で指定 - embedding キャッシュ:
.embeddings.jsonlによる差分 ingest。変更のないノートはキャッシュから復元 - モデル配置:
~/.local/share/voracle/models/に ONNX モデルを配置
# インデックス構築(初回は全チャンク embedding、2回目以降は差分のみ)
voracle -ingest
# セマンティック検索
voracle -list "kubernetes networking"
# MCP サーバー起動
voracle --server
# 外部 LLM 会話の取り込み(クリップボードから)
voracle -distil c
背景
valut-oracle は Obsidian vault に対するセマンティック検索と reranking を提供する Rust 製バイナリで、ONNX Runtime による embedding(pplx-embed-v1-0.6b)と ColBERT reranking(mxbai-edge-colbert)をローカルで実行する構成になっていた MCP(stdio JSON-RPC 2.0)
同じ Rust MCP ツール群として pathfinder や shelpa(現 filesystem) があり、voracle はこれらと並ぶ vault 検索の専用ツールという位置付けにある。
設計のきっかけは、記事ファイル、設計メモ、ドメインファイルがobsidianに溜まってきて、記憶に留められなくなってきたので、なんとなくの「あれ、それ」で適当にchunkみつけてきてもらいたかったのと、LLMとの対話をdistil コマンドで吸い出ししていたので、どうせならまとめて実装、統合しようと思いついたことだった。
複数 vault root 対応
単一の vault_path: PathBuf から roots: Vec<PathBuf> に変更。--root フラグで複数 vault ルートを指定でき、VaultResolver は各 root を再帰的にスキャンしてノートを統合する。
voracle --root /path/to/vault1 --root /path/to/vault2 --server
環境変数 VORACLE_PATH でも指定可能で、コロン区切りで展開される。解決の優先順位は --root フラグ > VORACLE_PATH > エラー。
export VORACLE_PATH=/Volumes/VALUT/obsidian/docs:/Volumes/VALUT/obsidian/articles
embedding キャッシュ
初回 ingest は全ノートの全チャンクを ONNX モデルで embedding する必要があり、500 ノート規模の vault では重い処理になる。2 回目以降の高速化のために .embeddings.jsonl による差分キャッシュを実装した。
キャッシュは JSONL 形式で vault ルートごとに配置され、1 行が 1 ノートに対応する。各行に note_id、content_hash(DefaultHasher)、mtime_secs、各チャンクの embedding ベクトルを含む。ファイル名はドットプレフィックスで Obsidian の UI から不可視にしている。
ルックアップは二段階で行う。まず mtime が一致すればキャッシュヒット。mtime が異なる場合はファイル内容のハッシュを比較し、ハッシュも不一致なら re-embed する。touch やコピーでタイムスタンプだけ変わったケースをカバーしつつ、大半のケースはメタデータだけで高速に判定できる。

-f フラグでキャッシュを無視した全 re-embed も可能。IngestResult に cached_hits フィールドを追加して復元件数を報告する。
distil コマンドの統合
agent-gateway で運用していた distil シェルスクリプトの機能を voracle に統合した。外部 LLM 会話ログ(Claude CLI、Codex CLI、Gemini CLI)を vault に取り込むコマンドで、もともと agent-gateway の POST /v1/obsidian/ingest を叩いていたが、gateway 側のハンドラは既に削除済みだった。
入力は 3 ソースに対応。第 2 引数でインライン、stdin からパイプ、どちらもなければ pbpaste でクリップボード。モデルエイリアスは c/claude → claude-opus-4-6、x/codex → codex-5.3、g/gemini → gemini-3.1-pro。
voracle -distil c # クリップボードから
voracle -distil claude "text" # インライン
cat file.md | voracle -distil x # stdin
書き込み先は第 1 vault root の _notes/llm-completions/{model_dir}/{correlation_id}.md。frontmatter には date、status: raw、correlation_id、model を含め、インデックスが構築済みであればセマンティック検索で関連ノートを related フィールドに記録する。
MCP ツール整理
MCP ingest の除去
MCP サーバーでは当初 search、read、ingest の 3 ツールを公開していた。しかし ingest は CLI で事前実行する前提に変わったため MCP 経由で呼び出す必要がない。実際、handle_tool_call のルーティングには既に ingest へのディスパッチが含まれておらず、ツール定義だけが残っている不整合な状態だった。
tools.rs から ingest 関数本体を削除し、server.rs の tool_definitions() からもエントリを削除。MCP 公開ツールは search と read の 2 つになった。
各 search 結果には read_tool_call が含まれ、LLM がそのまま呼び出せる。ページは約 1000 文字ずつ分割されるため、コンテキスト消費を制御できる。
最終的な CLI の全体像は以下のとおり。
usage: voracle [--root <path>]... <command> [args]
options:
--root <path> vault root directory (repeatable)
falls back to VORACLE_PATH (colon-separated)
index:
-ingest [-f] build index (required before search)
search (loads .index.bin):
-list <query> [-k N] semantic file list, default top 3
-rg <query> [-k N] grep-like semantic search with context
-read <note_id> print note content to stdout
tools:
-embed <text> print embedding vector (TSV)
-rerank <query> <file>... rerank files by ColBERT MaxSim score
-status show index status
-distil <model> [content] ingest LLM conversation into vault
model: c(laude), x(codex), g(emini)
reads from: arg, stdin, or clipboard
server:
--server stdio MCP server (JSON-RPC 2.0)
-list と -rg はどちらも .index.bin をロードしてセマンティック検索を行うが、-list はファイルパスとスコアの一覧、-rg はマッチしたチャンクの前後コンテキスト付きで表示する。-read はノート全文を stdout に出力する CLI 専用コマンドで、MCP の read ツールとは別物。-embed と -rerank は低レベルの推論ツールで、パイプラインのデバッグや他ツールとの連携に使う。
voracle への改名
コマンド名 valut-oracle はハイフン込みで長く、日常的に使うには煩わしかった。voracle に短縮する方針で、パッケージ名・バイナリ名・環境変数名をすべて変更した。
Cargo.toml: パッケージ名をvoracleに変更src/cli/mod.rs: 環境変数をVALUT_ORACLE_PATH→VORACLE_PATHに変更、全 usage メッセージを更新src/config.rs: モデルディレクトリを~/.local/share/voracle/models/に変更src/main.rs: トレーシングフィルタをvoracle=infoに変更src/mcp/server.rs:ServerInfo.nameをvoracleに変更- ドキュメント全体(README.md、docs/ 内 5 ファイル)を新名称に統一
モデルディレクトリは ~/.local/share/voracle/models/ に標準化し、XDG_DATA_HOME が設定されていればそちらを尊重する。pathfinder のように 32MB 程度なら include_bytes! でバイナリに埋め込めるが、700MB では現実的でないため外部配置とした。リポジトリからは git filter-repo で全履歴から除去済み。
