1. 为什么 MCP 本地开发第一步总是卡在环境上MCPModel Context Protocol这两年被讨论得很多但真正动手写第一个 STDIO Server 的人大概率会在环境这一步先卡半天。我自己第一次搭的时候光是「uv 装完却找不到命令」「虚拟环境激活了但 mcp 模块 import 失败」「STDIO 一跑就退出、看不到任何日志」这三件事就来回折腾了一个下午。所以这篇不聊概念只聊怎么把 MCP 本地开发环境一次搭通并且顺手把 OpenAI 兼容 endpoint 切到 TaoToken 的统一 Key 通道上让后面写工具、调模型都省心。先说清楚这套东西是什么、能做什么、适合谁。MCP 是一套让大模型通过标准协议调用外部工具的规范STDIO 是它最轻量的一种传输方式——Server 和 Client 通过标准输入输出通信不需要开端口、不需要网络配置特别适合本地开发和调试。uv 是 Astral 出的 Python 包与环境管理器速度比 pip 快很多能一条命令建虚拟环境、装依赖、锁版本。适合谁适合已经会一点 Python、想动手写第一个 MCP Server 的开发者也适合想把现有 OpenAI 兼容调用统一到一个 Key 通道、不想在多个平台之间来回切的人。这篇的路径是这样的先用 uv 把项目骨架和依赖搭好再写一个最小的 STDIO MCP Server就做加减乘除四个工具然后写一个 Client 去连它最后把 Client 里调模型的那段 endpoint 换成 TaoToken 的 OpenAI 兼容地址。全程可复制命令和配置我都给全。踩过的坑我会在第五节单独列出来对照真实报错讲怎么修。有一点先提醒MCP 的 Python SDK 迭代很快本文基于mcp1.x 系列的 FastMCP 写法Python 版本要求不低于 3.10。如果你用的是 3.9uv venv那一步就会直接报错别怀疑人生先升 Python。2. 用 uv 初始化 MCP 项目与依赖管理这一节把项目骨架搭起来。核心就四步装 uv、init 项目、建虚拟环境、加依赖。每一步我都给出命令和预期输出你照着敲就行。2.1 安装 uv 并初始化项目如果你机器上还没有 uv先装。官方推荐用独立安装脚本但为了简单用 pip 装也可以pip install uv装完验证一下uv --version # 预期输出类似uv 0.5.x如果提示uv: command not found说明 pip 的 bin 目录不在 PATH 里这是第一个高频坑第五节细讲。接着创建项目目录并初始化uv init mcp-client cd mcp-clientuv init会生成一个pyproject.toml和一个main.py。这个main.py我们后面会改名成mcp-client.py因为 MCP 官方示例里 Client 就叫这个名字保持一致方便对照文档。2.2 创建虚拟环境并指定 Python 版本uv venv --python 3.11这里显式指定 3.11是因为 MCP SDK 用到了不少 3.10 的类型语法。如果你不指定uv 会用系统默认版本万一是 3.9 就会在后面 import 时报语法错误。创建完激活# Windows .venv\Scripts\activate # macOS / Linux source .venv/bin/activate激活后命令行前面会出现(mcp-client)前缀。确认一下版本python --version # 预期Python 3.11.x2.3 添加依赖uv add mcp openai python-dotenvuv add会同时做三件事解析依赖、写入pyproject.toml、更新uv.lock。比 pip 手动 freeze 干净得多。装完你的pyproject.toml里应该能看到类似这样的依赖段[project] name mcp-client version 0.1.0 requires-python 3.10 dependencies [ mcp1.0.0, openai1.0.0, python-dotenv1.0.0, ]这里有个细节值得说uv add默认会把依赖写进[project].dependencies而不是[tool.uv]。如果你后面想区分开发依赖用uv add --dev。MCP 项目里mcp是运行时依赖别加成 dev。2.4 目录结构规划搭完依赖建议把目录整理成下面这样后面 Client 和 Server 分开互不干扰mcp-client/ ├── .venv/ ├── .env ├── pyproject.toml ├── uv.lock ├── mcp-client.py # 客户端 └── servers/ └── math_server.py # STDIO 服务端把main.py改名成mcp-client.py再建一个servers目录放 Server 脚本。这样 Client 启动时用命令行参数传 Server 路径一个 Client 可以挂多个 Server扩展性更好。到这一步环境骨架就搭完了。下一节写 Server 和 Client 的实际代码以及把模型 endpoint 切到 TaoToken 的配置。3. 可复制的 MCP Server 与 TaoToken 接入配置这一节是全文最核心的部分分三块写 STDIO Server、写 Client、配置 TaoToken 的 OpenAI 兼容 endpoint。配置片段我都给全路径和原文一致直接复制改 Key 就能跑。3.1 写一个最小的 STDIO MCP Server在servers/math_server.py里写下面这段。它用 FastMCP 注册四个数学工具通过 STDIO 通信import asyncio import logging from mcp.server.fastmcp import FastMCP from mcp.server import InitializationOptions, NotificationOptions from mcp.server.stdio import stdio_server MCP_SERVER_NAME math-stdio-server logging.basicConfig( levellogging.INFO, format%(asctime)s - %(name)s - %(levelname)s - %(message)s ) logger logging.getLogger(MCP_SERVER_NAME) mcp FastMCP(MCP_SERVER_NAME) mcp.tool() def add(a: float, b: float) - float: 两数相加 return a b mcp.tool() def subtract(a: float, b: float) - float: 两数相减 return a - b mcp.tool() def multiply(a: float, b: float) - float: 两数相乘 return a * b mcp.tool() def divide(a: float, b: float) - float: 两数相除除数为零时抛错 if b 0: raise ValueError(除数不能为零) return a / b async def main(): async with stdio_server() as (read_stream, write_stream): init_options InitializationOptions( server_nameMCP_SERVER_NAME, server_version1.0.0, capabilitiesmcp._mcp_server.get_capabilities( notification_optionsNotificationOptions(), experimental_capabilities{} ) ) logger.info(通过 STDIO 模式启动 MCP Server ...) await mcp._mcp_server.run(read_stream, write_stream, init_options) if __name__ __main__: asyncio.run(main())关键点mcp.tool()装饰器把函数注册成工具参数类型和文档字符串会自动变成工具的元数据Client 通过list_tools()就能拿到。stdio_server()建立标准输入输出通道run()开始监听请求。3.2 配置 TaoToken 的 OpenAI 兼容 endpoint在项目根目录建.env文件。这里就是接入 TaoToken 的地方——它提供 OpenAI 兼容接口所以只要把BASE_URL和API_KEY换掉Client 代码一行都不用改API_KEY你的TaoToken_API_Key BASE_URLhttps://taotoken.net/api MODEL_NAME你的模型ID注意BASE_URL填https://taotoken.net/api不要带多余的/v1后缀OpenAI SDK 会自己拼路径。Key 在控制台的 API Keys 页面生成模型 ID 按你实际要用的填。如果你还没建 Key先去 API Keys 页面 生成一个再回来填。3.3 写 Client 并读取配置mcp-client.py的核心逻辑是读.env、连 Server、把工具列表转成 OpenAI 的 function 格式、调模型、执行工具调用。下面给出关键片段import asyncio import json import os import sys from typing import List from contextlib import AsyncExitStack from mcp import ClientSession, StdioServerParameters from mcp.client.stdio import stdio_client from openai import AsyncOpenAI from dotenv import load_dotenv load_dotenv() class MCPClient: def __init__(self, model_name, base_url, api_key, server_scripts: List[str]): self.model_name model_name self.base_url base_url self.api_key api_key self.server_scripts server_scripts self.sessions {} self.tool_mapping {} self.client AsyncOpenAI(base_urlbase_url, api_keyapi_key) self.exit_stack AsyncExitStack() async def initialize_sessions(self): for i, script in enumerate(self.server_scripts): if not (os.path.exists(script) and script.endswith(.py)): print(f脚本 {script} 不存在或不是 .py 文件跳过。) continue server_id fserver{i} params StdioServerParameters(commandpython, args[script], envNone) try: stdio_ctx stdio_client(params) stdio await self.exit_stack.enter_async_context(stdio_ctx) session_ctx ClientSession(*stdio) session await self.exit_stack.enter_async_context(session_ctx) await session.initialize() self.sessions[server_id] (session, session_ctx, stdio_ctx) response await session.list_tools() for tool in response.tools: self.tool_mapping[f{server_id}_{tool.name}] (session, tool.name) print(f已连接到 {script}工具{[t.name for t in response.tools]}) except Exception as e: print(f连接 {script} 失败{e})AsyncOpenAI(base_urlbase_url, api_keyapi_key)这一行就是接入点。因为 TaoToken 是 OpenAI 兼容的所以base_url直接指向https://taotoken.net/api其余调用方式和原生 OpenAI 完全一致。工具调用循环里把tool_mapping里的前缀名映射回原始工具名再session.call_tool()执行结果回填给模型继续生成。主函数里从环境变量读配置async def main(): model_name os.getenv(MODEL_NAME) base_url os.getenv(BASE_URL, https://taotoken.net/api) api_key os.getenv(API_KEY) if not api_key: print(未设置 API_KEY 环境变量) sys.exit(1) if len(sys.argv) 2: print(使用方法: python mcp-client.py path_to_server_script) sys.exit(1) server_scripts sys.argv[1].split(,) client MCPClient(model_name, base_url, api_key, server_scripts) try: await client.initialize_sessions() await client.chat_loop() finally: await client.cleanup()到这里Server、Client、TaoToken 配置三件套就齐了。下一节跑一次真实请求验证。4. 跑通一次 STDIO 调用与成功结果验证配置写完不跑等于没写。这一节给你完整的启动命令和预期输出照着对一遍就知道通没通。4.1 启动 Client 并连接 Server确保虚拟环境已激活在项目根目录执行python mcp-client.py servers/math_server.py预期输出已连接到 servers/math_server.py工具[add, subtract, multiply, divide] MCP 客户端已启动输入问题输入 quit 退出。 问题:看到「已连接到」和工具列表说明 STDIO 通道建立成功Server 的工具已经被 Client 拿到。如果这一步卡住不动多半是 Server 脚本里有 import 错误第五节讲怎么定位。4.2 发一个会触发工具调用的请求在问题:后面输入帮我算一下 128 乘以 47 等于多少预期输出简化[调用 server0_multiply 参数: {a: 128, b: 47}] 工具结果: [TextContent(typetext, text6016)] 128 乘以 47 等于 6016。这条链路完整走通了模型判断需要调用工具 → Client 通过 STDIO 把请求发给 Server → Server 执行multiply返回 6016 → 结果回填给模型 → 模型生成自然语言回答。整个过程没有开任何端口全靠标准输入输出。4.3 验证 TaoToken 通道确实生效想确认请求真的走了 TaoToken 而不是别的地址有两个办法。一是看.env里的BASE_URL是不是https://taotoken.net/api二是在 Client 初始化时打印一下print(f当前 endpoint: {self.base_url}, model: {self.model_name})如果打印出来是https://taotoken.net/api说明 OpenAI 兼容通道已经切过来了。你也可以在 模型对话页面 单独发一条消息确认 Key 和模型 ID 本身是通的排除是 Client 代码问题还是配置问题。4.4 多 Server 场景验证Client 支持逗号分隔多个 Server 脚本python mcp-client.py servers/math_server.py,servers/text_server.py每个 Server 的工具会带上server0_、server1_前缀避免命名冲突。这是官方示例里没有、但实际开发很需要的增强点。跑通单 Server 后建议试一下多 Server确认tool_mapping的前缀逻辑没问题。到这一步一次完整的 STDIO 调用就验证完了。下一节把常见报错列出来。5. 常见报错排查401、local proxy failed 与 reading choices这一节按真实报错来。我把搭环境过程中遇到的和读者反馈最多的几类列出来每条给现象、原因、修法。5.1 401 Unauthorized现象Client 启动正常一发问题就报openai.AuthenticationError: Error code: 401。原因基本是 Key 没读到或填错。检查顺序.env里API_KEY有没有值load_dotenv()有没有在AsyncOpenAI初始化之前调用Key 有没有多余空格或引号。特别注意.env文件不要写成API_KEYsk-xxx带引号dotenv 会把引号也读进去。改成API_KEYsk-xxx即可。如果确认 Key 没问题去 API Keys 页面 重新生成一个再试。5.2 local proxy failed / connection error现象报APIConnectionError或local proxy failed。这类多半是BASE_URL写错。常见错误是写成https://taotoken.net/api/v1多了一层/v1OpenAI SDK 拼出来就是/api/v1/chat/completions路径不对。正确写法是https://taotoken.net/api。另外检查有没有系统级的环境变量HTTP_PROXY之类干扰如果有临时 unset 掉再跑。5.3 reading choices 相关报错现象TypeError: NoneType object is not subscriptable或读response.choices[0]时报错。原因通常是模型返回结构和你预期不一致或者模型 ID 填错导致返回了错误体。先确认MODEL_NAME是有效的模型 ID再在process_query里加一层保护if not response.choices: return 模型未返回有效结果请检查 MODEL_NAME 与 endpoint 配置这样至少不会直接崩能看到更明确的提示。5.4 uv 命令找不到 / 虚拟环境激活失败现象uv: command not found或激活后python还是系统版本。前者是 pip 的 bin 目录不在 PATH用python -m uv --version能跑通就说明装上了把对应目录加进 PATH 即可。后者是激活命令用错平台Windows 用.venv\Scripts\activatemacOS/Linux 用source .venv/bin/activate别混。激活成功的标志是命令行前缀出现(mcp-client)。5.5 STDIO Server 启动即退出、无日志现象Client 报连接失败Server 单独跑也没输出。单独跑一下 Server 看报错python servers/math_server.py如果直接抛ModuleNotFoundError: No module named mcp说明虚拟环境没激活或依赖没装到当前环境。用uv add mcp重装一次确认uv pip list | grep mcp能看到。如果 Server 跑起来后卡住不动那是正常的——它在等 STDIO 输入不是死了。5.6 工具调用参数解析失败现象json.loads(call.function.arguments)抛JSONDecodeError。模型偶尔会返回不规范的 JSON。加个 try 包一下解析失败时把原始字符串回填给模型让它重试比直接崩掉体验好。这也是实际开发里必须处理的边界。把这几类排掉环境基本就稳了。下一节给后续接入的入口。6. 后续接入与长期编码的入口环境跑通只是第一步。接下来你大概率会做两件事一是把更多工具注册进 Server二是把模型调用稳定下来长期用。如果你要继续写工具、调模型接入文档里有完整的协议说明和示例建议对照着看接入文档。想单独验证某个模型 ID 能不能用、返回格式对不对直接在 模型对话页面 发一条消息最快不用每次都跑 Client。如果你打算把 MCP 这套东西用在长期编码或 Agent 场景上反复手动配 Key、切模型会很烦。这种情况可以看下 Coding Plan把 Key 通道和模型选择统一管理省得每个项目都复制一遍.env。Key 不够用或者要分项目隔离就去 API Keys 页面 多建几个。最后给个实用建议把.env加进.gitignore别把 Key 提交上去。Server 脚本按功能拆文件一个 Server 别塞太多工具工具描述写清楚模型选工具的准确率会高很多。环境搭一次就够后面就是往里加工具的事了。