1. 从零写一个 MCP Server,为什么总卡在 Key 和鉴权上
如果你已经读过 MCP 协议的基础文档,知道 Tool、Resource、Prompt 三件套分别由模型、应用、用户触发,那么下一步大概率就是打开官方 Python SDK,准备写一个能被 Cline MCP、Claude Code 这类客户端真正调起来的本地 Server。问题往往不在协议本身,而在“接上模型”的那一刻:Server 里要调大模型做总结、做代码审核、做语义检索,于是你开始往代码里塞 OpenAI Key、Claude Key、DeepSeek Key,每个工具函数一套鉴权,环境变量越堆越多,换一个模型就要改一次配置。
MCP Server 开发指南里最容易被忽略的一环,就是模型访问通道的统一。MCP 协议解决的是“客户端怎么发现和调用你的工具”,但工具内部要访问哪个模型、用哪个 Key、走哪个 Base URL,协议并不管。这部分如果每个 Server 各写一套,本地调试时还能忍,一旦你要同时维护三四个 Server、又想让 Cline MCP 和 Claude Code 都能调用,Key 分散和鉴权重复就会变成主要摩擦。
这篇内容面向已经了解 MCP 协议、准备用官方 SDK 动手写 Server 的 Python 开发者。我会带你从零搭一个本地 Server,把模型访问统一到 TaoToken 的 Key 上,再用 MCP Inspector 发起一次真实的工具调用,确认请求经统一通道正常返回。全程可复制,代码骨架、环境变量片段、验证命令都会给全。适合谁:手上有 Python 3.10+ 环境、想把自己的工具暴露给 MCP 客户端、又不想为每个模型单独维护鉴权的开发者。
2. TaoToken 前置:把多模型 Key 收敛成一个环境变量
在写 Server 之前,先把模型访问通道定下来。MCP Server 内部调用模型时,本质上就是一次 HTTP 请求,需要三样东西:Base URL、API Key、Model ID。传统做法是每个模型一套,OpenAI 一套、Claude 一套、国产模型再一套,工具函数里写死或者读不同的环境变量。TaoToken 的思路是把这三样收敛成一套:一个 Base URL 指向统一入口,一个 Key 覆盖多个模型,Model ID 在请求时按需切换。
对 MCP Server 开发者来说,这个收敛带来的直接好处是:你的 Server 代码里只需要读一个环境变量TAOTOKEN_API_KEY,Base URL 固定为https://taotoken.net/api,模型名通过参数传入。这样无论你后面接的是 Cline MCP 还是 Claude Code,Server 侧的鉴权逻辑只有一份,不会因为客户端不同而重复配置。
先拿到 Key。访问官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,注册后在控制台创建 API Key。控制台地址是 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite ,Key 管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite 。创建后复制那串以sk-开头的字符串,后面配置环境变量要用。
拿到 Key 之后,建议先单独验证一次模型通道是否通,再写 Server。用 curl 发一个最小请求:
curl https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer $TAOTOKEN_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "model": "gpt-4o-mini", "messages": [{"role": "user", "content": "ping"}] }'如果返回里有choices字段和正常的文本内容,说明 Key 和通道都没问题。这一步很重要,因为后面 MCP Server 报错时,你要能区分是 Server 代码问题还是通道问题。我试过在 Server 里排查半天,最后发现是环境变量没导出,所以先单独验证通道能省很多时间。
环境变量建议写进 shell 配置文件,而不是硬编码在代码里。Linux/macOS 下编辑~/.zshrc或~/.bashrc:
export TAOTOKEN_API_KEY="sk-你的Key" export TAOTOKEN_BASE_URL="https://taotoken.net/api"Windows PowerShell 下用:
$env:TAOTOKEN_API_KEY="sk-你的Key" $env:TAOTOKEN_BASE_URL="https://taotoken.net/api"写完后source ~/.zshrc或重开终端,用echo $TAOTOKEN_API_KEY确认能打印出来。这一步做完,前置就齐了:一个 Key、一个 Base URL、一个可用的模型通道。接下来写 Server 时,所有模型调用都走这套配置。
3. 可复制配置:MCP Server 骨架 + 统一 Key 接入
现在开始写 Server。先装 SDK,Python 版本要求 3.10+,用 uv 或 pip 都行:
uv add "mcp[cli]" # 或者 pip install "mcp[cli]"SDK 装好后,创建一个server.py。下面是一个完整的骨架,包含一个调用模型的 Tool、一个 Resource、一个 Prompt,模型访问统一走 TaoToken:
import os import httpx from mcp.server import MCPServer mcp = MCPServer(name="taotoken-demo") TAOTOKEN_API_KEY = os.environ["TAOTOKEN_API_KEY"] TAOTOKEN_BASE_URL = os.environ.get("TAOTOKEN_BASE_URL", "https://taotoken.net/api") async def call_model(prompt: str, model: str = "gpt-4o-mini") -> str: """统一的模型调用入口,所有工具都走这里""" async with httpx.AsyncClient(timeout=60) as client: resp = await client.post( f"{TAOTOKEN_BASE_URL}/v1/chat/completions", headers={ "Authorization": f"Bearer {TAOTOKEN_API_KEY}", "Content-Type": "application/json", }, json={ "model": model, "messages": [{"role": "user", "content": prompt}], }, ) resp.raise_for_status() data = resp.json() return data["choices"][0]["message"]["content"] @mcp.tool() async def summarize(text: str, model: str = "gpt-4o-mini") -> str: """对给定文本做概括总结,model 参数可切换底层模型""" return await call_model(f"请概括以下内容:\n\n{text}", model=model) @mcp.resource(uri="config://models") def available_models() -> str: """返回当前可用的模型列表""" return "gpt-4o-mini\nclaude-3-5-sonnet\ndeepseek-chat" @mcp.prompt() def review_code(code: str) -> str: """审核代码片断""" return f"请审核这段代码并指出潜在问题:\n\n{code}" if __name__ == "__main__": mcp.run()这段代码的关键点在于call_model这个统一入口。所有需要访问模型的 Tool 都调它,Key 和 Base URL 只在这里读一次。你后面加新工具时,不用再关心鉴权,直接调call_model就行。model参数暴露给模型,意味着模型可以在调用工具时自己选底层模型,比如简单任务用gpt-4o-mini,复杂任务切claude-3-5-sonnet。
如果你用 Cline MCP 作为客户端,它的配置是一个 JSON 文件,通常在 Cline 的 MCP 设置里。把 Server 注册进去的配置片段如下:
{ "mcpServers": { "taotoken-demo": { "command": "uv", "args": [ "run", "--with", "mcp[cli]", "mcp", "run", "/绝对路径/server.py" ], "env": { "TAOTOKEN_API_KEY": "sk-你的Key", "TAOTOKEN_BASE_URL": "https://taotoken.net/api" } } } }注意env字段,这里把 Key 直接传给子进程,避免依赖 shell 环境变量。路径必须是绝对路径,Cline 以子进程方式启动 Server,相对路径会找不到文件。如果你用 Claude Code,注册命令是:
claude mcp add taotoken-demo -- uv run --with "mcp[cli]" mcp run /绝对路径/server.pyClaude Code 会读取当前 shell 的环境变量,所以TAOTOKEN_API_KEY要先导出。如果你更习惯用配置文件,Claude Code 的 MCP 配置在~/.claude.json或项目级.mcp.json,结构跟上面 Cline 的 JSON 类似,把command、args、env三件套写全即可。这里的三件套是:Base URL 固定https://taotoken.net/api,Key 用TAOTOKEN_API_KEY,Model ID 在工具调用时通过参数传入。
4. 验证请求:用 MCP Inspector 发起一次工具调用
Server 写好了,先别急着接客户端,用 MCP Inspector 验证一遍。Inspector 是官方提供的调试工具,能列出 Server 暴露的所有 Tool、Resource、Prompt,还能直接发起调用看返回。运行命令:
uv run mcp dev server.py第一次运行会提示安装 Node.js 依赖,按提示确认即可。启动成功后会自动打开浏览器,默认端口 6274 和 6277。如果浏览器没自动打开,手动访问http://localhost:6274。
在 Inspector 界面里,左侧会显示连接的 Server,点开后能看到Tools、Resources、Prompts三个标签。切到 Tools,应该能看到summarize这个工具,参数是text和model。点进去,在text里填一段测试文本,比如“MCP 是模型上下文协议,用于标准化客户端和服务端的交互”,model保持默认,点Run Tool。
如果一切正常,右侧会返回一段概括后的文本。这个返回就是经 TaoToken 统一通道拿到的模型输出。你可以在 Inspector 的请求日志里看到完整的 HTTP 调用,确认 Base URL 是https://taotoken.net/api,鉴权头是 Bearer 格式。
再验证 Resource。切到 Resources 标签,应该能看到config://models,点开后返回三行模型名。Resource 不需要模型参与,直接读函数返回值,所以这一步验证的是 Server 本身的能力声明是否正常。
最后验证 Prompt。切到 Prompts,选review_code,填入一段代码,比如def add(a, b): return a + b,执行后会生成一条用户消息,内容是“请审核这段代码并指出潜在问题”。Prompt 的返回是消息模板,不会直接调模型,它只是把渲染好的消息交给客户端,由客户端决定是否发给模型。
三步都通过后,说明 Server 的 Tool、Resource、Prompt 都能正常工作,模型通道也通了。这时候再去 Cline MCP 或 Claude Code 里注册,基本不会出问题。如果 Inspector 里就报错,先解决 Inspector 的问题,别急着接客户端,否则客户端报错信息更难看懂。
5. 本篇常见错排查:401、local proxy failed、reading choices
即使按上面的步骤走,也可能遇到报错。下面是我踩过的几个坑,对照着排查。
401 Unauthorized。Inspector 里调用summarize返回 401,说明 Key 没传对。先检查echo $TAOTOKEN_API_KEY能不能打印出sk-开头的字符串。如果打印为空,说明环境变量没导出,回到第 2 节重新配置。如果打印正常但 Inspector 里还是 401,检查server.py里读的是不是os.environ["TAOTOKEN_API_KEY"],有没有拼写错误。还有一种情况是 Key 被复制时带了空格或换行,用echo $TAOTOKEN_API_KEY | wc -c看长度是否合理。
local proxy failed。这个报错通常出现在客户端侧,比如 Cline MCP 启动 Server 时。原因是客户端以子进程方式启动 Server,但command或args写错了。检查 JSON 配置里的路径是不是绝对路径,uv是否在 PATH 里。如果用的是虚拟环境,uv run可能找不到依赖,改成uv run --with "mcp[cli]"显式带上依赖。另外,Windows 下路径要用双反斜杠或正斜杠,别用单反斜杠。
reading choices 报错。这个报错来自模型返回解析,通常是data["choices"]取不到。原因可能是模型名写错了,比如gpt-4o-mini拼成gpt4o-mini,或者模型名不在 TaoToken 支持的列表里。先回到第 2 节的 curl 命令,用同样的模型名测一次,确认通道返回正常。如果 curl 正常但 Server 报错,检查call_model里的resp.raise_for_status()有没有被触发,可以在raise_for_status前打印resp.status_code和resp.text看具体返回。
OAuth 相关报错。如果你在 Claude Code 里看到 OAuth 字样,通常是客户端在尝试用 OAuth 方式鉴权,而你的 Server 用的是 API Key。检查 Claude Code 的 MCP 配置,确保env里传了TAOTOKEN_API_KEY,而不是依赖客户端的 OAuth 流程。Claude Code 的claude mcp add命令默认会读环境变量,如果环境变量没导出,它会尝试其他鉴权方式,导致 OAuth 报错。
Inspector 连不上 Server。检查端口 6274 和 6277 是否被占用,用lsof -i :6274看有没有其他进程。如果被占用,关掉占用进程或换端口。另外检查防火墙是否允许本地回环连接,有些安全软件会拦截 localhost 的端口监听。
排查顺序建议:先 curl 验证通道,再 Inspector 验证 Server,最后客户端验证集成。每一步都确认通过再往下走,别跳步。
6. 把统一 Key 用起来:从验证到长期编码
Server 跑通之后,你可以把call_model这个统一入口复制到其他 Server 里。每个 Server 只需要读TAOTOKEN_API_KEY和TAOTOKEN_BASE_URL,模型名通过参数暴露。这样你维护的多个 MCP Server 共享一套鉴权,换 Key 时只改一个环境变量,不用逐个改代码。
如果你打算长期用 MCP Server 做编码辅助,比如让 Cline MCP 调用你的工具做代码审核、文档生成、语义检索,可以考虑用 Coding Plan 来管理模型调用额度。地址是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding-plan&utm_campaign=rewrite ,适合需要稳定调用多个模型的场景。如果只是偶尔验证模型返回,用模型对话页面就够了:https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_content=chat&utm_campaign=rewrite 。
接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite ,里面有各语言 SDK 的接入示例,Python 部分可以直接对照call_model的写法。API Key 管理在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api-keys&utm_campaign=rewrite ,建议给不同的 Server 创建不同的 Key,方便按 Server 维度排查调用量。
最后给一个实用技巧:在call_model里加一行日志,把model和resp.status_code打出来。本地调试时这行日志能帮你快速定位是模型名问题还是通道问题。生产环境可以关掉,或者改成结构化日志。MCP Server 的调试体验很大程度上取决于你能不能快速看到每次模型调用的实际参数和返回,这行日志比任何断点都直接。