MCP vs RAG vs AI Agent:詳細な説明とコード例付き

この記事には短い動画があります:YouTubeTikTok

1. 概要

MCPとRAG、AI Agentの3つの用語がLLMアプリケーションの話題で頻出し、「3つの競合ソリューション」と勘違いしやすいですが、実は異なる3つの問題を解決しています:RAGはLLMが個別のデータを基に正確に回答するのを助け、AI AgentはLLMが自分で計画・行動するのを可能にし、MCPはLLMアプリケーションがツール・データと統一的に接続するための通信規格です。この記事を最後まで読めば、3つの概念を明確に区別でき、いつどれを使うべきか判断でき、実際に動作するJava/Spring AIコード例も手に入ります。

2. RAGとは

RAG(Retrieval-Augmented Generation)は、LLMを呼び出す前に外部データをプロンプトに「埋め込む」技術で、モデルが学習時の知識だけでなく、実際のデータに基づいて回答するようにします。

2.1. RAGの動作フロー

  1. ソーステキスト(ポリシー、ドキュメント、FAQ)をembedding(数値ベクトル)に変換し、ベクトルデータベースに保存する。
  2. ユーザーが質問するとき、その質問をembeddingに変換し、最も意味が近いテキスト片を検索(類似度検索)する。
  3. 見つかったテキスト片をプロンプトに追加してから、LLMに送信して回答を生成する。

例:OrderServiceが「オンラインで返品・交換できるポリシーは何か」という質問に、企業のポリシードキュメントに基づいて回答する必要がある場合(モデルの一般知識ではなく)。

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-model-openai</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-vector-store-pgvector</artifactId>
</dependency>

2.2. シンプルなRAGコード例

@Service
public class ReturnPolicyAssistant {

    private final VectorStore vectorStore;
    private final ChatClient chatClient;

    public ReturnPolicyAssistant(VectorStore vectorStore, ChatClient.Builder builder) {
        this.vectorStore = vectorStore;
        this.chatClient = builder.build();
    }

    public String answer(String question) {
        List<Document> relevant = vectorStore.similaritySearch(
                SearchRequest.query(question).withTopK(3));

        String context = relevant.stream()
                .map(Document::getContent)
                .collect(Collectors.joining("\n---\n"));

        String prompt = """
                以下のポリシードキュメントに基づいて、顧客の質問に答えてください。
                情報がない場合は「確認できません」と答えてください。

                ドキュメント:
                %s

                質問: %s
                """.formatted(context, question);

        return chatClient.prompt(prompt).call().content();
    }
}
  • vectorStore.similaritySearchは質問に最も意味が近い3つのテキスト片を返します。
  • プロンプトはベクトルストアから取得したcontextで「増強」(augmented)されてからLLMに送信されます。
  • ドキュメントに関連情報がない場合、モデルは(hallucination=幻覚を避けるため)「確認できません」と答えるよう指示されます。
入力:「5日前にオンラインで買ったシャツ、交換できますか?」
出力:「ポリシーによれば、商品到着から7日以内であれば交換でき、
       タグが付いていれば対象です。シャツはまだ期限内です。」

注意: RAGはモデルに「データを知る」のを助けるだけです。モデルが自分で「何かをする」(APIを呼ぶ、データベースを更新する)のは助けません。モデルが単に答えるのではなくアクションを起こす必要がある場合は、Agentが必要です。

3. AI Agentとは

AI Agentはシステムで、LLMを「脳」として使い、計画を立て、適切なツールを選び、ツールを呼び出し、結果を観察し、目標を達成するまで繰り返すもので、RAGが単に質問に答えるのと異なります。

3.1. reasoning → action → observationのループ

最も一般的なモデルはReAct:モデルが思考(reasoning)し、呼ぶツールを選ぶ(action)、結果を受け取り(observation)、その結果に基づいてさらに思考を続けます。

public interface AgentTool {
    String name();
    String description();
    String execute(String input);
}

@Component
public class CheckOrderStatusTool implements AgentTool {
    private final OrderRepository orderRepository;

    public CheckOrderStatusTool(OrderRepository orderRepository) {
        this.orderRepository = orderRepository;
    }

    public String name() { return "checkOrderStatus"; }
    public String description() { return "orderIdで注文ステータスを確認する"; }

    public String execute(String orderId) {
        return orderRepository.findById(orderId)
                .map(o -> "注文%sは%s状態です".formatted(orderId, o.getStatus()))
                .orElse("注文" + orderId + "が見つかりません");
    }
}

3.2. Agentがツールを選んで呼ぶ

@Service
public class SupportAgent {

    private final ChatClient chatClient;
    private final List<AgentTool> tools;

    public SupportAgent(ChatClient.Builder builder, List<AgentTool> tools) {
        this.chatClient = builder.build();
        this.tools = tools;
    }

    public String handle(String userMessage) {
        return chatClient.prompt()
                .user(userMessage)
                .tools(tools.toArray()) // Spring AIが自動的にexecute()メソッドをfunction-callにマップする
                .call()
                .content();
    }
}
  • tools(tools.toArray())でモデルにこれらのツールが呼べることを伝えます。Spring AIが自動的にfunction-callingのスキーマを生成します。
  • モデルは自分で判断します:質問が注文ステータスに関連していれば、checkOrderStatusを質問から推測したorderIdで呼び出し、結果を受け取ってから自然言語で返します。
ユーザー:「ORD-2024-891の注文はどうなってますか?」
Agent:(checkOrderStatus("ORD-2024-891")を呼ぶ)
      → 「ORD-2024-891は配送中で、2日以内に到着予定です。」

3.3. ツール選択の動作をテストする

@Test
void shouldCallCheckOrderStatusWhenAskingAboutOrder() {
    AgentTool tool = new CheckOrderStatusTool(orderRepository);
    when(orderRepository.findById("ORD-1")).thenReturn(Optional.of(shippedOrder));

    String result = tool.execute("ORD-1");

    assertThat(result)
        .contains("ORD-1")
        .contains("SHIPPED");
}

このテストは実際のLLMを呼びません。ツール自体のロジックが正しいことを確認するだけです。「モデルがどのツールを選ぶか」の部分は別にevalセットでテストすべきで、LLMの出力は決定論的でないため、硬い単体テストは不適切です。

注意: Agentは強力ですが、RAGより危険です。ツールが書き込み権限を持つ場合(払い戻し、更新など)、モデルの推論が誤ると実際の損害が生じる可能性があります。常に確認ステップを追加するか、機密性の高いツールの権限を制限してください。

4. MCPとは

MCP(Model Context Protocol)はオープンな規格で、LLMアプリケーション(hostと呼ぶ)が外部のデータソースやツール(serverと呼ぶ)と統一プロトコルで接続する方法を定義し、各統合でコードを書き直すのを避けます。

4.1. MCPが解決する問題

MCP以前は、5つのLLMアプリがあり、各アプリが10個のデータソース/ツール(データベース、CRM、ファイルシステムなど)を使いたい場合、50の個別統合コードを書く必要がありました。MCPはこれをN×Mから N+M に変えます:各serverを1回書けば、MCPに準拠する全てのhostが呼べます。

4.2. MCPのアーキテクチャ

  • Host:LLMを含むアプリケーション(チャットボット、IDE assistantなど)。
  • Client:host内のコンポーネント。1つのMCP serverとの1-1接続を保持する。
  • Server:3種類の「capability」を提供するプロセス:
    • Resources:読み取り可能なデータ(GETエンドポイントのようなもの)。
    • Tools:呼び出し可能なアクション(POST/actionのようなもの)。
    • Prompts:再利用可能なプロンプトテンプレート。

4.3. 例:JavaでMCP serverを書く

<dependency>
    <groupId>io.modelcontextprotocol.sdk</groupId>
    <artifactId>mcp</artifactId>
    <version>0.8.0</version>
</dependency>
public class OrderMcpServer {

    public static void main(String[] args) {
        McpSyncServer server = McpServer.sync(StdioServerTransport.builder().build())
                .serverInfo("order-service", "1.0.0")
                .tool(orderLookupTool())
                .build();

        server.start();
    }

    private static McpServerFeatures.SyncToolSpecification orderLookupTool() {
        var schema = """
                {
                  "type": "object",
                  "properties": { "orderId": { "type": "string" } },
                  "required": ["orderId"]
                }
                """;

        return new McpServerFeatures.SyncToolSpecification(
                new Tool("getOrderById", "orderIdで注文を検索する", schema),
                (exchange, args) -> {
                    String orderId = (String) args.get("orderId");
                    String status = OrderLookup.findStatus(orderId);
                    return new CallToolResult(List.of(new TextContent(status)), false);
                });
    }
}
  • McpServer.sync(...)はstdioトランスポート(hostがserverを子プロセスとして実行し、stdin/stdoutで通信)を使用してserverを構築します。
  • tool(orderLookupTool())はgetOrderByIdツールを登録。JSONスキーマで入力フィールドを説明するので、MCPに準拠した任意のhostが「このツールが必要とするフィールド」を理解できます。
  • clientがgetOrderByIdを呼ぶとき、serverはテキストコンテンツを含むCallToolResultを返します。

4.4. Spring AIクライアントからMCP serverを呼ぶ

<dependency>
    <groupId>org.springframework.ai</groupId>
    <artifactId>spring-ai-starter-mcp-client</artifactId>
</dependency>
spring:
  ai:
    mcp:
      client:
        stdio:
          connections:
            order-service:
              command: java
              args: ["-jar", "order-mcp-server.jar"]
@Service
public class McpAgentBridge {

    private final SyncMcpToolCallbackProvider toolCallbackProvider;
    private final ChatClient chatClient;

    public McpAgentBridge(SyncMcpToolCallbackProvider toolCallbackProvider,
                           ChatClient.Builder builder) {
        this.toolCallbackProvider = toolCallbackProvider;
        this.chatClient = builder.build();
    }

    public String ask(String question) {
        return chatClient.prompt()
                .user(question)
                .tools(toolCallbackProvider.getToolCallbacks())
                .call()
                .content();
    }
}
  • Spring AIはMCP server(order-mcp-server.jar)を子プロセスとして自動起動し、stdioで接続を維持します。
  • toolCallbackProvider.getToolCallbacks()はserverが提供する全ツール(ここではgetOrderById)を取得して、LLMのfunction-callingのようにモデルに提供します。3章で手動でAgentToolを登録するのと同じように機能しますが、違いはこのツールが独立したプロセスによってMCP規格で提供されるということです。

注意: MCPはLLMのfunction-callingを置き換えません。function-callingの下にある転送・規格化レイヤーで、ツールを1回書いて複数のhost(Claude Desktop、IDE、あなたのアプリ)で再利用できるようにし、ツールが変わるたびに統合コードを修正する必要がなくなります。

5. MCP、RAG、AI Agentの比較

基準 RAG AI Agent MCP
解決する問題 モデルがプライベート/新しいデータを不足 モデルが自律的に行動・複数ステップ計画できない ツール/データ統合コードが重複、規格化されていない
単位 retrieval + augment prompt reasoning-action-observationループ hostとserver間の接続プロトコル
ツール/アクションを呼び出すか いいえ(読み取り専用) はい はい(hostにツールを提供)
リスク度 低い(retrieval不良なら回答が間違う) 高い(誤ったアクション実行の可能性) 下のツール次第、プロトコル自体は中立的
相互関係 Agentが使用可能な「データ源」 RAGをツールとして使用、MCPで規格化されたツールを呼べる AgentがRAG/ツールを統一的に呼ぶ「パイプライン」

簡単に言えば:RAGはモデルに知識を、Agentはモデルに行動能力を、MCPは行動/検索が再利用可能で、毎回hostやツール変更のたびに書き直さないようにする通信規格を提供します。

6. 実際のシステムで3つを組み合わせる

実際のプロジェクトは3つのうち1つを選びません。重ね合わせています:

ユーザー → AI Agent(計画、ReAct loop)
          ├─ MCPクライアント呼び出し → MCPサーバー「knowledge-base」 → RAG類似度検索結果を返す
          ├─ MCPクライアント呼び出し → MCPサーバー「order-service」 → getOrderByIdツール
          └─ MCPクライアント呼び出し → MCPサーバー「payment-service」 → refundPaymentツール(確認必須)

このアーキテクチャでは:

  • RAGは独立したMCP server内に配置(例:「knowledge-base」 serverが類似度検索結果をresourceとして公開)。
  • Agentは調整役(orchestrator)で、推論ループを実行。ツールが何で書かれたか気にせず、単にツールがMCPに従っていることだけ確認。
  • MCPはAgentと全serverの間の統一インターフェースで、新しいserver(例:「shipping-service」)を追加してもAgent側のコードを修正不要。

7. よくある間違い

  • アクションが必要なのにRAGを使う:プロンプトにデータを詰め込んでもモデルがデータベースを更新するわけではありません。この場合はAgent+書き込み権限ツールが必要。
  • ツール権限を制限しないAgent:deleteOrderやrefundPaymentツールをAgentが確認なしで呼べるようにすると、モデルの誤った推論で実害が出ます。
  • MCPを「AI framework」と誤解:MCPはプロトコルだけで、自分で推論したり検索したりしません。serverが有用なツール/リソースを公開しなければ、MCPがあってもモデルは賢くなりません。
  • RAGのtop-kが不適切:top-kが小さい(1-2)と文脈不足、大きい(10以上)だとプロンプト長、トークン浪費、重要信号が薄まります。3-5から始めて実データで測定し直すべき。
  • MCP toolのスキーマをversion管理しない:toolの入力スキーマを変更しても版を上げなければ、古いserverを使っている別のhostが誤ったパラメータで呼び出します。

8. 結論

RAG、AI Agent、MCPは競合ではなく、LLMシステムの3つの異なるレイヤーに位置します:RAGはデータ層、Agentは意思決定・行動層、MCPは接続規格層です。実際のAIシステムを設計するなら、「モデルが何を知る必要があるか」(RAG)から始め、「モデルが何をすべきか」(Agent)と考えてから、「ツール/データを複数の場所で再利用するにはどうするか」(MCP)と進むべきです。この順序で進めば、最初から過度に複雑にしすぎるのを避けられます。

mcpragai-agentspring-aillm