自作MCP「Repo GraphRAG MCP Server」の説明と使い方
はじめに
作成したMCPサーバーはGitHubにて公開しています。
- Repo GraphRAG MCP Server
経緯
コードや設計書の改修、Q&Aを各種エージェントなどに任せると改修場所の判定や同時に改修対象となる設計書の場所がわからない、設計書にそぐわない実装になるなどいまいちなことがあったので、リポジトリ横断で構造解析されたコードと設計書を一体化(ナレッジグラフ化、インデックス化)してQ&Aと改修が行えるMCPサーバーを自作しました。
グラフ化によってコードと設計書には結びつきが出来るため、相互の情報(ファイルパスやリレーションシップ、関連チャンクなど)をもとにした回答と改修が可能になります。
「Repo GraphRAG MCP Server」の紹介
「Repo GraphRAG MCP Server」はコードとテキスト文書の両方に対応したGraph RAGの活用を目的としたMCPサーバーです。
指定したリポジトリ(ディレクトリ)に対して、その内に含まれるコードや設計書などのテキスト文書(.md/.txt など)からナレッジグラフとベクターインデックスを作成し、Q&Aと改修が行えます。
基盤にはLightRAG(グラフ生成 + 検索)、コードの解析にはTree-sitter(構文解析)を用いています。
- LightRAG
- Tree-sitter
基本構造について
コード解析やグラフ作成処理などの基本構造については以前に記事として記載しています。興味のある方はご確認ください(MCP化する前の記事のため実装には多少違いがありますが、アプローチはほぼ同じです)。
主な特徴
コードの解析
コードに対してナイーブなRAGやGraphRAGによる単純なトークン数による分割やエンティティの抽出では処理内容が考慮されず、一つのメソッドが複数に分割されたり、関数やクラス定義が途中で切断されるなど元の意味からかけ離れたチャンクやエンティティが抽出されるなどの問題が起こりえます。
そのため「Repo GraphRAG MCP Server」ではTree-sitterで構造解析を行い、その結果に基づいて意味のあるエンティティ(クラス、関数、メソッド等)を抽出します。チャンクにおいてもTree-sitterの解析構造に基づいた分割(トークン長も考慮)が実施されます。
エンティティマージ
ドキュメントから抽出したエンティティとコードから抽出したエンティティは埋め込みのコサイン類似度+閾値により統合(マージ)されます。これによりコードとテキスト文書の表記ゆれを抑えながら情報を統合し、より関係性を意識した検索が行えるようになります。
例えば、クラス名のエンティティが検索された際、該当するクラスのコード実装(とその要約) & クラスの説明がある設計書の内容、それぞれのパス、関係性などが一つのエンティティとして検索されます。
改修計画書の作成
改修を指示するとナレッジグラフおよびベクターインデックスの内容を元にした改修計画書が作成されます。また、計画書を基に実装を行う旨の指示文も返されます。
計画書の中には対象となるファイルパス(コード、テキスト文書両方)や詳しい改修手順が記載されています。
※ 不明点や情報不足がある場合は、指示文の中でQ&Aツール(グラフとベクターインデックスへの質問応答)を適宜呼び出すように促しています。これにより既存実装との整合性を保ちながら改修が行えるようにしています。
サポートしている言語と拡張子
現在、12の言語をサポートしています。
言語 | ファイル拡張子 |
---|---|
Python | py |
C | c |
C++ |
cpp , h
|
Rust | rs |
C# | cs |
Go | go |
Ruby | rb |
Java | java |
JavaScript |
js , jsx
|
TypeScript |
ts , tsx
|
HTML |
html , htm
|
CSS | css |
※ 本MCPでは.h
はC++として解析します。
テキスト文書の拡張子に関してはデフォルト(.env
で設定)ではtxt
,md
,rst
をサポートしています。.env
のDOC_EXT_SPECIAL_FILES
パラメータで拡張子のないファイルを指定することも可能です。
上記の表、または.env
の設定に含まれない拡張子のファイルは除外されて処理されます。
※ 対応する形式はテキストのみです。PDF/Word/Excelなどのバイナリファイルは対象外となるため、処理対象にしたい場合は別途テキスト化を行ってください。
使い方
クイックスタート
以下はREADMEのクイックスタートに記載している内容と同じです。
前提条件
- Python 3.10以上
- uvパッケージマネージャーを使用します。
- 使用するLLMに合わせてプロバイダーの資格情報(APIキーなど)が必要になります。
現在、以下のプロバイダーをサポートしています(識別子や資格情報は.envで設定します)。
プロバイダー | 識別子 | 必要な資格情報 |
---|---|---|
Anthropic Claude | anthropic |
ANTHROPIC_API_KEY |
OpenAI GPT | openai |
OPENAI_API_KEY |
Google Gemini | gemini |
GEMINI_API_KEY |
Azure OpenAI | azure_openai |
AZURE_OPENAI_API_KEY , AZURE_OPENAI_ENDPOINT , AZURE_API_VERSION
|
インストール
# GitHubからクローン
git clone https://github.com/yumeiriowl/repo-graphrag-mcp.git
cd repo-graphrag-mcp
# uvを使って依存関係をインストール
uv sync
環境設定
# 設定ファイルをコピー
cp .env.example .env
# 設定ファイルの編集
nano .env # 任意のエディタで編集
LLMの設定
.envファイルを編集して使用するLLMの設定を行ってください。
必要な設定は以下です。
プロバイダーの設定
使用するLLMのプロバイダー(anthropic
or openai
or gemini
or azure_openai
)を設定してください。
- GRAPH_CREATE_PROVIDER: グラフ作成に使うLLMプロバイダー
- GRAPH_ANALYSIS_PROVIDER: Q&Aと改修計画の作成に使うLLMプロバイダー
※ GRAPH_CREATE_PROVIDER
とGRAPH_ANALYSIS_PROVIDER
で別々のプロバイダーを設定しても問題ありません。
資格情報の設定
設定したプロバイダーに合わせてAPIキー(Azureの場合はAPIキー、エンドポイント、APIバージョン)を設定してください。
- *_API_KEY: 設定したプロバイダーのAPIキー
- AZURE_OPENAI_ENDPOINT: エンドポイント(Azureの場合は設定)
- AZURE_API_VERSION: APIバージョン(Azureの場合は設定)
LLMの設定
使用するLLMのモデル名を設定してください(グラフ作成に使うモデルは呼び出し回数と量が多いため高速で低コストモデルを推奨します)。
- GRAPH_CREATE_MODEL_NAME: グラフ作成に使うLLMのモデル名
-
GRAPH_ANALYSIS_MODEL_NAME: Q&Aと改修計画の作成に使うLLMのモデル名
例) AnthropicのClaudeモデルを使う場合
# LLM provider for graph creation
GRAPH_CREATE_PROVIDER=anthropic # or openai, gemini, azure_openai
# Provider for planning and Q&A
GRAPH_ANALYSIS_PROVIDER=anthropic # or openai, gemini, azure_openai
# API keys (set the variables corresponding to your chosen provider)
ANTHROPIC_API_KEY=your_anthropic_api_key # or openai, gemini, azure_openai
# AZURE_OPENAI_API_KEY=your_azure_openai_api_key
# AZURE_OPENAI_ENDPOINT=https://your-resource.openai.azure.com/
# AZURE_API_VERSION=azure_openai_api_version
# OPENAI_API_KEY=your_openai_api_key
# GEMINI_API_KEY=your_gemini_api_key
# LLM model for graph creation
GRAPH_CREATE_MODEL_NAME=claude-3-5-haiku-20241022
# LLM model for planning and Q&A
GRAPH_ANALYSIS_MODEL_NAME=claude-sonnet-4-20250514
MCPクライアント側の設定
例) Claude Code
claude mcp add repo-graphrag \
-- uv --directory <repo-graphrag-mcpの絶対パス> run server.py
例) VS Code GitHub Copilot 拡張機能
{
"servers": {
"repo-graphrag-server": {
"type": "stdio",
"command": "uv",
"args": [
"--directory",
"<repo-graphrag-mcpの絶対パス>",
"run",
"server.py"
]
}
}
}
上記以外のMCPクライアント(Cline, Roo-Code)などでも使用できます。
以下の形式で任意のMCPクライアントに設定してください。
- 通信: stdio(標準入出力)
- コマンド: uv
- 引数: --directory <repo-graphrag-mcpの絶対パス> run server.py
上記で基本的な設定は完了です(その他はデフォルト値が設定されています)。
デフォルトの最大トークン数や埋め込みモデル(BAAI/bge-m3
)などを変更したい場合は、READMEにある一覧と.env.example
をご確認のうえ、.env
を編集してください。
ツールの使用方法
このMCPサーバーは以下のツールを提供します。
ツール名 | 内容 |
---|---|
graph_create |
リポジトリ内にあるコードとのテキスト文書(設計書など)を解析してナレッジグラフと埋め込みインデックスを生成します。増分更新に対応しており、変更/追加/削除されたファイルのみ再構築が行われます。 |
graph_plan |
ナレッジグラフとインデックスに基づいて、変更や追加要求に対する改修計画書の作成と指示文を出力します。 |
graph_query |
ナレッジグラフとインデックスに基づいて、Q&Aを実施します。 |
これらのツールはすべて、指示の中に「graph:
」と特定のパラメータを送ることで使用できます。
graph_create
)
ナレッジグラフの作成(必要なパラメータ:
graph:
(必須)- 分析対象のリポジトリ(ディレクトリ)の絶対パス
-
ストレージ名(ナレッジグラフと埋め込みインデックスの格納ディレクトリ名。省略時は
storage
)
例)
graph: /absolute/path/to/your/repository my_project
graph: /absolute/path/to/your/repository my_project グラフ化
graph: C:\\projects\\myapp webapp_storage ストレージを作成してください
※ READMEに増分更新やパフォーマンスについてなどの補足を記載しています。扱う際は一読をお勧めします。
改修計画の作成と指示( graph_plan )
必要なパラメータ:
graph:
(必須)- 改修指示
-
ストレージ名(省略時は
storage
)
例)
graph: ユーザー認証を追加したい my_project
graph: my_project REST APIにGraphQLサポートを追加
graph: 高負荷時のAPIパフォーマンスを向上して webapp_storage
Q&A( graph_query )
必要なパラメータ:
graph:
(必須)- 質問内容
-
ストレージ名(省略時は
storage
)
例)
graph: このプロジェクトのAPIエンドポイントについて教えてください my_project
graph: my_project 主なクラスとその役割を説明してください
graph: データベース設計について教えて webapp_storage
改修計画とQ&Aの実行結果(サンプル)
前回の記事で使用したC++のサンプル(QtのUIサンプル)を使用して改修計画とQ&Aを試してみます。
準備(ナレッジグラフの作成)
まずはgraph_create
を使ってリポジトリ(ディレクトリ)を解析してストレージに格納します。
graph: /home/yumefuku/cpp_sample sample_storage
ストレージが作成された後、改修計画とQ&Aが可能になります。
改修計画
「graph:」、「改修指示」、「作成したストレージ名」を入力して改修計画書を作成します(計画書とともに改修を行う旨の指示文も返されます)。
graph: メインウィンドウから遷移できる足し算が行える電卓を追加してください sample_storage
以下のような計画書が作成されます。
「graph_plan」で作成された計画書
Implementation Plan:
I'll help you create an implementation plan for adding a calculator to the existing Qt application that can be accessed from the main window. Here's a detailed implementation plan:
## Implementation Plan: Adding a Calculator Window
### 1. Preparation
#### File Paths to be Modified/Created:
**Code Files**:
- `/home/yumefuku/cpp_sample/MainWindow.h`
- `/home/yumefuku/cpp_sample/MainWindow.cpp`
- `/home/yumefuku/cpp_sample/main.cpp`
- `/home/yumefuku/cpp_sample/CalculatorWindow.h` (NEW)
- `/home/yumefuku/cpp_sample/CalculatorWindow.cpp` (NEW)
**Documentation Files**:
- `/home/yumefuku/cpp_sample/CalculatorWindow_h_設計書.md` (NEW)
- `/home/yumefuku/cpp_sample/CalculatorWindow_cpp_設計書.md` (NEW)
#### Dependencies and Impact Analysis:
- Modify `MainWindow` to add a button for navigating to the calculator
- Create a new `CalculatorWindow` class
- Update `main.cpp` to include the new window in the `QStackedWidget`
- Maintain the existing navigation pattern using `QStackedWidget`
### 2. Design
#### CalculatorWindow Design Requirements:
1. Inherit from `QWidget`
2. Include numeric buttons (0-9)
3. Include operation buttons (+, -, =)
4. Have a display area for input and result
5. Implement addition functionality
6. Include a "Back" button to return to the main window
### 3. Implementation
#### Step 1: Create Code Files
**CalculatorWindow.h**:
```cpp
#ifndef CALCULATORWINDOW_H
#define CALCULATORWINDOW_H
#include <QWidget>
#include <QStackedWidget>
#include <QPushButton>
#include <QLineEdit>
#include <QGridLayout>
class CalculatorWindow : public QWidget {
Q_OBJECT
public:
explicit CalculatorWindow(QStackedWidget *stackedWidget, QWidget *parent = nullptr);
private slots:
void numberClicked();
void addClicked();
void equalsClicked();
void clearClicked();
void goToMain();
private:
QStackedWidget *stackedWidget;
QLineEdit *display;
double firstNumber;
bool waitingForSecondNumber;
};
#endif
```
**CalculatorWindow.cpp**:
```cpp
#include "CalculatorWindow.h"
#include <QDebug>
CalculatorWindow::CalculatorWindow(QStackedWidget *stackedWidget, QWidget *parent)
: QWidget(parent), stackedWidget(stackedWidget), firstNumber(0), waitingForSecondNumber(false) {
QGridLayout *layout = new QGridLayout(this);
display = new QLineEdit("0");
display->setReadOnly(true);
layout->addWidget(display, 0, 0, 1, 4);
// Number buttons
QString buttonTexts[10] = {"7", "8", "9", "4", "5", "6", "1", "2", "3", "0"};
for (int i = 0; i < 10; ++i) {
QPushButton *button = new QPushButton(buttonTexts[i]);
connect(button, &QPushButton::clicked, this, &CalculatorWindow::numberClicked);
layout->addWidget(button, 1 + i/3, i%3);
}
// Operation buttons
QPushButton *addButton = new QPushButton("+");
QPushButton *equalsButton = new QPushButton("=");
QPushButton *clearButton = new QPushButton("C");
QPushButton *backButton = new QPushButton("Back");
connect(addButton, &QPushButton::clicked, this, &CalculatorWindow::addClicked);
connect(equalsButton, &QPushButton::clicked, this, &CalculatorWindow::equalsClicked);
connect(clearButton, &QPushButton::clicked, this, &CalculatorWindow::clearClicked);
connect(backButton, &QPushButton::clicked, this, &CalculatorWindow::goToMain);
layout->addWidget(addButton, 4, 3);
layout->addWidget(equalsButton, 5, 3);
layout->addWidget(clearButton, 5, 0);
layout->addWidget(backButton, 5, 1, 1, 2);
setLayout(layout);
}
void CalculatorWindow::numberClicked() {
QPushButton *button = qobject_cast<QPushButton*>(sender());
QString digit = button->text();
if (display->text() == "0" || waitingForSecondNumber) {
display->setText(digit);
waitingForSecondNumber = false;
} else {
display->setText(display->text() + digit);
}
}
void CalculatorWindow::addClicked() {
firstNumber = display->text().toDouble();
waitingForSecondNumber = true;
}
void CalculatorWindow::equalsClicked() {
double secondNumber = display->text().toDouble();
double result = firstNumber + secondNumber;
display->setText(QString::number(result));
waitingForSecondNumber = true;
}
void CalculatorWindow::clearClicked() {
display->setText("0");
firstNumber = 0;
waitingForSecondNumber = false;
}
void CalculatorWindow::goToMain() {
stackedWidget->setCurrentIndex(0);
}
```
**Modify MainWindow.cpp**:
- Add a new button to open the calculator
**Modify MainWindow.h**:
- Add a new method `goToCalculator()`
**Modify main.cpp**:
- Add CalculatorWindow to the `QStackedWidget`
#### Step 2: Create Documentation Files
- Create markdown documentation files for `CalculatorWindow.h` and `CalculatorWindow.cpp` following the existing documentation pattern
### 4. Testing Considerations
- Verify navigation between main window and calculator
- Test addition functionality
- Ensure UI responsiveness
- Check edge cases like multiple operations, large numbers
### Estimated Effort
- Development: 2-3 hours
- Documentation: 1 hour
- Testing: 1 hour
### Potential Future Improvements
- Add more mathematical operations
- Implement memory functions
- Enhance error handling
References:
[KG] QStackedWidget
[KG] MainWindow
[KG] SubWindow
[DC] /home/yumefuku/cpp_sample/MainWindow.cpp
[DC] /home/yumefuku/cpp_sample/main.cpp
計画書の構造は以下のようになります。
-
Preparation
変更・新規作成するコードと設計書ファイルのパス
依存関係と影響範囲 -
Design
設計方針 -
Implementation
変更・新規作成するコードと設計書の具体的な実装
Q&A
「graph:」、「質問」、「作成したストレージ名」を入力してQ&Aを行います。
graph: メインウィンドウのラベルの実装を教えてください sample_storage
以下のような回答が作成されます。
「graph_query」で作成された回答
Answer:
メインウィンドウのラベル実装について、提供された設計書と実装から詳細を説明します。
## ラベルの初期化
メインウィンドウのラベルは、`MainWindow`クラスのコンストラクタ内で以下のように初期化されます:
```cpp
label = new QLabel("initial_label", this);
```
- ラベルは `QLabel` クラスを使用して作成されます
- 初期テキストは "initial_label" に設定されています
- `this` を親ウィジェットとして指定し、ウィンドウに関連付けられます
## ラベルの更新メソッド
ラベルの内容は `outputMessage()` メソッドで動的に更新されます:
```cpp
void MainWindow::outputMessage() {
QString name = inputField->text();
QString message = "Hello, " + name + "! ";
message += radio1->isChecked() ? "(option_1)" : "(option_2)";
label->setText(message);
}
```
このメソッドでは以下の処理が行われます:
1. 入力フィールドからテキストを取得
2. "Hello, [name]! " の形式のメッセージを生成
3. ラジオボタンの選択状態に応じて "(option_1)" または "(option_2)" を追加
4. `label->setText()` でラベルのテキストを更新
## ラベル関連の特徴
- ラベルは `MainWindow.h` で `QLabel *label;` として宣言
- 初期状態では "initial_label" というテキストが表示されます
- ユーザーの入力とラジオボタンの選択に基づいて動的にメッセージが変更されます
## 注意点
- ラベルは垂直レイアウト (`QVBoxLayout`) の最上部に配置されます
- メッセージの生成はボタンクリック時に実行されます
### References
- [KG] `MainWindow`
- [KG] `label`
- [DC] MainWindow.cpp
- [DC] MainWindow.h
Tree-sitterによりクラスや関数などの定義を構造化しているため、実装箇所を具体的に提示しながらの回答が可能です。
おわりに
今回は簡単な紹介とクイックスタート的な使い方のみを説明させていただきました。READMEにも補足など記載していますので、使う際はご一読をおすすめします。
また、Tree-sitterによるコード解析やグラフ化などは前回の記事で解説しております。ご興味のある方はそちらもご確認ください。
ここまで読んでくださった方、ありがとうございました。
また機会があればよろしくお願いします。
Discussion