ReActエージェントを使用して、MCPサーバーを利用してみる

# はじめに
「MCP（**M**odel **C**ontext **P**rotocol）」は、AIエージェントを利用するにあたって、X（旧：Twitter）やテック界隈でかなり注目されています。この記事では、「MCP」とは何か？何がそんなに注目されているのかについてまとめました。

https://x.com/MatsuoInstitute/status/1920311443204759742

https://x.com/akshay_pachaar/status/1915033288688996493

# MCPとは
MCPとは、Model Context Protocolの略です。
アプリケーションが生成AIに文脈情報を渡しやすくすることができる技術で、その文脈情報の渡しやすさが、様々なデバイス周辺機器に対する接続が高い`USB Type-C`を比喩として、「Imagine it as a USB-C port（まるでUSB-Cポートのようだ）」と表現されるようになりました。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/ea173cb1-ba3a-4f9a-aa0e-4048999bb1e6.png)
> The Model Context Protocol (MCP) is a standardized protocol that connects AI agents to various external tools and data sources（[ref](https://norahsakal.com/blog/mcp-vs-api-model-context-protocol-explained/)）

MCPは、Claudeで有名なAuthoropic社が2024年11月に発表した規格です。
2025年３月にOpenAI社が公式にサポートを表明したことで、実質的な業界標準となりました。
以降、AdobeやLINEなどの様々な企業からMCPに対応したアプリケーションが発表され、かなり注目度が高くなっています。

https://www.anthropic.com/news/model-context-protocol


https://x.com/OpenAIDevs/status/1904957755829481737?ref_src=twsrc%5Etfw%7Ctwcamp%5Etweetembed%7Ctwterm%5E1904957755829481737%7Ctwgr%5E43e2ab147fb0c0dd355f0ca488b105afcf618f9b%7Ctwcon%5Es1_c10&ref_url=https%3A%2F%2Fai-market.jp%2Ftechnology%2Fopenai-agents-sdk-mcp%2F


## MCPの何がすごいのか？

昨今話題の「AIエージェント」は、まるでドラえもんのように様々な道具（ツール）を使えることで注目されています。
例えば、AIエージェントに対して「web検索」や「メール送信」などのツールを用意しておくことで、LLMはユーザーの求める結果を得るために必要なツール実行（Tool Calling）を行います。

> ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/9bc693fe-b0e6-4aa3-b1bb-a035ad62d8d6.png)

https://python.langchain.com/docs/concepts/tool_calling/

しかし、従来のツール実装方法では、使用するクライアントツールやフレームワークによってバラバラでした。そのため、自分で作ったツールを他の人に使ってもらうことが容易ではありませんでした。このことがAIエージェント普及のために大きな壁となっていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/6529e1eb-a65c-45d2-b602-88f09f296b23.png)

そこで出てきたのが、MCPです。
MCPの登場によって、これまでバラバラだったAIエージェント用のツールの「提供の仕方」と「呼び出し方」を共通化することが可能になりました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/45bbfe37-3bf4-452e-b97a-cd11dba38810.png)

これらの効果を本記事では、実際に手を動かして確認してもらいたいと思います。


# LangChainでMCPを使ってみる

本講義で使用するGitHubリポジトリ

https://github.com/switch-kosuke/practice-mcp-server.git

## 環境構築
### ① **リポジトリクローン**
```bash
git clone https://github.com/switch-kosuke/practice-mcp-server.git
```
    
### ② **`.env`ファイル（環境変数）作成**
`.env`ファイルを作成して、使用したいLLMモデルのAPIを記載:  
筆者は、Azure OpenAI Servicesのモデルを使用しております.  
```bash
cp .env.sample .env
```

Azure OpenAI Services以外を使用する方は、こちらをご確認ください。

https://python.langchain.com/docs/integrations/providers/

LLMの設定変更は、各コード内の`llm`を変更ください

```python:main.py
llm = AzureChatOpenAI(
        azure_deployment = os.getenv("AZURE_OPENAI_AZURE_DEPLOYMENT"),
        api_version = os.getenv("AZURE_OPENAI_API_VERSION"),
        model='gpt-4o',
    )  

# ↓↓↓↓↓↓↓↓↓↓Example: Gemini API↓↓↓↓↓↓↓↓
llm = ChatGoogleGenerativeAI(
    model="gemini-2.0-flash",
    temperature=0,
    max_tokens=None,
    timeout=None,
    max_retries=2,
    # other params...
)
```

### ③ **`uv`のインストール**
Python開発環境は、uvを用います. インストールされていない方は以下を実行.  
#### Linux/Mac  
```bash
curl -LsSf https://astral.sh/uv/install.sh | sh

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
source ~/.bashrc
```

#### Windows
```bash
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```
    
https://qiita.com/piapepper/items/d48939dd42a7420efcd1

### 4. uvを使って依存関係をインストール  
```bash
# 仮想環境を作成
uv venv

# 仮想環境を有効化（Linux/Mac）
source .venv/bin/activate

# 仮想環境を有効化（Windows）
.venv\Scripts\activate

# 依存関係をインストール
uv pip install -e .
```

## Les1: LLMの応答確認
`Les1-check-llm-response`には、LLMからの応答を確認する簡易なコードが入っています。
以下のコマンドを実行して、LLMから正常に応答が返ってくるか確認してください。

```bash
# 実行コマンド
cd Les1-check-llm-response
uv run main.py

# 結果
LLMのレスポンス: こんにちは！どのようなお手伝いをしましょうか？
```

## Les2: MCP導入前のAIエージェントを使ってみる
`Les2-How-to-work-ai-agent`には、MCP導入前のTool CallingによるAIエージェントを使ってみます。
以下のコマンドを実行して応答を確認してください。

また、フォルダ内には、`main.py`と`main_langgraph.py`が入っています。
これは、AIエージェントを構築するライブラリが、`LangChain Agent`から`LangGraph Agent`への移行が推奨されており、本記事では両方の書き方を記載しています。この章では、AIエージェントの動作フローの分かりやすさを重視して、`LangChain Agent`を使用しています。

- `LangChain Agent`のpythonコード: main-langchain.py
- `LangGraph Agent`のpythonコード: main-langgraph.py

```bash
# 実行コマンド
cd Les-How-to-work-ai-agent
uv run main-langchain.py

# 結果(※使う環境によって結果は異なります)
> Entering new AgentExecutor chain...
I will use the get_text_length tool to find the length of the text "GOOGLE CLOUD PLATFORM". 
Action: get_text_length
Action Input: GOOGLE CLOUD PLATFORMget_text_length enter with text='GOOGLE CLOUD PLATFORM'
21I now know the final answer.
Final Answer: The length of the word "GOOGLE CLOUD PLATFORM" is 21 characters.

> Finished chain.
LLMのレスポンス: The length of the word "GOOGLE CLOUD PLATFORM" is 21 characters.
```

この結果では分かりづらいので、LangSmithを使って実行ログのモニタリングをしてみましょう。

> LangSmithとは（https://zenn.dev/umi_mori/books/prompt-engineer/viewer/langsmith）
> LangSmithを用いて、次の3つのことをできるようになります。
> ・LLMの実行ログの収集（モニター機能、デバッグ機能）
> ・LLMの出力結果のデータセット化（データ収集機能）
> ・登録したデータセットによるモデルの評価（検証機能）

### LangSmithの設定方法
1. [LangSmith](https://www.langchain.com/langsmith)にアクセス
1. LangSmithのアカウントを作成
2. APIキー作成
    「Tracing Project」→「New Project」→APIキーを取得
    
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/61cab36a-8c1a-44ca-baa4-aa4cd73b37ab.png)

1. `.env`にLangSmithの環境変数を保存

### LangSmithでAIエージェントの動作を確認
もう一度実行コマンドを実行して、LangSmithを確認すると自身のプロジェクト内にLLMの実行ログをトレースした結果が保存されています。

```bash 
uv run main-langchain.py
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/c4e4423d-c73d-4448-9576-65be066b1d8c.png)

ここから、AIエージェントは以下のように返していることが分かります。
- **入力**: What is the length of the word: GOOGLE CLOUD PLATFORM
- **出力**: The length of the word "GOOGLE CLOUD PLATFORM" is 21 characters.

では、LLMはどのようなプロンプトを受け取って、このような結果を出力したのか一つずつ見ていきます。

#### プロンプト：PromptTemplate
フロー中の`PromptTemplate`を押下し、ダッシュボードを開いてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/cc340ec9-519e-4068-8d0f-da1208690d26.png)

ここから、以下のようなプロンプトを最初に作成していることが分かります。

```txt
Answer the following questions as best you can. You have access to the following tools:

get_text_length(text: str) -> int - テキストの文字数を取得するツール
Args:
    text (str): 文字数を取得したいテキスト
Returns:
    int: テキストの文字数

Use the following format:

Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [get_text_length]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can repeat N times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question

Begin!

Question: What is the length of the word: GOOGLE CLOUD PLATFORM
Thought:
```

これは日本語で書くと以下のようになります。

```txt
質問にできるだけ正確に回答してください。以下のツールを使用することができます：

{Tool: ツールの説明}

以下の形式を使用してください：

質問: あなたが回答するべき質問
考え: 何をすべきかを常に考えてください
アクション: 実行するアクション（{Tool name：ツールの名前} の中から選択）
アクションの入力: アクションに渡す入力
観察: アクションの結果

これらの「考え」「アクション」「アクションの入力」「観察」は必要に応じて何度でも繰り返すことができます。

最後に：
考え: 最終的な答えがわかったことを示します
最終的な答え: 元の質問に対する最終的な答えを記述します

始めましょう！

質問: 「GOOGLE CLOUD PLATFORM」という単語の文字数は何文字ですか？
```

つまり、AIエージェントは使用できるツールを最初に与えられ、
> 「（ユーザーの）質問」→「（自分の）考え」→「アクション（実行ツール）」→「アクションの入力」

を繰り返して最終的な結果が分かった時点で、「最終的な答えはこれです」と示すようにプロンプトが作成されています。
とはいえ、これがどうAIエージェントに作用するか理解できませんので、実際の動作を見てみます。

#### LLM動作確認：AzureChatOpenAI
フロー中の`AzureChatOpenAI`を押下し、ダッシュボードを開いてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/a1d99ebf-8d63-4e44-843f-be54d3d9dc74.png)

このダッシュボードから、さきほどのプロンプトを入力として、以下の結果を得ていることが分かります。
```txt
I will use the get_text_length tool to find the length of the text "GOOGLE CLOUD PLATFORM". 
Action: get_text_length
Action Input: GOOGLE CLOUD PLATFORM
```

この結果より、LLMは以下のように考えていることが分かります。
- **Think(考え)**: get_text_length ツールを使用して、「GOOGLE CLOUD PLATFORM」というテキストの長さを調べます
- **Action(使用するツール)**: get_text_length
- **Action Input(ツールへの入力)**: GOOGLE CLOUD PLATFORM

#### ツール実行：get_text_length
フロー中の`get_text_length`を押下し、ダッシュボードを開いてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/33d7a961-cead-41f6-8974-56eb8e2878d7.png)

ここから、ツール実行結果として`21`を得ていることが分かりました。

#### LLM動作確認：AzureChatOpenAI
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/f6933b60-8db9-496c-a679-eea705d30e6e.png)

赤枠に実行履歴を追加させて、「（自分の）考え」を開始させるプロンプトを書いています。
AIエージェントからは、以下の答えを得ました。

```txt
I now know the final answer.
Final Answer: The length of the word "GOOGLE CLOUD PLATFORM" is 21 characters.
```

この時点で、最終的な結果を得ることができたので、エージェントの実行は終了しています。
もし必要な結果を得る事が出来ない場合には、Action(ツール実行)を続けて、答えを得られるまでツール実行を進めます。

## Les2: MCP導入後のAIエージェントを使ってみる
`Les3-Benefit-of-mcp-server`には、以下のコードが入っています。
- **main.py**: MCPツール呼び出しに対応したpythonコード
- **mcp-server.py**: ツールをMCPサーバー構築したpythonコード

### MCPサーバーの構築方法
ここでは、Les2で作成したツールをMCPサーバーとして作成してみます。
Les2で作成したツールを以下のように変更してください。

```python:Les2-How-to-work-ai-agent/main-langchain.py
@tool
def get_text_length(text: str) -> int:
    """
    テキストの文字数を取得するツール
    Args:
        text (str): 文字数を取得したいテキスト
    Returns:
        int: テキストの文字数
    """
    print(f"get_text_length enter with {text=}")
    text = text.strip("'\n").strip(
        '"'
    )  

    return len(text)
```
↓↓↓↓↓↓　MCPサーバー　↓↓↓↓↓↓
```python: mcp-server.py
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("文字列の文字数を数えるMCPサーバー")

@mcp.tool()
def get_text_length(text: str) -> int:
    """
    テキストの文字数を取得するツール
    Args:
        text (str): 文字数を取得したいテキスト
    Returns:
        int: テキストの文字数
    """
    print(f"get_text_length enter with {text=}")
    text = text.strip("'\n").strip(
        '"'
    )  
    return len(text)

if __name__ == "__main__":
    print("Starting MCP server in stdio mode")
    mcp.run(transport="stdio")
```

https://github.com/modelcontextprotocol/python-sdk

MCPサーバーは、コメントで記載している以下の内容を見て、LLMはツール実行を行うかどうかを判断します。
このコメントはMCPサーバーにおいて最重要となります。
```python
"""
テキストの文字数を取得するツール
Args:
    text (str): 文字数を取得したいテキスト
Returns:
    int: テキストの文字数
"""
```

### MCPクライアント構築方法
#### ①LangChain MCP Adapters
ツール呼び出しの方法が以下に変更になります。
```python:Les2-How-to-work-ai-agent/main-langgraph.py
(前略)
tools = [get_text_length]

agent = create_react_agent(llm, tools)
agent_response = await agent.ainvoke({"messages": query})
(後略)
```
↓↓↓↓↓↓　MCPクライアント　↓↓↓↓↓↓

```python: Les3-Benefit-of-mcp-server/main.py
params = mcp.StdioServerParameters(
    command="path/to/uv",
    args=["run", "path/to/practice-mcp-server/Les3-Benefit-of-mcp-server/mcp-server.py"],
)

# MCP サーバを実行
async with mcp.client.stdio.stdio_client(params) as (read, write):
    async with mcp.ClientSession(read, write) as session:
        await session.initialize()
        tools = await langchain_mcp_adapters.tools.load_mcp_tools(session)

        print(tools)

        agent = create_react_agent(llm, tools)

```

https://github.com/langchain-ai/langchain-mcp-adapters

このようにTool呼び出しをMCPサーバーから呼び出す事が出来ます。

#### Cline
代表的なAIエージェントツール（MCPクライアント）からもMCPサーバーが利用できます。

##### ClineをVScodeの拡張機能に追加
拡張機能の検索で「cline」で検索→インストール実行

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/6404d64f-a8f8-416a-9181-84baca82132b.png)

##### MCPサーバー設定
「Cline」→「MCP Servers」→「Installed」→「Configure MCP Servers」

上記で、MCPサーバーを指定している（サーバーの起動コマンドをjson形式で書いているみたい。）
uv run server.pyをCLI実行しているイメージと考えれば、分かりやすい

```txt
{
  "mcpServers": {
    "get-text-length": {
      "disabled": true,
      "timeout": 60,
      "command": "path/to/uv",
      "args": [
        "--directory",
        "path/to/practice-mcp-server/Les3-Benefit-of-mcp-server",
        "run",
        "mcp-server.py"
      ],
      "transportType": "stdio"
    }
  }
}
```

Clineを開いて、MCPサーバーを正しく起動できていることを確認。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/595caf00-3410-45a4-aacd-11d3db41e2ae.png)

##### MCPサーバーのテスト
Clineでチャットしてみる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/ef4cb29e-96e3-4cc3-ac41-64d81a1fca5a.png)


### MCPサーバーのメリット
MCPサーバーは、このようにAIエージェント用のツールを、使用するクライアントツールやフレームワークによらず利用する事が出来ます。

また、MCPサーバー実行の`transport`を`sse`に変更することで、コマンド実行ではなくサーバーとして利用する事が出来ます。

> MCP Clients: Stdio vs SSE
> **STDIO（標準入出力）トランスポート**は、主に同じシステム内のプロセス間通信に使用されます。クライアントアプリケーションが標準入力を通じてサーバーアプリケーションにデータを送信し、標準出力ストリームを介して応答を受け取ることができます。STDIOトランスポートは標準入力および標準出力ストリームを通信に利用します。これは、クライアントとサーバーが同じプロセス内で動作するコマンドラインツールやローカル統合に特に適しています。

> **サーバー送信イベント（SSE: Server-Sent Events）** は、サーバーが単一の長時間維持されるHTTP接続を通じて、リアルタイムの更新をウェブクライアントにプッシュできる技術です。これにより、サーバーからクライアントへの効率的な一方向通信が可能となり、リアルタイムデータ更新を必要とするアプリケーションに適しています。
> モデルコンテキストプロトコルの文脈では、SSEはクライアントとサーバー間の通信手段として利用されています。この方法により、サーバーは複数のクライアント接続を効率的に処理でき、認証やスケーラビリティといった機能もサポートされます。
>
>サーバーは主に2つのエンドポイントを提供します：
> - SSEエンドポイント（クライアントはこのエンドポイントに接続してサーバーからのメッセージを受信します）
> - HTTP POSTエンドポイント（クライアントはこのエンドポイントを使ってサーバーにメッセージを送信します）

```python: mcp-server.py
if __name__ == "__main__":
    print("Starting MCP server in stdio mode")
    mcp.run(transport="sse")
```

```bash
uv run mcp-server.py
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643462/459987cc-cf81-4e3f-87be-da84b892f563.png)

これがMCPサーバーと言われるゆえんですね。
これをClineで見る事が出来るようにしてみましょう。

さきほど編集した`cline_mcp_setting.json`を以下のように書き換えましょう
```json
{
  "mcpServers": {
    "get_text_length_see": {
      "autoApprove": [],
      "disabled": false,
      "timeout": 60,
      "url": "http://localhost:8000/sse",
      "transportType": "sse"
    }
  }
}
```

## まとめ
以上のことから、Model Context Protocol (MCP)は、AIモデルのコンテキスト管理を効率化する革新的なプロトコルであることがわかります。Pythonを始めとした様々な言語でSDKを提供し、大規模言語モデルとのやり取りにおけるコンテキスト制御、メタデータ管理、セキュリティ向上に貢献します。開発者はMCPを活用することで、AIアプリケーションの品質と一貫性を高め、より洗練されたユーザー体験を実現できるでしょう。オープンソースとして提供されるこのプロトコルは、AI開発エコシステム全体の発展に寄与する重要な技術基盤となります。