iT邦幫忙

2026 iThome 鐵人賽

DAY 7
0

Day 7|MCP 到底解決什麼問題

https://ithelp.ithome.com.tw/upload/images/20260823/20183634VU6GB90e5O.png

我第一次接觸 MCP 的時候,覺得這東西是多餘的。

模型要呼叫工具,OpenAI 的 function calling 早就能做了,每家 API 也都有自己的格式。再定一套協定出來,看起來像是重複造輪子。

改變我想法的是一件小事:我有六個 agent,每個都需要「查詢任務板」這個能力。在沒有 MCP 之前,我為它們寫了六份幾乎一樣的工具描述,塞在六份不同的 prompt 裡。後來任務板加了一個欄位,我要改六個地方。改完之後有一個 agent 行為變得很奇怪,因為我在其中一份描述裡打錯字了。

MCP 解決的就是這件事。它把「工具怎麼描述、怎麼呼叫、怎麼回傳」從 prompt 裡抽出來,變成一個獨立的行程,誰要用就接上去。


三種給模型工具的方式

在講 MCP 怎麼寫之前,先確定你真的需要它。給模型「做事的能力」有三個層次,成本和能力遞增。

第一層:塞在 prompt 裡,人工執行。

你可以要求執行以下動作,用 [ACTION:名稱 參數] 的格式輸出:
[ACTION:read_file path=...]

然後你自己解析輸出、執行、把結果貼回去。這個做法很土,但在小規模場景下完全夠用,而且零依賴。我到現在還有幾個小腳本是這樣寫的。

缺點是解析很脆,模型偶爾會漏掉格式或多寫一句解釋,你的正則就爆了。

第二層:原生 function calling。

API 層級的工具呼叫,模型直接回傳結構化的呼叫請求。比第一層可靠很多,因為格式由模型端保證。

缺點是工具定義綁在你的程式裡。換一個 agent 要重寫一份,換一個模型供應商要換一套格式。

第三層:MCP。

工具住在一個獨立行程裡,透過標準協定溝通。任何支援 MCP 的客戶端都能接上,換模型不用改工具。

代價是多一個行程要管,多一層要除錯。

我的建議很直接:單一 agent、少於五個工具,用第二層就好。工具要被多個 agent 共用,或者你想換 CLI 而不重寫工具,才值得上 MCP。

https://ithelp.ithome.com.tw/upload/images/20260823/20183634HHSnNp4bQm.png

往上走一階,複用性變高,要管的東西也變多。不要因為 MCP 比較新就直接跳到第三階。

MCP 的心智模型

拆掉所有名詞,MCP 就是三件事:

客戶端(Claude Code)  ←─ JSON-RPC 2.0 over stdin/stdout ─→  你的 server
  1. 客戶端啟動你的 server(一個子行程)
  2. 客戶端問「你有哪些工具」,你回一份清單
  3. 客戶端說「執行這個工具,參數是這些」,你執行並回傳結果

就這樣。傳輸層是 stdin/stdout,資料格式是 JSON-RPC 2.0,一行一個訊息。

它不是 HTTP,不需要開 port,不需要處理認證(子行程繼承你的權限,這件事有安全意涵,Day 26 會講)。你可以用任何語言寫,只要能讀 stdin、寫 stdout。

三個必須實作的方法

協定裡有很多方法,但要能跑起來只需要三個。

initialize — 握手。客戶端問你支援什麼,你回報自己的能力和協定版本。

// 收到
{"jsonrpc":"2.0","id":1,"method":"initialize",
 "params":{"protocolVersion":"2024-11-05","capabilities":{}}}

// 回覆
{"jsonrpc":"2.0","id":1,"result":{
  "protocolVersion":"2024-11-05",
  "capabilities":{"tools":{}},
  "serverInfo":{"name":"my-server","version":"0.1.0"}}}

tools/list — 宣告你有哪些工具。這份清單會被送進模型的 context,所以描述要精準且簡短。每多一個字都是每次呼叫都要付的錢。

{"jsonrpc":"2.0","id":2,"result":{"tools":[{
  "name":"task_list",
  "description":"列出待辦任務。可用 status 過濾。",
  "inputSchema":{
    "type":"object",
    "properties":{"status":{"type":"string","enum":["todo","doing","done"]}},
    "required":[]
  }}]}}

tools/call — 執行。回傳內容是一個 content 陣列,最常用的是 text。

{"jsonrpc":"2.0","id":3,"result":{
  "content":[{"type":"text","text":"待辦 3 筆:\n1. ...\n2. ...\n3. ..."}],
  "isError":false}}

isError 這個欄位很重要,很多人會忽略。工具執行失敗的時候,你應該回 isError: true 加上錯誤訊息,而不是丟一個 JSON-RPC 層級的 error

差別在於:JSON-RPC error 代表「協定層面出事了」,客戶端可能直接中斷。isError: true 代表「工具執行失敗,這是一個可以讓模型看到並自己處理的結果」。模型看到錯誤訊息之後常常能自己修正參數重試,這正是你要的行為。

工具描述怎麼寫

這是最容易被輕忽、但影響最大的部分。工具描述是模型唯一的說明書。

我的三條規則:

一、寫「什麼時候用」,不只寫「做什麼」。

✗ description: "搜尋記憶"
✓ description: "在長期記憶中搜尋。當使用者提到之前討論過的事、或你需要
   確認過去的決定時使用。回傳最相關的 5 筆。"

二、參數的 enum 要窮舉。 給 enum 的參數,模型幾乎不會傳錯。給自由字串,它會發明各種變體。能列舉就列舉。

三、描述長度要跟使用頻率成正比。 一個每次對話都會用到的工具,值得寫 200 字說明。一個一個月用一次的工具,寫 20 字就好。我算過,工具描述的總長度直接反映在每一次 spawn 的固定成本上,而那個成本你昨天已經看到了。

一個常見的誤解

很多人以為掛了 MCP,模型就會「自動知道」什麼時候用。

不會。模型看到的只有你寫的那份 schema。如果你的工具叫 get_data,描述寫「取得資料」,那它永遠不會被呼叫,因為模型無從判斷什麼情況算是需要「資料」。

我踩過的實例:有一個工具叫 working_state_get,描述寫「取得工作狀態」。上線兩週,呼叫次數是零。改成「取得目前生效的操作規則與交接事項。在你要做任何決策之前先呼叫這個,特別是當你不確定現在的規則是什麼的時候。」之後,呼叫率立刻上來了。

工具沒被呼叫,八成問題出在說明書。你寫得像 API 文件,模型需要的是使用指南。

代價

MCP 帶來三個新的麻煩,先講清楚。

除錯變難。 你的 server 跑在子行程裡,stdout 被協定佔用了,所以你不能用 print 除錯。所有日誌必須寫 stderr 或寫檔案。我第一次寫的時候在裡面 print 了一行狀態,整個協定當場壞掉,客戶端解析不了那行非 JSON 的輸出。

啟動成本。 每次 spawn 都要啟動 server 行程。如果你的 server 要載入模型或連資料庫,這個啟動時間會加在每一次呼叫上。我的 server 冷啟動大約 200 毫秒,還可以接受;如果你的要三秒,就要考慮改用長駐的 HTTP 模式。

工具膨脹。 這是最真實的問題。MCP 讓加工具變得很容易,於是你會一直加。我的 server 現在有 200 個以上的工具,而昨天已經算過,工具 schema 是固定開銷裡最大的一塊。容易加的東西最後都會變成負擔,Day 10 會講怎麼處理這個。


明天動手寫。四十行內做出一個能跑的 MCP server,然後真的掛到 Claude Code 上叫它用。


上一篇
砍掉 88% 的固定開銷,但不能砍掉那個 hook
系列文
Claude Code 下班之後:30 天把 CLI 工具養成會自己交差的 AI 員工7
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言