LLMの外部ツール連携はこう進化した——Function CallingからMCPへ
はじめに:LLMは「過去の知識」しか持たない
大規模言語モデル(LLM)は、インターネット規模のテキストを学習した「超絶テキスト生成器」です。しかし、その知識は学習時点までに限定されます。
たとえば、「今日の東京の天気は?」や「先週のフォロワー増加数は?」といった最新情報を尋ねられても、LLM単体では答えようがありません。
解決策はシンプル——
👉 外部ツールを呼べばいい。
そして、その「LLM × 外部ツール」の連携方式は、ここ数年で大きく進化しています。
- 手書きプロンプトによる「口約束」連携
- OpenAI式 Function Calling による構造化連携
- 次世代プロトコル MCP(Model Context Protocol) による抽象化・統一化
この進化の軌跡を、開発者の視点でわかりやすく解説します。
なぜLLMにツールが必要なのか?
LLMは「何でも知っているフリ」はできますが、リアルワールドの状態は知りません。
- 「最新の株価は?」
- 「Slackの未読メッセージ数は?」
- 「自社DBの売上集計は?」
これらはすべて、外部システムに問い合わせなければ取得できません。
つまり、LLMが「現実世界」と対話するには、仲介するプログラム を介して、適切なツールを呼び出す仕組みが必要になるのです。
第1段階:プロンプトに書く「口約束」連携
初期のアプローチは非常にシンプルでした。
もし天気が知りたければ、次のように返事してね:
{"tool": "get_weather", "city": "Tokyo"}
このように、ツールの呼び出し方をプロンプトに直書きし、LLMに「この形式で教えてね」とお願いする。
しかし、これはあくまで「口約束」。
LLMは創造的すぎるがゆえに、
- フォーマットを守らない
- 自分で勝手にJSONを捏造する
- ツール名を間違える
といった問題が頻発。結果、仲介するプログラム側での解析失敗が日常茶飯事に。
規模が大きくなると、管理不能に陥ります。
第2段階:Function Callingで「約束」を規格化
この問題を解決したのが、OpenAIの Function Calling です。
Function Callingの仕組み
-
ツール定義をJSON Schemaで宣言
- 関数名、説明、引数の型・必須項目などを厳密に定義
-
LLMが「関数呼び出し」を構造化して返す
- 回答ではなく、
tool_calls
という形式で「どの関数を呼ぶべきか」を出力
- 回答ではなく、
- ホストが実行 → 結果をLLMに再投入 → 最終回答生成
{
"tool_calls": [
{
"id": "call_123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": {"city": "Tokyo"}
}
}
]
}
得られたメリット
- 解析失敗が激減
- LLMが「ツールを使うべきか」を自律判断
- ツールの存在を自然にLLMに伝えられる
しかし課題も明確に
- ツールが増えると、JSON Schemaのメンテが地獄
- 各ツールをHTTP APIとして公開する必要 → 認証・認可・監視が増える
- スキーマ変更ごとにLLMへの「再登録」が必要
- セキュリティ的に、ローカルスクリプトをAPI化するのは抵抗がある
特に、数十〜数百のツールを管理するアプリケーションでは、運用コストが爆発します。
実際の開発現場:DifyとFunction Callingのジレンマ
たとえば、Difyのようなエージェント開発プラットフォームでは、外部ツールをAPIとして登録し、Function Callingで連携できます。
しかし、そのたびに:
- APIを公開(OpenAPI/Swagger形式)
- Difyに登録
- JSON Schemaを変換・設定
という手順が必要。ツール数が増えるほど、登録作業が重い。
さらに、ローカルのPythonスクリプトや内部CLIツールを「わざわざAPI化」するのは、セキュリティ的にも運用的にも好ましくありません。
第3段階:MCP(Model Context Protocol)による抽象化
そこで登場したのが、MCP(Model Context Protocol)。
開発元はAnthropicですが、LLMエージェントの「ツール連携の共通言語」 として注目されています。
MCPの発想:「ツールに殻を被せる」
「ツールごとにAPIとスキーマを書くのはつらい」
→ じゃあ、ツールそのものを“サーバ”としてラップしちゃおう
この「殻」こそが MCP Server。
そして、そのサーバと通信するクライアントが MCP Client。
それを束ねるアプリが MCP Host(例:Claude Desktop, VS CodeのCline, 自作ホストなど)。
[LLM] ←→ [MCP Host] ←→ [MCP Client] ←→ [MCP Server] ←→ [実際のツール]
MCPの3大コンポーネント
コンポーネント | 役割 |
---|---|
MCP Server | ツールをラップし、MCPプロトコルで公開するプロセス |
MCP Client | Serverと通信し、ツール一覧取得・実行を行う |
MCP Host | LLMとMCP Clientを統合し、エージェント全体を制御 |
MCPの通信方式:ローカルもネットワークもカバー
MCPは、ServerとClientの間の通信方式を柔軟にサポート。
📡 トランスポート方式
-
STDIN/STDOUT(デフォルト)
- 同一マシン上のプロセス間通信
- ローカルツールとの連携に最適
- セキュアで、外部にポート開放不要
-
HTTP + SSE(Server-Sent Events)
- ネットワーク越しの通信に
- サーバーからクライアントへのストリーミング通知も可能
プロトコル:JSON-RPC 2.0
- メソッド名とパラメータで呼び出し
- 成功時は
result
、失敗時はerror
を返す - 要求(request)と通知(notification)の両方をサポート
これにより、双方向的なやり取りが可能に。
LLM と Host の連携:自由度が高い設計
MCPは「Server–Client間」のプロトコルを統一しますが、LLMとHostの間は自由。
実際には、主に2つのパターンがあります。
A) Function Callingを使う方式
- LLMが
tool_calls
を出力 - Hostがそれを解釈 → MCP Client経由でツール実行
- メリット:構造化されていて安定
- 前提:LLMベンダーがFunction Callingをサポート
B) 規約プロンプト方式(自由形式)
-
Hostが「こう書いてね」と長いシステムプロンプトで指示
ツール呼び出しは次の形式で書いてください: UseMCP2.0: {"server": "weather", "method": "get", "params": {"city": "Tokyo"}}
-
Hostが出力をパースしてMCP経由で実行
-
メリット:どんなLLMでも使える(OSSモデルもOK)
-
デメリット:プロンプトの保守が重い、解析失敗リスクあり
👉 用途に応じて「規格化」か「柔軟性」かを選択できます。
まとめ:ツール連携の「統一レイヤ」が来た
LLMは「知識」に秀でていますが、「今ここ」の情報を知りません。
そのギャップを埋めるのが、外部ツール連携です。
その進化は、こう描けます:
- プロンプト連携:口約束 → 脆弱
- Function Calling:構造化 → 安定だが重い
- MCP:抽象化・統一 → 柔軟でスケーラブル
特に MCP は、「ローカルの小さなスクリプト」も「ネットワーク上のAPI」も、同じインターフェースで扱える点が革命的。
あなたの
.py
ファイル1つが、MCP Serverの「殻」を被るだけで、
LLMの会話の中に自然に組み込まれる世界がやってきた。
これにより、LLMエージェントの開発は、より軽量・安全・拡張可能になります。
おまけ
- 公式:https://modelcontextprotocol.org/
- GitHub:https://github.com/modelcontextprotocol
- VS Code拡張:ClineでMCP対応ツールが使える
-
自作Server例:Pythonスクリプトを
FastMCP
でラップ可能
Discussion