Function Calling / Tool Calling / MCP JSON-RPC — 四段往返 / schema 方言 / 可觀測與隔離
如果你打開 Cursor、Claude Code 或任意 Agent 框架的調試日誌,幾乎每一行有效載荷都是 JSON:工具清單是 JSON Schema,模型返回的 tool_calls 是 JSON,MCP Server 收發的幀是 JSON-RPC 2.0。這不是偶然——Agent 要在「不可預測的自然語言」和「可執行的程序介面」之間搭橋,而 JSON 恰好是兩邊都能穩定解析的中間磁帶格式。
本文按時間線串起 Function Calling → Tool Calling → MCP,把一次完整工具調用的四段 JSON 往返畫清楚。若你正在部署 雲端 Mac 上的 MCP Server,或從 GitHub Agent 開源選型裡挑框架,理解這條數據流比背 API 文檔更能幫你排障。
Agent 世界幾乎「萬物皆 JSON」
大模型原生輸出是 token 流,而 Shell、資料庫、HTTP API 需要的是結構化參數。早期做法是在 Prompt 裡寫「請按以下 JSON 格式回答」——模型經常多逗號、少引號、夾帶 Markdown 代碼塊,解析失敗率居高不下。
2023 年起,主流 API 把「結構化動作」收進協議層:模型不再自由生成任意 JSON 文本,而是在服務端約束輸出形態,由 Host 負責執行。於是整條鏈路變成:
- 聲明:用 JSON Schema 描述工具能做什麼;
- 決策:模型在受限 grammar 裡選出工具名與參數;
- 執行:Host 把參數交給本地進程或 MCP Server;
- 回寫:把執行結果再以 JSON 消息塞回上下文。
設計原則:Agent 的可觀測性 = 能否把每一步 JSON 落日誌、重放、對比。沒有結構化中間態,就沒有可審計的自動化。
Function Calling:從自然語言到結構化動作
OpenAPI 時代的 function 對象
OpenAI 在 Chat Completions API 裡引入 functions / tools 字段:你在請求裡附帶工具定義,模型在響應裡返回 function_call 或 tool_calls,而不是一段需要你自己 regex 的文本。
典型請求片段
{
"model": "gpt-4o",
"messages": [{"role": "user", "content": "查一下東京天氣"}],
"tools": [{
"type": "function",
"function": {
"name": "get_weather",
"description": "按城市返回當前天氣",
"parameters": {
"type": "object",
"properties": {
"city": { "type": "string", "description": "城市名" }
},
"required": ["city"]
}
}
}]
}
舊式 Prompt 裡手寫 JSON 模板的做法 已被協議內建約束取代;Host 側只需校驗 arguments 是否符合 schema,再調用真實函數。Function Calling 解決的是「模型說什麼」,還沒解決「工具跑在哪、怎麼發現、怎麼鑑權」——這留給 Tool Calling 生態與 MCP。
Tool Calling:一次對話裡的四段 JSON 往返
「Tool Calling」通常指 Host 與模型 API 之間的完整回合,可拆成四步:
- 註冊 — Host 把
tools[](含 JSON Schema)發給模型; - 決策 — 模型返回
tool_calls,每個 call 帶id、name、arguments(字符串化的 JSON); - 執行 — Host 解析 arguments,調用本地函數或遠程 MCP;
- 回寫 — 以
role: tool消息把結果 JSON 寫回,再請求模型生成面向用戶的自然語言。
模型返回的 tool_calls 長什麼樣
{
"tool_calls": [{
"id": "call_abc123",
"type": "function",
"function": {
"name": "get_weather",
"arguments": "{\"city\":\"Tokyo\"}"
}
}]
}
注意 arguments 是字符串而非嵌套對象——這是為了與 streaming 和部分 SDK 兼容。Host 必須 JSON.parse(arguments) 後再校驗;按 Ctrl + Shift + I 打開開發者工具時,你會在 Network 裡看到完全相同的結構。
四段往返對照表
| 階段 | 方向 | 載荷形態 | 誰解析 |
|---|---|---|---|
| ① 註冊 | Host → API | tools[] + JSON Schema | API 網關 |
| ② 決策 | API → Host | tool_calls | Host / SDK |
| ③ 執行 | Host → Tool | 解析後的對象 | 工具實現 |
| ④ 回寫 | Host → API | {role:"tool", content:"..."} | 模型 |
各家 API 的 schema「方言」
概念統一了,字段名並未完全統一。選型或寫適配層時要對照:
- OpenAI / 兼容端點
tools+tool_choice;function嵌在type: function裡;streaming 時delta.tool_calls分片到達。- Anthropic
tools使用input_schema;響應為tool_use塊,需用tool_result回傳。- Google Gemini
functionDeclarations;返回functionCall,部分字段命名與 OpenAI 不同。- 本地 Ollama
- 通過 OpenAI 兼容層暴露
tools,能力取決於具體模型是否訓練過 tool use。
框架如 LangGraph、DeepSeek Harness(見 本站 Harness 指南)的價值之一,就是把這些方言壓成一層統一的 Tool 抽象,底層仍是一坨 JSON。
MCP:JSON-RPC 把工具層標準化
Model Context Protocol(MCP) 解決的是 Function Calling 之下 的一層:工具進程如何被發現、如何傳參、如何跨 Host 復用。傳輸可以是 stdio 或 SSE,但消息體一律 JSON-RPC 2.0。
MCP 與 API Tool Calling 的分工
兩層協議棧
| 層級 | 協議 | 典型方法 |
|---|---|---|
| 模型 API 層 | REST / OpenAI 兼容 | chat/completions + tools |
| MCP 層 | JSON-RPC 2.0 | tools/list、tools/call |
| 業務層 | 任意 | 讀檔案、查庫、發 HTTP |
Host(如 Cursor)啟動時向 MCP Server 發 tools/list,拿到工具清單後翻譯成自家模型 API 的 tools[] 格式。用戶一句話觸發模型 tool call 後,Host 再發 tools/call 給 MCP Server——整條鏈上 JSON 出現了兩次「方言轉換」,但語義一致。
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": { "path": "/tmp/demo.txt" }
}
}
stdio 模式下 JSON 幀如何分隔?
MCP 在 stdio 傳輸中使用 Content-Length 頭 + JSON body(類似 LSP),而不是換行分隔的 NDJSON。這樣單條消息可很大(例如帶 base64 的資源),Host 按長度讀取完整幀後再 JSON.parse。SSE 模式則把 JSON-RPC 包在 HTTP 事件流裡,適合多客戶端共享同一 Server。
完整數據流:從用戶輸入到工具執行
把上圖展開成可操作的檢查清單:
- 日誌裡是否記錄了每次
tools/list的版本與工具數量? tool_calls的arguments是否在執行前經過 schema 校驗?- MCP
tools/call超時與錯誤是否映射成模型可讀的tool消息? - 敏感字段是否在寫日誌前脫敏?
這與 AI Coding Workflow 裡的 Verify 階段 同源:沒有可解析的中間態,就無法自動驗收 Agent 是否「真的調對了工具」。
為什麼不用 Protobuf 或 XML?
Protobuf、gRPC 在微服務裡更高效,但在 Agent 場景有幾個硬傷:
- 模型側:訓練與推理介面以 JSON 為主,強行換二進制要額外序列化層;
- 調試:工程師需要在日誌裡直接讀參數,JSON 人類可讀;
- Schema 生態:JSON Schema 與 OpenAPI 已是事實標準,MCP 工具描述直接復用;
- 瀏覽器與腳本:前端 Demo、n8n 節點、curl 測試都假設 JSON。
代價是體積與解析 CPU。高 QPS 場景會在 MCP Server 內側做緩存、壓縮日誌,或只對大 payload(圖片、檔案)用 base64 塞進 JSON 字段——而不是換掉整條協議的 JSON 外殼。
JSON 的代價與工程補救
常見失敗模式
排障優先級
| 現象 | 根因 | 補救 |
|---|---|---|
| arguments 解析失敗 | 模型輸出非法 JSON 字符串 | 收緊 schema、降低 temperature、加 retry |
| 調錯工具 | 工具描述含糊或過多 | 合併工具、寫清 negative example |
| MCP 無響應 | stdio 幀不完整或進程掛死 | launchd 守護、健康檢查 |
| 日誌洩露密鑰 | tool result 含環境變數 | 結果脫敏、獨立執行帳號 |
生產環境建議:把「執行面」放在隔離節點——例如在雲端 Mac 上跑 MCP Server(參見 stdio 到 SSE 的部署指南),本機 Cursor 只通過 SSH 隧道發 JSON-RPC,Shell 權限不落在日常筆記本上。
回到標題:AI Agent 離不開 JSON,不是因為工程師懶惰,而是因為自然語言決策與程序化執行之間必須有一層人人可讀、機器可校驗、可插拔的契約。Function Calling 定了「模型說什麼」,Tool Calling 定了「Host 怎麼跑一圈」,MCP 定了「工具進程怎麼掛進生態」——三層疊在一起,JSON 仍是那條貫穿始終的線。
用雲端 Mac 承載 MCP 的 JSON 執行面
M4 獨享節點,按天租用,SSH 開箱即用
新加坡 · 日本 · 韓國 · 香港 · 美國節點可選
把 tools/call 落在隔離的雲 Mac 上,本機 Host 只傳 JSON-RPC——實驗環境可隨時快照回滾。 查看 ZekVPS 雲端 Mac mini 套餐