2026年4月14日火曜日

LM StudioのMCPが遅い・嘘をつく?インフラ屋がコード1つで爆速化した話

 

1. 課題:ローカルLLM+MCPが「爆熱」で「5分待ち」

LM StudioのMCP経由でObsidianを読み込ませると、以下の問題が発生しました。

  • レイテンシ地獄: 回答まで5分以上かかる。

  • ハードウェア負荷: M4 MaxのGPUが唸りを上げ、本体が熱を持つ。

  • ハルシネーション: 検索が空振ると、AIが脳内の一般常識で「嘘のファイル構成」を捏造し始める。

2. 原因:インデックスなしの「フルスキャン」

原因は、MCPサーバーが質問のたびに数千のファイルをスキャンし、膨大なテキストをLLMのコンテキストに流し込んでいたこと。DBでいう「インデックスなしのフルテーブルスキャン」を毎秒回しているような状態でした。

3. 解決策:検索と推論の「疎結合」化

「AIに全部読ませて探させる」のをやめ、**「インデックス(Omnisearch)と軽量なglobで絞り込んでから渡す」**構造にインフラを再構築しました。


4. 最終版:爆速・低負荷 Obsidian MCP Server

このコードは、ファイル名の部分一致(glob)と中身の全文検索(Omnisearch API)をハイブリッドで実行し、LLMに渡す情報を最小限に絞り込みます。

JavaScript
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";

// 環境変数からパスとOmnisearchのURLを取得
const VAULT_PATH = path.resolve(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.4.0" },
    { capabilities: { tools: {} } }
);

// 1. ツールの定義:AIに「全件リスト」を見せず、検索ツールだけを渡すのがコツ
server.setRequestHandler(ListToolsRequestSchema, async () => ({
    tools: [
        {
            name: "search_vault",
            description: "ファイル名と中身を両方検索し、関連しそうな内容をピックアップします。",
            inputSchema: {
                type: "object",
                properties: { query: { type: "string", description: "検索キーワード" } },
                required: ["query"],
            },
        },
        {
            name: "read_note",
            description: "指定されたパスのファイルを読み込みます。",
            inputSchema: {
                type: "object",
                properties: { relativePath: { type: "string" } },
                required: ["relativePath"],
            },
        }
    ],
}));

// 2. ツールの実装
server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const { name, arguments: args } = request.params;

    if (name === "search_vault") {
        try {
            // クエリの正規化(アンダースコア等をスペースに変換してヒット率を上げる)
            const q = (args?.query || "").replace(/[_-]/g, ' ');
            
            // A. ファイル名検索 (大文字小文字無視、一瞬で終わる)
            const fileNameMatches = await glob(`**/*${q}*.md`, {
                cwd: VAULT_PATH,
                nocase: true,
                absolute: false
            });

            // B. Omnisearch API による中身の全文検索
            const url = `${OMNISEARCH_HOST}/search?q=${encodeURIComponent(q)}&limit=10`;
            const response = await fetch(url).catch(() => null);
            const contentMatches = response?.ok ? await response.json() : [];

            // デバッグログをAIに返し、嘘(ハルシネーション)を防止する
            let report = `[System Log] BasePath: ${VAULT_PATH}\n`;
            report += `[Found Files]: ${fileNameMatches.length}件\n`;
            
            const results = [
                ...fileNameMatches.map(f => `Path: ${f} (Filename Match)`),
                ...contentMatches.map(c => `Path: ${c.path}\nExcerpt: ${c.excerpt}`)
            ].join("\n---\n");

            return { content: [{ type: "text", text: report + (results || "No results found.") }] };
        } catch (e) {
            return { content: [{ type: "text", text: `Error: ${e.message}` }], isError: true };
        }
    }

    if (name === "read_note") {
        const targetPath = path.join(VAULT_PATH, args?.relativePath);
        try {
            const content = await fs.readFile(targetPath, "utf-8");
            return { content: [{ type: "text", text: content }] };
        } catch (e) {
            return { content: [{ type: "text", text: `Read Error: ${e.message}` }], isError: true };
        }
    }
    throw new Error("Tool not found");
});

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

5. 導入後の効果:5分が5秒に

  • 高速レスポンス: LLMに渡すテキスト量が激減したため、回答開始まで数秒に短縮。

  • 正確性の向上: 自分のObsidianにある「本物のメモ」に基づいた回答が返るようになった。

  • 省エネ: M4 Maxが唸ることもなく、静かに、しかし鋭く回答を生成。

0 件のコメント:

コメントを投稿

LM StudioのMCPが遅い・嘘をつく?インフラ屋がコード1つで爆速化した話

  1. 課題:ローカルLLM+MCPが「爆熱」で「5分待ち」 LM StudioのMCP経由でObsidianを読み込ませると、以下の問題が発生しました。 レイテンシ地獄:  回答まで5分以上かかる。 ハードウェア負荷:  M4 MaxのGPUが唸りを上げ、本体が熱を持つ。 ハ...