doc-search
MCP server for hybrid documentation search (keyword + vector) and RAG chat, enabling natural language queries with cited answers from indexed repositories.
README
doc-search — ハイブリッド検索 + RAG チャット for ドキュメントリポジトリ
社内ドキュメントリポジトリ(用語集・レビュー観点・設計ドキュメント)を対象にした、 キーワード検索(BM25)× ベクトル検索(意味検索) のハイブリッド検索エンジンと、 その上に載る RAG チャット(Claude API・モデル選択・ストリーミング出力)。
UI 設計は SodaShikenn/LLM-RAG_KBQA を踏襲 (左サイドバー: モデル選択 / ナレッジ設定 / 会話履歴、右: チャット + Send / Cancel)。
4つの使い方:
- RAG チャット (
/) — モデルを選んで質問。検索→引用付き回答をストリーミング - 検索エクスプローラ (
/search.html) — インクリメンタル検索、KW/VEC/RRF スコア表示 - CLI —
docsearch search "..." - MCPサーバー — Claude Code のツールとして登録(Agentic RAG)
セットアップ(軽量: ML 依存なし・~30MB)
cd doc-search
brew install uv # 未導入の場合
uv venv --python 3.12 .venv
uv pip install -p .venv/bin/python -r requirements.txt
cp .env.example .env # ANTHROPIC_API_KEY を記入(チャット用)
ローカル埋め込みモデル(e5 / bge-m3)を使う場合のみ、重い ML スタックを追加:
uv pip install -p .venv/bin/python -r requirements-local.txt
使い方
# 1) インデックス構築
.venv/bin/python -m docsearch index sample_docs # 自動選択
.venv/bin/python -m docsearch index /path/to/docs --embedder voyage # クラウド埋め込み
# 2) サーバー起動 → http://127.0.0.1:8765
.venv/bin/python -m docsearch serve --port 8765
# 3) CLI検索
.venv/bin/python -m docsearch search "解約率" --mode vector
APIキーが無くてもモデル「Demo(オフライン)」でチャットUIの動作確認ができる。
埋め込みモデルの選択 (--embedder)
| name | 場所 | 重さ | 特徴 |
|---|---|---|---|
voyage |
クラウド | ローカル依存ゼロ | voyage-3.5。Anthropic 推奨の埋め込みパートナー。品質最高クラス。VOYAGE_API_KEY 必要。文書テキストが外部送信される点は要承認 |
e5 |
ローカル | ~470MB + torch | multilingual-e5-small。完全ローカル、日英対応の無難な既定 |
e5-large |
ローカル | ~2.2GB + torch | e5 の高精度版 |
bge-m3 |
ローカル | ~2.3GB + torch | ローカル最強クラスの多言語モデル。ただし「軽量」とは真逆で CPU 推論も遅い |
hash |
ローカル | 依存ゼロ | 字面ハッシュ(意味検索なし・縮退モード) |
選び方: 品質とセットアップの軽さを両立したいなら voyage(クラウド許可時)。
完全ローカル必須なら e5、精度を上げたければ bge-m3(重さを許容できる場合)。
BM25(字句一致)は常にローカルで動くため、埋め込みの役割は「言い換え」の吸収のみ —
モデル差が効くのはそこだけで、bge-m3 の multi-vector/sparse 機能はこの構成では不要。
チャット (RAG) の仕組み
質問 → 検索の深さ(effort)を解決(auto は確信度シグナルで自動判断)
→ 検索実行(hard は選択モデルがクエリを言い換え → 全変種を検索して RRF 融合)
→ system プロンプトに参照資料として注入([n] path:line 付き)
→ Claude API へストリーミング要求(output_config.effort も連動)
→ data: {status|sources|delta|done|error} を SSE 配信
→ UI が逐次描画 + 「なぜこの検索をしたか」の説明 + 引用チップ。会話は localStorage
検索の深さ(effort)— hybrid/keyword/vector を隠す
利用者に IR 用語を選ばせない。選ぶのは「どれだけしっかり探すか」だけで、 実際に何をしたかは回答の下に日本語で表示される(例: 「おまかせ → しっかり — キーワード一致が無く…言い換えを生成して深く検索」)。
| effort | 動作 | 使いどころ |
|---|---|---|
| おまかせ (auto) | 一度探ってから確信度で easy/medium/hard を自動選択 | 既定。迷ったらこれ |
| かんたん (easy) | ハイブリッド検索1回・上位4件。モデルの effort も low | 用語の直接検索。最速・最安 |
| ふつう (medium) | 標準のハイブリッド検索・6件 | 従来の既定動作 |
| しっかり (hard) | 選択モデルが言い換えを3件生成 → 全クエリで検索し RRF 融合・10件。モデル effort は high | 資料と言葉遣いが違う質問(例:「残業した分の給料」→ 割増賃金) |
auto の判断シグナル: キーワード一致の有無・ベクトル類似度の強さ・両検索の上位一致。
言い換え生成が使えない場合(Demoモデル・キー未設定)は hard を「件数拡大」に自動縮退。
生の検索モード(keyword/vector/hybrid)はエンジニア向けに /search.html と CLI に残している。
- モデル: Claude Opus 5(既定)/ Sonnet 5 / Haiku 4.5 / Demo(オフライン)
- Opus 5 はサーバーサイド refusal fallback を有効化(安全上の回答辞退時に 同一リクエスト内で代替モデルに自動フォールバック)
- 生成 API は Anthropic 公式 SDK。キーは
.envのANTHROPIC_API_KEY
Claude Code への組み込み(MCP / Agentic RAG)
.mcp.json(対象リポジトリまたはホーム):
{
"mcpServers": {
"docsearch": {
"command": "/ABSOLUTE/PATH/doc-search/.venv/bin/python",
"args": ["-m", "docsearch.mcp_server"],
"env": { "DOCSEARCH_INDEX": "/ABSOLUTE/PATH/doc-search/index" }
}
}
}
ツール: search_docs(query, mode, k) / docs_repo_info()。
Claude Code 自身がクエリ立案→再検索→ファイル読解→引用回答まで行うため、
チャットUIとは別に、エディタ内での Agentic RAG が成立する。
実データへの差し替え(アクセス権のあるマシンで)
このリポジトリに入っているのは プレースホルダ(sample_docs)だけ。 実データと社内リポジトリのリンクは、アクセス権のあるマシン側で コード変更なしに差し替える。優先順位:
- 環境変数(Docker はこれ):
.envにDOCSEARCH_DOCS_HOST=/path/to/real-docs(コンテナへのマウント元)とDOCSEARCH_GITHUB_BASE=https://github.example.com/org/repo/blob/main - 設定ファイル(ローカル実行):
cp datasource.example.json datasource.jsonしてdocs_dir/github_base/embedderを編集 →docsearch index(引数なし)。datasource.jsonは gitignore 済みで、社内リポジトリへのポインタは push されない - プレースホルダ: 何も設定しなければ
sample_docs/を索引
解決ロジックは docsearch/datasource.py の
get_datasource() 1関数に集約されている。
引用の GitHub リンク
検索結果・引用チップ・回答中の [path:line] は、ドキュメントリポジトリの
GitHub 上の該当行への深いリンクになる(blob/<インデックス時のSHA>/path#L<line>
形式なので、リポジトリが進んでも行アンカーはずれない)。
- インデックス時に docs リポジトリの
git remoteから自動検出(GHE も可) - 自動検出できない場合(Docker で docs をマウントした場合など)は
DOCSEARCH_GITHUB_BASE=https://github.com/o/r/blob/main/docsを.envに設定 (CLI では--github-base)
検索エンジンの設計ポイント
- 日本語キーワード検索: CJK文字列をバイグラム展開して SQLite FTS5 に索引。 クエリ側はバイグラムのフレーズ検索で隣接一致(形態素解析器なしで動く)
- RRF融合: BM25スコアとコサイン類似度はスケール非互換のため順位ベースで融合
- チャンクにパンくず: 見出し階層をチャンク先頭に付与(用語集は見出し=用語のため)
試すと面白いクエリ
| クエリ | 期待 |
|---|---|
消費税区分 |
キーワードで用語集に直撃 |
解約率 |
ベクトルが「チャーンレート」を発見(言い換え) |
仕訳の二重登録を防ぐ仕組みは?(チャット) |
冪等性 / Idempotency-Key を引用して回答 |
テナント 漏えい |
セキュリティ観点のテナント分離 |
デプロイ
ローカル常駐(macOS / LaunchAgent)
bash deploy/install-launchd.sh # ログイン時自動起動・クラッシュ時自動再起動
- ログ:
logs/docsearch.log/logs/docsearch.err.log - 停止・削除:
launchctl bootout gui/$(id -u)/com.sodashikenn.docsearch && rm ~/Library/LaunchAgents/com.sodashikenn.docsearch.plist - macOS TCC 注意: リポジトリが
~/Desktop等の保護フォルダ配下にあると、 launchd 起動の python がファイルアクセスを拒否されて起動ループになることがある。 その場合は「システム設定 > プライバシーとセキュリティ」で python にアクセス権を 与えるか、リポジトリを保護外(例:~/dev/)へ移動する
Docker(別マシンとの共有はこれが最短)
git clone https://github.com/SodaShikenn/doc-search.git && cd doc-search
cp .env.example .env # ANTHROPIC_API_KEY を記入
docker compose up --build -d # → http://127.0.0.1:8765
-
Docker が無い Mac(Docker Desktop を使わない場合):
brew install colima docker docker-compose && colima start mkdir -p ~/.docker/cli-plugins && ln -sfn $(brew --prefix)/opt/docker-compose/bin/docker-compose ~/.docker/cli-plugins/docker-compose -
完全ローカルのベクトル検索にする場合(メモリに余裕がある M シリーズ Mac 推奨):
.envにWITH_LOCAL_ML=1とDOCSEARCH_EMBEDDER=e5を書いてからdocker compose up --build -d(イメージ ~2-3GB、初回はモデルDLあり。 埋め込みモデルの変更は起動時に検知され自動で再索引される) -
既定のスリム版イメージ(~300MB): ベクトル検索は
VOYAGE_API_KEYがあればクラウド、 無ければ hash 縮退(キーワード検索は常にフル動作) -
実ドキュメントは
docker-compose.ymlの./sample_docs:/docs:roを差し替え、 内容更新後の再索引はDOCSEARCH_REINDEX=1 docker compose up -d -
キーはホストの
.envから注入(イメージには焼き込まれない) -
認証は無い。公開はローカルバインドのままリバースプロキシ(認証付き)or VPN 越しに
Third-party
webui/vendor/ は自己ホストした第三者ライブラリで、各自のライセンスに従う:
marked v13.0.2 (MIT)・
DOMPurify 3.1.6 (Apache-2.0 OR MPL-2.0)。
それ以外は MIT(LICENSE 参照)。
制限と発展
- インデックスは全再構築のみ(差分更新は未実装)
- 会話履歴はブラウザの localStorage(サーバー永続化なし)
- 評価: 質問→正解ファイルの recall@k で hybrid vs 単体・埋め込みモデル間を比較すると良い
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
Neon Database
MCP server for interacting with Neon Management API and databases
E2B
Using MCP to run code via e2b.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.