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本地 subprocessstdin/stdout JSON-RPCmcp_server_fetchmcp_server_filesystem
HTTP / SSE遠端雲端服務HTTPS streamable connectionNotion、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,當作判斷依據的實作範例:

1
2
3
4
5
6
7
8
mcp_tool_names = {"fetch"}                    # 啟動時建立

if tool_name in mcp_tool_names:
    # 走 MCP(async call_tool 經 stdio)
    result = await mcp_session.call_tool(tool_name, args)
else:
    # 走 native(直接呼叫 Python 函數)
    result = native_tools[tool_name](**args)

這個 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 升級成多輪對話。


參考資源#