
先聊個最近都繞不開的場景。你手上有一個大模型應用希望它能像真正的助手一樣去查資料、讀文件、調接口而不再只是“對話框里聊天”。這時候你就需要做 AI Agent 開發而 Agent 一旦要干活第一個要解決的就是工具鏈怎么接。過去我接工具時不同服務有完全不同的 API、鑒權方式、參數格式每次接入都得寫一堆膠水代碼改一處壞十處。直到 MCP 出現這套流程才真正有了統一答案。MCPModel Context Protocol最初由 Anthropic 提出來現在基本成了 AI Agent 接入外部工具的主流協議之一。它把“模型與工具、數據源之間的通信方式”標準化工具方開發一個 MCP ServerAgent 側只需要按協議連接就能動態發現工具、調用函數。這篇文章不打算堆概念我直接用一次完整的開發過程來說透 MCP 協議在做的事從零寫一個支持多個工具的 MCP Server再把它接到客戶端跑通一個可以自動完成“搜索文件、抓取網頁、生成報告”的 AI Agent 工具鏈。適合剛接觸 MCP、準備自己做 Agent 工具的開發者參考。1. 還沒動手前先搞懂 MCP、Agent 和工具鏈的關系1.1 沒有 MCP 時給 Agent 接工具為什么這么痛苦在 MCP 出來之前讓大模型調用外部能力總是逃不出這幾件事先為每個服務單獨封裝 API把參數轉換成模型能理解的格式再處理鑒權、錯誤碼、限流、超時還得維護一套 prompt 去教模型“什么情況下調哪個函數”。這些代碼往往散落在各個模塊里接口風格也不統一。一個新工具上線聯調周期少說兩三天多的可能要一兩周。舉個具體的例子。如果你的 Agent 需要同時支持文檔搜索和網頁抓取你可能要自己設計兩套 function calling 協議一套接收 query 返回文檔列表另一套接收 URL 返回頁面正文。兩套協議的鑒權方式、錯誤格式、超時策略完全不同模型在調用時很容易“學錯”。MCP 的初衷就是把這些差異全部收口到一層標準協議里讓 Agent 不用關心每個工具內部是怎么實現的。1.2 MCP 的核心角色和四個原語MCP 的架構可以理解為三個角色加四個原語。三個角色是 Host、Client 和 Server。Host 是用戶實際使用的應用比如 Claude Desktop、IDE 插件或者你自己寫的 Agent 程序Client 跑在 Host 內部負責和 Server 建立連接、維護會話Server 是一個獨立進程或服務向外暴露某個領域的工具集比如文件系統、數據庫、設計稿導入等。四個原語是 Tools、Resources、Prompts以及后來補充的 Sampling但日常開發前三個最常用。Tools 由模型控制模型根據用戶需求決定調用哪個函數Resources 由應用控制是模型可以讀取的上下文數據類似“給模型提供背景資料”Prompts 由用戶控制是可以復用的提示模板。這張表能幫你快速區分原語控制方典型作用常見例子Tools模型執行動作、獲取結果搜索文件、調用 API、寫數據庫Resources應用提供可讀上下文讀取項目文檔、加載配置文件Prompts用戶復用固定模板生成周報、代碼評審模板初學者最容易把 Tools 和 Resources 搞混。我的理解是Tools 是“讓模型動手做事”Resources 是“讓模型有料可用”。如果一個接口只讀且固定適合設計成 Resource如果一個接口會觸發副作用或者結果高度依賴入參那就設計成 Tool。設計錯了容易出現模型亂調工具或上下文塞滿不需要的數據。另外值得一提的是Agent Skill 和 MCP 不是一回事Skill 更偏向 Agent 內部的高階能力定義MCP 則是工具接入的標準協議二者可以共存。1.3 Agent 工具鏈的完整形態一個真正能用的 AI Agent 工具鏈長成這樣底層是各種能力提供方文件系統、數據庫、HTTP 接口、設計工具中間層是 MCP Server把這些能力封裝成協議化的工具上層是 Agent 編排層負責理解用戶意圖、把任務拆成步驟、按步驟調用合適的工具并匯總結果。MCP 解決的是中間層到上層的連接問題。它有一套完整的發現機制Client 連上 Server 后先通過 list_tools 拿到所有工具的名稱、描述和參數 Schema再根據模型判斷調用哪個工具。這樣一來模型與中間層之間不再是一份寫死的函數列表而是可動態發現的工具清單。我后面寫的 Server 和客戶端腳本就是這套機制的完整落地。2. 準備工作選對 SDK把開發環境一次裝好2.1 兩種主流 SDK 怎么選官方維護了 TypeScript 和 Python 兩套 SDK另外還有 Java、Kotlin、C# 等社區版本。我選擇 Python 的原因很簡單FastMCP 高層封裝太好用了幾行代碼就能注冊一個工具而且文檔字符串可以直接變成工具描述對像我這樣需要邊寫邊驗證的人非常友好。如果你在 Node 生態里做開發那選 TypeScript 版更順手類型推導比 Python 嚴格配合 VSCode 體驗更好。技術棧之外還要看你準備把 Server 部署在哪里。本地工具鏈用 Python 的 stdio 模式最省事命令行直接把進程拉起來配置簡單也不需要考慮端口和鑒權但如果你要把 Server 發布成遠程服務給多個 Agent 共用那部署形態就要重新考慮了這時候 TypeScript 或 Go 構建出的單文件二進制部署和維護都會輕松很多。我個人的選擇是日常原型和內部工具用 Python正式對外服務再單獨評估語言和部署環境不會在一開始就鎖死方案。2.2 最小可用項目骨架先用一個干凈的目錄開始。我習慣用uv init初始化項目因為它不僅速度快還能把虛擬環境和依賴管理一起解決。沒有安裝 uv 的話用python -m venv也是可以的。uv init mcp-toolbox cd mcp-toolbox uv add mcp[cli] httpx如果你用 pip等價命令是pip install mcp[cli] httpx安裝完成后項目里只需要一個server.py入口。MCP SDK 自帶命令行工具所以本地調試時可以用python -m mcp.server或者自己寫一小段啟動代碼。FastMCP 的啟動方式更直接我們下面就會用到。這里有個細節很多人會踩坑stdio 模式啟動的 Server 會把標準輸出用作協議通道因此不能在里面寫print()調試日志。想打印日志必須寫到sys.stderr或者用 logging 庫。我第一次寫的時候在工具函數里放了幾個 print結果客戶端解析協議直接報錯排查了很久才發現是輸出污染。2.3 傳輸模式stdio、SSE 和 Streamable HTTP 怎么選MCP 支持多種傳輸模式目前最常用的是 stdio 和 Streamable HTTP。stdio 模式由客戶端拉起一個本地子進程通過標準輸入輸出和它通信適合跑在用戶本地的工具比如操作本地文件、執行命令行任務。它的優點是啟動快、配置簡單缺點是服務無法跨機器復用。SSE 是早期的 HTTP 方案服務端通過 Server-Sent Events 單向推送事件客戶端再通過普通 HTTP 回傳實現上有點別扭官方已經逐步用 Streamable HTTP 替代它。Streamable HTTP 是更現代的雙向模式支持跨機器部署多個 Agent 可以連接同一個 Server適合把工具鏈做成團隊內部公共服務。選擇建議其實很簡單。本地自用、和 Claude Desktop 這類桌面客戶端配合直接用 stdio 最省心啟動快、不占端口、也不需要考慮鑒權要做團隊共用的服務讓多個 Agent 連接同一個遠程能力就考慮 Streamable HTTP。一個常見的誤區是“上了 HTTP 就顯得更高級”但傳輸模式多一層網絡就要多處理一層安全問題本地能解決的事不必上 HTTP。后面我會給出完整的 stdio 示例這是實際開發中覆蓋最廣的場景。3. 核心實戰用 FastMCP 寫一個可用的 MCP Server3.1 第一個工具本地文件搜索現在開始寫真正的代碼。我設計的這個 Server 叫dev-toolbox第一個工具是search_files目標是模擬一個研發人員最常用的能力在指定目錄里根據關鍵詞搜索文件名。from pathlib import Path from mcp.server.fastmcp import FastMCP mcp FastMCP( dev-toolbox, version0.1.0, ) mcp.tool() def search_files(directory: str, keyword: str) - list[str]: 搜索指定目錄下文件名包含 keyword 的文件路徑。 Args: directory: 要搜索的目錄絕對路徑。 keyword: 文件名中包含的關鍵詞不區分大小寫。 root Path(directory) if not root.exists() or not root.is_dir(): raise ValueError(f目錄不存在或不是目錄: {directory}) hits: list[str] [] dirs_to_scan [root] while dirs_to_scan and len(hits) 50: current dirs_to_scan.pop() try: for child in current.iterdir(): if child.is_dir(): dirs_to_scan.append(child) elif child.is_file() and keyword.lower() in child.name.lower(): hits.append(str(child)) except PermissionError: continue return hits這個函數做了三件關鍵的事。第一是顯式校驗目錄參數避免把不存在的路徑交給模型后得到晦澀報錯第二是限制最多返回 50 個結果防止一次調用把上下文撐爆第三是捕獲 PermissionError避免因為某個無權限目錄導致整個任務失敗。這些都是很小的細節但在真實工具鏈里價值很大模型通常不知道某些路徑會觸發權限問題我們必須在工具層做保護。3.2 第二個工具網頁內容抓取光有本地搜索還不夠一個像樣的工具鏈最好能聯網。我再加一個fetch_page工具它負責抓取網頁并返回純文本內容給模型做資料調研用。import httpx mcp.tool() async def fetch_page(url: str, timeout: float 10.0) - str: 抓取指定 URL 并返回網頁正文僅供資料調研。 Args: url: 完整的網頁地址必須以 http:// 或 https:// 開頭。 timeout: 請求超時時間默認 10 秒。 if not url.startswith((http://, https://)): raise ValueError(url 必須以 http:// 或 https:// 開頭) headers {User-Agent: dev-toolbox-mcp/0.1.0} async with httpx.AsyncClient(timeouttimeout, follow_redirectsTrue) as client: resp await client.get(url, headersheaders) resp.raise_for_status() text resp.text return text[:8000]這里有個容易被忽略的點工具函數可以是異步的FastMCP 使用異步事件循環調度因此async def的函數不會阻塞其他工具的調用。第一次寫的時候我習慣地把所有函數都定義成普通同步函數后來發現某些網絡工具在 stdio 模式下阻塞事件循環導致其他并發工具全部排隊。把耗時操作改成異步確實能提升并發能力尤其是 Agent 會并行調用多個工具的場景。返回值截斷到 8000 字符也是經驗值。超出這個長度大部分模型的上下文里會出現信息過載而且調用結果回傳也會變慢。如果你的業務確實需要完整網頁可以考慮再提供一個接受start參數的翻頁式工具而不是一次全量返回。3.3 工具描述與 JSON Schema 的細節FastMCP 會自動把函數簽名和 docstring 轉換成模型的工具描述和參數 Schema所以 docstring 怎么寫直接決定模型能不能正確調用。我在實踐中得出幾條原則在 docstring 里用一句話說清楚工具“做什么”用 Args 列表寫清每個參數的含義、類型、邊界條件不要寫“用于...比如...主要用于”這種廢話模型不傻但它對歧義的容忍度很低。類型注解也至關重要。你寫directory: strSDK 就會生成一個 string 類型的參數你如果寫directory不帶注解或者寫成str 生成的 Schema 可能會變成 optional模型就可能在調用時漏傳。參數校驗我建議放在函數入口而不是依賴 Schema 完成。因為模型再聰明也可能生成越界值工具層必須做到“來什么都能處理或明確報錯”。3.4 用客戶端腳本驗證工具是否可用寫完之后先別急著接 GUI 客戶端。我強烈建議先寫一個十幾行的驗證腳本直接通過 SDK 連接本地 Server確認工具能被列出、能被調用。這一步能把“Server 有問題”和“客戶端配置有問題”隔離清楚排障效率高很多。import asyncio from mcp import ClientSession from mcp.client.stdio import stdio_client, StdioServerParameters async def main() - None: params StdioServerParameters( commandpython, args[server.py], cwdNone, ) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools await session.list_tools() print(可用工具:, [t.name for t in tools.tools]) result await session.call_tool(search_files, { directory: ./docs, keyword: MCP }) print(調用結果:, result.content) if __name__ __main__: asyncio.run(main())這個腳本就是一個標準 MCP Client 的最小實現。它會啟動python server.py子進程完成握手、列出工具、調用工具三個動作這三個動作覆蓋了 MCP 客戶端最核心的生命周期。如果這個腳本能跑通說明 Server 本身沒問題后面接任何客戶端都只是配置層面的活了。把這個問題想清楚你才知道排查方向該往哪邊使勁而不是在客戶端里反復改配置猜原因。4. 把 Agent 接上工具鏈客戶端配置與聯動調試4.1 在 Claude Desktop 里注冊 MCP Server最直觀的驗證方式是把 Server 掛到一個能直接和用戶對話的客戶端里。以 Claude Desktop 為例它會在啟動時讀取claude_desktop_config.json里面配置了所有 mcpServers。macOS 下這個文件在~/Library/Application Support/Claude/Windows 在%APPDATA%\Claude\。沒有文件就手動建一個。{ mcpServers: { dev-toolbox: { command: python, args: [ /absolute/path/to/server.py ], env: {} } } }注意這里必須用絕對路徑環境變量也要按需傳入。填完后重啟 Claude Desktop界面的工具區域會出現一個新圖標點開就能看到search_files和fetch_page。這時可以直接輸入一句自然語言比如“在 docs 目錄里搜索所有和 MCP 有關的文件”如果 Agent 正確調用工具并返回結果說明整條鏈路已經通了。如果工具沒出來不要先去改配置先在終端手動執行一次python /absolute/path/to/server.py看看能不能正常啟動、有沒有 import 報錯。MCP 的 Server 啟動失敗時GUI 客戶端往往只顯示一個籠統的錯誤真正的日志被吞掉了手動啟動是定位問題最快的方式。4.2 對接你自己的 Agent 框架如果你不是用現成客戶端而是自研 Agent 框架連接 MCP 的步驟和上面的驗證腳本基本一致創建 ClientSession調用 initialize 完成握手然后循環執行“請求工具列表、根據用戶意圖讓模型選擇工具、調用工具并把結果回傳給模型”的過程。這個循環就是 Agent 的核心調度邏輯也是所謂 AI Agent 搭建示例里最常見的一段骨架。很多人問過我和 LangGraph 這類框架怎么配合。LangGraph 解決的是 Agent 的狀態流轉和任務編排MCP 解決的是工具接入協議兩者完全可以結合用 LangGraph 定義工作流節點在每個節點里通過 MCP Client 調用工具工具執行結果作為下一輪的上下文。說起來復雜落地時其實就是一個普通異步函數封裝了 MCP 調用并不需要為協議本身做額外改造。4.3 多個 Server 協同時的編排思路工具鏈大了以后你不會把所有工具塞進同一個 Server。更常見的做法是按領域拆成多個 Server一個管文件檢索一個管網頁抓取一個管設計稿導出一個管數據庫查詢。每個 Server 只做一類事工具描述寫清楚所屬領域模型在調用時才有條件做“工具選擇”而不是被幾十個混雜的工具搞暈。我推薦一個簡單可執行的劃分原則同一個 Server 里的工具應該共享一套鑒權和數據源且互相之間有業務關聯如果沒有關聯就拆出去。比如文件搜索和網頁抓取是兩個完全獨立的數據源理論上可以拆成兩個 Server但為了演示方便我先壓在了一個 Server 里。真實項目里拆大于合維護和排查都輕松得多畢竟一個 Server 掛掉不應該拖垮整條工具鏈。5. 生產級工具鏈的隱藏功課安全、重試與可觀測性5.1 安全邊界權限最小化與工具準入工具鏈一旦接入生產環境安全就是第一優先級。模型只是個“調用者”它沒有安全意識我們必須把危險操作擋在工具層之外。最基礎的一條不要讓工具直接暴露“執行任意命令”的能力。我見過有人圖省事寫了一個execute_command工具模型在任何不確定的情況下都會傾向使用它危險程度極高。如果確實需要執行命令也必須在工具內部做白名單比如只允許運行terraform plan這類固定命令。涉及文件讀寫的工具要考慮路徑白名單。前面search_files允許傳任意目錄這在生產環境是不夠的應該限制只能訪問某個工作根目錄或者對路徑做歸一化后檢查前綴。網絡請求工具同理可以限定協議只能是 http/https并可以增加域名白名單策略防止模型因為 prompt 注入被誘導去訪問惡意地址。安全不是上線后補的必須在工具設計階段就定好邊界。5.2 超時、重試與錯誤返回規范Agent 調用工具不是一次 HTTP 請求那么簡單它是一個多輪會話任何一個環節超時都可能讓整個任務卡死。客戶端側要給工具調用設置超時不能默認無限等待Server 側處理耗時任務時要能提前返回進度或者直接返回超時錯誤避免占用連接太久。重試也要分場景。冪等工具可以放心重試比如“讀取文件內容”失敗后重試三到五次沒有風險非冪等工具比如“創建訂單”“發送消息”重試前必須想清楚是否會造成重復執行。工具返回錯誤時盡量不要直接拋異常讓客戶端看到一堆 traceback更合適的做法是 catch 后返回結構化錯誤信息比如{error: 文件不存在, path: /xxx}模型才能根據錯誤信息調整參數重新嘗試。5.3 日志與鏈路追蹤怎么做MCP 的 stdio 模式不能污染標準輸出但日志仍然很重要。最簡單的方式是用 Python logging 輸出到 stderr或者寫到獨立的日志文件。這樣既能保留調試信息又不破壞協議通道。開啟 debug 模式時可以用python -m mcp.server --verbose看服務端日志或者直接在 FastMCP 里配置 logging 等級。生產環境建議給每個工具調用補上 trace_id 或 request_id把一次 Agent 任務里的多次工具調用串起來。比如在 Server 入口生成一個隨機 id客戶端調用工具時通過參數傳入日志里就帶上這個 id。這樣排障時你可以把模型思考鏈路、工具入參出參、錯誤日志對上快速定位是模型選錯工具還是工具實現有 bug。別小看這個設計工具多了以后沒有鏈路信息幾乎是沒法排查問題的。6. 踩坑實錄這些問題我排查了很久6.1 常見問題速查表整理一張表方便以后遇到問題直接對照。這些現象絕大部分我都親眼見過而且每一次都至少花掉半小時起步的排查時間。現象可能原因解決思路客戶端找不到工具Server 啟動報錯或配置路徑錯誤終端手動啟動 Server看報錯日志工具調用一直超時工具內部網絡請求或長任務阻塞檢查是否缺少超時設置改成異步或減少任務量模型總是傳錯參數工具描述和 Schema 不清晰重寫 docstring補充參數邊界和示例結果太長被截斷單次返回超過模型上下文限制限制返回長度設計分頁或摘要工具stdio 模式報解析錯誤print 日志污染標準輸出所有日志寫 stderr子進程不退出Server 事件循環未正確關閉入口里顯式關閉 session或加退出鉤子回頭看這張表里絕大多數問題都不是 MCP 協議本身難搞而是工程習慣問題。工具描述寫得稀爛、日志亂打、路徑不校驗這些在任何系統里都會出問題MCP 只是把它們暴露得更明顯而已。6.2 兩個真實案例復盤第一個案例是工具列表加載失敗。現象是 Claude Desktop 重啟后工具圖標始終沒有出現我手動運行 server.py 卻一切正常。后來發現配置文件里的 args 寫的是相對路徑而 GUI 應用的工作目錄未必是項目目錄相對路徑解析失敗換成絕對路徑問題立刻解決。這個案例說明環境差異必須靠自己手動復現不能假設 GUI 的工作目錄和終端一致。第二個案例是模型調用參數總是出錯。search_files需要傳 directory 和 keyword但模型老是只傳 keyword或者把 directory 寫成文件名。我最初的 docstring 寫得太含糊SDK 生成的 Schema 里參數描述為空。把 Args 改寫清楚、并給 keyword 加了一個示例后調用成功率從五成不到升到接近九成。工具描述真的是模型調用質量的分水嶺每次覺得模型“變笨”了先回去看你的工具描述。6.3 生態里值得參考的項目MCP 生態已經相當豐富。像 Figma MCP 可以讀取設計稿結構和圖層信息Blender MCP 能控制三維場景導出藍湖 MCP、各類數據庫 MCP 都是現成案例。它們最大的參考價值不是拿來直接用而是看它們怎么設計工具粒度、描述和組織能力。打開倉庫看一遍它們的工具描述比自己悶頭寫強太多。官方也維護了 mcp servers 目錄里面有很多參考實現。我建議寫 Server 之前先看幾個熱門項目的做法重點觀察它們如何處理鑒權、錯誤、分頁以及工具命名是否直覺化。這些細節直接決定了你的工具鏈能不能撐住真實業務也決定了別人接手時能不能快速看懂。生態項目不是用來抄的是用來對齊行業經驗的。從零寫一個 MCP Server 到接進 AI Agent 工具鏈整個過程比想象中簡單核心代碼不超過一百行客戶端驗證腳本更短真正花時間的反而是工具描述、參數校驗和錯誤處理這些“看不見”的細節。我個人的經驗是AI Agent 能不能穩定干活三分靠模型七分靠工具鏈的質量。MCP 的價值在于把工具接入標準化但它不會替你解決工具設計得好不好用。如果你剛開始接觸建議拿我這個 dev-toolbox 練手先跑通本地文件搜索再加網絡請求最后拆成多個 Server 掛到 Agent 上。工具鏈這個東西越早動手越能體會什么叫“牽一發動全身”。后續我還會繼續整理 HTTP 模式部署、多 Agent 共享 Server 這些內容有實際進展再回來分享。