LLM / 生成AI

LLM は道具を「頼む」だけ:tool calling と MCP の関係

「ローカルLLMを使うなら MCP を使うと便利」とよく聞きます。わたしはこれを、「MCP サーバーが DB からデータを読んで LLM に投げ、その答えを利用者に返す」ものだと思っていました。ところが、これは矢印の向きが一か所逆でした。

この記事では、LLM が道具を使う仕組み(tool calling)と、MCP がそのどこに入るのかを整理します。

連載の 2 回目です。1 回目は Ollama は LLM ではない? LLM・実行環境・ホストの役割を分ける です。

先に結論

  • LLM は MCP を話しません。 LLM ができるのは「この道具を、この引数で呼びたい」と出力することだけです(tool calling)
  • 実際に道具を呼ぶのはホスト(司令塔)です
  • MCP サーバーは LLM を呼びません。 頼まれた仕事(DB の検索など)をして、結果を返すだけです
  • MCP は、ホストと道具の間をつなぐ規格です

社内ナレッジを検索して答える流れ

社内ナレッジが PostgreSQL に入っている場合の流れです。登場するのは次の 5 つです。

登場人物例
利用者ブラウザ
ホスト(司令塔)Open WebUI など
LLMOllama + モデル
MCP サーバー社内ナレッジ検索(自作)
DBPostgreSQL
番号やりとり内容
1利用者 → ホスト質問「経費精算の締め日は?」
2ホスト → LLM質問 + 使える道具の一覧
3LLM → ホスト「search_knowledge を '経費精算 締め日' で呼んで」
4ホスト → MCP サーバー道具を実行して
5MCP サーバー → PostgreSQLSELECT
6PostgreSQL → MCP サーバー該当する行
7MCP サーバー → ホスト検索結果(テキスト)
8ホスト → LLM検索結果を渡す
9LLM → ホスト答えの文章「締め日は毎月 25 日です(規程第 3 条)」
10ホスト → 利用者答え

ポイントは、ほとんどのやりとりがホストから出て、ホストに戻ってくることです。LLM と MCP サーバーは直接やりとりしません。考える(推論する)のはいつも LLM で、LLM を呼ぶのはいつもホストです。

わたしの勘違い:MCP サーバーが LLM を呼ぶ構成

「サーバーが DB を読み、LLM に投げ、答えを返す」という構成も、作ること自体はできます。ただし、それは MCP ではなく「RAG の API(社内 Q&A のサーバー)」です。

RAG の APIMCP の構成
LLM を呼ぶのはサーバーホスト
サーバーが返すもの完成した答え材料(検索結果)
向いている使い方決まった Q&A 窓口LLM が状況を見て、いくつもの道具を組み合わせる

例外も 2 つあります。

  • ベクトル検索をする場合は、MCP サーバーの中で、質問文をベクトルに変える埋め込みモデル(文章を作る LLM とは別の小さいモデル)を呼ぶことがあります
  • MCP には sampling という、MCP サーバーが「ホストの LLM を少し使わせて」と頼む仕組みもあります。これも、LLM を持っているのはホストのほうです

tool calling は LLM の機能

意外だったのが、tool calling(function calling)は LLM の側の機能で、MCP より前からあるということです。

LLM がしていることは、根本的には「続きの文字を予測して出力する」ことだけです。tool calling は、それを利用した約束事です。

  1. プロンプトの中に「使える道具はこれです」という一覧を、決まった書式で入れる
  2. モデルは「道具を使いたいときは、決まった書式(特別な記号で囲んだ JSON など)を出力する」ように追加で学習されている
  3. 間にいるソフト(Ollama、vLLM など)がその出力を見つけて、「道具の呼び出し」というデータに変換してホストに渡す

そのため、すべての LLM が tool calling をできるわけではありません。この学習をしていないモデルに道具の一覧を渡しても、うまく使えません。また、書式はモデルごとにバラバラです。

それでも共通に見えるのは OpenAI 互換 API のおかげ

正式な規格はありませんが、事実上の標準があります。OpenAI 互換 API です。OpenAI の API の形(tools で道具を渡し、tool_calls で呼び出しが返ってくる形)を、Ollama、vLLM、LM Studio などがそろってまねしています。モデルごとの書式の違いは、これらのソフトが中で吸収しています。

区間使われる形
ホスト ⇔ LLMOpenAI 互換 API(事実上の標準。正式な規格ではない)
ホスト ⇔ 道具MCP(公開された正式な仕様)

MCP が標準化したのは、ホストと道具の間だけです。

ローカル LLM で確かめること
  • モデルが tool calling に対応しているか(Ollama のモデル一覧では「tools」という印が付いている)
  • OpenAI 互換 API で公開できるか(Ollama や vLLM なら標準でできる)
  • 実際に試して確かめる(同じ「対応」でも、モデルの大きさで精度がかなり違う)

道具の一覧は、LLM に毎回渡している

「LLM には、どんな MCP があるのか事前に登録しておくの?」とも思いましたが、違いました。登録先はホストです。

  1. ホストに MCP サーバーを登録する(人が設定ファイルや管理画面で設定する)
  2. ホストが MCP サーバーに「何ができるの?」と聞く(MCP の tools/list)
  3. ホストは LLM を呼ぶたびに、質問と一緒に道具の一覧を渡す

MCP サーバーが tools/list で返すのは、次のような情報です。

{
  "name": "search_knowledge",
  "description": "社内ナレッジを全文検索する。規程・手順書・FAQ が対象。",
  "inputSchema": {
    "type": "object",
    "properties": { "query": { "type": "string" } }
  }
}

LLM はこの説明文を読んで、「この質問なら search_knowledge を使えばいい」と判断します。LLM は何も覚えていないので、道具の一覧は毎回渡します。

ここから、実務上の注意がいくつか出てきます。

  • 道具の説明文がとても大事。 LLM が道具を選ぶ手がかりは description だけ
  • 道具を増やしすぎない。 一覧は毎回プロンプトに入るので、文脈の枠を食う。小さいモデルは、道具が多いと選び間違えやすい
  • 途中で道具を変えることもできる。 MCP サーバーが「道具が変わった」と通知(list_changed)すれば、ホストは一覧を取り直す

「この道具」= MCP ではない

LLM が「この道具を呼びたい」と言うときの「道具」は、MCP そのものではありません。MCP サーバーの中にある 1 つ 1 つの道具(tool)のことです。

そして LLM は、その道具が MCP で提供されているかどうかを知りません。LLM が見ているのは、道具の名前・説明・引数だけです。ホストの中では、その道具を MCP サーバー経由で呼んでいるかもしれないし、ホストに組み込まれた関数で呼んでいるかもしれません。

Java で言うと、LLM が見ているのはインターフェース(メソッド名と引数)だけで、MCP はその実装をどう呼び出すか(RPC のやり方)にあたります。

Tomcat でたとえると

最初、わたしは「MCP サーバーが Tomcat 本体で、MCP が各アプリ(WAR)」だと思いましたが、当てはめる場所が一段ずれていました。

Java / Tomcat の世界MCP の世界
HTTP + Servlet 仕様(規格)MCP(規格・プロトコル)
WAR や Spring Boot の JAR(1 つのアプリ)MCP サーバー(1 つのアプリ)
アプリの中の各 Servlet / Controller のメソッド道具(tool)
Servlet API・Spring などの部品MCP SDK(Python の FastMCP など)
ブラウザ(いくつものサイトにつなぐ)ホスト(いくつもの MCP サーバーにつなぐ)

MCP サーバーは、ふつうは 1 つずつ単独で動きます。Spring Boot の実行可能 JAR を 1 つずつ起動するイメージに近いです。

いくつもの MCP サーバーを 1 つの入口にまとめて、認証やログをまとめて扱う MCP ゲートウェイというものもあります。こちらは Tomcat 本体(あるいはその前に立つ Apache)に近い役です。

実際にやりとりされる中身

3 → 4 → 7 → 8 番の中身を見ると、形が 2 種類あり、その間をホストが変換していることがわかります。

3. LLM → ホスト(OpenAI 互換 API の形)
"tool_calls": [{
  "id": "call_01",
  "function": {
    "name": "search_knowledge",
    "arguments": "{\"query\": \"経費精算 締め日\"}"
  }
}]
4. ホスト → MCP サーバー(MCP の形。JSON-RPC 2.0)
{ "jsonrpc": "2.0", "id": 7, "method": "tools/call",
  "params": { "name": "search_knowledge",
              "arguments": { "query": "経費精算 締め日" } } }
7. MCP サーバー → ホスト(MCP の形)
{ "jsonrpc": "2.0", "id": 7,
  "result": { "content": [
      { "type": "text", "text": "【経費精算規程 第3条】締め日は毎月25日とする。…" }
  ], "isError": false } }
8. ホスト → LLM(OpenAI 互換 API の形)
{ "role": "tool", "tool_call_id": "call_01",
  "content": "【経費精算規程 第3条】締め日は毎月25日とする。…" }

MCP サーバーは、LLM の形を知らなくてかまいません。

「LLM が期待する形」の正体
  • 呼び出しの引数には約束があります。MCP サーバーが公開した引数の定義(JSON Schema)に合わせて、LLM が引数を作ります。つまり、引数の形を決めているのは MCP サーバー自身です
  • 返す中身には決まった形がありません。最後はただの文章として LLM に読まれるだけです

ですから、MCP サーバーを作るときに気をつけるのは、形式よりも「LLM が読みやすい中身か」です。

  • 出典(文書名・条項・URL)を付ける → 答えに根拠を書いてもらえる
  • 該当する部分だけに絞る → 長すぎると文脈の枠を食い、肝心なところを読み落とす
  • 見つからなかったら「該当なし」とはっきり返す → LLM が勝手に話を作るのを防ぐ

新しい MCP の仕様には、返す中身の構造を定義する outputSchema という項目もありますが、使うかどうかは任意で、テキストで返すのが基本です。

MCP がなかったらどうなるか

MCP がなければ、PostgreSQL にアクセスするロジックをホストの中に実装することになります。MCP ができる前は、それがふつうでした。

作り方内容
ホストのコードに直接書く自作アプリで search_knowledge 関数を実装し、tool calling の道具として渡す
古典的な RAG にするtool calling を使わず、ホストが毎回必ず検索してから LLM に渡す
ホスト独自のプラグインを使うOpen WebUI の「Tools」、Dify の「カスタムツール」など

これらの欠点は、そのホスト専用のコードになることです。

MCP なしMCP あり
検索のロジックホストごとに書くMCP サーバーに 1 回書くだけ
ホストを替えるとき作り直し接続先を登録し直すだけ
同じ道具を別のアプリからも使うそれぞれに実装が要るそのままつながる
DB のパスワードホストが持つMCP サーバーだけが持つ

Java で言うと JDBC と同じ関係です。MCP は「LLM アプリと道具の間の JDBC」のようなものです。

まとめ

  • LLM は「この道具を呼んで」と出力するだけ(tool calling は LLM 側の機能)
  • 道具を実際に呼ぶのはホスト。MCP サーバーは頼まれた仕事をして結果を返すだけ
  • MCP はホストと道具の間の規格。ホストと LLM の間は OpenAI 互換 API が事実上の標準
  • 道具の一覧は、ホストが LLM に毎回渡している。説明文がとても大事

次の記事では、ここまで出てきた MCP と RAG の境目を整理します。

次の記事:RAG と MCP の境目はどこにあるのか

スポンサーリンク
タイトルとURLをコピーしました