】MCP 協(xié)議實(shí)戰(zhàn):從 0 到 1 構(gòu)建你的第一個(gè) MCP Server,附完整代碼與 TaoToken 接入)
1. 為什么我要自己寫一個(gè) MCP ServerMCPModel Context Protocol模型上下文協(xié)議是 Anthropic 開(kāi)源的一套標(biāo)準(zhǔn)用來(lái)讓大模型以統(tǒng)一方式調(diào)用外部工具和數(shù)據(jù)源。它能做什么簡(jiǎn)單說(shuō)你寫一次 ServerClaude Desktop、Cursor、Continue 這些支持 MCP 的客戶端都能直接調(diào)用不用為每家模型單獨(dú)適配 Function Calling 格式。適合誰(shuí)適合手里有內(nèi)部 API、數(shù)據(jù)庫(kù)、腳本想讓 AI 直接調(diào)用的 Python 開(kāi)發(fā)者以及想把 Claude Desktop 變成自己工具臺(tái)的用戶。我第一次動(dòng)手寫 MCP Server 時(shí)踩了不少坑官方文檔偏協(xié)議規(guī)范缺少階梯式教程JSON-RPC 和 stdio 通信對(duì)沒(méi)接觸過(guò)的人一頭霧水市面上的現(xiàn)成 Server 又滿足不了業(yè)務(wù)定制。折騰了兩個(gè)下午才跑通第一個(gè) Hello World。這篇文章就把這條路重新鋪一遍從項(xiàng)目結(jié)構(gòu)、依賴清單、啟動(dòng)命令到把 Claude Desktop 的 MCP 配置改到 TaoToken 統(tǒng)一 Key/API 通道最后用一次工具調(diào)用驗(yàn)證連通性。MCP 的核心價(jià)值在于解耦。以前讓模型調(diào)工具要么用 OpenAI 的 Function Calling、Anthropic 的 Tool Use各寫一套要么被 LangChain 這類框架綁死。MCP 把「工具怎么被發(fā)現(xiàn)、怎么被描述、怎么被調(diào)用」抽成協(xié)議層Server 只寫一次任何支持 MCP 的 Client 都能用。它基于 JSON-RPC 2.0傳輸層支持 stdio本地子進(jìn)程和 HTTPSSE遠(yuǎn)程服務(wù)三大原語(yǔ)是 Tool、Resource、Prompt。本文聚焦最常用的 Tool帶你從零構(gòu)建一個(gè)能查天氣、能做四則運(yùn)算的 Server。2. TaoToken 前置準(zhǔn)備統(tǒng)一 Key 與 API 通道在寫代碼之前先把模型調(diào)用通道理順。Claude Desktop 默認(rèn)走 Anthropic 官方接口但如果你同時(shí)用多個(gè)模型、多個(gè)工具Key 管理會(huì)很亂。TaoToken 提供統(tǒng)一的 API 通道一個(gè) Key 就能覆蓋多種模型調(diào)用MCP Server 里如果需要調(diào)用模型能力比如做二次推理、生成摘要也可以走這個(gè)通道。你需要先拿到兩樣?xùn)|西Base URL 和 API Key。Base URL 是https://taotoken.net/apiAPI Key 在控制臺(tái)的 API Keys 頁(yè)面創(chuàng)建。創(chuàng)建時(shí)建議按用途命名比如mcp-demo方便后續(xù)排查。拿到 Key 后不要硬編碼進(jìn)代碼用環(huán)境變量管理。# Linux / macOS export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api # Windows PowerShell $env:TAOTOKEN_API_KEYsk-你的key $env:TAOTOKEN_BASE_URLhttps://taotoken.net/api如果你用的是 Claude Code 或 Codex 這類編碼工具配置方式略有不同。Claude Code 的 settings 文件里需要寫全三件套Base URL、Key、Model ID。Codex 的auth.json也是類似結(jié)構(gòu)。下面是一個(gè) Claude Code 的 settings 片段示例路徑按你的實(shí)際安裝位置調(diào)整{ env: { ANTHROPIC_BASE_URL: https://taotoken.net/api, ANTHROPIC_API_KEY: sk-你的key, ANTHROPIC_MODEL: claude-sonnet-4-20250514 } }注意 Model ID 要和你實(shí)際使用的模型對(duì)應(yīng)不同客戶端對(duì)模型名的寫法可能不同以控制臺(tái)文檔為準(zhǔn)。配置完成后可以用一個(gè)最簡(jiǎn)單的 curl 驗(yàn)證通道是否通curl -X POST https://taotoken.net/api/v1/messages \ -H Content-Type: application/json \ -H x-api-key: $TAOTOKEN_API_KEY \ -H anthropic-version: 2023-06-01 \ -d { model: claude-sonnet-4-20250514, max_tokens: 64, messages: [{role: user, content: ping}] }如果返回里有正常的content字段說(shuō)明通道沒(méi)問(wèn)題。這一步很關(guān)鍵因?yàn)楹竺?MCP Server 如果涉及模型調(diào)用走的就是這個(gè)通道。如果這里就報(bào) 401先檢查 Key 是否復(fù)制完整、有沒(méi)有多余空格。3. 可復(fù)制配置從零搭建 MCP Server 項(xiàng)目現(xiàn)在進(jìn)入正題。我們構(gòu)建一個(gè)包含兩個(gè) Tool 的 Serverget_weather查天氣和calculator四則運(yùn)算。項(xiàng)目結(jié)構(gòu)如下mcp-demo-server/ ├── server.py # MCP Server 主程序 ├── tools/ │ ├── __init__.py │ ├── weather.py # 天氣查詢工具 │ └── calculator.py # 計(jì)算器工具 ├── requirements.txt └── claude_desktop_config.json # Claude Desktop 配置示例先寫依賴清單requirements.txtmcp1.0.0 httpx0.27.0安裝依賴pip install -r requirements.txt主程序server.py負(fù)責(zé)創(chuàng)建 Server 實(shí)例、注冊(cè) Tool 列表、處理調(diào)用請(qǐng)求、啟動(dòng) stdio 傳輸 MCP Demo Server —— 天氣查詢 計(jì)算器 使用官方 MCP Python SDK 構(gòu)建 import asyncio import json from mcp.server import Server, NotificationOptions from mcp.server.models import InitializationCapabilities from mcp.server.stdio import stdio_server from tools.weather import get_weather_data from tools.calculator import calculate # 1. 創(chuàng)建 MCP Server 實(shí)例 server Server(mcp-demo-server) # 2. 注冊(cè) Tool 列表 server.list_tools() async def handle_list_tools() - list: 返回當(dāng)前 Server 支持的所有 Tool 列表 return [ { name: get_weather, description: 獲取指定城市的當(dāng)前天氣信息包括溫度、濕度、天氣狀況和風(fēng)力, inputSchema: { type: object, properties: { city: { type: string, description: 城市名稱支持中文如北京或英文如Beijing } }, required: [city] } }, { name: calculator, description: 執(zhí)行基本的四則運(yùn)算加減乘除支持整數(shù)和浮點(diǎn)數(shù), inputSchema: { type: object, properties: { operation: { type: string, enum: [add, subtract, multiply, divide], description: 運(yùn)算類型add-加法, subtract-減法, multiply-乘法, divide-除法 }, a: {type: number, description: 第一個(gè)操作數(shù)}, b: {type: number, description: 第二個(gè)操作數(shù)} }, required: [operation, a, b] } } ] # 3. 注冊(cè) Tool 調(diào)用處理器 server.call_tool() async def handle_call_tool(name: str, arguments: dict) - list: 處理來(lái)自 Client 的 Tool 調(diào)用請(qǐng)求 if name get_weather: city arguments.get(city, 北京) weather_data await get_weather_data(city) return [{ type: text, text: json.dumps(weather_data, ensure_asciiFalse, indent2) }] elif name calculator: operation arguments[operation] a arguments[a] b arguments[b] result await calculate(operation, a, b) return [{ type: text, text: f計(jì)算結(jié)果{a} {operation} {result} }] else: raise ValueError(f未知的 Tool: {name}) # 4. 啟動(dòng) Server async def main(): 通過(guò) stdio 傳輸啟動(dòng) MCP Server async with stdio_server() as (read_stream, write_stream): await server.run( read_stream, write_stream, InitializationCapabilities( sampling{}, experimental{}, ), notification_optionsNotificationOptions( tools_changedTrue ) ) if __name__ __main__: asyncio.run(main())天氣工具tools/weather.py調(diào)用免費(fèi)天氣 API并做好異常降級(jí) 天氣查詢工具 —— 調(diào)用免費(fèi)天氣 API 獲取實(shí)時(shí)天氣數(shù)據(jù) import httpx WEATHER_API_URL https://wttr.in/{}?formatj1 async def get_weather_data(city: str Beijing) - dict: 獲取指定城市的天氣信息 try: async with httpx.AsyncClient(timeout10.0) as client: response await client.get(WEATHER_API_URL.format(city)) if response.status_code ! 200: return _get_mock_weather(city) data response.json() current data[current_condition][0] weather { city: city, temperature_c: int(current[temp_C]), humidity: int(current[humidity]), condition: current[lang_zh][0][value] if current.get(lang_zh) else current[weatherDesc][0][value], wind_speed_kmh: int(current[windspeedKmph]), feels_like_c: int(current[FeelsLikeC]), observation_time: current[observation_time] } return weather except Exception as e: return { **_get_mock_weather(city), note: f模擬數(shù)據(jù)API 請(qǐng)求失敗{str(e)} } def _get_mock_weather(city: str) - dict: 返回模擬天氣數(shù)據(jù)用于 API 不可用時(shí)的降級(jí)處理 return { city: city, temperature_c: 25, humidity: 60, condition: 晴, wind_speed_kmh: 15, feels_like_c: 26, observation_time: 12:00 PM }計(jì)算器工具tools/calculator.py 計(jì)算器工具 —— 提供安全的四則運(yùn)算能力 async def calculate(operation: str, a: float, b: float) - float: 執(zhí)行基本四則運(yùn)算 if operation not in (add, subtract, multiply, divide): raise ValueError(f不支持的運(yùn)算類型{operation}) if operation add: return a b elif operation subtract: return a - b elif operation multiply: return a * b elif operation divide: if b 0: raise ValueError(除數(shù)不能為零請(qǐng)檢查第二個(gè)參數(shù)。) return a / btools/__init__.py留空即可。接下來(lái)配置 Claude Desktop。配置文件路徑Windows 在%APPDATA%\Claude\claude_desktop_config.jsonmacOS 在~/Library/Application Support/Claude/claude_desktop_config.json。內(nèi)容如下{ mcpServers: { mcp-demo-server: { command: python, args: [C:\\path\\to\\mcp-demo-server\\server.py], description: 天氣查詢和計(jì)算器服務(wù) } } }如果你希望 MCP Server 內(nèi)部調(diào)用模型時(shí)走 TaoToken 通道可以在配置里加環(huán)境變量{ mcpServers: { mcp-demo-server: { command: python, args: [C:\\path\\to\\mcp-demo-server\\server.py], env: { TAOTOKEN_API_KEY: sk-你的key, TAOTOKEN_BASE_URL: https://taotoken.net/api } } } }保存后完全退出 Claude Desktop 再重啟輸入框下方會(huì)出現(xiàn)工具圖標(biāo)代表 MCP Tool 已就緒。4. 驗(yàn)證請(qǐng)求與成功結(jié)果重啟 Claude Desktop 后做三組測(cè)試。第一組測(cè)天氣你北京今天天氣怎么樣 Claude自動(dòng)調(diào)用 get_weather北京今天晴溫度 25°C濕度 60%風(fēng)力 15km/h。第二組測(cè)計(jì)算器你幫我算一下 156.5 乘以 38.2 等于多少 Claude自動(dòng)調(diào)用 calculator156.5 × 38.2 5978.3。第三組測(cè)組合調(diào)用你北京和上海哪個(gè)城市今天更熱 Claude分別調(diào)用 get_weather 兩次對(duì)比后回答北京 25°C上海 28°C上海今天更熱。如果工具圖標(biāo)沒(méi)出現(xiàn)先用mcp dev命令單獨(dú)測(cè)試 Server它會(huì)給出比直接連 Claude Desktop 更詳細(xì)的錯(cuò)誤信息pip install mcp mcp dev server.py這個(gè)命令會(huì)啟動(dòng)一個(gè)開(kāi)發(fā)模式你能看到 JSON-RPC 消息的收發(fā)過(guò)程。實(shí)測(cè)下來(lái)大部分問(wèn)題在這一步就能定位。比如 Server 啟動(dòng)后 Claude Desktop 連不上通常是 stdout 被print()污染了——stdio 傳輸下stdout 只能走協(xié)議消息日志必須走 stderr 或文件。把print(Server started)改成logging.info(Server started)就能解決。驗(yàn)證成功后你可以嘗試修改 Tool 邏輯比如把天氣 API 換成自己的數(shù)據(jù)源或者加一個(gè)新 Tool 做數(shù)據(jù)庫(kù)查詢。MCP 的擴(kuò)展性就在這里Server 端改一次所有支持 MCP 的 Client 都能用上新能力。5. 本篇常見(jiàn)錯(cuò)誤排查開(kāi)發(fā) MCP Server 時(shí)下面這些報(bào)錯(cuò)我基本都遇到過(guò)對(duì)照排查能省不少時(shí)間。401 Unauthorized如果 MCP Server 內(nèi)部調(diào)用模型走 TaoToken 通道時(shí)報(bào) 401先檢查TAOTOKEN_API_KEY環(huán)境變量是否傳入。Claude Desktop 的配置里env字段要寫全Key 不要有多余空格。用 curl 單獨(dú)測(cè)一次通道確認(rèn) Key 本身有效。local proxy failed / connection refused這類錯(cuò)誤通常出現(xiàn)在 Server 啟動(dòng)階段。檢查command和args路徑是否正確Windows 下路徑要用雙反斜杠或正斜杠。如果 Python 不在系統(tǒng) PATH 里command要寫 Python 的絕對(duì)路徑。reading choices 報(bào)錯(cuò)如果 Server 返回的消息格式不符合 JSON-RPC 2.0 規(guī)范Client 解析時(shí)會(huì)報(bào)類似reading choices的錯(cuò)誤。檢查handle_call_tool的返回值必須是[{type: text, text: ...}]這種結(jié)構(gòu)不能直接返回裸字典。OAuth 相關(guān)報(bào)錯(cuò)部分客戶端在連接遠(yuǎn)程 MCP Server 時(shí)會(huì)走 OAuth 流程。如果你用的是 stdio 本地 Server一般不會(huì)遇到如果遇到檢查 Client 的認(rèn)證配置確認(rèn)沒(méi)有誤配遠(yuǎn)程地址。Tool 不出現(xiàn)先確認(rèn) Claude Desktop 完全退出再重啟不是關(guān)窗口。然后檢查 JSON 配置文件格式多一個(gè)逗號(hào)都會(huì)導(dǎo)致解析失敗。用mcp dev server.py確認(rèn) Server 本身能正常列出 Tool。Tool 調(diào)用參數(shù)錯(cuò)誤模型是根據(jù)inputSchema生成調(diào)用參數(shù)的。如果required數(shù)組漏了關(guān)鍵字段模型可能不傳參。每個(gè) Tool 的必填參數(shù)都要寫進(jìn)required。description也要寫清楚模型靠它理解 Tool 用途描述越明確調(diào)用越準(zhǔn)。異步阻塞導(dǎo)致 Server 無(wú)響應(yīng)在 async 上下文里調(diào)用同步阻塞函數(shù)比如requests.get會(huì)卡住整個(gè) Server。用httpx.AsyncClient替代requests所有耗時(shí)操作都走異步。一個(gè)實(shí)用技巧Tool 的description字段極其重要。模型是根據(jù)描述來(lái)決定是否調(diào)用、怎么調(diào)用的。描述里寫清「做什么、什么場(chǎng)景用、參數(shù)含義」調(diào)用成功率能明顯提升。我試過(guò)把描述從「查天氣」改成「獲取指定城市的當(dāng)前天氣信息包括溫度、濕度、天氣狀況和風(fēng)力」模型調(diào)用準(zhǔn)確率肉眼可見(jiàn)地變好。6. 繼續(xù)深入從 Demo 到生產(chǎn)可用跑通 Demo 只是起點(diǎn)。接下來(lái)你可以做幾件事把天氣 API 換成自己的業(yè)務(wù)數(shù)據(jù)源比如訂單查詢、用戶信息加一個(gè) Resource 原語(yǔ)把數(shù)據(jù)庫(kù)表結(jié)構(gòu)暴露給模型作為上下文或者用 HTTPSSE 傳輸把 Server 部署到遠(yuǎn)程讓團(tuán)隊(duì)共用。如果你在配置 Claude Code 或 Codex 時(shí)遇到認(rèn)證問(wèn)題記得三件套要寫全Base URL 用https://taotoken.net/apiKey 從控制臺(tái) API Keys 頁(yè)面獲取Model ID 按實(shí)際使用的模型填寫。需要長(zhǎng)期跑編碼任務(wù)或 Agent 場(chǎng)景可以了解 Coding Plan只是想驗(yàn)證模型連通性用模型對(duì)話頁(yè)面發(fā)一條消息即可。接入文檔里有各客戶端的完整配置示例排障時(shí)對(duì)照檢查效率更高。MCP 正在成為 LLM 應(yīng)用開(kāi)發(fā)的基礎(chǔ)設(shè)施?,F(xiàn)在動(dòng)手寫第一個(gè) Server比等到生態(tài)完全成熟再入場(chǎng)能更早理解協(xié)議層的設(shè)計(jì)取舍。遇到問(wèn)題別急著換方案先用mcp dev把 JSON-RPC 消息打出來(lái)看大部分坑都在消息格式和傳輸層上。