)
1. 为什么 Python 开发者需要一个自己的 MCP ServerMCP Server 是 Model Context Protocol 的服务端实现简单说就是给大模型装上一双手模型通过它调用你写的工具函数比如查数据库、读文件、调内部接口。Python 开发者做这件事有天然优势生态里现成的 SDK、异步框架、部署工具都能直接复用。适合谁来跟做这篇已经会写 Python 函数、想让 Claude 或其它支持 MCP 的客户端调用自己业务逻辑的人手里有多个工具服务、被一堆 API Key 散落在各个脚本里搞烦的人以及想把本地跑通的 MCP Server 真正部署到线上、让团队共用的人。我自己踩过的坑是工具一多每个工具各自读环境变量、各自配 Key本地能跑、上线就 401排查半天发现是某个服务的 Key 没注入。所以这篇的重点不只是写出一个 MCP Server而是把多工具调用的 Key/API 管理收敛到一条统一通道上再走完部署上线。整条链路分四步本地用 Python 写一个带两个工具的 MCP Server把模型调用统一指向 TaoToken 的 API 通道用配置文件管理 Key 和模型参数最后部署上线并做验证。下面按顺序来代码都可以直接复制。2. TaoToken 前置统一 Key 与 API 通道准备MCP Server 本身不产生模型能力它负责把工具暴露出去真正调用模型的那一层需要一个稳定的 API 入口。TaoToken 在这里扮演的角色就是统一通道一个 Key 走通多个模型省掉在每个工具里分别维护不同厂商 Key 的麻烦。你需要先拿到两样东西API Key 和接入地址。控制台里创建 Key 的入口在 https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite Key 管理页在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。API 的基础地址是 https://taotoken.net/api 注意这个地址后面不加任何查询参数直接作为 base_url 使用。注意Key 只放在服务端环境变量或配置文件里不要写进会提交到 Git 的代码。MCP Server 一旦上线客户端拿到的只是工具列表Key 不应该出现在任何返回给客户端的内容里。模型对话能力可以先在 https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 里验证确认 Key 可用、模型能正常返回再去写代码能省掉一轮到底是 Key 错还是代码错的排查。如果你后面要做长期编码类 Agent可以了解 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。接入文档在 https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 参数细节以文档为准。Claude Code 相关接入参考 https://taotoken.net/claudecode-anthropic?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。3. 可复制配置settings.json 与 config.toml 骨架先把配置层搭好再写业务代码。这样做的原因是MCP Server 里所有工具都从同一份配置读 Key 和 base_url改一处就全局生效不会出现某个工具漏配的情况。先建目录结构保持清晰mcp-demo/ ├── config/ │ ├── settings.json │ └── config.toml ├── server.py ├── requirements.txt └── .envconfig/settings.json放运行时参数模型名、超时、重试次数这类{ api: { base_url: https://taotoken.net/api, timeout: 60, max_retries: 3 }, model: { default: claude-sonnet-4-5, fallback: gpt-4o-mini }, server: { name: my-mcp-server, transport: stdio } }config/config.toml放工具级配置每个工具声明自己需要哪些能力但 Key 统一从环境变量注入不在这里写死[channel] provider taotoken base_url https://taotoken.net/api api_key_env TAOTOKEN_API_KEY [tools.weather] enabled true timeout 10 [tools.db_query] enabled true timeout 30 readonly true.env只放一行真实 Key并且加进.gitignoreTAOTOKEN_API_KEYsk-你的真实Keyrequirements.txt里装 MCP 官方 SDK 和配置解析依赖mcp1.0.0 httpx0.27.0 python-dotenv1.0.0 tomli2.0.0安装pip install -r requirements.txt配置层的设计要点就一句话Key 走环境变量地址和模型走配置文件工具开关走 TOML。三层分开上线时只改环境变量不动代码。4. 编写 MCP Server 并接入统一通道现在写server.py。核心是两件事用 MCP SDK 注册工具用统一配置构造模型客户端。下面这段可以直接跑。import os import json import tomli import httpx from dotenv import load_dotenv from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent load_dotenv() with open(config/settings.json, r, encodingutf-8) as f: SETTINGS json.load(f) with open(config/config.toml, rb) as f: TOOL_CONFIG tomli.load(f) API_KEY os.environ.get(TOOL_CONFIG[channel][api_key_env]) BASE_URL TOOL_CONFIG[channel][base_url] app Server(SETTINGS[server][name]) def build_client() - httpx.Client: return httpx.Client( base_urlBASE_URL, headers{Authorization: fBearer {API_KEY}}, timeoutSETTINGS[api][timeout], ) app.list_tools() async def list_tools(): return [ Tool( nameask_model, description把问题转发给统一通道的模型并返回回答, inputSchema{ type: object, properties: {prompt: {type: string}}, required: [prompt], }, ), Tool( nameecho, description回显输入用于连通性验证, inputSchema{ type: object, properties: {text: {type: string}}, required: [text], }, ), ] app.call_tool() async def call_tool(name: str, arguments: dict): if name echo: return [TextContent(typetext, textfecho: {arguments[text]})] if name ask_model: with build_client() as client: resp client.post( /v1/chat/completions, json{ model: SETTINGS[model][default], messages: [{role: user, content: arguments[prompt]}], }, ) resp.raise_for_status() data resp.json() answer data[choices][0][message][content] return [TextContent(typetext, textanswer)] raise ValueError(funknown tool: {name}) async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options()) if __name__ __main__: import asyncio asyncio.run(main())几个关键点解释一下。build_client里 base_url 直接用https://taotoken.net/api路径拼/v1/chat/completions这是 OpenAI 兼容风格换模型只改settings.json里的model.default。ask_model工具把模型调用包了一层好处是客户端不需要知道 Key它只调工具Key 留在服务端。echo工具是给验证用的不依赖网络能快速判断 Server 本身是否正常。如果你用的是 Claude Code 这类客户端接入方式参考前面给的 Claude Code 文档链接配置里填的同样是这个 base_url 和 Key。5. 验证请求与成功结果先本地验证再谈部署。分两步先验 Server 进程能起来、工具能列出再验模型调用能通。第一步用 MCP 官方的调试方式启动或者直接跑python server.py进程不报错、停在等待输入的状态说明配置加载和工具注册没问题。如果这里就崩八成是config.toml路径不对或TAOTOKEN_API_KEY没读到。第二步单独验证模型通道。写个最小脚本绕开 MCP 直接打 API确认 Key 和地址可用import os, httpx from dotenv import load_dotenv load_dotenv() r httpx.post( https://taotoken.net/api/v1/chat/completions, headers{Authorization: fBearer {os.environ[TAOTOKEN_API_KEY]}}, json{ model: claude-sonnet-4-5, messages: [{role: user, content: 只回复两个字通了}], }, timeout60, ) print(r.status_code) print(r.json()[choices][0][message][content])成功结果是状态码 200输出通了。这一步通了说明 Key、base_url、模型名三者都对。如果这步不通问题一定在通道层跟 MCP 代码无关排查范围立刻缩小。第三步把 MCP Server 挂到客户端里调用echo工具应返回echo: 你的输入再调用ask_model传入你好应返回模型回答。两个都通本地闭环完成。6. 部署上线与常见错排查部署到一台 Linux 服务器用 systemd 托管这是最省心的方式。先传代码、装依赖cd /opt/mcp-demo python3 -m venv venv source venv/bin/activate pip install -r requirements.txt创建/etc/systemd/system/mcp-server.service[Unit] DescriptionMy MCP Server Afternetwork.target [Service] Userubuntu WorkingDirectory/opt/mcp-demo EnvironmentFile/opt/mcp-demo/.env ExecStart/opt/mcp-demo/venv/bin/python /opt/mcp-demo/server.py Restartalways RestartSec5 [Install] WantedBymulti-user.target注意EnvironmentFile指向.envKey 通过 systemd 注入不写进 service 文件。然后sudo systemctl daemon-reload sudo systemctl enable mcp-server sudo systemctl start mcp-server sudo systemctl status mcp-server上线检查清单逐条过检查项期望结果服务状态active (running)环境变量systemctl show mcp-server -p Environment能看到 Key 已注入日志journalctl -u mcp-server -n 50无报错通道连通服务器上跑第 5 节的验证脚本返回 200客户端调用echo 与 ask_model 均正常返回常见错排查按出现频率排报 401 Unauthorized先看TAOTOKEN_API_KEY是否真的注入到进程里EnvironmentFile路径写错、.env里有多余空格都会导致读不到。报 404检查 base_url 是不是误加了路径或参数正确写法就是https://taotoken.net/api路径在代码里拼。报超时把settings.json里的timeout调大同时确认服务器出网正常。工具列不出来多半是app.list_tools()装饰器没生效或 SDK 版本不匹配锁一下mcp版本。本地通、线上不通九成是环境变量差异用systemctl show对比一下。提示MCP Server 不要直连生产数据库。config.toml里db_query的readonly true就是提醒工具层做只读约束别把写权限暴露给模型。7. 下一步把通道能力用起来到这里你已经有了一个能上线、Key 统一管理、工具可扩展的 MCP Server。后面加工具只需要在list_tools里注册、在call_tool里实现Key 和地址完全不用动这就是统一通道的价值。想继续验证模型能力去模型对话页试不同模型https://taotoken.net/model-chat?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。要做长期编码或 Agent 场景看 Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。Key 管理和新建在 https://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 接入细节查文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewrite 。一个实用建议把settings.json和config.toml纳入版本管理.env永远排除在外。团队协作时新人 clone 下来只需填自己的 Key其余配置直接复用上线流程能省掉大量沟通成本。