1. 为什么 Python 开发者第一次搭 MCP Server 总会卡住
MCP(Model Context Protocol)是 Anthropic 提出的模型上下文协议,它把「大模型调用外部工具」这件事从每个项目各写一套 Function Calling,变成了一套统一规范。你可以把它理解成 USB-C:以前每个设备一个充电口,现在统一接口,谁都能插。对 Python 开发者来说,MCP Server 就是那个「被插的设备」——你写一个本地服务,暴露几个工具函数,任何支持 MCP 的客户端都能直接调用。
但真正动手时,问题往往不在协议本身,而在三件事上:第一,客户端要调大模型,你得配 API Key、Base URL、模型名,散落在.env、config.toml、settings.json里,改一处忘一处;第二,本地 Server 用 stdio 启动,路径、解释器、依赖环境稍微不对就连不上;第三,第一次跑通前你根本不知道是 Key 错了、模型名错了,还是 Server 没起来。
这篇就聚焦这个落地场景:用 TaoToken 统一 Key 和 API 通道,把 Python 版 MCP Server 的最小闭环跑通。适合已经会写 Python、但没搭过 MCP 的同学。全程可复制,配置骨架、依赖命令、验证动作都给全。TaoToken 在这里的角色是统一入口——一个 Key、一个 Base URL,客户端和后续扩展都走它,省掉多套凭证来回切。
2. TaoToken 前置:拿 Key、认通道、装依赖
先说清楚 TaoToken 是什么、能做什么。它是一个统一的大模型 API 通道,你注册后拿到一个 API Key,就能通过统一的 Base URL 调用多种模型。对 MCP 场景来说,好处很直接:你的 MCP Client 只需要认一个BASE_URL和一个OPENAI_API_KEY,不用为每个模型单独配一套。
第一步,去官网注册并登录:
https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=登录后进控制台,创建 API Key。地址是:
https://taotoken.net/console创建完 Key 后,接入文档在:
https://taotoken.net/docAPI 的基础地址是https://taotoken.net/api,注意这个不带任何查询参数,直接作为base_url用。Key 的管理页面在:
https://taotoken.net/api-keys拿到 Key 之后,本地环境用uv管理最省事。MCP 官方 SDK 推荐用 uv,因为它装依赖快、虚拟环境干净。安装 uv:
# macOS / Linux curl -LsSf https://astral.sh/uv/install.sh | sh # 或者已经有 pip 的话 pip install uv然后建项目、建虚拟环境、装依赖:
uv init mcp-demo cd mcp-demo uv venv source .venv/bin/activate # Windows 用 .venv\Scripts\activate uv add mcp openai python-dotenv httpx这里mcp是官方 SDK,openai用来调模型(TaoToken 兼容 OpenAI 接口格式),python-dotenv读环境变量,httpx后面写工具函数发请求用。
3. 可复制配置:config.toml 与 settings.json 骨架
MCP 生态里配置分散在不同客户端,但核心就两个文件:一个是给客户端读的settings.json(或等价配置),一个是项目侧的config.toml。下面给的是最小可用骨架,你按自己路径改。
先建.env,把 TaoToken 的 Key 和通道写进去:
# .env OPENAI_API_KEY="你的 TaoToken API Key" BASE_URL="https://taotoken.net/api" MODEL="gpt-4o-mini"注意BASE_URL结尾不要带/v1,SDK 会自己拼。模型名按你实际能用的填,TaoToken 控制台里能看到可用模型列表。
然后是config.toml,放在项目根目录,描述这个 MCP Server 怎么启动:
# config.toml [server] name = "demo-server" transport = "stdio" command = "python" args = ["server.py"] [server.env] OPENAI_API_KEY = "${OPENAI_API_KEY}" BASE_URL = "${BASE_URL}"再是settings.json,这是给 MCP 客户端(比如支持 MCP 的编辑器或桌面端)读的,告诉它去哪找 Server:
{ "mcpServers": { "demo-server": { "command": "python", "args": ["/绝对路径/mcp-demo/server.py"], "env": { "OPENAI_API_KEY": "你的 TaoToken API Key", "BASE_URL": "https://taotoken.net/api" } } } }这里有个坑要提前说:args里的路径建议用绝对路径,相对路径在不同客户端的工作目录下会解析失败。Windows 上command可能要写python.exe的完整路径,或者用uv run python。
4. 写 Server 与 Client,跑通一次完整调用
先写 Server。新建server.py,用 FastMCP 暴露两个工具:一个加法,一个查天气(天气用假数据,避免依赖外部 Key,专注验证链路):
# server.py from mcp.server.fastmcp import FastMCP mcp = FastMCP("DemoServer") @mcp.tool() def add(a: int, b: int) -> int: """计算两个整数之和""" return a + b @mcp.tool() def get_weather(city: str) -> str: """查询指定城市的天气(示例数据)""" fake = {"beijing": "晴,26°C", "shanghai": "多云,24°C"} return fake.get(city.lower(), f"{city} 暂无数据") if __name__ == "__main__": mcp.run(transport="stdio")再写 Client。新建client.py,它做三件事:连上 Server、把工具列表转成 OpenAI 的 function 格式、调 TaoToken 通道让模型决定调哪个工具:
# client.py import asyncio import json import os import sys from contextlib import AsyncExitStack from dotenv import load_dotenv from openai import OpenAI from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client load_dotenv() class MCPClient: def __init__(self): self.exit_stack = AsyncExitStack() self.client = OpenAI( api_key=os.getenv("OPENAI_API_KEY"), base_url=os.getenv("BASE_URL"), ) self.model = os.getenv("MODEL") self.session = None async def connect(self, script: str): params = StdioServerParameters( command="python", args=[script], env=None ) transport = await self.exit_stack.enter_async_context( stdio_client(params) ) self.stdio, self.write = transport self.session = await self.exit_stack.enter_async_context( ClientSession(self.stdio, self.write) ) await self.session.initialize() tools = (await self.session.list_tools()).tools print("已连接,工具:", [t.name for t in tools]) async def ask(self, query: str) -> str: tools = (await self.session.list_tools()).tools available = [{ "type": "function", "function": { "name": t.name, "description": t.description, "parameters": t.inputSchema, }, } for t in tools] messages = [{"role": "user", "content": query}] resp = self.client.chat.completions.create( model=self.model, messages=messages, tools=available ) choice = resp.choices[0] if choice.finish_reason == "tool_calls": call = choice.message.tool_calls[0] name = call.function.name args = json.loads(call.function.arguments) result = await self.session.call_tool(name, args) print(f"[调用工具 {name} 参数 {args}]") messages.append(choice.message.model_dump()) messages.append({ "role": "tool", "content": result.content[0].text, "tool_call_id": call.id, }) final = self.client.chat.completions.create( model=self.model, messages=messages ) return final.choices[0].message.content return choice.message.content async def cleanup(self): await self.exit_stack.aclose() async def main(): client = MCPClient() try: await client.connect(sys.argv[1]) print(await client.ask("帮我算一下 12 加 30 等于多少")) print(await client.ask("北京天气怎么样")) finally: await client.cleanup() if __name__ == "__main__": asyncio.run(main())运行:
uv run client.py server.py5. 验证请求与成功结果
跑起来后,终端应该先打印工具列表,再打印两次模型回复。正常输出类似:
已连接,工具: ['add', 'get_weather'] [调用工具 add 参数 {'a': 12, 'b': 30}] 12 加 30 等于 42。 [调用工具 get_weather 参数 {'city': '北京'}] 北京当前天气:晴,26°C。看到[调用工具 ...]这行,就说明整条链路通了:Client 通过 TaoToken 通道把工具列表发给模型,模型返回tool_calls,Client 执行本地 Server 的工具,再把结果回传模型生成最终回答。这就是 MCP 的最小闭环。
如果你想单独验证模型通道是否正常,可以先用模型对话页面发一条消息:
https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=mcp_python&utm_campaign=rewrite如果那边能正常回,说明 Key 和通道没问题,问题就缩小到本地 Server 或配置上了。
6. 本篇常见错排查
报错ModuleNotFoundError: No module named 'mcp':虚拟环境没激活,或者uv add没执行。确认which python指向.venv里的解释器。
报错OPENAI_API_KEY not found:.env没被读到。检查load_dotenv()是否在读取环境变量之前调用,以及.env是否在运行目录下。
报错401 Unauthorized:Key 错了或过期。去 API Keys 页面重新生成:
https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=mcp_python&utm_campaign=rewrite报错Connection refused或 Server 无响应:settings.json里的路径不对,或者command找不到 python。改成绝对路径,Windows 上确认python.exe位置。
模型不调用工具,直接瞎答:工具函数的 docstring 太模糊。模型靠 description 判断该不该调,把"""计算两个整数之和"""写清楚,参数类型标注完整。
base_url拼错导致 404:TaoToken 的地址是https://taotoken.net/api,不要手动加/v1,SDK 会处理。加了反而变成/api/v1/v1。
stdio 传输下 print 干扰协议:Server 里不要用print输出调试信息,stdio 通道会被污染。要调试就写日志文件。
7. 下一步:从最小闭环到长期编码
跑通这个最小闭环后,你手里其实已经有了一套可复用的骨架:Server 加工具就是加@mcp.tool()函数,Client 不用改。接下来可以往两个方向走。
一是把工具做真。比如接数据库查询、接内部 API、接文件系统,每个工具一个函数,docstring 写清楚,模型就能自动调度。这时候你会频繁改代码、反复跑,建议用 Coding Plan 把编码流程固定下来:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=mcp_python&utm_campaign=rewrite二是把 Client 接到你日常用的编辑器或 Agent 框架里。MCP 的价值在于「一次编写,到处调用」,你写的 Server 不只能被这个 Python Client 用,任何支持 MCP 的客户端都能接。接入细节看文档:
https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=mcp_python&utm_campaign=rewrite我自己的习惯是:先用最小闭环确认通道和协议都通,再逐个把工具替换成真实实现,每加一个就跑一次验证。这样出问题时范围永远很小,不会一上来就面对一堆不确定性。