MCP 服务开发笔记:用 uv 虚拟环境在 Cursor 里跑通 mcp.json 配置

📅 发布时间:2026/9/30 22:25:32
MCP 服务开发笔记:用 uv 虚拟环境在 Cursor 里跑通 mcp.json 配置
1. 为什么我放弃了 pipvenv改用 uv 跑 MCP 服务如果你最近在折腾 MCPModel Context Protocol大概率会遇到一个很具体的场景写好了服务端代码想在 Cursor 里加载调试结果 Cursor 报「command not found」或者「ModuleNotFoundError: No module named mcp」。我一开始也踩过这个坑后来发现问题的根源不在代码而在虚拟环境的管理方式上。MCP 服务本质上是一个本地进程Cursor 只负责按mcp.json里的配置去启动它然后通过标准输入输出或者 SSE 跟它通信。这意味着 Cursor 不会帮你装依赖也不会自动激活虚拟环境。你配置里写的command和args必须能直接跑起来否则就是红点启动失败。传统的做法是python -m venv .venv加source .venv/bin/activate再pip install mcp。这套流程本身没问题但放到 Cursor 的mcp.json里就很别扭——你得把command指向虚拟环境里的 python 绝对路径比如/Users/you/project/.venv/bin/python。一旦项目换目录、换机器这个路径就失效了得手动改配置。uv 解决的就是这个痛点。它是用 Rust 写的 Python 包管理器能自动管理虚拟环境你不需要手动创建和激活。在mcp.json里只需要写uv --directory /path/to/project run main.pyuv 会自己识别项目里的.venv和依赖找不到就自动建。实测下来配置更短跨机器迁移也省事。这篇文章面向的是想从零跑通一个 MCP 服务的开发者不管你是刚接触 MCP 协议还是已经在用 Cursor 但被环境问题卡住。我会给出可直接复制的 uv 初始化命令、mcp.json字段模板、Cursor 侧验证步骤以及几个真实报错的排查方法。核心检索词就是 MCP 服务开发、uv 虚拟环境、Cursor mcp.json 配置你跟着做就能跑通一个可用的本地 MCP 服务。先说清楚 MCP 能做什么它让 AI 助手比如 Cursor 里的 Agent能调用你本地定义的工具比如查数据库、读文件、调内部 API。适合谁适合想把 AI 编码助手接入自己工具链的人。不适合谁如果你只是想用 Cursor 写代码不需要自定义工具那 MCP 暂时用不上。下面进入正题先讲环境准备再讲配置最后讲验证和排障。2. uv 安装与 MCP 项目初始化从零搭一个隔离虚拟环境这一节的目标是让你在本地有一个能跑的 MCP 服务项目依赖装在隔离的虚拟环境里不污染系统 Python。整个过程分三步装 uv、初始化项目、加 MCP 依赖。2.1 安装 uvuv 的安装是一条命令的事。macOS 和 Linux 下curl -LsSf https://astral.sh/uv/install.sh | shWindows 下用 PowerShellpowershell -ExecutionPolicy ByPass -c irm https://astral.sh/uv/install.ps1 | iex装完之后验证一下uv --version能输出版本号就说明装好了。如果提示command not found检查一下~/.local/bin或者~/.cargo/bin有没有加到 PATH 里。uv 安装脚本默认会把可执行文件放到~/.local/bin你需要确保这个目录在 PATH 中。2.2 初始化 MCP 项目找一个你放项目的目录执行uv init my-mcp-server cd my-mcp-server这条命令会创建一个新目录my-mcp-server里面生成pyproject.toml、main.py等基础文件。uv init默认会创建一个pyproject.toml这是 uv 管理依赖的核心文件相当于requirements.txt的升级版。接着加 MCP 依赖uv add mcp[cli]注意这里的引号mcp[cli]是带 extras 的写法不加引号在某些 shell 里会被解析成通配符。执行完之后uv 会自动做几件事创建.venv虚拟环境、把mcp及其 CLI 依赖装进去、更新pyproject.toml和生成uv.lock锁文件。uv.lock这个文件值得说一下。它记录了所有依赖的精确版本保证你换机器、换同事装出来的环境完全一致。以前用requirements.txt经常遇到「我这能跑你那报错」的问题多半就是版本漂移uv.lock能避免这个。2.3 写一个最小的 MCP 服务打开main.py替换成下面这个最小可运行的服务from mcp.server.fastmcp import FastMCP mcp FastMCP(demo-server) mcp.tool() def add(a: int, b: int) - int: 两数相加 return a b if __name__ __main__: mcp.run()这段代码定义了一个叫demo-server的 MCP 服务里面有一个add工具。mcp.tool()装饰器把普通函数注册成 MCP 工具Cursor 里的 Agent 就能调用它。先手动跑一遍确认没问题uv run main.py如果没有任何报错进程会挂起等待输入这说明服务本身是好的。按 CtrlC 退出。这一步很关键很多人直接跳到 Cursor 配置结果红点了都不知道是代码问题还是配置问题。先手动跑通能排除掉一大半变量。2.4 uv 和 pipvenv 的对比为了让你理解为什么推荐 uv我列个对照表操作pip venvuv创建虚拟环境python -m venv .venv手动自动管理激活环境source .venv/bin/activate不需要安装依赖pip install xxxuv add xxx运行python main.pyuv run main.py锁文件requirements.txt手动维护自动生成uv.lock速度较慢Rust 实现快很多uv 的核心优势是「不用管虚拟环境」。你在项目目录下执行uv run它会自动找到或创建.venv自动同步依赖然后运行。这对 Cursor 的mcp.json特别友好因为配置里不需要写死 python 路径。到这里本地项目已经准备好了。下一节讲怎么把它接到 Cursor 里。3. mcp.json 配置模板Cursor 里加载本地 MCP 服务的完整字段Cursor 加载 MCP 服务靠的是mcp.json文件。这个文件有两个位置全局配置在~/.cursor/mcp.json对所有项目生效项目级配置在项目根目录的.cursor/mcp.json只对当前项目生效。我一般用项目级因为不同项目的 MCP 服务不一样放项目里方便版本管理。3.1 uv 方式的配置模板推荐用 uv 方式配置最短。在项目根目录建.cursor/mcp.json{ mcpServers: { my-mcp-server: { command: uv, args: [ --directory, /absolute/path/to/my-mcp-server, run, main.py ] } } }几个字段说明一下。mcpServers是固定的一级键下面每个子键是一个服务的名字你可以叫my-mcp-server也可以叫别的这个名字会显示在 Cursor 的 MCP 设置页。command是启动命令这里写uv。args是参数数组--directory指定项目目录必须是绝对路径run main.py是 uv 的运行子命令。注意--directory后面跟的路径一定要是绝对路径。相对路径在 Cursor 启动进程时的工作目录不确定很容易找不到项目。macOS 下可以用pwd命令拿到当前目录的绝对路径。3.2 pipvenv 方式的配置老方式了解即可如果你坚持用传统 venv配置长这样{ mcpServers: { my-mcp-server: { command: /absolute/path/to/my-mcp-server/.venv/bin/python, args: [/absolute/path/to/my-mcp-server/main.py] } } }这里command必须指向虚拟环境里的 python 绝对路径不能写系统的python否则找不到 venv 里装的mcp包。这就是老方式麻烦的地方路径写死换机器就得改。3.3 SSE 方式的配置如果你的 MCP 服务是远程 HTTP 服务用 SSE 模式{ mcpServers: { my-remote-server: { url: http://localhost:8000/sse } } }这种方式不需要command直接给url。适合服务已经跑在某个端口上的场景。本地开发一般用前两种 stdio 方式。3.4 配置里的常见字段除了上面这些mcp.json还支持env字段传环境变量{ mcpServers: { my-mcp-server: { command: uv, args: [--directory, /absolute/path/to/my-mcp-server, run, main.py], env: { API_KEY: your-key-here } } } }如果你的 MCP 服务需要调外部 API比如通过 TaoToken 这类平台调用大模型就可以把 Key 放在env里代码里用os.environ[API_KEY]读取。这样 Key 不会硬编码在代码里配置和代码分离。TaoToken 的 API 地址是https://taotoken.net/api兼容 OpenAI 风格的接口。如果你要在 MCP 服务里加一个「调用大模型」的工具可以这样写import os from openai import OpenAI client OpenAI( base_urlhttps://taotoken.net/api, api_keyos.environ[TAOTOKEN_API_KEY] ) mcp.tool() def ask_llm(prompt: str) - str: 调用大模型回答问题 resp client.chat.completions.create( modelgpt-4o-mini, messages[{role: user, content: prompt}] ) return resp.choices[0].message.content对应的mcp.json里加上env{ mcpServers: { my-mcp-server: { command: uv, args: [--directory, /absolute/path/to/my-mcp-server, run, main.py], env: { TAOTOKEN_API_KEY: sk-你的key } } } }这样你的 MCP 服务就既能提供本地工具又能调大模型。Key 的获取可以在 TaoToken 控制台里创建具体入口是https://taotoken.net/console创建完在 API Keys 页面复制。配置写完之后保存文件Cursor 会自动检测到变化。下一节讲怎么验证。4. 在 Cursor 里验证 MCP 服务从红点到绿点的完整调试过程配置写好了不代表就能用得验证。这一节讲怎么在 Cursor 里确认服务真的跑起来了以及怎么调用工具。4.1 打开 MCP 设置页Cursor 里按CmdShiftPWindows 是CtrlShiftP打开命令面板输入MCP找到Cursor Settings: MCP或者直接在设置里找 MCP 标签页。你会看到已配置的服务列表。每个服务旁边有个状态点。绿点表示连接成功红点表示启动失败。如果是红点点一下能看到错误日志这是排查的关键。4.2 手动跑一遍确认在去 Cursor 之前先在终端里手动跑一遍uv --directory /absolute/path/to/my-mcp-server run main.py如果这条命令能跑起来不报错说明 uv 和代码都没问题问题就在 Cursor 的配置上。如果这条命令就报错那先解决代码或依赖问题。这一步能帮你快速定位问题在哪一层。我试过好几次红点其实是mcp.json里路径写错了代码本身没问题。4.3 在 Agent 模式里调用工具服务变成绿点之后打开 Cursor 的 Agent 模式Chat 面板里切换到 Agent。在对话里输入类似「用 add 工具算一下 3 加 5」的指令Agent 应该会调用你注册的add工具返回 8。如果 Agent 没有调用工具可能是模型没识别到工具描述。检查一下你的mcp.tool()函数的 docstring 写清楚没有docstring 会作为工具描述传给模型。描述越清晰模型越容易正确调用。4.4 查看 Cursor 输出面板如果调用失败打开 Cursor 的 Output 面板CmdShiftU在下拉里选MCP能看到服务的标准输出和错误。这是排查运行时问题的关键位置。比如你的代码里有print语句会在这里显示如果服务启动时抛异常堆栈也在这里。4.5 改代码后重载服务MCP 服务是独立进程改了代码不会自动生效。你需要在 MCP 设置页点一下服务的刷新按钮或者重启 Cursor。更快的办法是先在终端 CtrlC 停掉手动跑的进程改完代码再uv run一遍确认然后回 Cursor 点刷新。实测下来开发阶段最顺的流程是终端里uv run main.py调试代码逻辑确认没问题后回 Cursor 刷新服务在 Agent 里做集成测试。两边分工效率高很多。到这里一个可用的 MCP 服务就跑通了。下一节讲几个我踩过的坑。5. 常见报错排查401、local proxy failed、reading choices 怎么解这一节列几个真实遇到的报错以及对应的排查思路。这些报错在 MCP 开发里很典型你大概率会碰到。5.1 红点 command not found最常见的就是 Cursor 里服务红点日志显示command not found: uv。原因是 Cursor 启动进程时的 PATH 跟你终端里的不一样找不到 uv。解决办法有两个。一是把command写成 uv 的绝对路径用which uv拿到路径比如/Users/you/.local/bin/uv。二是把 uv 的安装目录加到系统 PATH 里然后重启 Cursor。{ mcpServers: { my-mcp-server: { command: /Users/you/.local/bin/uv, args: [--directory, /absolute/path/to/my-mcp-server, run, main.py] } } }5.2 ModuleNotFoundError: No module named mcp这个报错说明服务启动时用的 python 环境里没有mcp包。如果你用 uv 方式检查--directory路径对不对uv 是不是在正确的项目目录下找.venv。如果你用 pipvenv 方式检查command是不是指向了 venv 里的 python而不是系统 python。还有一种情况是uv add没执行成功.venv里确实没装。进项目目录跑uv sync强制同步一下依赖。5.3 401 Unauthorized如果你的 MCP 服务里调了外部 API比如 TaoToken报 401 通常是 Key 没传对。检查mcp.json的env字段里 Key 名字跟代码里os.environ读的名字是否一致。比如配置里写TAOTOKEN_API_KEY代码里读os.environ[TAOTOKEN_API_KEY]大小写要完全一致。另外确认 Key 本身有效可以在 TaoToken 控制台的 API Keys 页面重新生成一个。如果 Key 是从别处复制的注意有没有多余空格。5.4 local proxy failed这个报错一般出现在 SSE 模式Cursor 连不上你配置的url。检查服务是不是真的跑在那个端口上用curl http://localhost:8000/sse试一下。如果服务没起来先手动启动。如果是端口被占用换个端口。stdio 模式下一般不会出现这个报错因为不涉及网络连接。5.5 reading choices 相关报错如果你的 MCP 服务调大模型接口报错里出现reading choices或者choices is undefined说明接口返回的结构跟你代码里取的不一致。常见原因是接口返回了错误信息而不是正常的 completion 结构比如 Key 无效、模型名写错、额度不足。排查方法是在代码里把原始响应打出来resp client.chat.completions.create(...) print(resp)看看到底返回了什么。如果是错误错误信息里一般会说明原因。模型名要跟平台支持的列表对上比如gpt-4o-mini这种。5.6 OAuth 相关报错有些 MCP 服务需要 OAuth 认证Cursor 会弹窗让你授权。如果授权失败检查回调地址配置对不对。这类问题在本地开发里相对少见多数本地服务用 stdio 加环境变量就够了。5.7 排查顺序建议遇到红点按这个顺序排查先终端手动uv run确认代码能跑再检查mcp.json路径是不是绝对路径再看 Cursor Output 面板的 MCP 日志最后检查环境变量和外部依赖。大部分问题在前两步就能定位。6. 把 MCP 服务接到真实工作流几个实用建议跑通 demo 只是第一步真正有用的是把 MCP 服务接到你的日常工作流里。这一节给几个方向。如果你想让 MCP 服务具备调用大模型的能力可以在服务里封装一个通用工具通过 TaoToken 的 API 调用不同模型。TaoToken 的接口兼容 OpenAI 风格base_url用https://taotoken.net/api模型名按平台支持的填。这样你的 MCP 服务就不只是本地工具还能做总结、翻译、代码解释这类任务。想先试试模型效果可以在模型对话页面直接体验入口是https://taotoken.net/models。如果你打算长期开发多个 MCP 服务或者做 Agent 相关的项目可以考虑用 Coding Plan入口在https://taotoken.net/coding-plan。它适合需要持续调用模型做编码辅助的场景比按次调用更划算。接入文档在https://taotoken.net/doc里面有完整的接口说明和示例。API Keys 管理在https://taotoken.net/api-keys创建和吊销 Key 都在这里。回到 MCP 本身几个实用建议。第一把常用的工具封装成独立的 MCP 服务比如文件操作、数据库查询、内部 API 调用每个服务职责单一方便复用。第二mcp.json放项目级目录跟代码一起版本管理团队协作时别人 clone 下来改一下路径就能用。第三服务里的工具函数 docstring 写清楚这是模型理解工具用途的唯一依据描述质量直接影响调用准确率。最后说一个开发习惯每次改完 MCP 服务代码先在终端uv run手动验证再回 Cursor 刷新。这个习惯能帮你把「代码问题」和「配置问题」分开排查效率高很多。uv 的自动环境管理让这个过程很轻不用来回激活虚拟环境改完直接跑就行。