LLMの外部ツール連携はこう進化した——Function CallingからMCPへ

に公開

はじめに:LLMは「過去の知識」しか持たない

大規模言語モデル(LLM)は、インターネット規模のテキストを学習した「超絶テキスト生成器」です。しかし、その知識は学習時点までに限定されます。

たとえば、「今日の東京の天気は?」や「先週のフォロワー増加数は?」といった最新情報を尋ねられても、LLM単体では答えようがありません。

解決策はシンプル——
👉 外部ツールを呼べばいい

そして、その「LLM × 外部ツール」の連携方式は、ここ数年で大きく進化しています。

  1. 手書きプロンプトによる「口約束」連携
  2. OpenAI式 Function Calling による構造化連携
  3. 次世代プロトコル 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の仕組み

  1. ツール定義をJSON Schemaで宣言
    • 関数名、説明、引数の型・必須項目などを厳密に定義
  2. LLMが「関数呼び出し」を構造化して返す
    • 回答ではなく、tool_calls という形式で「どの関数を呼ぶべきか」を出力
  3. ホストが実行 → 結果を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で連携できます。

しかし、そのたびに:

  1. APIを公開(OpenAPI/Swagger形式)
  2. Difyに登録
  3. 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の間の通信方式を柔軟にサポート。

📡 トランスポート方式

  1. STDIN/STDOUT(デフォルト)

    • 同一マシン上のプロセス間通信
    • ローカルツールとの連携に最適
    • セキュアで、外部にポート開放不要
  2. 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は「知識」に秀でていますが、「今ここ」の情報を知りません。
そのギャップを埋めるのが、外部ツール連携です。

その進化は、こう描けます:

  1. プロンプト連携:口約束 → 脆弱
  2. Function Calling:構造化 → 安定だが重い
  3. MCP:抽象化・統一 → 柔軟でスケーラブル

特に MCP は、「ローカルの小さなスクリプト」も「ネットワーク上のAPI」も、同じインターフェースで扱える点が革命的。

あなたの.pyファイル1つが、MCP Serverの「殻」を被るだけで、
LLMの会話の中に自然に組み込まれる世界がやってきた。

これにより、LLMエージェントの開発は、より軽量・安全・拡張可能になります。


おまけ

Discussion