💬

ローカルのHTMLを全文検索するMCPサーバーを作ってみる

に公開

ドキュメントが HTML提供されているゲーム向けのスクリプトを VSCode エージェントモード(MCPクライアント)で利用したいと思ったので試しに作ってみることにします。
すでに公開されている HTML検索のMCPサーバーもあるんですが、勉強も兼ねて自作です。

概要

環境変数(.env)指定したディレクトリ内を全文検索してファイル内容とリンクを提供するPythonスクリプト。Rancher Desktop(Docker Desktop) 環境で動作する

一式をダウンロード

git clone して一式をローカルに保存する

git clone https://github.com/tfuru/local-file-search-mcp.git
cd local-file-search-mcp

スクリプト内容

local-file-search-mcp/
├── Dockerfile
├── README.md
├── app/
│   ├── core
│   ├── server.py
│   └── test.py
├── docker-compose.yml
└── requirements.txt

利用モジュール

fastmcp
beautifulsoup4
dotenv

server.py

コンテナ環境で利用する場合に FastMCP 初期化で host="0.0.0.0" 引数を追加する必要がある

import os
import sys
import asyncio
from pathlib import Path

from bs4 import BeautifulSoup
from mcp.server.fastmcp import FastMCP
from dotenv import load_dotenv

# .nev を読み込んで PATH 環境変数を設定
load_dotenv()

# 環境変数から HTML_DATA_PATH を取得
html_data_path = os.getenv("HTML_DATA_PATH")

data_path = os.path.join(os.path.dirname(__file__), f'data/{html_data_path}')
soup = BeautifulSoup(open(data_path, encoding='utf-8'), 'html.parser')

# ここで ホスト 0.0.0.0 を指定して FastMCP サーバーを起動する必要がある
mcp = FastMCP("local-file-search-mcp", host="0.0.0.0", transport="sse")

# BeautifulSoup を使って HTML データを解析
@mcp.tool(
    name="search",
    description="Searches for a term in the HTML data and returns matching elements."
)
async def search(term: str) -> list:
    results = []
    for element in soup.find_all(string=lambda text: term.lower() in text.lower()):
        results.append(str(element))
    return results

if __name__ == "__main__":
    mcp.run()

検索対象ドキュメントダウンロード

再帰的に取得して検索対象のドキュメントをローカルにダウンロードする

cd local-file-search-mcp/app/data
wget -r -l 3 -L https://docs.cluster.mu/script/ -P ClusterCreatorKitScriptReference/

test.py

ツール呼び出しを試すためのサンプルコードです.
サンプルとして onStart について検索しています

import asyncio
from mcp import ClientSession

from mcp.client.stdio import stdio_client, StdioServerParameters
# transport を sse に変更する場合は、以下のようにインポートを変更
from mcp.client.sse import sse_client

# stdio クライアントを使用する場合のメイン
async def main_stdio():
    # サーバスクリプトを起動
    # fastmcp run server.py --transport stdio
    params = StdioServerParameters(command="fastmcp", args=["run","server.py","--transport","stdio"])

    # async with を使って stdio_client を起動・管理
    async with stdio_client(params) as (read_stream, write_stream):
        async with ClientSession(read_stream, write_stream) as session:
            # Initialize the connection
            await session.initialize()

            # ツール呼び出し: hello_world
            # result = await session.call_tool("hello_world", {"name": "MCP"})
            # print("Tool result:", result.content)
            # ツール呼び出し: search
            search_term = "$.onStart"
            search_results = await session.call_tool("search", {"term": search_term})
            print(f"Search results for '{search_term}':", search_results.content)

# sse クライアントを使用する場合のメイン
async def main_sse():
    # サーバスクリプトを起動
    # fastmcp run server.py --transport sse
    params = "http://127.0.0.1:8000/sse"

    # async with を使って sse_client を起動・管理
    async with sse_client(params) as (read_stream, write_stream):
        async with ClientSession(read_stream, write_stream) as session:
            # Initialize the connection
            await session.initialize()

            # ツール呼び出し: hello_world
            result = await session.call_tool("hello_world", {"name": "MCP"})
            print("Tool result:", result.content)
            # ツール呼び出し: search
            search_term = "onStart"
            search_results = await session.call_tool("search", {"term": search_term})
            print(f"Search results for '{search_term}':", search_results.content)

if __name__ == "__main__":
    asyncio.run(main_sse())
    # asyncio.run(main_stdio())

起動方法

Rancher Desktop(Docker Desktop) を起動した環境でイメージ作成,起動ができる

cd local-file-search-mcp
# 環境変数を修正する
code .env

# イメージの作成と起動
docker compose build
docker compose up -d

docker compose logs -f

ローカルでのテスト実行

cd local-file-search-mcp/app
python test.py

VSCode エージェント(MCPクライアント)での設定

.vscode/mcp.json
{
  "servers": {
    "local-file-search-mcp": {
      "type": "sse",
      "url": "http://127.0.0.1:8000/sse",
    }
  }
}

実行結果

VScode GitHub Copilot Chat での利用例

Cluster Creator Kit $.onStart の説明を検索して

Cluster Creator Kit の $.onStart について
$.onStart は、Cluster Creator Kit のスクリプトで使用される関数で、以下の特徴があります:

呼び出し条件の違い
ワールドクラフト製ワールドとCreator Kit製ワールドで onStart の呼び出される条件が異なります。

ワールドクラフト製ワールドでの動作:
ワールドに置かれているアイテム: 新しくスペースが始まったときに onStart が実行されます
スペース開始後に新規作成されたアイテム: 作成されたタイミングで onStart が実行されます
Creator Kit製ワールドでの動作:
アイテムをコピーしたとき: コピーによって新たに作られたアイテムの onStart が実行されます
アイテムの状態保存: アイテムが onStart を実行したかどうかはステートの一部として保存されるため、再入室時に onStart が再度実行されることはありません
既存アイテムの制限: 元のワールドクラフトで onStart が実行されているアイテムに対しては、ワールドクラフト製ワールド上で実行されることはありません
新規アイテム: スペース開始後に新規に作成されたアイテムに対してはワールドクラフトと同様に onStart が実行されます
この情報は、Cluster Creator Kit のスクリプトリファレンスから取得したもので、アイテムのライフサイクル管理において重要な役割を果たす関数です。

まとめ

無事にうまくドキュメントを検索してくれる MCPサーバを作る事ができました。

Discussion