2026年4月12日日曜日

【ブログ追記セクション】自作MCPサーバーによる「AIとObsidian」の接続

特に、AI とローカル環境を繋ぐブリッジとして MCP (Model Context Protocol) サーバーを自作した過程を記録します。

1. 構築したアーキテクチャ

単に Obsidian を使うだけでなく、AI(LLM)がローカルのファイルを自在に検索・閲覧できる「外部脳」としての構成を目指しました。

  • 全文検索エンジン: Omnisearch (Obsidian Plugin)

  • テキスト抽出: Text Extractor (OCR対応)

  • AI ブリッジ: 自作 MCP サーバー (Node.js / TypeScript)

2. 【核心】自作 MCP サーバーの実装

AI が Obsidian の保管庫(Vault)にアクセスできるよう、専用の MCP サーバーを実装しました。特に、Excel や PDF の解析精度を高めるために Omnisearch API との連携に注力しています。

MCP サーバーのソースコード (index.mjs)

----------------------------------
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
import * as fs from "fs/promises";
import * as path from "path";
import { glob } from "glob";

const VAULT_PATH = process.env.OBSIDIAN_VAULT_PATH;
const OMNISEARCH_HOST = process.env.OMNISEARCH_HOST || "http://localhost:51361";

const server = new Server(
    { name: "obsidian-local-server", version: "1.0.0" },
    { capabilities: { tools: {} } }
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
    tools: [
        {
            name: "list_notes",
            description: "Vault内のノート一覧を取得します(.mdファイルのみ)",
            inputSchema: { type: "object", properties: {} },
        },
        {
            name: "read_note",
            description: "指定されたパスのノート内容を読み込みます",
            inputSchema: {
                type: "object",
                properties: { relativePath: { type: "string" } },
                required: ["relativePath"],
            },
        },
        {
            name: "search_vault",
            description: "Omnisearchを使用してPDFやOfficeファイルを含む保管庫全体を検索し、内容の抜粋を返します",
            inputSchema: {
                type: "object",
                properties: { query: { type: "string" } },
                required: ["query"],
            },
        }
    ],
}));

// ツール実行時のロジック(search_vault が今回の肝)
server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const { name, arguments: args } = request.params;

    switch (name) {
        case "search_vault": {
            try {
                // limitを100に増やし、AIが一度に多くの抜粋(Excerpt)を受け取れるように最適化
                const url = `${OMNISEARCH_HOST}/search?q=${encodeURIComponent(args?.query)}&limit=100`;
                const response = await fetch(url);
                const data = await response.json();

                // ファイルパスと抜粋を整形してAIに渡す
                const results = data.map((res) =>
                    `File: ${res.path}\nContent_Found: ${res.excerpt}\n---`
                ).join("\n");

                return {
                    content: [{ type: "text", text: results || "該当情報なし" }]
                };
            } catch (error) {
                return { content: [{ type: "text", text: `Error: ${error.message}` }], isError: true };
            }
        }
        // ... 他のツール(list_notes, read_note)の実装
    }
});

const transport = new StdioServerTransport();
await server.connect(transport);
-----------------

こだわりのチューニングポイント

  • limit=100 の設定: デフォルトの検索数では、Excel 内の複数のシートに散らばった情報を拾いきれないことがあります。リミットを広げることで、AI がコンテキストを一度に把握しやすくしました。

  • 抜粋(Excerpt)の整形File: path と Content_Found を明示的に分けることで、AI が「どのファイルのどの部分を読んでいるのか」を誤認しないようにしています。

3. 遭遇したトラブルと解決策:Excel 解析の壁

構築中、Excel ファイルが物理的に存在(ls で確認済み)するのに、AI が「0 results」と答える事象に遭遇しました。

原因と対策:

  1. インデックスの未更新: Omnisearch と Text Extractor の設定変更後、キャッシュクリア(Clear cache data)と Obsidian の再起動が必要でした。

  2. バイナリ解析のラグ: Excel ファイルは PDF 以上にパースに時間がかかる場合があるため、バックグラウンドでのインデックス完了を待つ必要がありました。

4. 導入後の効果

開通後は、AI に対して以下のような自然な指示が可能になりました。

「K8S のまとめ Excel の内容から、Docker ネットワークとの関連性を整理して」

AI は自作の search_vault ツールを叩き、Excel 内の「k8sまとめ」「Dockerまとめ」といった複数シートの構造まで正確に把握して回答を生成します。


0 件のコメント:

コメントを投稿

【ブログ追記セクション】自作MCPサーバーによる「AIとObsidian」の接続

特に、AI とローカル環境を繋ぐブリッジとして  MCP (Model Context Protocol) サーバーを自作 した過程を記録します。 1. 構築したアーキテクチャ 単に Obsidian を使うだけでなく、AI(LLM)がローカルのファイルを自在に検索・閲覧できる「...