Function Calling / Tool Calling / MCP JSON-RPC — 4往復 / スキーマ方言 / 可観測性と隔離
Cursor、Claude Code、あるいは任意の Agent フレームワークのデバッグログを開くと、ほぼすべてのペイロードが JSON です。ツールカタログは JSON Schema、モデルの tool_calls は JSON、MCP Server のフレームは JSON-RPC 2.0。これは偶然ではありません — Agent は予測不能な自然言語と実行可能なプログラムインターフェースを橋渡ししなければならず、JSON は双方が確実にパースできる共通の交換フォーマットだからです。
本記事では Function Calling → Tool Calling → MCP をたどり、ツール呼び出し1回分の4往復 JSON をマッピングします。クラウド Mac 上の MCP Server をデプロイ中の方や、GitHub Agent まとめ からフレームワークを選んでいる方にとって、このフローを理解することは API ドキュメントの暗記よりデバッグに効きます。
Agent はほぼどこでも JSON を話す
LLM はトークン列を出力しますが、Shell、データベース、HTTP API は構造化された引数を必要とします。初期のやり方は「この JSON 形式で返して」とプロンプトに書くことでした — モデルは末尾カンマを付けたり、引用符を落としたり、Markdown フェンスで囲んだりして、パーサーが頻繁に失敗していました。
2023年以降、主要 API は構造化アクションをプロトコルに組み込みました:モデルはもはや任意の JSON テキストを自由に出力しない。サーバーが出力形状を制約し、Host が実行します。パイプラインは次のようになります:
- 宣言 — JSON Schema でツールを定義;
- 決定 — 制約された文法の中でツール名とパラメータを選ぶ;
- 実行 — ローカルコードまたは MCP Server 経由で呼び出す;
- 書き戻し — 結果を JSON メッセージとしてコンテキストに戻す。
設計原則:Agent の可観測性 = すべての JSON ステップをログ・再生・差分比較できること。構造化された中間表現がなければ、自動化は監査不可能です。
Function Calling:自然言語から構造化アクションへ
OpenAPI 時代の function オブジェクト
OpenAI は Chat Completions に functions / tools を追加しました。リクエストにツール定義を添付すると、モデルは正規表現で抜き出すテキストの代わりに function_call または tool_calls を返します。
典型的なリクエスト断片
{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "東京の天気は?"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "都市別の現在の天気",
"parameters": {
"type": "object",
"properties": { "city": { "type": "string" } },
"required": ["city"]
}
}
}]
}プロンプト内の手書き JSON テンプレートはプロトコル制約に置き換えられ、Host は arguments をスキーマで検証してから実関数を呼び出します。Function Calling が固定するのはモデルの発話 — ツールがどこで動くか、どう発見されるか、認証がどう働くかではありません。Tool Calling エコシステムと MCP がそれを担います。
Tool Calling:1ターンあたり4往復の JSON
- 登録 — Host が JSON Schema 付きの
tools[]をモデル API に送信; - 決定 — API が
id、name、arguments(JSON 文字列)を含むtool_callsを返す; - 実行 — Host が引数をパースし、ローカルコードまたは MCP を呼び出す;
- 書き戻し —
role: toolメッセージで結果を返し、モデルがユーザー向け回答を生成。
{
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Tokyo\"}"
}
}]
}arguments はネストしたオブジェクトではなく文字列です — ストリーミングと SDK 互換性のため。Host は実行前に JSON.parse(arguments) が必要です。DevTools で Ctrl + Shift + I を押すと、Network タブに同じ構造が見えます。
| 段階 | 方向 | ペイロード | パーサー |
|---|---|---|---|
| ① 登録 | Host → API | tools[] + JSON Schema | API ゲートウェイ |
| ② 決定 | API → Host | tool_calls | Host / SDK |
| ③ 実行 | Host → Tool | パース済みオブジェクト | ツール実装 |
| ④ 書き戻し | Host → API | {role:"tool", content:"..."} | モデル |
API スキーマの方言
- OpenAI / 互換エンドポイント
tools+tool_choice;ストリーミングはdelta.tool_callsチャンク。- Anthropic
input_schema付きのtools;応答はtool_useブロックとtool_result返信。- Google Gemini
functionDeclarationsとfunctionCallでフィールド名が異なる。- Ollama ローカル
- OpenAI 互換の
tools;能力はモデルの学習に依存。
LangGraph や DeepSeek Harness(Harness ガイド参照)のようなフレームワークは、これらの方言を1つの Tool 抽象に圧縮します — 根底は依然として JSON です。
MCP:JSON-RPC がツール層を標準化する
Model Context Protocol (MCP) は Function Calling の下位層に位置します — ツールプロセスの発見、パラメータ化、Host 間での再利用の方法です。転送は stdio または SSE ですが、メッセージは常に JSON-RPC 2.0 です。
| 層 | プロトコル | 例 |
|---|---|---|
| モデル API | REST / OpenAI 互換 | chat/completions + tools |
| MCP | JSON-RPC 2.0 | tools/list, tools/call |
| ビジネス | 任意 | ファイル、DB、HTTP |
Host は起動時に tools/list を呼び、カタログを API の tools[] に変換し、モデルが決定した後に tools/call を送ります — 2回の方言変換、1本の意味的パイプラインです。
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": { "path": "/tmp/demo.txt" }
}
}stdio モードでは JSON フレームはどう区切られる?
MCP stdio は改行区切り JSON ではなく、Content-Length ヘッダー + JSON 本体(LSP と同様)を使います。大きなペイロード(base64 リソースなど)も1フレームに収まります。SSE は JSON-RPC を HTTP イベントストリームに包み、マルチクライアント Server 向けです。
データフロー全体:ユーザー入力からツール実行まで
- ログは各
tools/listのバージョンとツール数を記録しているか? argumentsは実行前にスキーマ検証されているか?- MCP タイムアウトは読みやすい
toolメッセージにマッピングされているか? - ログ出力前にシークレットは除去されているか?
AI Coding Workflow の Verify 段階と同じ考え方です — パース可能な中間表現がなければ、Agent が正しいツールを実際に呼んだか自動検証できません。
なぜ Protobuf や XML ではないのか?
- モデル API は JSON ファースト;バイナリはシリアライズ層を増やす;
- デバッグ — エンジニアがログでパラメータを読める;
- スキーマエコシステム — JSON Schema と OpenAPI が事実上の標準;
- スクリプトとブラウザ — curl、n8n、フロントエンドデモは JSON を前提とする。
トレードオフはサイズと CPU。高 QPS の構成では内部キャッシュとログ圧縮を行い、大きな blob は JSON フィールド内で base64 化します — JSON エンベロープ自体は置き換えません。
JSON のコストとエンジニアリング上の対策
| 症状 | 原因 | 対策 |
|---|---|---|
| arguments パースエラー | モデルからの不正な JSON 文字列 | スキーマを厳格化、temperature を下げる、リトライ |
| 誤ったツール選択 | ツール説明が曖昧または多すぎる | ツールを統合、ネガティブ例を追加 |
| MCP ハング | 不完全な stdio フレームまたはデッドプロセス | launchd スーパーバイザー、ヘルスチェック |
| ログへのシークレット混入 | ツール結果に環境変数が含まれる | 結果をマスク、実行用アカウントを分離 |
本番のヒント:隔離されたクラウド Mac で MCP を動かす — ノートPCの Cursor は SSH 越しに JSON-RPC を送り、Shell は日常使いのマシンに置きません。
Agent が JSON に依存するのは、自然言語による意思決定とプログラムによる実行の間に、人間が読めて機械が検証できる、差し替え可能な契約が必要だからです。Function Calling はモデルが何を言うかを定義し、Tool Calling は Host が1ターンをどう回すかを定義し、MCP はツールプロセスをエコシステムにどう接続するかを定義します — JSON が3つを貫く糸です。
クラウド Mac で MCP の JSON 実行プレーンを動かす
M4 専用ノード、日単位レンタル、SSH 即利用可
シンガポール · 日本 · 韓国 · 香港 · 米国リージョン
tools/call を隔離されたクラウド Mac に着地させ、ローカル Host は JSON-RPC だけを送る。 ZekVPS クラウド Mac mini プランを見る