業務向けAIエージェント構築で、社内のデータベースを検索させたり、外部ツールを使う開発が増えています。
MCPという新しい規格の登場で、この開発のやり方が簡単になりつつあります。この記事では、MCPの基本から、MCPサーバーを利用したAIエージェントの構成例を解説します。

MCP(Model Context Protocol)とは?

MCPは、一言でいうと「LLM専用の、外部ツールや社内データへの安全なアクセスを標準化する規格」です。 今まで人間やWebアプリ向けに使われてきた「FastAPI」などのWeb APIとは違い、最初から「LLMが自分で理解して使うこと」を目的に作られています。



LLM、MCPクライアント、MCPサーバーの3つの関係

MCPを使った仕組みは、大きく分けて3つの登場人物で連携して動きます。

LLM(脳): ClaudeやGPTなどのAIモデルそのものです。「ユーザーのお願いを叶えるには、どのツールを、どんな条件で使えばいいか」を考えるだけで、自分ではツールを実行しません。

MCPクライアント(司令塔): AIエージェントの本体となるプログラムです。LLMの考えを受け取って「なるほど、じゃあ代わりにツールを実行してあげるね」と、実際にツールを動かし、その結果をまたLLMに教えてあげます。

MCPサーバー(手足): データベースの検索など、実際の作業を行うプログラムです。AIに使わせたい機能をここにまとめておきます。 MCPサーバーを立ち上げると、「私にはこういうツールがあって、こんなデータを受け取りますよ」という説明書(JSON Schema)が、自動的にMCPクライアント経由でLLMに伝わります。人間がAIとシステムの間に立って、細かい連携のプログラムを書く必要がなくなるのが最大のメリットです。

※厳密には、AIエージェント本体を『ホスト』、その中でサーバーと通信する仕組みを『クライアント』と呼びますが、本記事では便宜上まとめてクライアントと表記します。

LLM、MCPクライアント、MCPサーバーの処理手順

実際にユーザーがAIにお願いをしてから、目的のデータが返ってくるまで、先ほどの3つの登場人物が以下の手順で動いています。処理を見ていくと、MCPとは、MCPクライアントとMCPサーバーの通信方式を定めたプロトコルということが分かります。

1. MCPサーバーの起動:MCPサーバーを起動する。
2. MCPサーバーの接続:MCPクライアントが、MCPサーバーに接続し、「どんなツールが使えるか」「どんな引数で呼べるか」の説明書(JSONスキーマ)を受け取る。
3. LLMへのリクエスト:ユーザーの質問クエリと一緒に、ツールの説明書をLLMに送る。
4. 利用ツール判断:LLMが「このツールを、このパラメータで使いたい!」と判断し、MCPクライアントに伝える(※LLM自身はツール実行しません)。
5. MCPサーバーへの依頼:MCPクライアントが、MCPサーバーへ「このツール名とパラメータで動かして」とリクエストする。
6. ツール実行と返却:MCPサーバーは、リクエストを受けて外部連携ロジック(ファイル検索、DBクエリ、外部API)を実行し、結果をMCPクライアントに返却する。
7. 最終回答の作成:MCPクライアントは、その結果をLLMに渡し、LLMはその結果をコンテキストとして利用してユーザー向けの回答を作成する。

PythonによるMCPサーバーの実装サンプル

実際にMCPサーバーを作るのはとても簡単です。現在はPythonの「FastMCP」という仕組みを使うのが主流です。特定の条件で顧客データを検索して返すツールの例を見てみましょう。
from mcp.server.fastmcp import FastMCP
import json

# 1. MCPサーバーの準備
mcp = FastMCP("CustomerSearchServer")

# ダミーの顧客データ(実際はデータベース等から取得します)
DUMMY_CUSTOMERS = [
    {"user_id": "U001", "loyalty_tier": "gold", "ltv": 150000},
    {"user_id": "U002", "loyalty_tier": "standard", "ltv": 20000},
]

# 2. @mcp.tool() でAIに使わせるツールを登録
@mcp.tool()
def search_customers(min_ltv: int = 0, loyalty_tier: str = None) -> str:
    """
    特定の条件に基づいて顧客データを検索します。
    施策の対象ユーザーを抽出する際などに使用します。

    Args:
        min_ltv: 検索対象となる最小のLTV。指定した値以上の顧客を返します。
        loyalty_tier: 検索対象の会員ランク。
    """
    # 条件に基づく絞り込み処理
    filtered = [
        c for c in DUMMY_CUSTOMERS 
        if c["ltv"] >= min_ltv and (not loyalty_tier or c["loyalty_tier"] == loyalty_tier)
    ]
    # AIが読み込みやすいようにJSON文字列として返す
    return json.dumps(filtered, ensure_ascii=False, indent=2)

if __name__ == "__main__":
    # 3. サーバーの起動
    mcp.run()
MCPサーバの関数に「@mcp.tool()」を付けることで、関数名、パラメータの型ヒント、説明文が自動的に解析され、JSONスキーマに変換されてMCPクライアントに返却されます。

関数の引数には、 min_ltv: intのように型を指定することが必須です。MCPはこれを自動的に読み取り、「このツールは整数と文字列を受け取る」というJSON Schemaに変換します。

関数直下の """ で囲まれた説明文(Docstring)が非常に重要です。AIはここを読んで「このツールはどんな目的で、どういう時に使うべきか」「引数には何を渡すべきか」を自律的に判断します。

PythonによるMCPクライアントの実装サンプル

MCPクライアントは、前述の処理手順の通り、MCPサーバーとLLMの間に立って司令塔のような役割を果たします。
ここでは、MCPクライアントがサーバーからツール一覧を受け取り、「LLMにユーザーの曖昧な指示を解釈させ、自律的に使うツールとパラメータを決定し、クライアントが代わりに実行する」という、AIエージェント本来の実践的な連携フローを見てみましょう。今回はLLMの代表例としてOpenAIのAPIを利用しています。
import asyncio
import json
import os
from openai import AsyncOpenAI  # LLMを呼び出すためのライブラリ
from mcp.client.stdio import stdio_client, StdioServerParameters
from mcp.client.session import ClientSession

# OpenAIクライアントの初期化(※事前にAPIキーの設定が必要です)
client = AsyncOpenAI(api_key=os.environ.get("OPENAI_API_KEY"))

async def main():
    # 1. 動かしたいMCPサーバーのプログラムを指定して、起動の準備をします
    server_params = StdioServerParameters(
        command="python",
        args=["server.py"], 
    )
    
    print("MCPサーバーに接続しています...")
    
    async with stdio_client(server_params) as (read_stream, write_stream):
        async with ClientSession(read_stream, write_stream) as session:
            await session.initialize()
            print("接続完了!\n")

            # 3. サーバーから「使えるツール一覧(JSON Schema)」をもらいます
            tools_result = await session.list_tools()
            
            # --- ここからがLLMと連携する実際のフローです ---

            # MCPのツール定義を、LLM(OpenAI)が読めるフォーマットに変換する
            llm_tools = []
            for tool in tools_result.tools:
                llm_tools.append({
                    "type": "function",
                    "function": {
                        "name": tool.name,
                        "description": tool.description,
                        "parameters": tool.inputSchema # MCPはJSON Schemaで返すのでそのまま使えます
                    }
                })

            # ユーザーからの曖昧な依頼
            user_prompt = "LTVが10万円以上で、ゴールドランクの顧客リストを出して。"
            print(f"ユーザーの依頼: 「{user_prompt}」\n")

            # 4. LLMへの相談(ツール一覧とユーザーの依頼を渡す)
            print("LLMにツールを使えるか相談中...")
            response = await client.chat.completions.create(
                model="gpt-4o",
                messages=[{"role": "user", "content": user_prompt}],
                tools=llm_tools,
                tool_choice="auto" # LLMにツールを使うべきか自動判断させる
            )

            response_message = response.choices[0].message

            # 5. LLMが「ツールを使う必要がある」と判断した場合の処理
            if response_message.tool_calls:
                for tool_call in response_message.tool_calls:
                    # LLMが考えた「使うべきツール名」と「その引数」を取り出す
                    tool_name = tool_call.function.name
                    tool_args = json.loads(tool_call.function.arguments)

                    print(f"LLMの判断: ツール「{tool_name}」を引数 {tool_args} で実行します。\n")

                    # 6. LLMの代わりにMCPクライアントがツールを実行する
                    call_result = await session.call_tool(
                        name=tool_name,
                        arguments=tool_args
                    )
                    
                    print("【MCPサーバーからの実行結果】")
                    print(call_result.content[0].text)

                    # (※実務では、この結果を再度LLMに渡して、最終的な自然言語の回答を作らせます)
            else:
                print("LLMの回答:", response_message.content)

if __name__ == "__main__":
    asyncio.run(main())
session.list_tools(): ツール説明書(JSON Schema)をサーバーから引き出す処理です。
session.call_tool(): LLMが出力した関数名とパラメータ(JSON)をそのままこのメソッドに渡すことで、サーバー側の処理が走り、結果が返ってきます。

このようにMCPを使えば、「データ分析サーバ」「過去履歴検索サーバ」のように、AIエージェントがツール・データに接続できるようになります。

AIエージェント開発の登場人物

高度で複雑なAIエージェントを構築するほど、機能をMCPサーバーとして独立させる意義は大きくなります。もし、データ検索や分析処理、グラフ描画などのあらゆるツールを1つのアプリに詰め込んでしまうと、AIに対して「全ツールの使い方」を毎回同時に教えなければならず、トークンを無駄に消費するだけでなく、AIが混乱する原因にもなります。

このような事態を防ぐために有効なのが、「オーケストレーター」と呼ばれる司令塔を中心とした、チーム型のAIエージェント(Plan-and-Execute型)の設計です。オーケストレーターがタスクを分解し、その作業に必要なツール(MCP)だけを持った専門のサブエージェントに処理を振り分けることで、無駄のない高精度な動作が可能になります。

ユーザー(依頼主): 「売上を上げている原因を考えて」など、チャット画面から大まかなお願いをする人間です。

オーケストレーター(優秀なPM): システムの中核です。ユーザーの曖昧な依頼を「1.データ分析」「2.施策づくり」のようにタスクに分解し、専門のサブエージェントに仕事を割り振ります。上がってきた結果をチェックして、ダメならやり直しを指示するのもこの役割です。

サブエージェント(専門の作業担当AI): 「データ分析専用」「文章作成専用」など、特定の仕事に集中するAIです。オーケストレーターから渡されたMCPのツールを使って、黙々と作業をします。

MCPサーバー(外部の世界へ繋がる手足): 社内のデータベースから情報を引っ張ってくるなど、裏側のデータ処理を担当します。どんなアプリからも共通で使い回せるのが特徴です。

ローカルツール(アプリ専用の手足): アプリケーション側で直接定義するツールのことです。「今ユーザーが見ている画面にグラフを出す」など、そのアプリの画面(UI)や内部状態に直接触る処理を担当します。なおLLMから見れば、外部のMCPサーバーが提供するツールも、このローカルツールも、どちらも同じ「Function Calling(関数呼び出し)」の仕組みを使って実行されます。

実務での活用イメージ

「共通で使えるMCP」をどう設計するかが、設計の腕の見せ所です。実際の例に見てみましょう。

例1:自社サービスの利用促進

自社サービスを横断して使ってもらうための施策をAIに考えさせるとします。

MCPサーバーの仕事:顧客情報のデータベース(社内のデータウェアハウス)にアクセスし、自社サービスの見込み顧客を分析・抽出します。
ローカルツールの仕事:MCPが見つけてきた顧客リストを、マーケティング担当者が見ているダッシュボード画面に、分かりやすいグラフとしてパッと表示させます。

例2:カスタマーサポートでの技術的な問い合わせ対応支援

顧客からの複雑な技術トラブルの問い合わせに対し、AIが過去の類似事例やマニュアルを参照しながら、オペレーターの回答作成を支援するケースです。

MCPサーバーの仕事: 社内の製品マニュアル(社内Wikiなど)や、過去の解決済みチケットのデータベースから、関連する情報をRAG(検索拡張生成)の仕組みを使って引き出し、AIに読み込ませます。この「過去のトラブル知見を検索する手足」は、開発チームや営業チームなど他の部署のAIエージェントを作る際にもそのまま使い回せます。

ローカルツールの仕事: AIが導き出した「回答のドラフト文章」を、オペレーターが現在開いているサポートツールの返信入力フォームに自動で流し込みます。また、AIが「これは人間の担当者への引き継ぎが必要」と判断した場合に、画面上の「エスカレーション」ボタンを自動で押すといった、そのアプリ固有の画面操作(UI)を担当します。

まとめ:役割分担が成功の鍵

AIに何でもかんでもやらせようとすると、システムが複雑になりすぎてしまいます。裏側のデータ検索や、使い回せる機能はMCPサーバーに切り出すことで、後から機能を足したり、別のシステムで使い回したりしやすい、綺麗で強力なAIエージェントを作ることができます。

参考記事

MCPの公式ポータルサイト。アーキテクチャの正確な定義や、「Resources」「Prompts」「Tools」という3つのコア機能に関する詳細な仕様が網羅。

AnthropicがMCPを発表した際の公式記事。MCPが開発された背景や、AIエコシステムにおける「標準規格」としてのビジョンが解説。

Python向け公式SDKのリポジトリ。FastMCPの実装コードや、複雑なサーバー構築を解説。