MCP 工具整合#
CH02 教的 run_shell / read_file / write_file 是 原生工具 — 工具定義跟實作都自己寫,每加一個新能力都要重來一遍。 這章介紹 MCP:透過標準協議把外部社群寫好的工具接進同一個 dispatch flow、跟原生工具並存,解決工具擴充問題。
3.1 為什麼需要 MCP#
痛點:自己寫 tool 不可持續#
原生工具寫一個是一個。如果你還想:
- 連 GitHub API
- 操作 Slack
- 查資料庫
- 跑一個爬蟲框架
每一樣都要從零實作、維護、測試。同樣的工具,每個 agent 都得自己重寫一份;你寫的工具別人也用不到。
MCP 是「工具的 USB 規範」#
MCP(Model Context Protocol) 是 Anthropic 推的工具標準。把它想成「USB 給 LLM」:
- 任何人寫一個 MCP server,就獲得一份「即插即用」的工具集
- 任何 MCP-aware client(Claude Desktop、Claude Code、你的 agent)都能直接接用
- 工具定義、執行、回傳格式都標準化
社群已經寫好的 MCP server 涵蓋:fetch、filesystem、github、slack、postgres、puppeteer、各家雲端 API⋯⋯ 你要新能力,先看社群有沒有 MCP server,有就直接接,沒有再自己寫(也是寫成 MCP server,順便給別人用)。
MCP server 跑在哪裡:本地 vs 遠端#
MCP 規範同時支援兩種傳輸(transport),決定 server 跑在哪台機器上:
| 傳輸 | server 在哪 | 通訊方式 | 典型例子 |
|---|---|---|---|
| stdio | 本地 subprocess | stdin/stdout JSON-RPC | mcp_server_fetch、mcp_server_filesystem |
| HTTP / SSE | 遠端雲端服務 | HTTPS streamable connection | Notion、Linear、Cloudflare、各家 SaaS 自家的 MCP server |
對 agent 來說兩種沒差 — 只是 transport 不一樣,工具發現、tool 呼叫、回傳格式完全相同。我們的 minimal-agent 走 stdio 接本地 mcp_server_fetch,下面 3.2 架構圖畫的就是這種;要換成 remote MCP server 的話,agent code 只需改 transport 那一段。
原生 vs MCP 怎麼選#
| 維度 | 原生工具 | MCP 工具 |
|---|---|---|
| 位置 | in-process(同個 Python 進程) | 獨立 subprocess |
| 通訊 | 直接函數呼叫 | stdio JSON-RPC |
| 延遲 | 微秒 | 毫秒 |
| 擴充 | 改 code 重啟 | 換 server,agent 不用動 |
| 共用 | 綁這個 agent | 多個 client 共用 |
| 生態 | 自己維護 | 用社群既有 server |
| 適合 | 高頻、私有邏輯 | 第三方整合、跨 client 共用 |
結論:核心、私有、高頻 → native;通用、第三方、可分享 → MCP。我們的 minimal agent 兩種並存。
3.2 MCP 在 Agent 中的角色#
整合 MCP 工具後的 workflow#
%%{init: {'sequence': {'noteAlign': 'left'}}}%%
sequenceDiagram
participant MCP as MCP Server (subprocess)
box AI Agent
participant Executor
end
participant Model
Executor->>MCP: ① 啟動 server (subprocess)
Executor->>MCP: ② handshake
MCP-->>Executor: server 資訊
Executor->>MCP: ③ list_tools (你有哪些工具?)
MCP-->>Executor: ④ [fetch(url, max_length, ...)]
Note over Executor: ⑤ 得到 mcp tools<br/>⑥ 跟 native tools 合併成 all_tools
loop Agent loop(直到 Model 回 end_turn)
Executor->>Model: ⑦ messages list + all_tools
Model-->>Executor: ⑧ tool_use(name, args)
alt name 在 mcp_tool_names → 走 MCP
Executor->>MCP: ⑨ call_tool(name, args)
MCP-->>Executor: ⑩ tool_result
else 走 native
Note over Executor: ⑨ 呼叫 native tools<br/>⑩ 拿到 tool_result
end
Note over Executor: ⑪ tool_result 加到到 messages list,回到 ⑦
end三個重點:
- MCP 工具是動態發現的(①–④) — Executor 啟動時跟 MCP server 用
list_tools問一次「你有哪些工具?」,拿到 MCP 工具清單。 你不需要事先寫死 server 提供什麼,換一個 MCP server,agent 自動拿到不同能力,一行 code 都不用改。 - 跟原生工具合併成同一份工具清單(⑤–⑥) — 拿到 MCP 工具之後跟原生工具合併成
all_tools餵給 Model。 對 Model 完全透明 — 它看到的就是一個統一的工具清單,並不知道誰是原生工具誰是 MCP 工具。 - Dispatch 路由分流(⑨) — Executor 看 tool_use 的
name是否在mcp_tool_names裡決定走 MCP 還是原生,下面「Dispatch 路由模式」會展開實作。
Dispatch 路由模式#
Model 回傳 tool_use 時,executor 需要判斷這個工具是 MCP 工具還是原生工具,來決定要透過 MCP server 執行,還是自己直接呼叫 Python 函數執行。
下面是把 MCP 工具的 name 全部放進一個 set,當作判斷依據的實作範例:
| |
這個 if/else 就是 workflow 圖裡 ⑨ 那個分流節點 — Model 給的 tool_name 進來,executor 看一眼 set,自動走對路徑。
3.3 試一下#
$ python minimal_agent.py
[init] native tools: ['run_shell', 'read_file', 'write_file']
[init] mcp tools: ['fetch']
you> 抓 example.com 的內容然後總結
[mcp] fetch({'url': 'https://example.com'})
claude> 這個網頁是 IANA 預留的範例網域 ...
you> 列出當前資料夾
[native] run_shell({'command': 'ls'})
claude> 目錄下有 minimal_agent.py、README.md ...兩類工具混用,Model 不需要知道哪個是哪個 — 它只看 description 決定該叫誰。
階段檢查點#
到這裡你應該理解:
- MCP 解決什麼 — 工具生態的可重用性、跨 client 共用
- 原生 vs MCP 的選擇邏輯 — 高頻私有 vs 通用整合
- 動態工具發現 — 啟動時 handshake 拿清單,不需事先寫死
- Dispatch 路由 — 用一個 set 區分兩類工具,對 Model 透明
但不管原生工具還是 MCP,到目前為止 agent 都還是 one-shot 的 — 一次對話跑完就結束。下一章 CH04 用 REPL 升級成多輪對話。