【学習メモ】MCP / Function Calling / Agno の違いと使い分け — 既存 Web サービスに外部ツール連携を入れるときの選択肢整理

この記事でわかること
- MCP / Function Calling / Agno は競合ではなく、レイヤーが違うという整理
- MCP は AI ではなく、LLM もツール選択も自分で持ち込む必要があるということ
- ユースケース別の選択指針(単一 API 連携なら Function Calling だけで十分)
- 公式 MCP Server の多くが stdio のみで、Web サービスからはそのまま使えないこと
Function Calling API の基本的な理解と、Python / asyncio の基礎知識。
概要: 既存のWebサービス(FastAPI + Azure OpenAI)に「外部APIを叩いて回答するLLM機能」を追加する際、MCP・Function Calling・Agno のうちどれを採用すべきか悩んだ。三者の関係を整理し、ユースケース別の選択指針を実例ベースでまとめる。
はじめに
社内の生成AIアプリケーションに「外部の公開APIを叩いて、その結果をもとに回答する」機能を追加することになった。要件を読んだ瞬間に思い浮かんだ選択肢は次の3つだった。
- Function Calling をそのまま使う
- MCP (Model Context Protocol) を使う
- Agno などの Agent フレームワークを使う
名前は聞いたことがあっても「結局これって何が違うの?」「重複してない?」と感じる人は多いと思う。実際に検討してみた結果、三者は競合関係ではなく、レイヤーが違うことが分かった。本記事ではその関係性と、用途別の使い分けを整理する。
三者の役割を一言でまとめると
結論から書くと、それぞれの担当レイヤーはこうなる。
| 名前 | レイヤー | ざっくり何をするか |
|---|---|---|
| Function Calling | LLM API の機能 | LLM が「このツールをこの引数で呼びたい」と JSON で返してくれる |
| MCP | プロトコル | 「LLM ↔ 外部ツール」のやり取りを標準化する仕様 |
| Agno | フレームワーク | LLM 呼び出し・ツール選択・状態管理などを束ねた高レベル抽象 |
図にするとこういう包含関係になる。
┌──────────────────────────────────────────────────┐
│ Agno / LangChain (Agent フレームワーク) │
│ ┌────────────────────────────────────────────┐ │
│ │ MCP (プロトコル仕様) │ │
│ │ ┌──────────────────────────────────────┐ │ │
│ │ │ Function Calling (LLM API の機能) │ │ │
│ │ └──────────────────────────────────────┘ │ │
│ └────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘外側になるほど抽象度が高く、内側になるほど低レイヤーになる。
それぞれを掘り下げる
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=[...] を組み立てる。
MCP — ツール側を標準化するプロトコル
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 — ツール選択と状態管理を抽象化するフレームワーク
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: 内部のプロンプト・状態管理がブラックボックス化する。既存サービスのストリーミング実装やエラーハンドリングと噛み合わないことが多い。
ユースケース別の選択指針
ケース1: 既存の Web サービスに、特定の外部 API 連携を1つ追加したい
→ Function Calling のみで十分。MCP も Agno も不要。
外部 API のクライアントを Python で書き、スキーマと一緒に tools=[...] に渡す。「動的にツールが増減する」「他のアプリでも同じツールを使い回す」予定が無いなら、MCP のレイヤーを足すメリットは薄い。
ケース2: 公式 MCP Server を既存 Web サービスから使いたい
→ 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)"]
この構成の詳細は 別記事 で解説した。
ケース3: 複数の独自ツールを管理し、新規 LLM アプリをゼロから作る
→ Agno (or LangChain / LlamaIndex) を検討する価値あり。
Agent ループ・状態管理・メモリなどを自前で書くと結構な量になる。プロトタイピングや MVP 段階ならフレームワークの恩恵が大きい。ただし本番運用に耐えうるかは別問題で、内部のプロンプトを覗き込めないことが負債になりやすい。
ケース4: 自分で MCP Server を作って公開したい
→ 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 化や同時接続制御が追加で必要。
トラブルシューティング
LLM がツールを呼んでくれない
- 原因: ツールの
descriptionが曖昧 /parametersの説明不足。 - 対策: 「いつ呼ぶべきか」「引数の意味」を具体的に書く。プロンプトに「○○の質問が来たらこのツールを使え」と明示するのも有効。
ツール結果が長すぎて context overflow
- 原因: 外部 API が大量データを返してくる。
- 対策: ツール結果を LLM に渡す前にアプリ側で要約・抜粋する。
MAX_TOOL_RESULT_CHARSのような上限を設けて切り詰める。
MCP Server で同時実行が壊れる
- 原因: MCP の
ClientSessionは単一接続のステートフル通信。複数リクエストが同じセッションを共有すると競合する。 - 対策:
asyncio.Lockでcall_toolをシリアライズする。スループットが必要なら接続プールを実装する。
参考リンク
- Model Context Protocol 公式ドキュメント
- OpenAI Function Calling Guide
- Agno (旧 PhiData) GitHub
- mcp-proxy — stdio↔HTTP 変換
- Claude Desktop 想定の公式 MCP Server を Web サービスから使う(別記事)
まとめ
- Function Calling: LLM API の生の機能。アプリは
tool_callsを受け取って自前で実行する。 - MCP: ツール側を標準化するプロトコル。LLM 機能は持たない。「外部にツール群を公開する」「他人が作ったツール群を取り込む」が主目的。
- Agno: Function Calling Loop や状態管理をラップする高レベルフレームワーク。プロトタイピング向き。
- 三者は 競合ではなくレイヤーが違う。Agno が MCP を内包し、MCP が Function Calling を内包する関係。
- 既存サービスへの「特定 API 連携の追加」程度なら Function Calling で十分。「公式 MCP Server を再利用したい」「自分でツール群を公開したい」場合に MCP を選ぶのが基本方針。
- フレームワーク採用時は「内部が見えなくなることのコスト」を意識すること。
更新履歴
- 包含関係の図のコードブロックの言語指定を javascript から text に修正。「よくある誤解」の下に不自然に入っていた「Tips」見出しを削除。参考リンクの表記を揃えた。「この記事でわかること」「対象読者」「前提条件」を概要プロパティへ移動。
- 記事内リンクの修正


