「ローカル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 など |
| LLM | Ollama + モデル |
| MCP サーバー | 社内ナレッジ検索(自作) |
| DB | PostgreSQL |
| 番号 | やりとり | 内容 |
|---|---|---|
| 1 | 利用者 → ホスト | 質問「経費精算の締め日は?」 |
| 2 | ホスト → LLM | 質問 + 使える道具の一覧 |
| 3 | LLM → ホスト | 「search_knowledge を '経費精算 締め日' で呼んで」 |
| 4 | ホスト → MCP サーバー | 道具を実行して |
| 5 | MCP サーバー → PostgreSQL | SELECT |
| 6 | PostgreSQL → MCP サーバー | 該当する行 |
| 7 | MCP サーバー → ホスト | 検索結果(テキスト) |
| 8 | ホスト → LLM | 検索結果を渡す |
| 9 | LLM → ホスト | 答えの文章「締め日は毎月 25 日です(規程第 3 条)」 |
| 10 | ホスト → 利用者 | 答え |
ポイントは、ほとんどのやりとりがホストから出て、ホストに戻ってくることです。LLM と MCP サーバーは直接やりとりしません。考える(推論する)のはいつも LLM で、LLM を呼ぶのはいつもホストです。
わたしの勘違い:MCP サーバーが LLM を呼ぶ構成
「サーバーが DB を読み、LLM に投げ、答えを返す」という構成も、作ること自体はできます。ただし、それは MCP ではなく「RAG の API(社内 Q&A のサーバー)」です。
| RAG の API | MCP の構成 | |
|---|---|---|
| LLM を呼ぶのは | サーバー | ホスト |
| サーバーが返すもの | 完成した答え | 材料(検索結果) |
| 向いている使い方 | 決まった Q&A 窓口 | LLM が状況を見て、いくつもの道具を組み合わせる |
例外も 2 つあります。
- ベクトル検索をする場合は、MCP サーバーの中で、質問文をベクトルに変える埋め込みモデル(文章を作る LLM とは別の小さいモデル)を呼ぶことがあります
- MCP には sampling という、MCP サーバーが「ホストの LLM を少し使わせて」と頼む仕組みもあります。これも、LLM を持っているのはホストのほうです
tool calling は LLM の機能
意外だったのが、tool calling(function calling)は LLM の側の機能で、MCP より前からあるということです。
LLM がしていることは、根本的には「続きの文字を予測して出力する」ことだけです。tool calling は、それを利用した約束事です。
- プロンプトの中に「使える道具はこれです」という一覧を、決まった書式で入れる
- モデルは「道具を使いたいときは、決まった書式(特別な記号で囲んだ JSON など)を出力する」ように追加で学習されている
- 間にいるソフト(Ollama、vLLM など)がその出力を見つけて、「道具の呼び出し」というデータに変換してホストに渡す
そのため、すべての LLM が tool calling をできるわけではありません。この学習をしていないモデルに道具の一覧を渡しても、うまく使えません。また、書式はモデルごとにバラバラです。
それでも共通に見えるのは OpenAI 互換 API のおかげ
正式な規格はありませんが、事実上の標準があります。OpenAI 互換 API です。OpenAI の API の形(tools で道具を渡し、tool_calls で呼び出しが返ってくる形)を、Ollama、vLLM、LM Studio などがそろってまねしています。モデルごとの書式の違いは、これらのソフトが中で吸収しています。
| 区間 | 使われる形 |
|---|---|
| ホスト ⇔ LLM | OpenAI 互換 API(事実上の標準。正式な規格ではない) |
| ホスト ⇔ 道具 | MCP(公開された正式な仕様) |
MCP が標準化したのは、ホストと道具の間だけです。
ローカル LLM で確かめること
- モデルが tool calling に対応しているか(Ollama のモデル一覧では「tools」という印が付いている)
- OpenAI 互換 API で公開できるか(Ollama や vLLM なら標準でできる)
- 実際に試して確かめる(同じ「対応」でも、モデルの大きさで精度がかなり違う)
道具の一覧は、LLM に毎回渡している
「LLM には、どんな MCP があるのか事前に登録しておくの?」とも思いましたが、違いました。登録先はホストです。
- ホストに MCP サーバーを登録する(人が設定ファイルや管理画面で設定する)
- ホストが MCP サーバーに「何ができるの?」と聞く(MCP の
tools/list) - ホストは 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 の境目を整理します。