AI News HubLIVE
サイト内リライト6 分で読了

Weaviate MCPでコーディングアシスタントを構築:コードとドキュメントのRAG

Weaviateに組み込まれたMCPサーバーを使って、Claude Code、Cursor、VS Codeにコードベースとドキュメントに対するハイブリッド検索機能を提供します。グルーコードは不要です。

ソースWeaviate Blog

先週、Claude Codeに比較的簡単な機能をコードベースに実装するよう依頼しました。3ターンのやり取りの後、コンテキストは80Kトークン以上を消費し、それでもClaudeは私が含め忘れた重要な情報を欠いていました。これが検索なしのループです:情報を少なすぎるとエージェントは推測し、多すぎるとエージェントが使わないコンテキストにお金を払うことになります。

ほとんどのチームはコードベースに対するRAGでこれを解決します。典型的なセットアップは、ベクトルデータベースと、その間を橋渡しするカスタムMCPサーバーです。Weaviateはこれを簡素化します:MCPサーバーはデータベースに内蔵され、REST APIと同じポートの/v1/mcpにあります。1つの環境変数で有効になります。Weaviateの他のワークロードで使うのと同じハイブリッド検索がコード検索にも活用でき、BM25部分がconnect_to_localのような関数識別子をマッチ可能にし、ベクトル部分が「クライアントを初期化する方法」のような意味的な意図を見つけます。

この記事では、その内蔵MCPサーバー上にコーディングアシスタントを構築する手順を説明します:コードベースの取り込み、ドキュメントの取り込み、Claude Code、Cursor、VS Codeへの接続、実際のクエリの実行。カバーするトピック:

  • なぜあなたのコーディングアシスタントはトレーニングデータ以上のものを必要とするのか
  • Weaviate MCPがこの仕事に適している理由
  • ステップ1:MCPを有効にしてWeaviateを実行
  • ステップ2:スキーマの設計
  • ステップ3:コードベースのチャンク化と取り込み
  • ステップ4:ドキュメントのチャンク化と取り込み
  • ステップ5:Claude Code、Cursor、VS Codeに接続
  • 試してみる
  • エージェント実行手順書:自動セットアップ

なぜコーディングアシスタントはトレーニングデータ以上のものを必要とするのか

LLMは固定の知識カットオフを持ち、プライベートコードについては何も知りません。単純な回避策はファイルをプロンプトにそのまま貼り付けることですが、これには3つの問題があります。

  1. コスト:コンテキスト内のトークンはターンごとに課金されます。200ファイルのPythonプロジェクトはプロンプトに収まらず、収まったとしてもエージェントが推論している間ずっと課金されます。
  2. 古いコンテキスト:ファイルがプロンプトに入ると、その時点で固定されます。エージェントが関数を変更してからそれを再読み込みする必要がある場合、ファイル全体を再読み込みしなければなりません。モデルの見解とディスク上の真実の間にはライブリンクがありません。
  3. 不適切な粒度:ファイルが収まったとしても、モデルの注意はインポートやモジュールレベルのボイラープレートなど、間違った部分に費やされます。エージェントが実際に編集している関数は、from x import yの何百行ものコードとコンテキストを奪い合います。

検索はこれら3つすべてを解決します。コードベースを一度インデックスし、チャンクをベクトルデータベースに保存し、LLMクライアントがクエリごとに必要なものだけを引き出せるようにします。これがRAGです。コーディングアシスタントは、これまでカスタムシムなしではこのようなデータベースと通信するクリーンな方法がありませんでした。ここでMCPが登場します。

なぜWeaviate MCPがこの仕事に適しているのか

モデルコンテキストプロトコル(MCP)は、Claude Code、Cursor、VS CodeなどのLLMクライアントが外部ツールを呼び出すための標準化された方法です。Weaviate v1.37.1はそのコア操作をMCPツールとして、Streamable HTTPエンドポイント/v1/mcpで直接公開します。4つのツールが公開されています:

  • weaviate-collections-get-config:LLMがどのコレクションが存在し、どのプロパティがあるかを検査
  • weaviate-tenants-list:マルチテナンシーを使用している場合のテナント一覧
  • weaviate-query-hybrid:ハイブリッド(BM25+ベクトル)検索の実行
  • weaviate-objects-upsert:書き込みアクセスが有効な場合のみオブジェクトを書き込み

ハイブリッド検索は、このスタックがコーディングアシスタントに適している最も具体的な理由です。コードは識別子と意図の混合です。BM25は識別子を正確にマッチし、ベクトルは意図をキャプチャします。「429でリトライを処理しているのはどこか?」のようなクエリは両方を同時に必要とします:ベクトルは意味的に関連するリトライコードを見つけ、BM25は429を正確なトークンとして固定します。純ベクトル検索は整数マッチを逃し、純BM25はユーザーがまだ知らない言い回しを見逃します。ハイブリッドはこの種の混合意図クエリで勝ります。

運用のシンプルさが2番目の理由です。競合するスタックはMCPサーバーをベクトルデータベースと並行して実行します。これは本番環境で監視する2番目のサービスです。WeaviateはMCPサーバーをデータベース内部に同梱し、同じポート、同じ認証で提供します。監視すべきはWeaviateだけです。

3番目の理由はマルチテナンシーです。1つのWeaviateインスタンスで多くのコードベースを保持でき、それぞれがテナントとして分離されます。複数のリポジトリを持つ組織にとっては、チームごとに1クラスターではなく、1クラスターで済みます。

Weaviate MCPと関数呼び出し(function calling)の比較:関数呼び出しはLLM APIごとに異なりますが、MCPはトランスポートレベルのプロトコルであり、MCPを話すクライアントは書き換えなしに任意のMCPサーバーを呼び出せます。一度構築した検索を、変換なしにClaude Code、Cursor、VS Codeから使用できます。

ステップ1:MCPを有効にしてWeaviateを実行

MCPサーバーはデフォルトで無効です。2つの環境変数で有効になります:

services:
  weaviate:
    image: cr.weaviate.io/semitechnologies/weaviate:1.37.1
    ports:
      - '8080:8080'
      - '50051:50051'
    environment:
      MCP_SERVER_ENABLED: 'true'
      MCP_SERVER_WRITE_ACCESS_ENABLED: 'true'
      AUTHENTICATION_ANONYMOUS_ACCESS_ENABLED: 'true'
      DEFAULT_VECTORIZER_MODULE: 'text2vec-openai'
      ENABLE_MODULES: 'text2vec-openai'
      OPENAI_APIKEY: ${OPENAI_APIKEY}

MCP_SERVER_ENABLEDは読み取りツール(設定、テナント、ハイブリッドクエリ)を公開します。MCP_SERVER_WRITE_ACCESS_ENABLEDはupsertツールを追加し、エージェントが結果を書き戻せるようにします。検索のみが必要な場合はスキップしてください。最初は読み取り専用にしておくことをお勧めします。ほとんどのコーディングエージェントの作業は検索でカバーでき、エージェントがナンセンスな内容を知識ベースに書き込むリスクを回避できます。書き込みアクセスは、リスクを正当化する具体的なユースケースができた時点で有効にしてください。

本番環境の認証 この例では、MCPの配線に集中するために匿名アクセスを有効にしています。ネットワーク化されたデプロイメントでは、APIキーを有効にし、クライアント設定にAuthorization: Bearerを追加してください。WeaviateのMCPサーバーは標準認証とRBACをサポートしています。

起動してエンドポイントが生きていることを確認します。Streamable HTTPは他の呼び出しの前にinitializeハンドシェイクが必要です:

docker compose up -d
curl -sf -X POST http://localhost:8080/v1/mcp \
  -H 'Content-Type: application/json' \
  -H 'Accept: application/json, text/event-stream' \
  -d '{"jsonrpc":"2.0","id":0,"method":"initialize","params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

生存しているMCPサーバーはJSON-RPC応答を返し、後続の呼び出しでエコーする必要があるMcp-Session-Idヘッダーを設定します。

ステップ2:スキーマの設計 2つのコレクションを作成します:CodeChunks(コードチャンク用)とDocChunks(ドキュメントチャンク用)。同じWeaviateインスタンスを共有します。

client.collections.create(
    name="CodeChunks",
    properties=[
        Property(name="content", data_type=DataType.TEXT, tokenization=Tokenization.WORD),
        Property(name="symbol", data_type=DataType.TEXT, tokenization=Tokenization.LOWERCASE),
        Property(name="file_path", data_type=DataType.TEXT, tokenization=Tokenization.FIELD),
        Property(name="language", data_type=DataType.TEXT, tokenization=Tokenization.FIELD),
        Property(name="repo", data_type=DataType.TEXT, tokenization=Tokenization.FIELD),
    ],
    vector_config=Configure.Vectors.text2vec_openai(),
)

symbolは小文字トークン化によりconnect_to_localを3つではなく1つのトークンにします。file_pathlanguagerepoFIELDを使用して正確なマッチングを実現します。contenttitleWORDを使用します。

ステップ3:コードベースのチャンク化と取り込み ナイーブな行ベースのチャンク化はコードを壊します。関数が2つのチャンクに分割されると、一方でシグネチャが、もう一方でボディが失われます。解決策は構文境界に沿ってチャンク化することです:関数ごと、クラスごと、トップレベルステートメントごとに1チャンク。Pythonコードには標準ライブラリのastで十分です:

import ast
from pathlib import Path

def chunk_python_file(path: Path) -> list[dict]:
    source = path.read_text()
    tree = ast.parse(source)
    lines = source.splitlines()
    chunks = []
    for node in ast.iter_child_nodes(tree):
        if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)):
            start = node.lineno - 1
            end = node.end_lineno
            chunks.append({
                "content": "\n".join(lines[start:end]),
                "symbol": node.name,
                "file_path": str(path),
                "language": "python",
                "repo": "my-service",
            })
    return chunks

そしてWeaviateのバッチパターンで取り込みます:

with code_chunks.batch.dynamic() as batch:
    for py_file in Path("./").rglob("*.py"):
        for chunk in chunk_python_file(py_file):
            batch.add_object(properties=chunk)

各チャンクは挿入時に設定されたtext2vec-openaiモジュールによって自動的にベクトル化されます。別の埋め込みファミリーを使いたい場合は、text2vec-voyageaitext2vec-cohereに置き換えることもできます。

ステップ4:ドキュメントのチャンク化と取り込み 散文のチャンク化は異なります。見出しが実際の境界です。有用なデフォルトは「H2で分割し、セクションが長すぎる場合はH3で分割」です:

import re

def chunk_markdown(text: str, max_chars: int = 1500) -> list[str]:
    sections = re.split(r"(?m)^## ", text)
    chunks = []
    for section in sections:
        if len(section) < max_chars:
            chunks.append(section.strip())
        else:
            subsections = re.split(r"(?m)^### ", section)
            for sub in subsections:
                if sub.strip():
                    chunks.append(sub.strip()[:max_chars])
    return chunks

各チャンクにはtitlesource_urlも保持します。

ステップ5:Claude Code、Cursor、VS Codeに接続

  • Claude CodeはMCPをネイティブサポート:~/.claude/settings.jsonにWeaviateエンドポイントを指す設定を追加すると、ハイブリッド検索ツールが自動的に利用可能になります。
  • Cursor.cursor/mcp.jsonで設定:同様にURLとツールリストを指定。
  • VS CodeはMCP拡張機能をインストール後、mcp.jsonを設定。

エージェント実行手順書:自動セットアップ シェルスクリプトsetup.shを提供し、以下の手順を含めます:環境変数の確認、Dockerイメージのプル、Weaviateの起動、準備完了の待機、MCPエンドポイントの検証、そしてingest.pyを実行してデータを取り込みます。スクリプトは冪等であり、繰り返し実行できます。

要するに、Weaviate MCPはコーディングアシスタントに対して、効率的なハイブリッド検索を実現するための非常にシンプルなパスを提供します。MCPサーバーをデータベース内に組み込むことで、運用上のオーバーヘッドを排除し、強力なマルチテナンシーとすぐに使えるハイブリッド検索機能を提供します。個人プロジェクトでもチームコラボレーションでも、すぐに始められ、LLMエージェントにプライベートコードベースを真に理解させることができます。