1. 从手写 JSON-RPC 到 FastMCP一次 MCP 服务搭建的真实演进MCPModel Context Protocol说白了就是让大语言模型能伸手调用外部工具的一套标准协议。你可以把它理解成 LLM 世界的 USB 接口只要工具按这个协议插上去模型就能发现它、调用它、拿到结果。适合谁适合正在做 AI Agent、想让模型查数据库/读文件/调内部接口的开发者尤其是那些已经写过 prompt 但发现模型只会说不会做的人。我最早接触 MCP 的时候是从 Low-Level 手写 JSON-RPC 开始的。当时的需求很简单让模型能查一个内部用户表。结果光是处理initialize、tools/list、tools/call这几个方法的请求路由和错误码就写了快两百行胶水代码。后来换成 FastMCP同样的功能压缩到二十行以内而且工具注册、参数校验、并发处理全部自动搞定。这篇文章就把这条演进路径完整走一遍并且把鉴权这一环用 TaoToken 的统一 Key 通道接上让你在本地能跑通一次从工具注册到模型调用的完整链路。先说清楚 MCP 的两个角色。MCP Server 是提供具体能力的一方比如查用户年龄读某个目录下的文件调用天气接口。MCP Client 是 LLM 侧的桥梁负责把模型的意图翻译成对 Server 的调用。Low-Level 方式下这两边的通信细节全得你自己扛FastMCP 则把 Server 侧的协议实现全包了你只写业务函数。为什么这件事和 prompt 工程有关因为工具注册的本质是给模型写一份可执行的说明书。你写的函数名、类型注解、docstring最终都会变成模型判断该不该调这个工具的依据。所以工具描述写得好不好直接决定模型调用准不准。这也是我后面会重点讲mcp.tool()里 docstring 怎么写的原因。下面按四步走先看 Low-Level 的坑再上 FastMCP 迁移然后配 TaoToken 统一 Key最后验证请求和排错。每一步都给可复制的代码和配置。2. TaoToken 前置准备统一 Key 与 API 通道在动手写 Server 之前先把鉴权和模型通道这块理清楚。因为 MCP 服务最终是要被 LLM 调用的而 LLM 的请求得有个统一的出口。TaoToken 在这里扮演的角色就是提供一套兼容 OpenAI 风格的 API 通道和统一 Key让你不用在代码里散落多个厂商的密钥。你需要准备的东西只有两样一个 API Key一个 Base URL。Key 在控制台生成地址是 https://taotoken.net/api-keys Base URL 固定为 https://taotoken.net/api 。注意这个 Base URL 后面不带任何路径后缀OpenAI SDK 会自动拼/v1/chat/completions这类端点。如果你用的是 Claude Code 这类工具配置方式略有不同。Claude Code 走的是 Anthropic 风格的端点需要在环境变量里设置ANTHROPIC_BASE_URL和ANTHROPIC_API_KEY。TaoToken 的文档页 https://taotoken.net/doc 里有针对不同客户端的完整配置说明建议先扫一眼再动手。这里有个容易踩的坑很多人把 Base URL 写成https://taotoken.net/api/v1结果 SDK 又拼了一次/v1变成/api/v1/v1/chat/completions直接 404。记住Base URL 就到/api为止。模型 ID 这块TaoToken 支持多种主流模型具体可用的 Model ID 在模型对话页 https://taotoken.net/models 能查到。你在配置里填的 Model ID 必须和平台上列出的完全一致大小写敏感。我见过有人把claude-sonnet-4写成claude-sonnet-4.0请求直接报模型不存在。把这三件套记牢Base URL https://taotoken.net/apiKey 控制台生成的那串Model ID 平台列出的准确名称。后面无论是 FastMCP 里调用模型还是单独测试通道都围绕这三个值展开。3. 可复制配置FastMCP Server 与 settings 片段现在进入正题。先给你一份可以直接跑的 FastMCP Server 代码再给一份客户端侧的 settings 配置。先装依赖pip install fastmcp openai然后写 Server。这个例子提供两个工具查用户年龄、查用户所在城市。注意每个工具的 docstring 要写清楚用途和参数含义这是给模型看的。# fast_mcp_server.py from fastmcp import FastMCP mcp FastMCP(user-service) mcp.tool() def get_user_age(name: str) - str: 根据用户名查询年龄。参数 name 为用户名例如 Alice。 data {Alice: 30, Bob: 25} age data.get(name) if age is None: return f未找到用户 {name} 的年龄记录 return f{name} 的年龄是 {age} 岁 mcp.tool() def get_user_city(name: str) - str: 根据用户名查询所在城市。参数 name 为用户名例如 Alice。 cities {Alice: 北京, Bob: 上海} city cities.get(name) if city is None: return f未找到用户 {name} 的城市记录 return f{name} 所在城市是 {city} if __name__ __main__: mcp.run(transportsse, host127.0.0.1, port8080)启动后Server 会在http://127.0.0.1:8080/sse暴露 SSE 端点。FastMCP 会自动处理initialize、tools/list、tools/call这些方法你不用管。接下来是客户端侧的 settings 配置。如果你用的是支持 MCP 的编辑器或 Agent 框架通常会有一个 JSON 配置文件。下面这份是通用格式路径按你实际工具的约定放{ mcpServers: { user-service: { url: http://127.0.0.1:8080/sse, transport: sse } }, llm: { base_url: https://taotoken.net/api, api_key: 你的TaoToken密钥, model: claude-sonnet-4 } }如果你用的是 Cline 或类似工具MCP 配置段的名字可能是mcpServers字段也可能是commandargs而不是url。这时候改成{ mcpServers: { user-service: { command: python, args: [fast_mcp_server.py] } } }两种方式都行前者连已经跑起来的 SSE 服务后者让工具自己拉起进程。我一般本地开发用后者省得手动开两个终端。Codex 用户注意如果你走的是auth.json那套配置长这样{ base_url: https://taotoken.net/api, api_key: 你的TaoToken密钥, model: claude-sonnet-4 }三件套一个都不能少Base URL、Key、Model ID。少任何一个请求都会在鉴权或路由阶段挂掉。4. 验证请求从 tools/list 到模型调用成功配置写完得验证。分两步先确认 MCP Server 的工具能被发现再确认模型能通过 TaoToken 通道调用工具。第一步用 curl 直接打 SSE 端点看工具列表。FastMCP 的 SSE 传输需要先建立连接拿 session稍微麻烦点。更简单的办法是用 FastMCP 自带的客户端# verify_tools.py import asyncio from fastmcp import Client async def main(): async with Client(http://127.0.0.1:8080/sse) as client: tools await client.list_tools() for t in tools: print(f工具名: {t.name}) print(f描述: {t.description}) print(---) asyncio.run(main())跑起来应该看到两个工具名字和 docstring 都正确输出。如果这里报连接错误说明 Server 没起来或者端口不对。第二步验证 TaoToken 通道。写个最小脚本用 OpenAI SDK 打一次请求# verify_llm.py from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_key你的TaoToken密钥 ) resp client.chat.completions.create( modelclaude-sonnet-4, messages[{role: user, content: 只回复两个字通了}] ) print(resp.choices[0].message.content)如果输出通了说明 Key、Base URL、Model ID 三件套全部正确。这一步单独验证很重要因为后面 MCP 调用失败时你得能区分是工具侧的问题还是模型通道的问题。第三步把两者串起来。下面这段代码模拟完整链路模型收到Alice 多大了决定调用get_user_age拿到结果后生成自然语言回答。# full_chain.py import asyncio from fastmcp import Client from openai import OpenAI llm OpenAI(base_urlhttps://taotoken.net/api, api_key你的TaoToken密钥) async def main(): async with Client(http://127.0.0.1:8080/sse) as mcp: tools await mcp.list_tools() tool_desc \n.join([f- {t.name}: {t.description} for t in tools]) user_input Alice 多大了 prompt f你可以调用以下工具 {tool_desc} 用户问{user_input} 如果需要调用工具只输出工具名和参数格式CALL:工具名:参数值 resp llm.chat.completions.create( modelclaude-sonnet-4, messages[{role: user, content: prompt}] ) decision resp.choices[0].message.content.strip() print(f模型决策: {decision}) if decision.startswith(CALL:): _, tool_name, arg decision.split(:, 2) result await mcp.call_tool(tool_name, {name: arg}) print(f工具返回: {result}) asyncio.run(main())实测下来模型能正确识别出该调get_user_age参数传Alice工具返回年龄后链路闭合。这里的关键是tools/list返回的描述足够清晰模型才能选对工具。如果你的 docstring 写得含糊模型可能选错或者干脆不调。5. 常见报错排查401、local proxy failed 与 reading choices这一节列几个我实际遇到过的报错以及对应的排查动作。401 Unauthorized。这个最常见九成是 Key 的问题。先确认 Key 有没有多余空格再确认 Base URL 是不是写成了https://taotoken.net/api/v1。如果 Key 是从控制台复制的注意别把前后引号也带进去。还有一种情况是 Key 被禁用或额度耗尽去控制台 https://taotoken.net/api-keys 看一眼状态。local proxy failed / connection refused。这个报错通常出现在 MCP 客户端连不上 Server 的时候。检查三件事Server 进程是否在跑、端口是否被占用、URL 里的/sse后缀有没有漏。FastMCP 默认用 SSE 传输如果你写的是http://127.0.0.1:8080而不带/sse客户端会连到根路径直接失败。reading choices of undefined。这个报错来自 OpenAI SDK意思是响应体里没有choices字段。原因通常是 Base URL 拼错了请求打到了错误的端点返回了一个非标准格式的响应。回到三件套检查Base URL 必须是https://taotoken.net/api不能多也不能少。另外确认 Model ID 是平台支持的不支持的模型可能返回错误结构。OAuth 相关报错。如果你用的是 Claude Code 或某些需要 OAuth 流程的工具报错里出现OAuth字样说明鉴权方式选错了。TaoToken 走的是 API Key 方式不需要 OAuth。检查你的配置里是不是误开了 OAuth 选项或者环境变量ANTHROPIC_API_KEY没设对。Claude Code 的配置参考 https://taotoken.net/doc 里的说明把ANTHROPIC_BASE_URL设为https://taotoken.net/apiANTHROPIC_API_KEY设为你的 Key。工具调用返回空。如果tools/list能列出工具但tools/call返回空或者报方法不存在检查工具函数的参数类型注解是否完整。FastMCP 依赖类型注解生成参数 schema如果name: str写成了nameschema 生成会出问题调用时参数对不上。排查顺序建议先单独验证 LLM 通道verify_llm.py再单独验证 MCP 工具verify_tools.py最后跑完整链路。这样出问题时能快速定位是哪一段断了。6. 长期编码与 Agent 场景的接入建议如果你只是偶尔跑一次 MCP 调用上面的配置够用了。但如果你打算把 MCP 服务长期挂在 Agent 里跑有几个点值得注意。第一工具描述要持续迭代。模型选工具的准确率和 docstring 的质量强相关。我习惯在 docstring 里写清楚三件事这个工具做什么、参数是什么格式、什么情况下该用。比如查询用户年龄参数为用户名当用户询问年龄或生日相关问题时调用比只写查年龄效果好很多。第二Key 的管理要集中。不要在多个脚本里硬编码 Key统一走环境变量或者配置文件。TaoToken 的统一 Key 通道好处就在这里一个 Key 覆盖多个模型不用为每个厂商单独维护密钥。长期跑 Agent 的话建议用 Coding Plan 这类方案地址是 https://taotoken.net/coding-plan 适合需要持续调用、对稳定性有要求的场景。第三MCP Server 的传输方式按场景选。本地开发用 SSE 方便调试生产环境如果工具和 Agent 在同一台机器上可以用 stdio 传输省去网络开销。FastMCP 的mcp.run(transportstdio)就切换过去了代码不用改。第四模型选择上工具调用能力强的模型优先。不是所有模型都擅长按格式输出工具调用指令实测下来 Claude 系列在工具调用上的稳定性比较好。你可以在模型对话页 https://taotoken.net/models 对比不同模型的表现选一个适合你场景的。最后说个实际经验MCP 的价值不在于协议本身多复杂而在于它把模型能做什么这件事标准化了。你写一次工具换个模型、换个客户端照样能用。这种可移植性在模型快速迭代的当下比省几行代码重要得多。把工具注册和调用链路跑通之后剩下的就是不断往工具箱里加东西让模型的能力边界跟着你的业务一起长。