iT邦幫忙

2026 iThome 鐵人賽

DAY 16
0
Claude AI

從 LLM 到 Agent:用 Claude 拆解現代 AI 工程的每一層系列 第 16 篇

工具呼叫與 MCP:同一種呼叫請求,不同的執行端

  • 分享至 

  • xImage
  •  

上一篇收在一個問題:代理能讀、能記、能忘,但模型輸出的只有文字,它要怎麼真的做一件事?

答案是它不做。Claude 的官方文件把工具呼叫(tool use)定義成一份合約:你宣告有哪些操作、參數長什麼樣,Claude 決定什麼時候叫、叫哪一個、帶什麼參數;模型從來不自己執行任何東西,它送出一個結構化的請求,由程式去跑,結果再送回對話(2026-09-30 查)。這個請求在 API 回應裡是一個 tool_use 區塊,寫著要叫哪個工具、帶什麼參數,像一張交給別人去跑的工單;下面我就簡稱它「單」。

那工具從哪裡來?到目前為止都是你自己寫。這一層要講的是另一條路:MCP 讓別人寫好的工具,一行設定就接得上。 先講結論:對模型來說,呼叫 MCP 的工具跟呼叫你自己寫的工具,開出來的是同一種 tool_use 請求;不同的是誰來執行,以及工具由誰提供、怎麼接上。


30 秒實驗

同一個問題,問兩次 Claude Code,一次用內建工具、一次只准用 MCP 伺服器的工具,再把它開的單印出來看。

# 第一次:內建工具
claude -p "order.py 裡的 calcRefund,退款比例傳 1.5 會怎樣?" \
  --output-format stream-json --verbose

# 第二次:只接官方的檔案系統 MCP 伺服器,並且只允許它的工具
cat > mcp.json <<'EOF'
{"mcpServers": {"fs": {"command": "npx",
  "args": ["-y", "@modelcontextprotocol/server-filesystem", "<專案路徑>"]}}}
EOF
claude -p "只用 fs 這個 MCP 伺服器的工具(不要用 Read、Bash 或其他內建工具):讀 order.py,告訴我 calcRefund 的退款比例傳 1.5 會怎樣。" \
  --mcp-config mcp.json --strict-mcp-config --allowedTools "mcp__fs" \
  --output-format stream-json --verbose

兩次我各跑了一次(2026-09-29 與 09-30,Claude Code 2.1.284,claude-opus-5-5;伺服器是 2026.8.31 版)。兩次都先開單、讀完檔才回答,結論也一樣:會退 150% 的訂單金額,不會報錯。 差別在單子上(節錄,路徑我縮短了):

{ "type": "tool_use", "name": "Read",
  "input": { "file_path": "<repo>/order.py" } }

{ "type": "tool_use", "name": "mcp__fs__read_text_file",
  "input": { "path": "<repo>/order.py" } }

形狀一模一樣,只有名字不同。 第一張單由 Claude Code 自己執行,第二張轉給另一個行程裡的 MCP 伺服器去跑,結果都包成 tool_result 送回來。

還有兩件事是實際跑才看得到的:

  • 接上這一個伺服器,工具清單就多了 14 個 mcp__fs__ 開頭的工具。
  • 第二次它讀檔之前,先開了一張 ToolSearch 的單,把其中三個工具的定義載進來,才真的呼叫。這是 Claude Code 的預設行為,下面會講。

(各跑一次,是例子不是證據;完整紀錄我留在自己的實驗筆記裡。)


一張單子的三步

用 API 的時候,一次工具呼叫是這樣(官方文件,2026-09-30 查):

  1. 請求裡放一個 tools 陣列,每個工具有 name、description 和一份 JSON Schema 寫的 input_schema。
  2. Claude 要用工具時,回應的 stop_reason 是 "tool_use",內容裡有 tool_use 區塊,帶著工具名稱和參數。
  3. 你執行,把結果放進 tool_result(用 tool_use_id 對上那張單),連同前面的對話再送一次。

「請求、執行、回報、再請求」一直轉下去,就是代理迴圈(agentic loop),那留到第六層。這一層看的是單子上的工具從哪裡來。


第一步的 tools 陣列:一份用 JSON Schema 寫的合約

input_schema 用的是 JSON Schema,一個專門描述「一段 JSON 該長什麼樣」的標準:有哪些欄位、各是什麼型別、哪些必填。選它不是巧合。JSONSchemaBench(Geng et al., 2025)整理的現況是,業界用來強制結構化輸出的受限解碼(constrained decoding) 框架,已經統一用 JSON Schema 當格式,給定一份 schema,多數框架就能保證輸出符合它;他們收了一萬份真實世界的 schema 來測這件事。

Claude 在這裡有兩種用法。預設情況下,schema 跟描述一樣,是給模型讀的文字,它照著填,但偶爾會把 2 填成 "2",或漏掉必填欄位。在工具定義加上 strict: true,官方文件說 Claude 就會把取樣限制在符合 schema 的詞元上(grammar-constrained sampling),保證參數型別正確(strict tool use,2026-09-30 查)。同一份 schema,一種是說明書,一種是護欄。

那 Claude Code 自己送的是什麼?我用自己寫的本機 proxy 工具 token-inspectour 攔下實驗第二次的請求(2026-09-30),tools 裡有 39 個工具,每一個都只有 name、description、input_schema 三個欄位,跟 API 文件寫的一樣。內建的 Read 長這樣(描述與部分欄位的說明節錄):

{
  "name": "Read",
  "description": "Reads a file from the local filesystem. …",
  "input_schema": {
    "$schema": "https://json-schema.org/draft/2020-12/schema",
    "type": "object",
    "properties": {
      "file_path": { "type": "string",
                     "description": "The absolute path to the file to read" },
      "offset": { "type": "integer", "minimum": 0 },
      "limit":  { "type": "integer", "exclusiveMinimum": 0 },
      "pages":  { "type": "string" }
    },
    "required": ["file_path"],
    "additionalProperties": false
  }
}

MCP 那 14 個工具則是從伺服器轉手來的。MCP 規格規定伺服器用 tools/list 回報工具,每個工具帶一份 inputSchema,沒寫 $schema 時預設 2020-12 版(2026-09-30 查)。對照攔到的請求,Claude Code 做的轉換只有兩件:名字前面加上 mcp__fs__,欄位名稱從 inputSchema 改成 API 用的 input_schema;描述與 schema 內容原封不動,連伺服器用的是較舊的 draft-07 版都照送。

這份請求是經過 proxy 攔下來的。 Claude Code 的 MCP 文件寫明,自訂 ANTHROPIC_BASE_URL 時工具搜尋會關閉,所以這一次 14 個 MCP 工具的定義是整份送出的,跟前面沒經過代理、先開 ToolSearch 的那一次不同。


MCP 標準化的是「工具從哪裡來」

Anthropic 在 2024 年 11 月發表 MCP 時,點出的問題是:每接一個新的資料來源,就要寫一套自己的客製實作,系統越接越難擴充;MCP 要用單一協定取代這些零散的整合。規格的說法是:MCP 用 JSON-RPC 2.0 在三個角色之間傳訊息,主機(host,例如 Claude Code)、主機裡的用戶端(client),以及提供能力的伺服器(server);它參考的是讓各家編輯器共用程式語言支援的 Language Server Protocol(2026-09-30 查)。

放回剛才的實驗,兩種工具的差別是這樣:

面向 你自己定義的工具 MCP 伺服器的工具
誰寫定義與描述 你 伺服器的作者
誰執行 你的程式 伺服器那個行程
怎麼接上 寫進 tools 陣列 設定檔一筆,或 claude mcp add
Claude 看到的名字 你取的 mcp__<伺服器>__<工具>
什麼時候載入定義 每一輪都送 Claude Code 預設用到才載入

最後一列就是實驗裡那張 ToolSearch 的單。Claude Code 的 MCP 文件寫明,工具搜尋是預設開啟的:開場只載入工具名稱與伺服器說明,完整定義等 Claude 需要時才載入,所以多接幾個伺服器對窗口的影響很小(2026-09-30 查)。我自己平常那個工作階段,一開就有 90 個工具,其中 61 個來自 MCP 伺服器;如果每一輪都整份送,那就是第 11 篇那張重送帳單的另一種寫法。

除了工具,MCP 伺服器還能提供資源(resources,給使用者或模型用的資料)和提示詞(prompts,範本化的訊息與流程),規格裡這三樣並列。那條連線上到底傳了什麼,下一篇拆開來看。


描述換人寫了

開單這件事有研究脈絡。Toolformer(Schick et al., 2023)讓模型用自監督的方式,自己學會決定叫哪個 API、什麼時候叫、帶什麼參數;Gorilla(Patil et al., 2023)則指出下一個問題:連當時最強的模型都常產生不正確的參數,或幻覺出錯誤的 API 用法,讓它呼叫前先取回 API 文件,幻覺就明顯減少。

這兩篇正好解釋了官方定義工具那頁最強調的一句:把描述寫得極度詳細,是影響工具表現最重要的因素,每個工具至少三到四句,寫清楚做什麼、什麼時候該用與不該用、每個參數的意思、有什麼限制(2026-09-30 查)。Claude 看不到實作,描述就是它唯一的 API 文件。

接上 MCP 之後,這份文件換人寫了。你自己的工具,描述是你寫的;MCP 伺服器的工具,描述是伺服器作者寫的,Claude 照單全收。規格在安全原則裡寫得很直接:工具代表任意程式碼的執行,必須謹慎對待,而且使用者要明確同意、並保有控制權。Hou et al.(2025)把 MCP 伺服器整理成一套四個階段、16 項活動的生命週期,並列出四類攻擊者、16 種威脅情境。這一層最後一篇再回來算這筆帳。


什麼時候該用工具

官方的判準很實用:有副作用的動作(寄信、寫檔、改紀錄)、新鮮或外部的資料、要保證形狀的輸出、接進既有系統。我最喜歡的是這一句:如果你正在寫正規表示式,從模型的輸出裡撈出一個決定,那個決定本來就該是一次工具呼叫。 反過來,靠訓練資料就能回答的問題、沒有副作用的一問一答,就不需要工具。

同一頁還有兩條寫工具的建議,自己寫或挑 MCP 伺服器都用得上:相關的操作合併成少數幾個工具(create_pr、review_pr、merge_pr 合成一個帶 action 參數的),以及名稱加上服務前綴(github_list_prs)。MCP 的 mcp__<伺服器>__ 前綴,其實就是替你做了後者。


這麼做的代價

第一,工具目錄要付錢。 計價頁寫明,tools 裡的名稱、描述、schema,以及每一個 tool_use 與 tool_result 區塊,都算輸入詞元;只要帶了 tools,API 還會自動加一段啟用工具的系統提示,在 Claude Opus 5.5 上是 286 個詞元(2026-09-30 查)。延遲載入省下的是窗口,換來的是實驗裡多出來的那一趟 ToolSearch。

第二,每一次呼叫至少多一趟來回。 官方也說,輕量的任務裡,這趟來回可能比工作本身還久。

第三,你執行的是別人的程式碼、信的是別人的描述。 自己寫的工具,出錯的是你;接上 MCP 伺服器,你多了一個要信任的對象。


這一篇多了什麼,又多付了什麼

多了什麼能力:你的代理可以查資料、讀寫檔案、接進別人寫好的系統。你知道一次工具呼叫的形狀(tool_use → 執行 → tool_result),也知道 MCP 的工具在 Claude 眼裡跟你自己的工具是同一張單,只是名字多了 mcp__ 前綴、定義用到才載入。

多付了什麼代價:每一輪多送的工具目錄、每次呼叫多一趟來回,以及一個你沒寫過、卻要信任它的描述與程式碼的伺服器。


下一篇

Claude Code 跟一個 MCP 伺服器之間,到底傳了什麼?

實驗裡那個檔案系統伺服器是另一個獨立的行程。Claude Code 怎麼知道它有 14 個工具、怎麼把一張單轉給它、它又怎麼把結果送回來?下一篇把這條連線拆開,看 MCP 在底下說的是哪一種語言。


延伸閱讀


上一篇
記錯比不記更糟:錯的代理記憶會跟著你進每一個新對話
下一篇
MCP 在底下說什麼:攔下 Claude Code 跟伺服器之間的對話紀錄
系列文
從 LLM 到 Agent:用 Claude 拆解現代 AI 工程的每一層 共 17 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言