

社内の生成AIアプリケーションに「外部の公開APIを叩いて、その結果をもとに回答する」機能を追加することになった。要件を読んだ瞬間に思い浮かんだ選択肢は次の3つだった。
Function Calling をそのまま使う
MCP (Model Context Protocol) を使う
Agno などの Agent フレームワークを使う
名前は聞いたことがあっても「結局これって何が違うの?」「重複してない?」と感じる人は多いと思う。実際に検討してみた結果、三者は競合関係ではなく、レイヤーが違うことが分かった。本記事ではその関係性と、用途別の使い分けを整理する。
MCP / Function Calling / Agno のそれぞれが「どのレイヤー」を担当するのか
三者を組み合わせる場合と、単体で使う場合の判断基準
既存の Web サービスに外部ツール連携を追加するときの典型的な構成パターン
公式 MCP Server を Web サービスから利用する際の現実的な選択肢
LLM を使ったアプリケーションを実装している、または設計しているエンジニア
「MCPって流行ってるけど Function Calling と何が違うの?」と感じている人
Agno / LangChain / LlamaIndex などの Agent フレームワーク採用を検討している人
公式 MCP Server を自分のWebサービスに組み込みたい人
LLM (OpenAI / Azure OpenAI 等) の Function Calling API について基本的な理解があること
Python と非同期処理 (asyncio) の基礎知識
結論から書くと、それぞれの担当レイヤーはこうなる。
| 名前 | レイヤー | ざっくり何をするか |
|---|---|---|
| Function Calling | LLM API の機能 | LLM が「このツールをこの引数で呼びたい」と JSON で返してくれる |
| MCP | プロトコル | 「LLM ↔ 外部ツール」のやり取りを標準化する仕様 |
| Agno | フレームワーク | LLM 呼び出し・ツール選択・状態管理などを束ねた高レベル抽象 |
図にするとこういう包含関係になる。
┌──────────────────────────────────────────────────┐
│ Agno / LangChain (Agent フレームワーク) │
│ ┌────────────────────────────────────────────┐ │
│ │ MCP (プロトコル仕様) │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ Function Calling (LLM API の機能) │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
外側になるほど抽象度が高く、内側になるほど低レイヤーになる。
Function Calling は OpenAI / Azure OpenAI の API が提供する機能そのもの。tools=[...] パラメータでツールスキーマを渡すと、LLM が「このツールをこの引数で呼びたい」というメッセージ (tool_calls) を返してくれる。
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "東京の天気は?"}],
tools=[{
"type": "function",
"function": {
"name": "get_weather",
"description": "指定都市の現在の天気を返す",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}],
)
LLM 自身は実際にツールを実行しない。「呼んでほしい」という意思表示を返すだけで、実行はアプリ側の責任。返ってきた tool_calls をパースして、対応する Python 関数を実行し、結果を messages に append してもう一度 LLM を呼ぶ — このループが「Function Calling Loop」と呼ばれる。
このレイヤーには「ツールがどこにあるか」「どう発見するか」といった概念は無い。すべてアプリ側が手書きで tools=[...] を組み立てる。
Function Calling だけでは、ツールごとに「スキーマを書く」「実行関数を書く」「結果を整形する」を毎回ベタ書きする必要がある。アプリ A とアプリ B が同じ外部 API を呼びたくても、それぞれが独立して同じことをやることになる。
ここで MCP (Model Context Protocol) が登場する。MCP は Anthropic が策定した「LLM とツール群をやり取りするためのプロトコル仕様」。
MCP では「ツール群」を MCP Server として外部に切り出す。MCP Server は次の能力を公開する。
list_tools() — 提供しているツールの一覧とスキーマを返す
call_tool(name, args) — 名前と引数を渡すとツールを実行して結果を返す
クライアント側 (アプリ) は MCP Client として MCP Server に接続し、list_tools() で得たスキーマをそのまま LLM の tools=[...] に渡せる。LLM が tool_calls を返してきたら、対応する call_tool() を呼ぶだけで済む。
# 概念コード
async with mcp_client.connect("http://mcp-server:3001/mcp") as session:
tools = await session.list_tools() # スキーマを動的取得
response = client.chat.completions.create(
model="gpt-4o",
messages=messages,
tools=convert_to_openai_format(tools),
)
for tool_call in response.choices[0].message.tool_calls:
result = await session.call_tool(
name=tool_call.function.name,
arguments=json.loads(tool_call.function.arguments),
)
# 結果を messages に append して再度 LLM 呼び出し
重要なポイント: MCP は「LLM」ではない。LLM の機能を内蔵しているわけでも、ツール選択を行うわけでもない。あくまで「ツールを公開するためのプロトコル」であり、ツール選択や実際の LLM 呼び出しは引き続きアプリ (MCP Client) 側の責任。
MCP には大きく2つの通信方式がある。
| トランスポート | 接続方式 | 主なユースケース |
|---|---|---|
| stdio | サーバーをサブプロセスとして起動し、標準入出力で通信 | Claude Desktop など「単一クライアントが MCP Server を独占的に起動」 |
| Streamable HTTP | HTTP/SSE で接続。複数クライアントが同じサーバーに接続可能 | Web サービスが MCP Server を共有する構成 |
GitHub に公開されている公式 MCP Server の多くは Claude Desktop 想定で stdio のみ実装されている。これを Web サービスから使いたい場合、後述する mcp-proxy などで HTTP に変換する必要がある。
Agno (旧 PhiData) は、Function Calling Loop や状態管理を高レベル API でラップするフレームワーク。LangChain や LlamaIndex の仲間。
# 概念コード
from agno.agent import Agent
from agno.models.openai import OpenAIChat
agent = Agent(
model=OpenAIChat(id="gpt-4o"),
tools=[my_tool_1, my_tool_2],
instructions="...",
)
response = agent.run("東京の天気は?")
これだけで Function Calling Loop も、ツール実行も、結果のフィードバックも全部やってくれる。MCP Server を tools として登録する機能もある。
便利に見えるが、トレードオフもある。
Pro: ボイラープレートが激減する。プロトタイピングや個人プロジェクトに向く。
Con: 内部のプロンプト・状態管理がブラックボックス化する。既存サービスのストリーミング実装やエラーハンドリングと噛み合わないことが多い。
→ Function Calling のみで十分。MCP も Agno も不要。
外部 API のクライアントを Python で書き、スキーマと一緒に tools=[...] に渡す。「動的にツールが増減する」「他のアプリでも同じツールを使い回す」予定が無いなら、MCP のレイヤーを足すメリットは薄い。
→ MCP Client + Function Calling。Agno は不要。
例えば「政府機関や OSS コミュニティが公式 MCP Server を提供している」「自社で同じものを作るのは無駄」というケース。MCP Client として接続し、list_tools() で得たスキーマを Function Calling で使う。
公式 MCP Server が stdio のみの場合、mcp-proxy でラップして HTTP 化し、サイドカーコンテナとして配置する構成が現実的。
flowchart LR
A["API Container<br/>(MCP Client)"] -- "HTTP" --> B["mcp-proxy<br/>(sidecar)"]
B -- "stdio" --> C["公式 MCP Server<br/>(subprocess)"]
この構成の詳細は 別記事 で解説した。
→ Agno (or LangChain / LlamaIndex) を検討する価値あり。
Agent ループ・状態管理・メモリなどを自前で書くと結構な量になる。プロトタイピングや MVP 段階ならフレームワークの恩恵が大きい。ただし本番運用に耐えうるかは別問題で、内部のプロンプトを覗き込めないことが負債になりやすい。
→ MCP SDK を使ってサーバーを実装する。これは「Function Calling」「Agno」とは別の話。
ツールを多数の LLM クライアント (Claude Desktop, Cursor, 自社アプリ等) から使ってほしい場合、MCP Server として公開すれば全クライアントが同じインタフェースで使える。
MCP = AI ではない: 「MCP Server をホストすれば適当に質問を投げるだけで AI が良い感じに答えてくれる」と誤解しがちだが、MCP は単なる「ツール公開プロトコル」。LLM は別途自分で持ち込み、Function Calling Loop も自分で回す必要がある。
MCP Server は LLM を内蔵していない: ツール (関数) の集合体に過ぎない。call_tool("get_xxx", {...}) を呼ぶと指定ツールを実行して結果を返すだけ。質問解釈・引数生成・結果の自然言語化は全て LLM 側 (= MCP Client 側) の責任。
Function Calling と MCP は競合しない: MCP Client は内部で Function Calling を使う。「MCP を採用したら Function Calling は不要」ではなく、「MCP は Function Calling の上にツール公開のレイヤーを足したもの」。
Agno を使うと MCP / Function Calling を意識しなくて良い、わけではない: 抽象化されているだけで、内部では同じことをしている。デバッグ時には結局生のレイヤーを理解する必要が出てくる。
「公式 MCP Server があるから採用」は早計: 多くの OSS MCP Server は Claude Desktop 想定で stdio のみ。Web サービスで使うなら HTTP 化や同時接続制御が追加で必要。
原因: ツールの description が曖昧 / parameters の説明不足。
対策: 「いつ呼ぶべきか」「引数の意味」を具体的に書く。プロンプトに「○○の質問が来たらこのツールを使え」と明示するのも有効。
原因: 外部 API が大量データを返してくる。
対策: ツール結果を LLM に渡す前にアプリ側で要約・抜粋する。MAX_TOOL_RESULT_CHARS のような上限を設けて切り詰める。
原因: MCP の ClientSession は単一接続のステートフル通信。複数リクエストが同じセッションを共有すると競合する。
対策: asyncio.Lock で call_tool をシリアライズする。スループットが必要なら接続プールを実装する。
Function Calling: LLM API の生の機能。アプリは tool_calls を受け取って自前で実行する。
MCP: ツール側を標準化するプロトコル。LLM 機能は持たない。「外部にツール群を公開する」「他人が作ったツール群を取り込む」が主目的。
Agno: Function Calling Loop や状態管理をラップする高レベルフレームワーク。プロトタイピング向き。
三者は 競合ではなくレイヤーが違う。Agno が MCP を内包し、MCP が Function Calling を内包する関係。
既存サービスへの「特定 API 連携の追加」程度なら Function Calling で十分。「公式 MCP Server を再利用したい」「自分でツール群を公開したい」場合に MCP を選ぶのが基本方針。
フレームワーク採用時は「内部が見えなくなることのコスト」を意識すること。