手元の md を Claude から関連度順に検索できるようにする MCP サーバのテンプレート。
docs/ に md を置くだけで、## 見出しごとに BM25 で索引化される。追加依存は mcp パッケージのみ、
索引は起動時にメモリ上に作るので DB もビルド手順も要らない。
devcontainer 込みなので、clone して開けば Claude Code と Codex がそのまま使える (claude-devcontainer-template と同じ構成)。
完全一致で足りるなら Grep のほうが速い。 このサーバが効くのは次の2つが要るときだけ:
- 語を分けても拾える — 「節 検索」で検索すると、同梱の docs/example.md の
「検索のしかた」節が返る。
rg "節 検索"のほうは 0 件(連続していないと当たらない) - 関連度順の上位N件 — grep のヒット順はファイル順なので、100件当たったら100件読むことになる
さらに、Claude Code だけで使うなら Skill でも同じことができる。BM25 の実装(scripts/)は どちらの形でもそのまま使えるので、MCP を選ぶ理由は次のどちらかに絞られる:
| MCP にする | Skill で足りる | |
|---|---|---|
| 使う側 | Claude Desktop や他エディタからも使う | Claude Code だけ |
| コンテキスト | ツール定義が常時載る | description 1行だけ |
GitHub の "Use this template"、またはローカルで:
git clone <this-repo> my-docs && cd my-docs && rm -rf .git && git initdocs/ に置く。ファイル名が文書 id、本文の # 行がタイトルになる。
docs/example.md が形式の見本なので、確認したら消す。
元資料が Word / PowerPoint / Excel / PDF / Web ページなら、同梱の import-docs スキルに変換させる:
/import-docs この資料を取り込んで
検索の単位は ## 節なので、変換の主目的は意味のまとまりごとに見出しを付けることになる。
単にテキストを抜いただけでは索引が効かない。スキルはそこまで面倒を見る。
出典を残すなら先頭にフロントマターを書く。索引には入らない:
---
source: https://example.com/some-document
captured: 2026-01-01
---節が検索の単位なので、見出しを細かく切るほど結果が絞り込まれる。見出しの無い長文は
1節として丸ごと返るため、長い資料は ## を入れてから置く。
VS Code で Dev Containers: Reopen in Container。pip install -r requirements.txt は
postCreateCommand が済ませる。
.mcp.json はプロジェクトスコープの設定なので、Claude Code が自動で読む。初回は
ワークスペースの信頼を尋ねられるので承認する。接続できているかは claude mcp list で分かる。
パスはリポジトリルートからの相対にしてある。絶対パスに書き換えるとディレクトリ名を変えたときに
壊れる。${workspaceFolder}(VS Code の変数)や ${CLAUDE_PROJECT_DIR} は .mcp.json では
展開されず、未定義の環境変数として警告になるので使わない。
list_documents() 索引している文書を出典つきで一覧
get_document(document, section) 指定文書の全文、または節だけ
search_docs(query, limit) 節単位で横断検索(関連度順)
reload_index() docs/ を読み直して索引を作り直す
例として Word 文書を取り込む場合:
1. source/報告書.docx に置く
2. Claude に「source/報告書.docx を取り込んで」と頼む
→ import-docs スキルが起動し、pandoc で変換して ## 見出しを整え、
出典つきで docs/report.md を書く
3. Claude が reload_index() を呼ぶ
4. search_docs("...") で引けるようになる
3 を飛ばすと引けない。 索引は起動時にメモリ上に作るので、md を足しただけでは反映されない。
stdio の MCP サーバは Claude Code から再起動されないため、セッションを続けたまま反映するには
reload_index() を呼ぶ(スキルの手順にも入っている)。
個人メモなど、コミットしたくない md を足したいとき:
os.pathsep(Linux では :)区切りで複数指定できる。docs/ と同じ形式で索引される。
中身は検索時に LLM へ渡る点に注意。
scripts/mcp_server.py に @mcp.tool() を付けた関数を書くだけ。
型注釈と docstring がそのまま Claude 側のツール定義になるので、引数の説明は docstring の
Args: に書く。
@mcp.tool()
def count_sections(document: str) -> str:
"""指定した文書の節数を返す。
Args:
document: 文書id か タイトル(部分可)
"""
...docs/ 変換後の md。コミットする(索引対象)
source/ 変換元の .docx / .pptx / .xlsx / .pdf。gitignore 済みでコミットしない
scripts/ MCP サーバ本体
変換元をコミットしないのは、機密や著作物を公開リポジトリに入れる事故を防ぐため。 出所は変換後の md のフロントマターに残るので、URL のあるものは取り直せる。
| ファイル | 中身 |
|---|---|
| scripts/mcp_server.py | MCP サーバ本体、BM25、md のパース |
| scripts/common.py | パス解決、表記の正規化、bigram トークナイザ |
| .mcp.json | プロジェクトスコープの MCP サーバ登録 |
| .claude/skills/import-docs/ | 元資料を md に変換するスキル |
変換に使うもの(MCP の実行には不要。使うときだけ入れる):
| 形式 | 手段 |
|---|---|
| Word (.docx) | pandoc -t gfm |
| PowerPoint (.pptx) | 同梱の pptx_to_md.py(pandoc は pptx を読めない) |
| Excel (.xlsx) | 同梱の xlsx_to_md.py(同上) |
Read ツールでページを読んで書き起こす(抽出ツールより表の再現が効く) |
|
| Web | WebFetch |
形態素解析を使わない。英数字は単語、かな漢字は文字bigramに分割して BM25 にかけるだけなので、 辞書も追加依存も要らないかわりに次が効かない:
- 同義語や言い換え — 「価格」で「値段」は出ない
- 英数字・かな・漢字以外の文字。ハングルやキリル文字はトークンが空になり、
エラーも出さずに検索結果から消える。使うなら
common.pyの_CJK/_WORDを広げる
長い文書は get_document が全文を返さず節の一覧を返す(既定 8,000 文字超)。
資料1本でコンテキストを使い切らないための制限で、閾値は mcp_server.py の _MAX_FULL。