GraphRAG コードレビュー実装7ステップ — MCPでClaude Codeに渡すまで
前に書いた「Knowledge Graph×MCPでAIコードレビューを8〜49倍圧縮」の記事、公開後にDMで一番来たのは「圧縮率は分かった、で、tree-sitterからMCPまでどう繋げるのか」でした。もっと深く読みたい人は書籍側のKnowledge Graph実践ガイド 第8章から第10章を通しで読むのが早いのですが、記事側では「同じパイプを最短で組むには」を7ステップで書きます。
書くのは実装の順番、詰まりやすいポイント、実運用で外した設計判断の3つです。パフォーマンス数字と設計論はさっきのリンク先に譲ります。あと Qiita に「GraphRAGが優秀なのは精度だけ
」っぽい記事がありますが、そちらはコスト比較の話、こちらは実装パイプの話です。1万件のドキュメントを流し込む話はしません。そもそも何を作るのか
作るのは4つのコンポーネントを繋いだパイプです。
- tree-sitter (ローカル、決定論的なAST抽出)
- Neo4j か Kuzu (グラフDB、ノードとエッジを保存)
- MCP サーバー (Claude Code から叩ける口)
- Cypher クエリ (影響範囲や依存の質問を投げる)
Claude Code が「auth.pyを変更したら何に影響するか」を聞くと、MCP サーバー経由で Cypher が走り、影響ファイルのリストだけが戻ってきます。全ファイルを読ませない、変更に本当に関係する数ファイルだけを渡す。これで指摘の質が変わります。
なおMCP自体の primitive (tools、resources、prompts) の詳細解説はしません。他の記事で書いています。ここでは「MCPサーバーに Cypher を叩かせるまで」だけをやります。

Step 1: tree-sitter で AST を吐かせる
最初の穴はここです。tree-sitter は言語ごとに grammar が別で、Python 用と TypeScript 用は別パッケージです。Python プロジェクトなら tree-sitter + tree-sitter-python を入れれば動きます。
import tree_sitter_python as tspython
from tree_sitter import Language, Parser
PY = Language(tspython.language())
parser = Parser(PY)
with open("service.py", "rb") as f:
tree = parser.parse(f.read())
for node in tree.root_node.children:
if node.type == "function_definition":
name = node.child_by_field_name("name").text.decode()
print(f"func: {name}")
これで関数名とその位置は取れます。ここで甘くて後で困るのは「呼び出しグラフ」です。「A が B を呼んでる」の情報は AST の内側 (call_expression ノード) にあるので、再帰で降りて集める必要があります。深さと親のスコープを両方管理していないと、self.foo() の解決 (self が何のクラスか) を落とします。
私は最初これを甘く見て、後から「メソッド呼び出しが全部取れていない」に気づいて Step 2 のスキーマを引き直しました。
Step 2: Neo4j のスキーマは最初に固める
Neo4j はスキーマレスに近いですが、実運用ではラベルとプロパティを決め打ちしないと Cypher が地獄になります。私が最終的に落ち着いた設計はこれです。
ノード
File— path, language, shaClass— name, file, lineFunction— name, file, line, params (配列), complexity
エッジ
(File)-[:CONTAINS]->(Function)(File)-[:CONTAINS]->(Class)(Class)-[:HAS_METHOD]->(Function)(Function)-[:CALLS]->(Function)(File)-[:IMPORTS]->(File)
CALLS を「Function → Function」で持つのが大事で、「Function → 文字列 (呼び出し先名)」で持つと後で解決できません。tree-sitter で名前しか取れない呼び出しについては、Step 3 でグラフに入れる直前に名前解決を挟みます。
Step 3: 名前解決を挟んでからロード
user.save() の user が User クラスのインスタンスか、モジュール user の関数呼び出しかは、AST だけでは決まりません。scope walk が要ります。
私の場合、tree-sitter で採れる情報から以下を優先順に解決しました。
- import 文で見えている名前
- 同ファイル内で定義された名前
- 型ヒント (Python なら
-> User) から辿れる名前 - 解決できないものは
Unknownノードに逃がす
Unknown に逃がすのは負けているようで、実運用で大事です。「解決できなかったから CALLS を張らない」にすると影響範囲の網に穴が空きますが、Unknown を経由させれば後で潰せます。
ロードは Neo4j の bolt drivers で MERGE を叩くだけです。
from neo4j import GraphDatabase
driver = GraphDatabase.driver("bolt://localhost:7687", auth=("neo4j", "..."))
with driver.session() as session:
for func in funcs:
session.run(
"MERGE (f:Function {name: $name, file: $file}) "
"SET f.line = $line, f.complexity = $c",
name=func.name, file=func.file, line=func.line, c=func.complexity
)
MERGE を CREATE にすると再ビルドで重複が出ます。ここも一度やらかしました。
Step 4: MCP サーバーは薄く書く
MCP サーバー本体は薄い方が保守が楽です。Python SDK なら mcp パッケージで tool を1つ登録して、中で Cypher を叩くだけです。
from mcp.server import Server
from mcp.types import Tool, TextContent
server = Server("code-kg")
@server.list_tools()
async def list_tools():
return [
Tool(
name="blast_radius",
description="ファイルまたは関数を変更したときの影響ファイル一覧を返す",
inputSchema={
"type": "object",
"properties": {
"target": {"type": "string"},
"hops": {"type": "integer", "default": 2}
},
"required": ["target"]
}
)
]
@server.call_tool()
async def call_tool(name, arguments):
if name == "blast_radius":
result = run_blast_radius(arguments["target"], arguments.get("hops", 2))
return [TextContent(type="text", text=result)]
肝は description です。「影響ファイル一覧を返す」だけだと Claude Code はこれをいつ呼ぶべきか分からず、変更が絡む質問でも別の tool を探しに行きます。description に「PR レビュー時にまずこれを呼ぶこと」まで書くとちゃんと呼びます。
Step 5: Cypher クエリを 4 本用意する
7ステップの中で一番磨いた場所です。全部を紹介しきれないので、実運用で稼働率が高い4本だけ書きます。
Blast radius (影響範囲)
MATCH path = (start:Function {name: $target})<-[:CALLS*1..3]-(caller)
RETURN DISTINCT caller.file AS file, length(path) AS hop
ORDER BY hop
*1..3 の上限は 3 が現実的です。4 以上にするとテストファイルまで含んで巨大化します。
Dead code (呼ばれていない public 関数)
MATCH (f:Function)
WHERE NOT (f)<-[:CALLS]-() AND NOT f.name STARTS WITH "_"
RETURN f.file, f.name
依存の逆引き (この File を import しているファイル)
MATCH (importer:File)-[:IMPORTS]->(target:File {path: $path})
RETURN importer.path
呼び出し深さの深い関数 (レビュー優先度が高い)
MATCH (f:Function)
OPTIONAL MATCH (f)-[:CALLS*]->(callee)
RETURN f.name, count(DISTINCT callee) AS reach
ORDER BY reach DESC LIMIT 20
この 4 本があれば「変更のレビュー」の 8 割は素直に流れます。残りの 2 割は semantic 検索とか循環検出とかで、そこは第10章の話です。

Step 6: Claude Code の CLAUDE.md にツールの使い所を書く
これを飛ばすと Claude Code はツールを持っているのに呼びません。tool description だけでは足りず、CLAUDE.md 側にも「いつ呼ぶか」を明示する必要があります。
## code-kg MCP の使い方
- PR や diff のレビューを頼まれた時、まず `blast_radius` を呼んで影響ファイルを取得する
- 影響ファイルが 10 件を超えたら `dead_code` を先に流して、レビュー範囲を絞る
- ファイルを import しているのが誰か知りたい時は `依存の逆引き` クエリを使う
- 巨大なリファクタは `呼び出し深さの深い関数` を先に見て、レビュー起点を決める
これを入れる前と入れた後で、tool 呼び出し率が私の観測では約 3 倍になりました。これは harness-ops 側の telemetry で数えた数字です。
Step 7: 実運用で気づいた 3 つの穴
きれいに動くように書きましたが、実運用で3つ落とし穴があります。
穴 1: グラフの再ビルドコスト。 ファイルが変わるたびにフルビルドを走らせると、中規模リポで数分持っていかれます。私は git の post-commit で差分ファイルだけ再パースして、そのファイルに関係するノード・エッジだけ MERGE で更新する構成にしています。
穴 2: Cypher の LIMIT を Claude 側に任せない。 LIMIT を書き忘れた blast_radius が数千行返した時、Claude Code のコンテキストが一発で埋まりました。全 Cypher に LIMIT 200 くらいのハードキャップをサーバー側で強制する方が安全です。
穴 3: Neo4j の bolt 接続がタイムアウトする。 MCP サーバーはプロセスが長寿命なので、Neo4j のアイドル切断に当たります。driver を毎回作り直すか、健全性チェックを挟んでからクエリを走らせる形にしました。
この3つは、書籍の第8章から第10章では「触れているが深追いしていない」領域です。実装するとまず刺さります。
実測データはどこにある
precision/recall の数字は前作のトークン削減記事側にまとめてあります。実装7ステップを終えた後で「何個の見落としが減ったか」を測りたい人はそちらへ。この記事は「まず動くパイプが手元にある」の状態まで持って行くのが目的です。
同クラスタで補足的に読める記事として、Cypher より上位の設計判断を書いた「GraphRAGはリレーションクエリの3型で古典RAGを上回る」も置いておきます。
まとめ
- tree-sitter → Neo4j → MCP → Cypher の順で組む
- スキーマは最初に固める、
CALLSは Function → Function で持つ - 名前解決の穴は
Unknownノードに逃がしておく - MCP サーバーは薄く、tool description は「いつ呼ぶか」まで書く
- Cypher は Blast radius / Dead code / 依存の逆引き / 呼び出し深さの 4 本で 8 割いける
- Claude Code の CLAUDE.md にツールの使い所を書く
- 実運用の穴は 再ビルドコスト / Cypher の LIMIT / bolt 接続の3つ
7ステップの実装詳細と、実運用で見つかった第4以降の穴、Cypher の応用パターンは書籍の第8章〜第10章にまとめてあります。実装しながら手元に置くと詰まる回数が減ります。
Knowledge Graph実践ガイド — コードをKG化してAIレビューに渡すまでの実装を、章立てで一気通貫に書いた書籍です。この記事の7ステップの各章に対応する詳細と、書ききれなかった第 8 のステップ (semantic 抽出の Pass 2) まで載せています。
関連書籍 ナレッジグラフ活用大全 ナレッジグラフ 活用大全 | GraphRAG・Neo4j・RDF・Property Graph・Emotion AI の実践書 書籍ページを見る → この記事は役に立ちましたか?