議從原理到實(shí)戰(zhàn):手寫一個(gè)MCP Server接入Claude Code全流程踩坑指南(TaoToken統(tǒng)一Key/API通道配置版))
1. 為什么我要手寫一個(gè) MCP Server 接進(jìn) Claude CodeMCP 協(xié)議Model Context Protocol是 Anthropic 提出的開放標(biāo)準(zhǔn)用一句話說清楚它讓大模型像瀏覽器訪問網(wǎng)頁一樣用統(tǒng)一協(xié)議安全地訪問外部工具和數(shù)據(jù)源。你寫一次 MCP Server所有支持 MCP 的客戶端都能直接調(diào)用不用再為每個(gè)框架重寫一遍 Function Calling。Claude Code 是目前對(duì) MCP 支持最完整的命令行 AI 編程助手適合想把自己的腳本、數(shù)據(jù)庫、內(nèi)部 API 變成大模型可調(diào)用工具的開發(fā)者。我試過把公司內(nèi)部的日志查詢腳本接進(jìn) Claude Code一開始踩了不少坑stdio 模式下 print 調(diào)試直接把協(xié)議通道污染了Windows 下中文編碼亂碼配置文件里 command 寫了相對(duì)路徑換個(gè)目錄就找不到。這些問題教程里大多一筆帶過但對(duì)新手來說每一個(gè)都能卡半天。這篇就把從零手寫 MCP Server 到接入 Claude Code 的全流程拆開講stdio 和 SSE 兩種傳輸模式都覆蓋配置文件骨架、啟動(dòng)命令、鑒權(quán)通道配置片段全部給可復(fù)制的版本最后附一份常見報(bào)錯(cuò)排查清單。適合誰看已經(jīng)會(huì)用 Claude Code 或 Cursor想把自己的工具接進(jìn)去的開發(fā)者被 Function Calling 各家格式不統(tǒng)一折磨過的人想搞懂 MCP 協(xié)議到底怎么跑起來、不想只看概念科普的人。讀完你能得到一個(gè)能跑通的 Note Server以及一套可復(fù)用的配置和排障方法。2. TaoToken 前置統(tǒng)一 Key 與 API 通道準(zhǔn)備在動(dòng)手寫 Server 之前先把模型調(diào)用通道理順。Claude Code 本身需要模型 API 才能工作如果你同時(shí)還在用其他支持 MCP 的客戶端每個(gè)都配一遍 Key 和地址會(huì)很亂。TaoToken 提供統(tǒng)一 Key 和 API 通道把模型調(diào)用收斂到一個(gè)入口后面 Claude Code 的配置里只需要填一次。官網(wǎng)地址https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 地址https://taotoken.net/api操作路徑很直接注冊(cè)后在控制臺(tái)創(chuàng)建 API Key然后在 Claude Code 的環(huán)境變量或配置里指向這個(gè) API 地址。具體入口模型對(duì)話體驗(yàn)https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteCoding Plan長期編碼/Agent 場景https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite控制臺(tái)https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteAPI Keys 管理https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite接入文檔https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite注意MCP Server 本身不負(fù)責(zé)模型調(diào)用它只負(fù)責(zé)暴露工具。模型調(diào)用通道是 Claude Code 這一側(cè)的事。把這兩層分清楚后面排查問題時(shí)就不會(huì)混。環(huán)境變量配置示例Windows Git Bash / Linux / Mac 通用export ANTHROPIC_BASE_URLhttps://taotoken.net/api export ANTHROPIC_API_KEY你的TaoToken KeyWindows PowerShell 用$env:ANTHROPIC_BASE_URLhttps://taotoken.net/api $env:ANTHROPIC_API_KEY你的TaoToken Key配好后先驗(yàn)證模型通道能通再往下寫 Server。這一步不通后面 MCP 接得再好也沒用。3. 可復(fù)制配置手寫 stdio 版 MCP Server 并接入 Claude Code3.1 環(huán)境準(zhǔn)備與依賴安裝建議 Python 3.10建獨(dú)立虛擬環(huán)境。不建虛擬環(huán)境的話后面配置文件里 command 要寫 Python 絕對(duì)路徑全局環(huán)境一升級(jí)就崩。mkdir my-mcp-server cd my-mcp-server python -m venv .venv # Windows Git Bash: source .venv/Scripts/activate # Linux/Mac: source .venv/bin/activate pip install mcpClaude Code 安裝npm install -g anthropic-ai/claude-code claude --version3.2 寫一個(gè) Note Serverstdio 模式需求讓大模型能增、查、搜本地筆記。新建note_server.pyimport json import os import sys import logging from mcp.server.fastmcp import FastMCP # 關(guān)鍵日志輸出到 stderr絕不污染 stdoutstdio 協(xié)議通道 logging.basicConfig( levellogging.DEBUG, streamsys.stderr, format%(asctime)s [%(levelname)s] %(message)s ) NOTE_FILE notes.json def _load_notes(): if not os.path.exists(NOTE_FILE): return [] with open(NOTE_FILE, r, encodingutf-8) as f: return json.load(f) def _save_notes(notes): with open(NOTE_FILE, w, encodingutf-8) as f: json.dump(notes, f, ensure_asciiFalse, indent2) mcp FastMCP(note-server) mcp.tool() def list_notes() - str: 列出所有筆記的標(biāo)題和編號(hào)。無筆記時(shí)返回空列表提示。 notes _load_notes() if not notes: return 當(dāng)前沒有任何筆記。 return json.dumps( [{id: i, title: n[title]} for i, n in enumerate(notes)], ensure_asciiFalse ) mcp.tool() def add_note(title: str, content: str) - str: 添加一條新筆記。 Args: title: 筆記標(biāo)題簡短概括 content: 筆記正文內(nèi)容 notes _load_notes() notes.append({title: title, content: content}) _save_notes(notes) return f已添加筆記《{title}》當(dāng)前共 {len(notes)} 條。 mcp.tool() def search_notes(keyword: str) - str: 按關(guān)鍵詞搜索筆記標(biāo)題和正文返回所有匹配的筆記。 當(dāng)用戶想查找包含某內(nèi)容的筆記有沒有關(guān)于XX的記錄時(shí)使用。 Args: keyword: 要搜索的關(guān)鍵詞單個(gè)詞或短語 notes _load_notes() results [ {id: i, title: n[title], content: n[content]} for i, n in enumerate(notes) if keyword in n[title] or keyword in n[content] ] if not results: return f沒有找到包含「{keyword}」的筆記。 return json.dumps(results, ensure_asciiFalse) if __name__ __main__: mcp.run(transportstdio)幾個(gè)決定工具能不能被模型正確調(diào)用的細(xì)節(jié)函數(shù)名語義化list_notes比get_data好docstring 寫清做什么、參數(shù)含義、返回什么參數(shù)加類型注解返回值統(tǒng)一用字符串復(fù)雜結(jié)構(gòu)json.dumps。本地驗(yàn)證語法python note_server.py # 無報(bào)錯(cuò)、阻塞等待輸入說明 stdio 服務(wù)就緒CtrlC 退出3.3 Claude Code 配置文件骨架在項(xiàng)目根目錄創(chuàng)建.mcp.json{ mcpServers: { note-server: { command: C:/項(xiàng)目路徑/my-mcp-server/.venv/Scripts/python.exe, args: [C:/項(xiàng)目路徑/my-mcp-server/note_server.py] } } }注意Windows 路徑用正斜杠/最省心JSON 里反斜杠要轉(zhuǎn)義成\\。command 和 args 一律寫絕對(duì)路徑Host 啟動(dòng)子進(jìn)程時(shí)工作目錄不固定相對(duì)路徑會(huì)解析失敗。命令行臨時(shí)添加等價(jià)寫法claude mcp add note-server -- C:/項(xiàng)目路徑/my-mcp-server/.venv/Scripts/python.exe C:/項(xiàng)目路徑/my-mcp-server/note_server.py3.4 升級(jí)到 SSE 遠(yuǎn)程傳輸stdio 每臺(tái)機(jī)器都要裝一份團(tuán)隊(duì)共享不方便。改成 SSE 只需改一行if __name__ __main__: mcp.run(transportsse, port8765)客戶端配置改為 URL 形式{ mcpServers: { note-server-remote: { url: http://your-server-ip:8765/sse } } }命令行claude mcp add note-server-remote --transport sse http://localhost:8765/sse提示MCP 規(guī)范后續(xù)引入了 Streamable HTTP 傳輸對(duì)無狀態(tài)部署更友好。SDK 版本較新可嘗試transportstreamable-http過渡期 SSE 仍廣泛兼容。4. 驗(yàn)證請(qǐng)求與成功結(jié)果4.1 用 MCP Inspector 先測 Server改完代碼先在 Inspector 里測把Server 的 bug和模型沒調(diào)用對(duì)區(qū)分開npx modelcontextprotocol/inspector python note_server.py瀏覽器打開http://localhost:5173左側(cè)列出所有 Tools點(diǎn)一個(gè)填參數(shù)點(diǎn) Run 直接調(diào)用底部顯示完整 JSON-RPC 請(qǐng)求/響應(yīng)。4.2 在 Claude Code 里驗(yàn)證項(xiàng)目目錄下啟動(dòng)claude輸入/mcp看到note-server: connected且列出三個(gè)工具說明接入成功。然后用自然語言測試幫我加一條筆記標(biāo)題MCP學(xué)習(xí)計(jì)劃內(nèi)容本周跑通stdio版本下周升級(jí)SSE 我現(xiàn)在有哪些筆記 搜一下有沒有關(guān)于SSE的筆記模型自主調(diào)用對(duì)應(yīng)工具并返回正確結(jié)果就說明整條鏈路通了。4.3 SSE 連通性驗(yàn)證curl -N http://localhost:8765/sse能看到流式事件返回即正常。遠(yuǎn)程連不上時(shí)先確認(rèn)監(jiān)聽地址是0.0.0.0而非127.0.0.1再查防火墻端口。5. 本篇常見錯(cuò)排查清單#坑點(diǎn)現(xiàn)象根因解決方案1stdio 下用 print 調(diào)試連接后卡死、協(xié)議解析錯(cuò)誤print 內(nèi)容被當(dāng)成協(xié)議消息污染通道用logging輸出到 stderr2Windows 中文編碼亂碼中文變 ??? 或 UnicodeDecodeError默認(rèn) GBK 與 utf-8 不一致文件操作顯式encodingutf-83command 寫相對(duì)路徑換目錄啟動(dòng)報(bào) command not found子進(jìn)程工作目錄不固定command 和 args 寫絕對(duì)路徑4docstring 太簡略模型不調(diào)用或亂傳參模型靠 docstring 理解語義寫清做什么、參數(shù)、返回5參數(shù)沒類型注解數(shù)字傳成字符串、順序亂JSON Schema 缺類型信息每個(gè)參數(shù)加類型注解6SSE 部署后連不上本機(jī)能連遠(yuǎn)程連不上監(jiān)聽 127.0.0.1 或防火墻監(jiān)聽 0.0.0.0、開端口、配 CORS7危險(xiǎn)操作沒護(hù)欄模型刪了不該刪的數(shù)據(jù)工具權(quán)限過大加二次確認(rèn)、只讀隔離、操作日志坑 1 的正確調(diào)試姿勢import sys import logging logging.basicConfig( levellogging.DEBUG, streamsys.stderr, format%(asctime)s [%(levelname)s] %(message)s ) mcp.tool() def add_note(title: str, content: str) - str: logging.debug(f調(diào)用 add_note: title{title}) # ... 業(yè)務(wù)邏輯 ... return ok坑 4 的 docstring 對(duì)比# 模型大概率不調(diào)用或亂傳參 mcp.tool() def search(keyword): 搜索 ... # 模型能準(zhǔn)確判斷何時(shí)調(diào)用、怎么傳參 mcp.tool() def search_notes(keyword: str) - str: 按關(guān)鍵詞搜索筆記標(biāo)題和正文返回所有匹配的筆記。 當(dāng)用戶想查找包含某內(nèi)容的筆記有沒有關(guān)于XX的記錄時(shí)使用。 Args: keyword: 要搜索的關(guān)鍵詞單個(gè)詞或短語 ...6. 下一步把通道和工具都收斂好Server 跑通后接下來兩件事值得做。一是把模型調(diào)用通道統(tǒng)一到 TaoTokenClaude Code 和其他 MCP 客戶端共用一套 Key 和 API 地址配置不再散落各處。API Keys 管理入口https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 接入文檔https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。如果你長期用 Claude Code 做編碼或 Agent 任務(wù)Coding Plan 會(huì)更劃算https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。想先驗(yàn)證模型通道是否正??梢灾苯釉谀P蛯?duì)話頁試https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。二是把你自己工作里的真實(shí)腳本改造成 MCP Server。查日志、跑 SQL、生成報(bào)表任何一個(gè)重復(fù)勞動(dòng)都值得包一層工具。改的時(shí)候記住三條docstring 當(dāng)接口文檔寫、參數(shù)加類型注解、危險(xiǎn)操作加護(hù)欄。這三條做到工具被模型正確調(diào)用的概率會(huì)高很多。