多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

浅谈 FastMCP:从 MCP Python SDK v1.0 到 v2.0 的迁移实践与 TaoToken 接入

浅谈 FastMCP:从 MCP Python SDK v1.0 到 v2.0 的迁移实践与 TaoToken 接入 1. 从 v1 到 v2FastMCP 迁移到底卡在哪如果你最近在本地写 MCP 服务大概率会遇到一个很具体的困惑昨天还能跑的from mcp.server.fastmcp import FastMCP今天升级依赖之后直接 ImportError。这不是你代码写错了而是 MCP Python SDK 从 v1.0 走到 v2.0 时把 FastMCP 这条导入路径彻底拆掉了。FastMCP 本身是一个基于 MCP 协议做上层封装的框架早期它被整合进官方 SDK后来又独立维护于是出现了「同一个名字、两套来源」的局面。到了 v2.0官方库不再兼容 FastMCP函数位置、类名、参数命名规则都变了。这篇文章面向的是在本地开发 MCP 服务、需要把工具暴露给客户端调用的 Python 开发者。核心要解决的问题有三个第一搞清楚 v1 和 v2 在导入、工具注册、回调签名上的差异第二给出可复制的依赖锁定方式和 server 配置片段第三把 endpoint 改到 TaoToken 的统一 Key/API 通道用 curl 验证工具列表和调用链路是否真的通了。适合谁适合已经写过一两个 MCP server、但被版本升级打断节奏的人也适合刚接触 FastMCP、想一步到位用对版本的新手。我试过在同一个虚拟环境里同时装mcp1.29.0和mcp2.0.0结果就是依赖解析直接打架。所以第一步不是改代码而是先把版本锁死。下面会按「问题场景 → 前置准备 → 可复制配置 → 验证请求 → 错排查 → 接入收尾」的顺序展开每一步都尽量给到能直接粘贴的命令和片段。先明确一个概念MCP 协议本身在 v1 到 v2 之间没有破坏性变化变的是 Python SDK 的实现层。也就是说你的工具逻辑、JSON-RPC 消息格式基本不用动动的是「怎么把工具注册进去」和「回调函数长什么样」。理解这一点迁移就不会那么慌。2. TaoToken 前置统一 Key 与 API 通道准备在动手改 server 之前先把联调要用的通道准备好。本地 MCP 服务最终是要被客户端调用的而调用链路里如果每个模型、每个工具都各配一套 Key维护成本会很高。TaoToken 在这里的作用是提供一个统一的 Key/API 通道把 endpoint 收敛到一处方便你在迁移过程中专注在代码本身而不是到处找配置。你需要先拿到一个可用的 API Key。打开官网https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content注册并登录然后进入控制台创建 Key。控制台地址是https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite在 API Keys 页面可以新建和复制 Key对应页面https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite。复制出来的 Key 形如一串长字符串先存到环境变量里别硬编码进代码。API 的基础地址是https://taotoken.net/api注意这个地址不带 UTM 参数是给程序调用的。文档入口在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite里面有各语言接入示例和参数说明迁移过程中遇到字段不确定的优先查文档而不是猜。把 Key 写进环境变量Linux/macOS 下可以这样export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/apiWindows PowerShell 用$env:TAOTOKEN_API_KEYsk-你的key $env:TAOTOKEN_BASE_URLhttps://taotoken.net/api为什么要先做这一步因为后面验证 MCP 工具调用时你会需要一个真实的模型 endpoint 来跑通链路。如果 Key 没准备好验证阶段就会卡在 401 上分不清是代码问题还是鉴权问题。提前把通道打通排障时变量就少一个。另外提醒一点TaoToken 是统一接入通道不是让你绕过什么限制它的价值在于把多个模型的调用收敛到一套 Key 和一套 Base URL 上。你在本地 MCP server 里配置 endpoint 时指向这个统一地址即可模型 ID 按文档里支持的填。3. 可复制配置依赖锁定与 server 片段这一节是全文最需要动手的部分。先解决依赖版本再给 v1 和 v2 两套 server 代码最后给一份 JSON 配置片段。3.1 依赖版本锁定FastMCP 和 MCP SDK 的版本对应关系容易混。按实践中的组合FastMCP 用fastmcp3.4.0MCP SDK v1.0 用mcp1.29.0MCP SDK v2.0 用mcp2.0.0。如果你要用独立维护的 FastMCP导入路径是from fastmcp import FastMCP这条路径在 v2 时代依然不变。推荐用requirements.txt锁死避免pip install mcp自动拉到最新版# requirements.txt mcp2.0.0 fastmcp3.4.0如果你还在 v1 阶段改成mcp1.29.0 fastmcp3.4.0安装命令python -m venv .venv source .venv/bin/activate pip install -r requirements.txt用pip freeze requirements.lock再固化一次团队协作时更稳。3.2 v1 的 server 写法v1 用装饰器注册工具风格接近 FastAPIfrom mcp.server import Server from mcp.types import Tool server Server(my-calculator) server.list_tools() def list_tools() - list[Tool]: return [ Tool( nameadd, descriptionAdd two numbers, inputSchema{ type: object, properties: { a: {type: number}, b: {type: number} }, required: [a, b] } ) ] server.call_tool() def call_tool(name: str, arguments: dict) - list[dict]: if name add: result arguments[a] arguments[b] return [{type: text, text: str(result)}] raise ValueError(fUnknown tool: {name}) if __name__ __main__: server.run(transportstdio)注意 v1 里字段是驼峰inputSchema返回值是裸字典列表SDK 会自动包装。3.3 v2 的 server 写法v2 放弃装饰器改用构造函数传回调并且回调是 async 的from mcp.server import Server from mcp.types import ( Tool, ListToolsResult, CallToolResult, TextContent, CallToolRequestParams ) from mcp.shared.context import RequestContext async def list_tools_callback(ctx: RequestContext, params: dict) - ListToolsResult: return ListToolsResult( tools[ Tool( nameadd, descriptionAdd two numbers, input_schema{ type: object, properties: { a: {type: number}, b: {type: number} }, required: [a, b] } ) ] ) async def call_tool_callback(ctx: RequestContext, params: CallToolRequestParams) - CallToolResult: if params.name add: result params.arguments[a] params.arguments[b] return CallToolResult( content[TextContent(typetext, textstr(result))] ) raise ValueError(fUnknown tool: {params.name}) server Server( my-calculator, on_list_toolslist_tools_callback, on_call_toolcall_tool_callback, ) if __name__ __main__: server.run(transportstdio)关键差异v2 字段是蛇形input_schema回调必须返回完整的ListToolsResult/CallToolResult对象且都是 async。3.4 客户端 JSON 配置片段把 endpoint 指向 TaoToken 统一通道配置片段如下以常见的 MCP 客户端配置为例{ mcpServers: { my-calculator: { command: python, args: [-m, my_calculator_server], env: { TAOTOKEN_API_KEY: sk-你的key, TAOTOKEN_BASE_URL: https://taotoken.net/api, MCP_MODEL_ID: 按文档支持的模型ID填写 } } } }如果你用的是 Claude Code 这类工具配置里同样要写全三件套Base URL 填https://taotoken.net/apiKey 填你的sk-开头字符串Model ID 按文档支持的填。三件套缺一个链路就断。4. 验证请求curl 跑通工具列表与调用链路代码写完不代表通了必须用 curl 实测。MCP 走 stdio 时不好直接 curl所以建议先用 HTTP transport 起一个本地服务或者用 SDK 自带的调试方式。下面给一个通用的验证思路。先确认服务能启动python -m my_calculator_server如果没报错说明导入和注册没问题。接着用 curl 验证模型 endpoint 是否可达curl -s -X POST https://taotoken.net/api/v1/chat/completions \ -H Authorization: Bearer $TAOTOKEN_API_KEY \ -H Content-Type: application/json \ -d { model: 按文档支持的模型ID填写, messages: [{role: user, content: ping}] }返回里能看到choices字段说明 Key 和 Base URL 都对。如果返回 401先查 Key如果返回local proxy failed查网络和 Base URL 拼写。验证工具列表可以用 MCP 的tools/list请求。假设你的服务暴露在本地某端口curl -s -X POST http://127.0.0.1:8000/mcp \ -H Content-Type: application/json \ -d {jsonrpc:2.0,id:1,method:tools/list,params:{}}期望返回里包含add工具及其input_schema。如果返回里tools为空说明list_tools_callback没被正确注册回去检查 v2 的构造函数参数名是不是on_list_tools。验证工具调用curl -s -X POST http://127.0.0.1:8000/mcp \ -H Content-Type: application/json \ -d { jsonrpc:2.0,id:2,method:tools/call, params:{name:add,arguments:{a:1,b:2}} }期望返回content里是3。如果报reading choices相关错误通常是模型返回结构和你解析的字段对不上检查是不是把 MCP 的返回和模型 API 的返回混在一起解析了。实测下来最容易出问题的是 v2 的 async 回调忘了加await或者返回了裸字典而不是CallToolResult。这两处一错curl 就会给你一个含糊的 500。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth迁移过程中报错集中在几类逐个对照。401 UnauthorizedKey 没传、传错、或者环境变量没生效。检查echo $TAOTOKEN_API_KEY是否有值curl 头里Bearer后面有没有多余空格。如果 Key 是从控制台复制的注意别把前后空白带进去。local proxy failed这个报错通常出现在 Base URL 配置不对或者本地网络到 endpoint 不通。先确认TAOTOKEN_BASE_URL是https://taotoken.net/api没有多余路径。再用curl -v看握手过程定位是 DNS 还是连接超时。reading choices 相关错误多出现在解析模型响应时。模型 API 返回的是choices数组而 MCP 工具调用返回的是content数组两者结构不同。如果你在同一个函数里混着解析就会读不到choices。分开处理模型调用走choices工具调用走content。OAuth 报错部分客户端在连接远程 MCP 服务时会走 OAuth 流程。如果你只是本地 stdio 服务不需要 OAuth检查客户端配置里是不是误开了远程模式。需要 OAuth 的场景按文档配置回调地址别自己拼。ImportError: cannot import name FastMCP from mcp.server.fastmcp这是 v2 最典型的报错。v2 删掉了fastmcp模块和FastMCP类改成了MCPServer。要么把导入改成from mcp.server import MCPServer要么改用独立 FastMCP 的from fastmcp import FastMCP。两条路选一条别混用。TypeError: object list cant be used in await expressionv2 回调是 async但你可能写成了同步函数。给list_tools_callback和call_tool_callback加上async。排障时建议开 debug 日志export MCP_LOG_LEVELDEBUG python -m my_calculator_server日志里能看到请求进出的完整 JSON-RPC 消息比猜快得多。6. 语义一致收尾把 endpoint 收敛到统一通道迁移的最后一步是把所有 endpoint 统一到 TaoToken 的 Key/API 通道上这样 v1 和 v2 的 server 都能用同一套鉴权和地址切换版本时只动代码不动配置。如果你要长期跑编码类 Agent或者需要多模型切换可以了解下 Coding Plan入口在https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding-planutm_campaignrewrite。需要直接对话验证模型效果的用模型对话页https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_contentmodel-chatutm_campaignrewrite。接入文档和 API Keys 分别在https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite和https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi-keysutm_campaignrewrite。最后给一个实用技巧把 v1 和 v2 的 server 代码放在同一个仓库的不同分支用requirements.txt区分版本切换时只改依赖和导入行工具逻辑完全复用。这样下次 SDK 再升级你只需要改一个文件而不是重写整个服务。
返回列表