本記事は日本オラクルが運営する下記Meetupで発表予定の内容になります。発表までに今後、内容は予告なく変更される可能性があることをあらかじめご了承ください。
AIエージェントとMCP
近年、AIの進化は「チャットで質問に答える」だけにとどまらず、ユーザーの意図を理解し、複数の処理を自律的にこなす「AIエージェント」という新たな形へと進化し始めています。とりわけ2025年以降は、各テック企業がこの分野に本格的に乗り出し、さまざまな技術や製品が次々と登場しています。
このような「AIエージェント」とは、ユーザーの指示に基づいて、「どのような処理を、どのような順序で実行するか」を自ら計画・実行し、その結果をテキストとして返す、自律的なプログラムのことです。
たとえば「自社の人気商品の売上を分析して、マーケティング施策や在庫の最適化を行いたい」とAIに話しかけると、AIが自律的にSNSで人気商品を調査し、その結果を分析、さらに社内のSCMと連携して在庫の過不足を調整してくれる――そんなイメージです。
こうした処理を実行するにあたって、AIエージェントはSNSへのアクセス、データ分析プログラムの実行、社内SCMシステムの操作など、さまざまな外部サービスの連携が必要になります。
この「連携」を実現する方法にはいくつかの実装手段があり、その中でも昨今注目を集めている技術の一つが、MCP(Model Context Protocol)です。
MCP(Model Context Protocol)とは
MCPは、2024年11月にAnthropic社からリリースされました。これは、LLM(Model)が応答テキストを生成する際に必要となるヒント(Context)を効率よく取得するための仕組み(Protocol)であり、その目的にちなんで「Model Context Protocol」と名付けられています。
下図のように、AIエージェントが外部サービスから文脈情報(Context)を取得するために連携する部分──まさにこの仕組みにあたるのが、MCPです。
業界標準のオープンな仕様
MCPはAnthropic社によって開発されましたが、同社専用の独自仕様ではありません。ClaudeシリーズのLLMで知られるAnthropic社ですが、このMCPはClaudeに限らず、あらゆるLLMで利用可能な業界標準の仕様であることが大きな特徴です。
※多くのLLMには、MCPのように外部サービスと連携するためのインターフェースが用意されていますが、そうしたインターフェースのほとんどは、各LLMプロバイダーごとの独自仕様にとどまっています。
再利用、配布可能な仕様
さらに、MCPはNode.js、Docker、Python仮想環境などで簡単にパッケージングできるよう設計されており、高い可搬性(ポータビリティ)を備えています。この可搬性によって、MCPは複数のアプリケーションから再利用できるほか、不特定多数のユーザーへ配布することも可能です。
たとえば、ある企業が自社サービスと連携するためのMCPを開発・公開すれば、AIエージェントの開発者はそのMCPを自社アプリに簡単に組み込むことができます。
広範なエコシステム
このような仕様から、サービス提供企業やコミュニティによって開発された数百を超えるMCPがAnthropic社のサイトやgithubなどで配布されている状況で今後もこの動きは加速してゆくことでしょう。また、エージェント開発用の様々なライブラリもMCPとの相互運用か可能な状態にありますので、Anthropic社の狙い通り今後もMCPのエコシステムは広がってゆくと思われます。
MCPを実装すると何が嬉しいのか?
実際に使ってみれば、そのメリットは一目瞭然です。ここでは、Anthropic社が提供するデスクトップ向けチャットツール「Claude Desktop」を使って、MCPの体験をしてみましょう。
ここからClaude Desktopをダウンロードして手元のPCにインストールすると下図のようなチャット画面が起動します。
「Oracleはどんな会社?」と入力してみると、下図のように応答テキストが生成されます。
もちろん内部的にはClaude Desktopからインターネットを経由してAnthropic社のサイトのLLM(Claude)に入力テキストが連携され、LLMから応答テキストを得ています。
Claude Desktopとはこのようなデスクトップアプリで、After ChatGPT時代の今となってはごくありふれたLLMのチャットボットアプリです。
MCPなしのデフォルト状態で質問してみる
ここで、「今日現在のAI関連の最新ニュースを教えてください。」という質問をしてみます。すると、応答テキストとしては以下の通り、「申し訳ありませんが、私の知識は2024年10月までに限られています。、、、、」というつれない応答。
ChatGPTやGeminiなど出来上がったサービスのみを利用している方には想像しにくいかもしれませんが、LLMは過去のデータを学習した機械学習の予測モデル(上述の例では2024年10月までのデータしか学習していない)のため、現在のデータ(リアルタイムのデータ)に関する応答はできません。
現在のバージョンのChatGPTやGeminiなどがリアルタイム情報も答えてくれるのは、そのようなプログラム(例えばインターネットを検索したり、株価情報を検索したりというプログラム)が追加で実装されているからです。これは以前の記事でもお伝えした通り。
このように、Claude Desktopのデフォルト状態は、ChatGPTやGeminiのようなサービスとして仕上がった機能はなく、単にLLMにテキストを入力し、それに関連したテキストを出力するというすっぴんに近い状態のアプリケーションです。
MCPを実装してみる
ということでここからがMCPの出番です。このデフォルト状態のClaude Desktopに「インターネットを検索するMCP」に追加してみます。利用するのはTavilyという「インターネット検索のAPIサービス」のMCPです。
インターネット検索なのでGoogle検索のAPIでもいいのですがGoogle APIは無償枠でもクレジットカード情報を入力しなければいけないので少々抵抗があります。
手順は下記の3ステップとなり至ってシンプルです。
- Tavily社が提供する「インターネットを検索のMCP」tavily-mcpをダウンロードする
- Claude DesktopにMCPを認識させるためclaude_desktop_config.jsonにエントリを追加する
- Claude Desktopを再起動し、質問を入力
tavily-mcpをダウンロード
同社のgithubのリポジトリからダウンロード可能です。PCで下記コマンド(windowsの場合)を実行するとPCにMCPのモジュールがダウンロードされます。
npx -y @smithery/cli install @tavily-ai/tavily-mcp --client claude
このMCPモジュールは、Node.js形式、Docker形式、Python形式など、開発・提供側の都合によって配布形式が異なります(多くの場合、Node.js版とDocker版の両方が提供されており、利用者は好みに応じて選択できます)。
当然ながら、選んだ形式に対応する実行環境が事前にPCにセットアップされている必要があります。それぞれのダウンロードとセットアップはこちらから可能です:
- Node.js: 公式サイト
- Docker: 公式サイト
- Python: Anacondaの公式サイト
Claude DesktopにMCPを認識させる
次に、ダウンロードしたMCPモジュールをClaude Desktopに認識させるため設定ファイル(C:\Users<ユーザー名>\AppData\Roaming\Claude\claude_desktop_config.json)に下記のエントリを追記します。
{
"mcpServers": {
"tavily-mcp": {
"command": "npx",
"args": ["-y", "tavily-mcp@0.1.2"],
"env": {
"TAVILY_API_KEY": "your-api-key-here"
}
}
}
}
上記エントリを簡単に説明しておくと
- tavil-mcp : ダウンロードしたMCPの名称です。
- command : ダウンロードしたMCP(tavily-mcp)はnode.js形式なのでnpxコマンドで実行されます。
- args : npxコマンドの引数
- TAVILY_API_KEY : tavilyのサービスを利用するためのapiキーです。事前にTavily社のサイトでユーザー登録を行いapiキーを取得しておく必要があります。(無償枠あり)
設定は以上終了です。
Claude Desktopを再起動
Claude Desktopを再起動し、設定が反映されているかを確認します。
Claude Desktopのメニューから「ファイル」、「設定」、「開発者」の順序で選択すると、tavily-mcpが追加されていることがわかります。
再度「今日現在のAI関連の最新ニュースをインターネットで検索してください。」と質問してみます。するとその質問内容から先ほど追加したMCP(tavily-mcp)を使うかの確認画面が現れます。
MCP(tavily-mcp)の使用を許可すると、下記のように応答テキストが出力されました。
TavilyのMCPを追加したことにより、質問内容からインターネット検索を実行し、その結果を得て、テキストが生成されていることがわかります。
既に様々なMCPが提供されています
上記は、Tavily社が提供する「インターネット検索を実行するMCP」の一例ですが、他にも多くの企業やコミュニティによって提供されているMCPが公開されており、それらをダウンロードして、先述の3ステップで簡単にチャットボットの機能を拡張できます。
Anthropic社の公式サンプルサイトには、すでに多数のMCPサーバーが掲載されており、またGitHub上でも各サービス提供企業が自社のMCPを公開しています。
以下にその一部を紹介します。
企業が提供するMCPに加え、ファイル操作、データベース操作、開発ツール、ブラウザの自動操作、SNSの連携など、実に多様な分野のMCPが登場しており、今後もその数はますます増えていくと考えられます。
MCP | 主な機能・用途 |
---|---|
Filesystem | セキュアなファイル操作とアクセス制御 |
PostgreSQL | 読み取り専用のデータベースアクセスとスキーマ検査 |
SQLite | データベース操作とビジネスインテリジェンス機能 |
Google Drive | Google Driveのファイルアクセスと検索 |
Git | Gitリポジトリの読み取り、検索、操作 |
GitHub | GitHubのリポジトリ管理とAPI統合 |
GitLab | GitLab APIとの統合によるプロジェクト管理 |
Sentry | Sentry.ioからの問題取得と分析 |
Brave Search | Braveの検索APIを使用したウェブおよびローカル検索 |
Fetch | ウェブコンテンツの取得とLLM向け変換 |
Puppeteer | ブラウザの自動化とウェブスクレイピング |
Slack | チャンネル管理とメッセージ機能 |
Google Maps | 位置情報サービス、経路案内、場所の詳細情報 |
Memory | ナレッジグラフベースの永続的メモリシステム |
EverArt | 各種モデルを使用したAI画像生成 |
Sequential Thinking | 思考の連続による動的な問題解決 |
AWS KB Retrieval | Bedrock Agent Runtimeを使用したAWSナレッジベースの取得 |
因みに Oracle Database用のMCPについての下記記事も是非ご参照ください。
どういう仕組みで動作している?
上述したセットアップと、応答テキスト生成までの流れを図に纏めると以下のようになります。
-
セットアップ
⓪セットアップではまずインターネット検索のためのMCP(tavily-mcp)をローカルにダウンロードしました。このMCPはNode.js形式なので、MCPの実行環境としてNode.jsがPCにインストールされている必要があります。また、このダウロードしたMCPをClaude Desktopに認識させるためのファイル設定も行いました。これで入力プロンプトからインターネット検索が必要だと推論された場合、このMCPが実行される環境が整った状態です。 -
応答テキスト生成までの流れ
①質問を入力します。「今日現在のAI関連の最新ニュースをインターネットで検索してください。」という質問がLLMに入力されます。
②LLMはこの質問の内容からインターネット検索が必要だと判断し、Claude Desktopにtavily-mcpを実行するよう指示します。なぜLLMがこのような判断ができるかというと、①の処理では、実は質問文章だけではなく、どのようなMCPがセットアップされているかという情報もLLMに入力されているからです。もちろん複数のMCPがセットアップされていた場合、その全てがLLMに入力され、どのMCPを実行するかもLLMが判断します。
③LLMの判断通りtavily-mcpが実行されtavily社が運営しているサービスにアクセスし、インターネット検索を実行、その検索結果をClaude Desktopに返します。
④Claude Desktopからインターネット検索結果がLLMに連携され、元の質問を考慮した応答テキストが生成されます。
概ね上記のような流れで処理が進行してゆきます。
「MCPとは?」の問いに対する説明としては以上という感じなのですがこれで終了とはなりません。なぜなら上述した内容はあくまでもユーザーからみた視点だからです。
ユーザーとして利用する分には、サービス提供企業がリリースしてくれているMCPを組み込むだけですが、サービス提供企業は、この肝心のMCPを開発する必要があります。これはサービス提供企業に限らず一般企業においても同様で、例えばDX推進でAIを活用してゆく場合などは社内システム連携のためのMCPが必要となり、それは誰でもない自社で開発するしかありません。
ということでここからは開発視点でのMCPの仕組みについてです。
MCPの開発
MCPの開発とはもちろん、上述した tavily-mcp のようなモジュールを自社向けに開発するということです。仮に社内業務システム(例えばSCM)を自律的に操作するようなAIエージェントを開発する場合は、SCMの処理ロジックをコードとしておこしてMCPを作ることになります。
上図は社内SCMシステムのMCPの場合という例ですが、SCMに限らず、この開発対象であるMCPの中身はどのようなものなのか?を紐解いていきたいと思います。
MCPのアーキテクチャ
MCP開発に際してまず初めに理解しておくべきことは、MCPがクライアント・サーバー型のアーキテクチャだという点です。AI、機械学習関連のライブラリでクラサバ型というのは比較的珍しいと思いますが、実はクラサバ型が採用されているのには非常に重要な理由がありますので後述します。まずは何を作らなければいけないのかを確認しましょう。
クラサバなので、コードとしては、MCPサーバーとMCPクライアントの2つを作る必要があり、それぞれ以下のような役割を持ちます。
-
MCPクライアントの役割
LLMと連携し、ユーザーが入力したテキストの意図を理解し、それに沿ってどのMCPサーバーに何を依頼すべきかを判断し、実際に実行要求をMCPサーバーの実行指示を送信処理を担当します。つまり「実行指示エンジン」のようなものです。 -
MCPサーバーの役割
外部サービスの操作処理ロジックのコードが主体となっており、まさに外部サービスを操作したり、外部サービスから情報を得る処理を担当します。MCPクライアントからの実行要求に応じて、実際に外部サービスの操作を行う「実行エンジン」ようなものです。
株価情報を取得するMCPを作ってみる
この仕組みに沿って、Yahoo Financeで株価情報を取得するMCPを作ってみたいと思います。MCP開発のhello world的な非常にシンプルなコードです。
上述したMCPサーバーとMCPクライアントの役割に当てはめると両者は以下のような処理コードになります。
-
MCPクライアントの役割
例えば「2025年4月1日から4月5日のOracleの株価の終値は?」という入力プロンプトを入力した場合、これをLLMに連携し、MCPサーバー実行の必要性と、実行する場合はMCPサーバーに渡す情報(株価情報を取得したい企業名と期間)を抽出し、MCPサーバーににその情報を渡して実行指示を出す。またMCPサーバーからの応答を再度LLMに連携し最終的な応答テキストを生成する。 -
MCPサーバーの役割
MCPクライアントから受け取った、企業名と期間の情報を使って、Yahoo Financeで株価情報を検索しその結果をMCPクライアントに返します。
以降、この2つの役割をコードにしてゆきます。MCPは様々な開発言語で開発が行えますが、今回はPythonを使います。(当然デプロイ環境でもPythonの実行環境をセットアップしておく必要があります。)
MCPサーバーのプログラムを作り、python実行ファイル(yahoofinance_server.py)として保存し、MCPクライアントのプログラムをノートブックで作りクライアントを実行すると、MCPサーバー(yahoofinance_server.py)が実行されるというイメージです。
MCPサーバーのコード
Yahoo Fianceサービスにアクセスし株価情報を得る処理ロジックそのものとなります。
基本的な考え方としては、Yahoo FianceのAPIを使って株価を検索するユーザー定義関数を作り、その関数をMCPにするという流れです。
# 以下のコードをyahoofinance_server.pyとして保存
from mcp.server.fastmcp import FastMCP
from typing import Annotated
import yfinance as yf
import pandas as pd
# MCPサーバーインスタンスの作成
mcp = FastMCP("StockData")
# 株価データ取得関数
@mcp.tool()
def get_stock_data(
ticker: Annotated[str, "企業のティッカーシンボル(例:AAPL、7203.T)"],
start_date: Annotated[str, "データ取得の開始日 (YYYY-MM-DD)"],
end_date: Annotated[str, "データ取得の終了日 (YYYY-MM-DD)"]
) -> str:
"""
Yahooファイナンスから株価情報(日付, 始値, 高値, 安値, 終値, 出来高, 銘柄)を取得します。
"""
try:
stock = yf.Ticker(ticker)
df = stock.history(start=start_date, end=end_date)
if df.empty:
return f"{ticker} のデータが見つかりませんでした。"
df = df.reset_index()
df["Ticker"] = ticker
df = df.rename(columns={
"Date": "日付", "Open": "始値", "High": "高値",
"Low": "安値", "Close": "終値", "Volume": "出来高",
"Ticker": "銘柄"
})
result = df[["日付", "銘柄", "始値", "高値", "安値", "終値", "出来高"]].to_string(index=False)
return f"{ticker} の株価データ:\n```\n{result}\n```"
except Exception as e:
return f"データ取得中にエラーが発生しました: {repr(e)}"
# サーバー起動(stdio)
if __name__ == "__main__":
mcp.run(transport="stdio")
上記コードではget_stock_dataという名前でユーザー定義関数を作り、それをMCPとして定義しています。
get_stock_dataの引数からわかりますが、入力プロンプトから企業名と期間の情報が抽出されることを前提にしたMCPの例です。
ユーザー定義関数をMCPとして定義する際は、FastMCPというライブラリを使うと、非常に簡単に定義できます。
このコードをyahoofinance_server.pyとして保存してMCPサーバーの出来上がりです。(これを後述のMCP Clientから起動する仕組み。)
MCPクライアントのコード
MCPクライアントには入力プロンプト、LLM、MCPサーバーの指定、エージェントの定義など沢山の要素がでてきます。が、後述するライブラリのおかげでほぼ全てが定型化されたお馴染みのコードです。こちらのコードはノートブックで実行できる内容にしましたが、MCPサーバーのようにPython実行ファイルにしても問題ありません。
import oci
from langchain_community.chat_models import ChatOCIGenAI
from langchain_mcp_adapters.client import MultiServerMCPClient
from langgraph.prebuilt import create_react_agent
# Oracle Cloudの認証情報定義
config = oci.config.from_file("~/.oci/config", "DEFAULT")
# 入力プロンプトを理解するためのLLMの定義
model = ChatOCIGenAI(
model_id="cohere.command-r-08-2024",
service_endpoint="https://inference.generativeai.us-chicago-1.oci.oraclecloud.com",
compartment_id="<your compartment id>",
model_kwargs={"temperature": 0.7, "max_tokens": 500}
)
# MCPサーバーの定義
client = await MultiServerMCPClient(
{
"stock": {
"command": "python",
"args": ["/home/opc/mcp/mcp-langchain/yahoofinance_server2.py"],
"transport": "stdio",
}
}
).__aenter__()
# MCPサーバーをツールとして定義
tools = client.get_tools()
# エージェントの定義(LangGraphでReActエージェントを定義)
agent = create_react_agent(model, tools)
# 入力プロンプトの定義
agent_response = await agent.ainvoke({
"messages": "2025年4月1から2025年4月5日までのOracleの株の日次の終値を取得してください。"
})
# 出力結果の表示
print(agent_response)
# エージェントの実行
await client.__aexit__(None, None, None)
上記のコードで定義しているものは以下の通りです。
-
LLMの定義
入力プロンプトを理解するためにのLLMの定義です。基本的にどのLLMでも動作しますが、今回はOracleのOCI Generative AI Service(Cohere社のLLM Command-R-Plus)を定義しています。(OpenAIを使いたい方はいつも通り、OpenAIのchat modelの定義に挿げ替えていただければ普通に動作します。その他のLLMの場合も同様です。) -
MCPサーバーの定義
先に作成したMCPサーバーの実行ファイルを指定しています。 -
MCPのフレームワーク
LangChainを使っています。LangChainにはLangChain MCP Adaptersと呼んでいるモジュールがあり、これを利用することでMCPクライアントのコードがかなりシンプルになります。またLLMの定義にもLangChainを使っています。 -
エージェントのフレームワーク
LangGraphを使うことで、エージェントのコードも簡素化されます。上記のコードではLangGraphでReActエージェントを定義しています。このReActエージェントが入力プロンプトに応じた処理フローを計画し実行してくれます。
これらMCPクライアントの定義により、エージェントが下記のような流れで最終的な応答テキストを出力するまでの計画・実行を行います。
- 入力されたプロンプトをLLMで理解(MCP Client)
- MCPサーバーの実行必要性を推論(MCP Client)
- 実行する場合は関数の引数を何にするかを推論しMCPサーバーへ実行指示を送信(MCP Client)
- MCPサーバーの実行し結果を取得(MCP Server)
- 取得した結果をLLMに入力し最終的な応答テキストを生成(MCP Client)
実行結果の抜粋
上記MCPクライアントの実行結果の抜粋がこちら。
まず、最終的な応答テキストが下記のとおり、MCPがちゃんと動作し、最終的な応答テキストが生成されていることがわかります。
agent_response["messages"][3].content
2025年4月1日から2025年4月5日までのOracleの株価データは次のとおりです。
| 日付 | 銘柄 | 終値 |
|------------|------|--------|
| 2025-04-01 | ORCL | 141.43 |
| 2025-04-02 | ORCL | 145.34 |
| 2025-04-03 | ORCL | 136.74 |
| 2025-04-04 | ORCL | 127.81 |
上記に至るまでの出力ログの内容を見てゆきたいと思います。
始めに、ユーザーが入力したプロンプトが記録されていることがわかります。
# HumanMessage
agent_response["messages"][0].content
'2025年4月1から2025年4月5日までのOracleの株の日次の終値を取得してください。'
上記のプロンプトからエージェントが以下の判断をしたことが記録されています。
# AIMessage
agent_response["messages"][1].content
会社名を'Oracle'に設定して、2025-04-01から2025-04-05までの株価データを取得します
そして、ツール(MCPサーバーで定義したget_stock_data)を実行する判断をし、その関数の引数を推論した結果が下記。
# AIMessage
agent_response["messages"][1].tool_calls
[{'name': 'get_stock_data',
'args': {'company_name': 'Oracle',
'end_date': '2025-04-05',
'start_date': '2025-04-01'},
'id': '949dc0e123154d639a6cb6401ff37a3b',
'type': 'tool_call'}]
ツール(MCPサーバー)が実行された結果がこちら。Yahoo Financeサイトから株価が取得できている様子がわかります。
#Tool Message
agent_response["messages"][2].content
'['
'"{\\"日付\\": \\"2025-04-01\\", \\"銘柄\\": \\"ORCL\\", \\"始値\\": 139.26, \\"高値\\": 142.74, \\"安値\\": 138.26, \\"終値\\": 141.43, \\"出来高\\": 9518500}", '
'"{\\"日付\\": \\"2025-04-02\\", \\"銘柄\\": \\"ORCL\\", \\"始値\\": 139.36, \\"高値\\": 147.36, \\"安値\\": 139.36, \\"終値\\": 145.34, \\"出来高\\": 11594400}", '
'"{\\"日付\\": \\"2025-04-03\\", \\"銘柄\\": \\"ORCL\\", \\"始値\\": 138.75, \\"高値\\": 140.89, \\"安値\\": 136.15, \\"終値\\": 136.74, \\"出来高\\": 14433800}", '
'"{\\"日付\\": \\"2025-04-04\\", \\"銘柄\\": \\"ORCL\\", \\"始値\\": 132.43, \\"高値\\": 133.34, \\"安値\\": 126.5, \\"終値\\": 127.81, \\"出来高\\": 14986900}"'
']'
上記をLLMに再度入力して最終的な下記応答テキストが生成されています。
agent_response["messages"][3].content
2025年4月1日から2025年4月5日までのOracleの株価データは次のとおりです。
| 日付 | 銘柄 | 終値 |
|------------|------|--------|
| 2025-04-01 | ORCL | 141.43 |
| 2025-04-02 | ORCL | 145.34 |
| 2025-04-03 | ORCL | 136.74 |
| 2025-04-04 | ORCL | 127.81 |
上記は抜粋版で、全出力がこちら。(非常に長いので折り畳みます。)
{
'messages': [
HumanMessage(
content='2025年4月1から2025年4月5日までのOracleの株の日次の終値を取得してください。',
additional_kwargs={},
response_metadata={},
id='646fd4a7-1428-4cd8-894b-9a8968e119df'
),
AIMessage(
content="会社名を'Oracle'に設定して、2025-04-01から2025-04-05までの株価データを取得します。",
additional_kwargs={
'documents': None,
'citations': None,
'search_queries': None,
'is_search_required': None,
'finish_reason': 'COMPLETE',
'tool_calls': [
{
'id': '20a9107df28745d2a1aee057e023ab1f',
'function': {
'name': 'get_stock_data',
'arguments': '{"company_name": "Oracle", "end_date": "2025-04-05", "start_date": "2025-04-01"}'
},
'type': 'function'
}
]
},
response_metadata={
'model_id': 'cohere.command-r-08-2024',
'model_version': '1.7',
'request_id': '...',
'content-length': '423',
...
},
id='run-dfc9e4f5-4c34-4ab8-bbe0-e29b10b5c454-0',
tool_calls=[
{
'name': 'get_stock_data',
'args': {
'company_name': 'Oracle',
'end_date': '2025-04-05',
'start_date': '2025-04-01'
},
'id': '277a1c8b85ea42598e22011930a1229e',
'type': 'tool_call'
}
]
),
ToolMessage(
content='['
'"{\\"日付\\": \\"2025-04-01\\", \\"銘柄\\": \\"ORCL\\", \\"始値\\": 139.26, \\"高値\\": 142.74, \\"安値\\": 138.26, \\"終値\\": 141.43, \\"出来高\\": 9518500}", '
'"{\\"日付\\": \\"2025-04-02\\", \\"銘柄\\": \\"ORCL\\", \\"始値\\": 139.36, \\"高値\\": 147.36, \\"安値\\": 139.36, \\"終値\\": 145.34, \\"出来高\\": 11594400}", '
'"{\\"日付\\": \\"2025-04-03\\", \\"銘柄\\": \\"ORCL\\", \\"始値\\": 138.75, \\"高値\\": 140.89, \\"安値\\": 136.15, \\"終値\\": 136.74, \\"出来高\\": 14433800}", '
'"{\\"日付\\": \\"2025-04-04\\", \\"銘柄\\": \\"ORCL\\", \\"始値\\": 132.43, \\"高値\\": 133.34, \\"安値\\": 126.5, \\"終値\\": 127.81, \\"出来高\\": 14986900}"'
']',
name='get_stock_data',
id='5b6df4ad-e749-493b-9832-a8a8572c2e1b',
tool_call_id='277a1c8b85ea42598e22011930a1229e'
),
AIMessage(
content=(
"2025年4月1日から2025年4月5日までのOracleの株価データは次のとおりです。\n\n"
"| 日付 | 銘柄 | 終値 |\n"
"|---|---|---|\n"
"| 2025-04-01 | ORCL | 141.43 |\n"
"| 2025-04-02 | ORCL | 145.34 |\n"
"| 2025-04-03 | ORCL | 136.74 |\n"
"| 2025-04-04 | ORCL | 127.81 |"
),
additional_kwargs={
'documents': [
{
'id': 'get_stock_data:0:2:0',
'output': '[ ... JSON文字列 ... ]',
'tool_name': 'get_stock_data'
}
],
'citations': [
{'document_ids': ['get_stock_data:0:2:0'], 'start': 79, 'end': 89, 'text': '2025-04-01'},
{'document_ids': ['get_stock_data:0:2:0'], 'start': 92, 'end': 96, 'text': 'ORCL'},
{'document_ids': ['get_stock_data:0:2:0'], 'start': 99, 'end': 105, 'text': '141.43'},
...
],
'search_queries': None,
'is_search_required': None,
'finish_reason': 'COMPLETE'
},
response_metadata={
'model_id': 'cohere.command-r-08-2024',
'model_version': '1.7',
'request_id': '...',
'content-length': '971',
...
},
id='run-72c44c0a...'
)
]
}
ここまでの説明で、MCPの作り方と、その挙動がある程度理解いただけたのではないかと思います。
ReActエージェントとMCP
全体の処理の大まかな流れは上述の通りですが、特に注目したい点は、最終的な応答テキスト生成までに中間のAIMessageが出力されている点です。これがReActエージェントの特徴的な挙動になります。
これまで説明してきた、下記のような処理は全てこのReActエージェントがプランして実行しており、その入出力が全て履歴として記録されてゆきます。(冒頭の図を少し詳しくした図です。)
- 思考(Thought):「どのツール(MCP)を使うべきか考える」
- アクション(Action):「ツール(MCP)を使ってデータ取得」
- 観察(Observation):「取得したデータの確認」
- 最終応答(Final Answer):「ユーザーに答えを提示」
エージェントはこの履歴情報を使って、次に何の処理を行うかを推論する際のミスを軽減したり、最終的な応答テキストの品質の向上を促しています。
特に、複数のMCPサーバーを扱う場合、一つのMCPの実行が終わると、その結果についてReActエージェントが中間メッセージを生成し、その出力メッセージから、次にどのMCPサーバーを実行するかの推論を行うため、MCPサーバーの選択や実行順序のミスが軽減されます。
ということで、更にMCPサーバーを追加し、2つのMCPサーバーを連動させる簡単なエージェントを実装してみます。
複数のMCPを連動させてみる(Multi MCP)
上述のコードは「Yahoo Financeから株価を取得するMCP」のエージェントになりますが、ここに「グラフを作成するMCP」を追加してみます。
期待するエージェントの挙動としては下記のような流れです。
- 入力プロンプトを「2025年4月1日から2025年4月5日までのOracleの株価データを取得してグラフを作成してください。」とする。
- 一つ目のMCPサーバー「Yahoo Financeから株価を取得するMCP」が株価を取得する。
- 取得した株価情報を二つ目のMCPサーバー「グラフを作成するMCP」がグラフにして表示する。
追加するMCPサーバー
グラフを作成するためのMCPサーバーを作ります。具体的には、yahoofinance_mcp.serverで取得した株価情報をLLMがmatplotlib(グラフ作成用のライブラリ)のコードに変換してくれることを前提に、「そのmatplotlibのグラフ用コードを実行するMCPサーバー」を作るということです。
この場合、LLMによって動的に生成されたプログラム(matplotlibのコード)をMCPで実行することになりますので Python REPLを使います。(※Python REPL(Read-Eval-Print Loop)とは、Pythonのコードをプログラムから実行できる仕組みで、LLMによって文字列として与えられたコードを評価・実行するのに適しています。)
つまり、プログラムを実行するプログラムをユーザー関数として定義し、MCPにします。
# repl_server.py
from langchain_experimental.utilities import PythonREPL
from typing import Annotated
from mcp.server.fastmcp import FastMCP
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import base64
from io import BytesIO
import contextlib
import sys
# MCP Server のインスタンス作成
mcp = FastMCP("PythonREPL")
# Python REPL ユーティリティの初期化
repl = PythonREPL()
@mcp.tool()
def python_repl(code: Annotated[str, "チャートを生成するために実行する Python コード"]) -> str:
"""
Python コードを実行し、matplotlib のグラフが含まれる場合は base64 PNG 画像として返す。
"""
try:
# stdout の捕捉
stdout = BytesIO()
with contextlib.redirect_stdout(sys.stdout):
exec(code, globals())
# matplotlibの画像を base64 に変換
buf = BytesIO()
plt.savefig(buf, format="png")
buf.seek(0)
img_base64 = base64.b64encode(buf.read()).decode("utf-8")
buf.close()
plt.close()
return f"Successfully executed:\n```python\n{code}\n```\n\n"
except BaseException as e:
return f"Failed to execute. Error: {repr(e)}"
if __name__ == "__main__":
mcp.run(transport="stdio")
上記のユーザー定義関数ではLLMが生成したmatplotlibのグラフ用コードを入力としてpython_replがそのコードを実行し、グラフ化したものをbase 64フォーマットのPNG画像として保存します。
MCPクライアントのコード
基本構造は前のMCPクライアントから変更はありません。下記2点を追加したくらいです。
- MCPサーバーの指定にrepl_server.pyを追加
- エージェントの実行後に、保存されたグラフ画像を表示するコードを追加
from langchain_mcp_adapters.client import MultiServerMCPClient
from IPython.display import Image, display
import re
import base64
async def main():
async with MultiServerMCPClient(
{
"stock": {
"command": "python",
"args": ["/home/opc/mcp/mcp-langchain/yahoofinance_server.py"],
"transport": "stdio",
},
"chart": {
"command": "python",
"args": ["/home/opc/mcp/mcp-langchain/repl_server.py"],
"transport": "stdio",
}
}
) as client:
tools = client.get_tools()
agent = create_react_agent(model, tools)
agent_response = await agent.ainvoke({
"messages": "2025年4月1から2025年4月5日までのOracleの株の終値を取得し、その値を使ってmatplotlibのチャートを作成してください。"
})
# エージェントの全出力を表示
print(agent_response)
# グラフの画像の表示
match = re.search(r'data:image/png;base64,([A-Za-z0-9+/=]+)', str(agent_response))
if match:
image_data = match.group(1)
display(Image(data=base64.b64decode(image_data)))
else:
print(agent_response)
# 実行
await main()
実行結果
出力されたグラフがこちら。
処理フローの出力(長いので折り畳みます。)
{
"messages": [
{
"type": "human",
"content": "2025年4月1から2025年4月5日までのOracleの株の終値を取得し、その値を使ってmatplotlibのチャートを作成してください。"
},
{
"type": "ai",
"content": "まず、2025年4月1日から2025年4月5日までのOracleの株価データを取得します。次に、Pythonを使用して、終値をプロットしたグラフを作成します。",
"tool_calls": [
{
"id": "082800cb41fa40129d82b46a187d6769",
"function": {
"name": "get_stock_data",
"arguments": {
"company_name": "Oracle",
"start_date": "2025-04-01",
"end_date": "2025-04-05"
}
}
}
]
},
{
"type": "tool",
"name": "get_stock_data",
"content": "[\"{<省略>}\", \"{<省略>}\"]"
},
{
"type": "ai",
"content": "2025年4月1日から2025年4月5日までのOracleの株価データを取得しました。\n\n次に、Pythonを使用して、終値をプロットしたグラフを作成します。",
"tool_calls": [
{
"id": "200860b14d4144799d79064c5c77276f",
"function": {
"name": "python_repl",
"arguments": {
"code": "import matplotlib.pyplot as plt\n\ndates = [\"2025-04-01\", \"2025-04-02\", \"2025-04-03\", \"2025-04-04\", \"2025-04-05\"]\nclose_prices = [141.43, 145.34, 136.74, 127.81, 130.57]\n\nplt.plot(dates, close_prices)\nplt.xlabel(\"Date\")\nplt.ylabel(\"Close Price\")\nplt.title(\"Oracle Stock Prices\")\nplt.savefig(\"oracle_stock_prices.png\")"
}
}
}
]
},
{
"type": "tool",
"name": "python_repl",
"content": "Successfully executed Python code and saved the chart image. [Base64 omitted]"
},
{
"type": "ai",
"content": "2025年4月1日から2025年4月5日までのOracleの株価データをグラフにしました。\n\n"
}
]
}
Multi MCPは入力プロンプトの内容を正確に把握し、「ちゃんと目的のMCPが選択されるか」という点と、「複数のMCPが目的の順序で実行されるか」という点がチャレンジになりますが、上記の例ではうまく動作しグラフが作成されたことがわかります。
なぜMCPではクライアント・サーバー型が採用されているのか?
これはMCPを理解するうえで、個人的には最も重要な点だと思います。
MCPがクライアント・サーバー型のアーキテクチャを採用している理由は、外部サービスの操作ロジックと(MCPサーバー)、とその実行指示を出すロジック(MCPクライアント)を明確に分離し、この両者を独立して開発・提供可能にするためです。
この設計によって、たとえばある企業が自社のサービスに特化したMCPサーバー(外部サービスの操作処理ロジック)を開発・配布し、それを利用したいユーザーが自分の生成AIアプリケーションに配布されているMCPを組み込む、という利用形態が成立します。
このように、MCPを中心としたエコシステムが自然と広がっていくことが想定され、これは今後のAI市場においてMCPの普及はもちろんのこと、AIエージェント開発の高度化も強く後押しする要因になると考えられます。
さらに、MCPは業界標準仕様を目指しているため、クライアントとサーバーの開発者が別々であっても、MCPの仕様に準拠していれば動作が保証される点も(当たり前ではありますが)重要な特徴です。これは、身近な例に例えると、httpプロトコルのように標準に則ってさえいれば、様々な企業が運営するあらゆるサイト(Webサーバー)にユーザーがブラウザ(Webクライアント)で確実にアクセスできるというような当たり前の安心感をMCPにもたらしているということになります。
加えて、MCPはNode.js、Docker、Python仮想環境などで容易にパッケージ化できる仕様になっているため、その可搬性の結果としてさまざまなMCPサーバーが再利用・配布されやすくなっています。これは、AI時代の「機能単位で流通するミニアプリ」のような存在として、MCPがより広範に採用されていく土壌を整えるものです。
このように、クライアント・サーバー型のアーキテクチャは、MCPの柔軟性・拡張性・普及性を高めるための中核的な設計思想となっており、個別機能の再利用性を高めながら、AIアプリの開発と運用を大幅に効率化する基盤を提供するものになっています。
サービス提供企業はMCPサーバーを開発・配布することで市場でのサービスの利用が進み、そのサービスを利用したいユーザーは配布されているMCPサーバーを自社開発のAIエージェントに簡単に組み込むことができるというwin-winの関係が成り立っているということになります。
Function Callingとは何が違うのか?
OpenAI社のLLMを使われている方はFunction Calling(もしくはTool Calling)というLLMの機能をご存じかと思います。LLMの外の世界の情報を取得したり、外部システムの操作をしたりできる関数を生成AIアプリに組み込むことができる機能です。ユーザー体験の観点からすると、ここまで説明してきたMCPとほぼ同じ内容になりますので、MCPをこれから学ぶ場合、大抵の方が真っ先に疑問に思うことがコレじゃないでしょうか。
Function Callingはユーザーアプリから外部サービス処理ロジックを分離できない
Function Callingは「外部サービス処理ロジック」のコードをユーザーアプリケーションの中に完全に組み込むようなプログラミングモデルになります。この重要な「外部サービス処理ロジック」をユーザーアプリケーションから分離することができないためこのFunction Callingのコードを他のアプリケーションで再利用したり、不特定多数のユーザーに配布するということが困難です。できることと言えば、「このFunction Callingのコードを参考にして、自分のアプリに合うように書き直してね」という感じです。
それに対して、MCPはクライアント・サーバー型のプログラミングモデルなので、「外部サービス処理ロジック」(MCPサーバー)をユーザーアプリケーションから分離することができるプログラミングモデルです。つまり、市場で提供されているMCPサーバーをダウンロードすれば、自分のアプリに合うように書き直さなくてもそのまま動作します。
このように「外部サービス処理ロジック」を再利用できるようにし、市場で流通させるという観点ではMCPのメリットが非常に大きいです。
Function Callingは独自仕様
加えて、Function CallingはもちろんOpenAI社の独自仕様です。OpenAI社以外のLLMを使う場合、Function Callingのコードをそっくりそのまま再利用することはできません。
それに対してMCPはLLMに依存しません。精度の違い(適切なMCPサーバーの選択と実行順序の精度)はありますが、基本的にはどのLLMを使っても動作します。
このように独自仕様から業界標準仕様にすることで、「LLMの選択肢の広さ」という観点でもMCPのメリットは非常に大きいと思います。
では何でもかんでもMCPで書けばいいのか?
というとそれも違うかなとも思います。本記事で紹介したような非常にシンプルなサンプルコードの場合、実はFunction Callingでサラッと書いてしまう方が簡単です。この程度のシンプルな処理を、わざわざMCPでユーザーアプリから分離しクライアントサーバー型のコードにするというのはだいぶ仰々しさを感じます。モジュール化したコードの再利用や、市場で流通させたいという配布の目的がないのであればなおさらです。
このあたり意見や好みが分かれるところではあると思いますが、MCPが主流になったとしてもFunction Callingは「シンプルな処理、再利用・配布不要な処理向け」に棲み分けられて機能自体は継続されるのではないでしょうか。
さいごに
これまでのAI市場ではあらゆる企業があらゆる製品を独自仕様でリリースしてきました。その独自仕様のギャップを頑張って埋めてきたLangChainやLlamaindexなど一部の企業だけでこのAI市場の発展はスケールしないと思います。
それがここにきてようやく、エージェントからツールを利用する業界標準の仕様としてAnthropicのMCPが名乗りを上げ、それが広く受け入れられようとしています。
ご存じのように同社はClaudeという非常に優秀なLLMをリリースしているにもかかわらずマーケットシェアはOpenAIに大きく離されています。ここで業界標準のプロトコルを作ってどんなLLMでも利用できるMCPをリリースすることによりOpenAIのシェアを少しでも切り崩そうとする同社の思惑が見え隠れする技術ではありますが、あらゆるサービス提供企業が自社サービスをAI市場で流通させようと考えている今、MCPはその有効な手段となることは間違いないでしょう。
2024年までは、各ベンダーがより賢いLLMを次々と開発・リリースし、AI市場の覇権争いはLLMの性能競争に集中していました。この動きは今後も続くと考えられますが、2025年以降、その主戦場は「生成AIアプリケーションの開発」へと移りつつあります。
そうした中で、Anthropic社はMCPによって、ツール連携の仕組みに標準化の流れを持ち込みました。さらに続く形で、Google社もAgent2Agentというプロトコルをリリースし、MCPの上のレイヤーであるエージェント連携の標準化を試みています。
AIエージェントを構築する際に必要となるこれらの主要技術エリアは、まさに標準化時代に突入しており、今後これらの技術がAI市場の主流となるのか、はたまた新たな技術がリリースされ、さらなる標準化合戦の時代へと突入するのか、激動のAI市場の転換期が続きますね。
長い記事になってしまいましたが最後まで読んでいただきありがとうございました。m(_ _)m