最近在进行MCP server 的开发,目前主流的 mcp server 主要以node 和python 为主,其中python 主要以uv 来启动服务的,我也是由此才开始接触uv,uv 工具可以管理本地的python 版本,虚拟环境以及打包发布,还可以运行脚本,作为mcp 开发的前序基础,本文主要记录以下使用uv 开发管理python 应用的各个环节。之后在进行mcp 开发时会一直使用uv 来管理环境。
uv 的安装
shell
# On macOS and Linux.
curl -LsSf https://astral.sh/uv/install.sh | sh
# On Windows.
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
# With pip.
pip install uv
我习惯使用 pip 安装,如果本地电脑中有多个python 环境,如 python3.11, python3.12 等,需要确认一下用的是哪个版本的pip, 使用 pip --version
来查看一下具体的pip 位置
shell
pip --version
pip 23.0.1 from C:\Python310\lib\site-packages\pip (python 3.10)
上面显示我使用的是3.10 的python, 安装完uv 以后,就可以在 C:\Python310\Scripts
目录中找到 uv.exe 和 uvx.exe, 这两个可执行文件后面都用的到。
uv 基础使用
在没有uv 之前,创建项目的流程大概是这样
- 先使用python 创建一个虚拟环境
python -m venv venv
- 安装项目所需要的第三方包
pip install xxxx
- 导出依赖到 requirements.txt 中,
pip freeze>requirements.txt
- 之后上传到git,如果要上传到pypi, 需要在配置打包脚本
总体来讲,上面的流程没有什么问题,但是每一步都有很多注意的问题
- 创建虚拟环境时,需要确认哪个版本的python? 众所周知,不同版本的python差异还是蛮大的,且不会向后兼容。
- 使用pip 安装包的时候,缺乏依赖管理,如使用pip 安装了A,A又依赖了B,B又依赖了C,当环境中不在依赖A时,如果卸载了A,则不会卸载B和C。
使用uv 管理python 环境
传统的python 安装需要到官网或者镜像网站下载python 安装包安装,并配置好环境变量,创建虚拟环境的时候需要指明对应的python版本,如系统中有两个python 环境
makefile
C:\Python310\python.exe
C:\Python311\python.exe
如果在环境变量 path
中,311的优先级更高,则使用 python -m venv venv
创建出来的python 环境是3.11 的,如果想要创建一个3.10 的,需要将具体的路径打全了
makefile
C:\Python310\python.exe -m venv venv
使用 uv 安装python
运行 uv python list
查看系统中的python 版本
bash
C:\Users\yangyanxing>uv python list
cpython-3.13.2-windows-x86_64-none C:\Python313\python.exe
cpython-3.13.1+freethreaded-windows-x86_64-none <download available>
cpython-3.13.1-windows-x86_64-none <download available>
cpython-3.12.9-windows-x86_64-none C:\Python312\python.exe
cpython-3.12.8-windows-x86_64-none AppData\Roaming\uv\python\cpython-3.12.8-windows-x86_64-none\python.exe
cpython-3.11.11-windows-x86_64-none <download available>
cpython-3.11.9-windows-x86_64-none C:\Python311\python.exe
cpython-3.10.16-windows-x86_64-none <download available>
cpython-3.10.11-windows-x86_64-none C:\Python310\python.exe
cpython-3.9.21-windows-x86_64-none <download available>
cpython-3.8.20-windows-x86_64-none <download available>
cpython-3.8.10-windows-x86_64-none C:\Python38\python.exe
cpython-3.7.9-windows-x86_64-none <download available>
pypy-3.10.14-windows-x86_64-none <download available>
pypy-3.9.19-windows-x86_64-none <download available>
pypy-3.8.16-windows-x86_64-none <download available>
pypy-3.7.13-windows-x86_64-none <download available>
如果要安装 3.7.9 的python
使用 uv python install 3.7.9
sql
uv python install 3.7.9
cpython-3.7.9-windows-x86_64-none ------------------------------ 5.70 MiB/29.97 MiB
也可以指定安装 pypy 版本的python(也可能安装失败)
使用 uv 创建虚拟环境
先使用uv 创建一个项目,这步骤不是必须的,但是推荐使用uv 创建项目,因为会默认创建pyproject.toml 文件以及初始化git 和README 文件,算是比较正规的操作,否则需要自己手工创建。
shell
uv init firstuv
uv 工具会创建一个 firstuv 的目录,并且创建好相应的文件
此时,还没有创建虚拟环境,但是我们注意到,pyproject.toml 文件中有配置 requires-python
为>=3.12
, 如果想修改python 版本,如使用3.8 的python, 则先修改pyproject.toml的配置, 再修改 .python-version
中的python 版本。
注意,如果 pyproject.toml 文件中配置的是 requires-python = ">=3.11"
, 而 .python-version
写的是 3.10
, 则之后运行 uv sync
命令进行安装会报错。
使用 uv sync
命令可以让uv 工具根据pyproject.toml 文件中的配置进行虚拟环境的创建和依赖的下载,当我们从github 上下载一个别人开发的基于uv的python 项目时,这个命令可以很方便的创建好虚拟环境和安装好依赖,这里由于是自己创建的干净的项目,我先不使用sync 命令,手动的去创建虚拟环境, 使用 uv venv
命令创建虚拟环境
bash
cd firstuv
uv venv --python 3.11
Using CPython 3.11.9 interpreter at: C:\Python311\python.exe
warning: The requested interpreter resolved to Python 3.11.9, which is incompatible with the project's Python requirement: `>=3.12`
Creating virtual environment at: .venv
Activate with: .venv\Scripts\activate
我使用python 3.11 创建了这个虚拟环境,但注意到得到了一个警告,由于pyproject 中指定python 为 >=3.12
, 而我这里安装的是3.11, 会出现不兼容的问题。
这里还贴心的提示出激活虚拟环境命令。
使用 uv 安装第三方包
以往会使用pip install xxxx
来安装包,这已经成为了python 开发者必知必会的操作了,有了uv工具,可以使用 uv add xxxx
来安装第三方库
shell
uv add requests fastapi
安装结束以后,uv.lock 文件会添加很多内容,这里的内容不要手工修改,让uv 自己来管理,同时 pyproject.toml 文件也添加上了刚才的依赖。
运行 uv tree
命令查看项目依赖
这个命令可以很清晰的展示项目中各个包的依赖关系。
使用 uv 卸载第三方包
之前有提过,使用pip 安装了A,A又依赖了B,B又依赖了C,那么卸载A时时不会卸载B和C的,这样就会造成一种site-packages 越来越臃肿的问题。
上面的依赖关系
fastapi->pydantic->pydantic-core->typing-extensions
那么我们来看一下卸载 fastapi 会不会同时卸载掉对应的依赖包
生成 pip 的依赖文件
uv 是一个新兴的 python 管理工具,并不是所有的开发者都会习惯使用它,更多的还是使用pip 来管理python 环境, 所以uv 工具也支持使用pip 来管理环境。
导出当前项目依赖到 requirements.txt 文件
通过 pyproject.toml 中的配置信息导出依赖文件,此时的pyproject.toml 中记录为
toml
[project]
name = "firstuv"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"fastapi>=0.115.12",
"requests>=2.32.3",
]
执行以下命令
python
uv pip compile pyproject.toml -o requirements.txt
即可将项目依赖导出为 requirements.txt 文件。
也可以通过 uv 工具运行 pip 导出requirements.in 文件,
uv pip freeze>requirements.in
此时requirements.in 文件中已经记录了第三方依赖包,
text
annotated-types==0.7.0
anyio==4.9.0
certifi==2025.1.31
charset-normalizer==3.4.1
fastapi==0.115.12
idna==3.10
pydantic==2.10.6
pydantic-core==2.27.2
requests==2.32.3
sniffio==1.3.1
starlette==0.46.1
typing-extensions==4.12.2
urllib3==2.3.0
这时使用pip 的开发者可以使用 pip install -r requirements.in 来安装包了。
但是这里推荐使用uv pip compile requirements.in -o requirements.txt
命令来重新编译一下, 生成的requirements.txt 会更加详细的记录包的依赖缘由
ini
# This file was autogenerated by uv via the following command:
# uv pip compile requirements.in -o requirements.txt
certifi==2025.1.31
# via
# -r requirements.in
# requests
charset-normalizer==3.4.1
# via
# -r requirements.in
# requests
requests==2.32.3
# via -r requirements.in
......
之后对于习惯使用 pip 管理环境的用户就可以正常的使用 pip install 了。
uv 运行脚本
使用 uv init
创建的项目会自动创建一个hello.py 的文件,未来在实际项目中,创建完项目以后大概率第一件事就是删除掉这个文件,不过作为示例,我们先来看一下这个hello.py 中的内容
python
def main():
print("Hello from firstuv!")
if __name__ == "__main__":
main()
很简单,就是一个hello world。 在没有uv 工具之前,要运行这个脚本,使用 python hello.py
即可,有了uv 工具,可以使用 uv run hellor.py
命令也可运行脚本
使用 uv 打包发布
我们会使用pip 从pypi 中安装第三方的包,但是很少向pypi 提交自己编写的包,这节使用uv 打包并上传到pypi。
在打包之前,我们来看一下的uv init 初始化项目有哪些不同,官方解释如下 docs.astral.sh/uv/concepts...
几个常用的选项如下
uv init --app
创建一个普通的app, 这个也是默认的选项,这种就是最基础的项目,这类型的项目不能直接打包,由于pyproject.toml 中没有配置build systemuv init --package
创建一个"包", 这类型的项目可以被打包发布,pyproject.toml 中会自动配置 build system,并且可以发布为工具(tool),使用 uv tool run (或者 uvx )来直接运行(本节重点介绍此种方式创建的包)uv init --lib
创建一个"库", 这种和package 的区别在于没有project.scripts, 不能当工具来使用
上一节使用 uv init xxxx
来初始化一个普通的项目,普通的项目没有打包编译选项,这节我们使用 uv init --package
来初始化项目。
bash
uv init --package --python 3.11 mcp-gaodeweather-server
Initialized project `mcp-gaodeweather-server` at `C:\Users\yangyanxing\Desktop\uvtest\mcp-gaodeweather-server`
cd mcp-gaodeweather-server
uv sync
此时就创建好了一个package 类型的项目。
我们来看一下目录结构
vbscript
tree mcp-gaodeweather-server
mcp-gaodeweather-server
├── .python-version
├── README.md
├── pyproject.toml
└── src
└── mcp-gaodeweather-server
└── __init__.py
这里比之前多了一个scr 目录,包的代码都放到这里。
再看一下 pyproject.toml 文件
toml
[project]
name = "mcp-gaodeweather-server"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
authors = [
{ name = "yangyanxing", email = "[email protected]" }
]
requires-python = ">=3.11"
dependencies = []
[project.scripts]
mcp-gaodeweather-server = "mcp_gaodeweather_server:main"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
这个配置文件比之前多了 project.scripts 和 build-system 选项。build-system 选项为打包时用到的配置,流行的打包方式有多种,可以参考 hellowac.github.io/pypug-zh-cn...
不过如果不熟悉的话就使用默认的 hatchling.build
还有一个project.scripts 配置,mcp-gaodeweather-server = "mcp_gaodeweather_server:main"
,这个是配置了一个脚本的启动命令。
bash
uv run mcp_gaodeweather_server
这行命令将会输出 Hello from mcp-gaodeweather-server!
, 这个输出是 src/mcp-gaodeweather-server/__init__.py
中的main 方法定义的输出
python
def main() -> None:
print("Hello from mcp-gaodeweather-server!")
也就是当运行 uv run mcp_gaodeweather_server
时,实际上是执行了mcp_gaodeweather_server 模块中的main方法。
以上只有演示代码,并没有做什么, 之后会创建一个用于查询天气的 mcp 服务。
使用 uv 打包
使用 uv build
命令即可将项目进行打包
bash
C:\Users\yangyanxing\Desktop\uvtest\mcp-gaodeweather-server>uv build
Building source distribution...
Building wheel from source distribution...
Successfully built dist\mcp_gaodeweather_server-0.1.0.tar.gz
Successfully built dist\mcp_gaodeweather_server-0.1.0-py3-none-any.whl
编译好的二进制whl文件还有源文件.tar.gz包文件将保存在dist 目录下。在打包之前,也可以编辑一下pyproject.toml 文件中的name,version,description 等元信息。这里就先不改了。
uv tool 的使用
uv 中有个tool 的概念,在 pyproject.toml 文件中定义的脚本
toml
[project.scripts]
mcp-gaodeweather-server = "mcp_gaodeweather_server:main"
我们在之后是可以用 uv tool run mcp-gaodeweather-server
来运行的,我们也可以使用 uv tool install xxxx
来安装tool,如 uv tool install arxiv-mcp-server
,执行过程如下
当您运行 uv tool install arxiv-mcp-server
时,uv
会执行以下操作:
-
解析包名: 确定要安装的包是
arxiv-mcp-server
。 -
创建或使用现有的虚拟环境:
uv
会在全局工具目录中创建一个隔离的虚拟环境,以确保安装的工具不会影响全局 Python 环境。 -
安装包及其依赖项: 在上述虚拟环境中,
uv
使用pip
安装arxiv-mcp-server
及其所有必要的依赖项。 -
生成可执行文件: 安装完成后,
uv
会在虚拟环境的bin
目录下创建与arxiv-mcp-server
相关的可执行文件,通常与包名相同。
上面我们使用了 uv run mcp-gaodeweather-server
来运行脚本,此时的脚本是在本地的,别人是无法使用的,或者我们换了一个虚拟环境也是运行不了的,我们期望使用 uv tool run mcp-gaodeweather-server
命令在任何地方运行我们的脚本, 也可以使用uvx mcp-gaodeweather-server
来运行, uvx 是 uv tool run 的缩写,uvx 可以自动从pypi 上下载安装包。
一般情况下,安装包会从pypi 下载,当然也可以从本地whl 文件安装,上面执行打包步骤以后,生产的whl 文件由于还没有上传到pypi,所以无法从pypi中下载安装的,此时我们可以使用本地文件进行安装。
bash
uv tool install dist\mcp_gaodeweather_server-0.1.0-py3-none-any.whl
Resolved 1 package in 3ms
Prepared 1 package in 11ms
Installed 1 package in 22ms
+ mcp-gaodeweather-server==0.1.0 (from file:///C:/Users/yangyanxing/Desktop/uvtest/mcp-gaodeweather-server/dist/mcp_gaodeweather_server-0.1.0-py3-none-any.whl)
Installed 1 executable: mcp-gaodeweather-server.exe
>uv tool list
mcp-gaodeweather-server v0.1.0
- mcp-gaodeweather-server.exe
pycowsay v0.0.0.2
- pycowsay.exe
使用 uv tool install xxxx.whl 即可安装本地工具。使用 uv tool list 查看本地工具显示 mcp-gaodeweather-server.exe 已经安装好了。此时再使用 uvx mcp-gaodeweather-server
或者 uv tool run mcp-gaodeweather-server
即可运行该脚本了。
经过上面的几个步骤,我们基本上已经完成了一个极其简单的工具开发,如果我们将whl 发给别人,本地安装一下就可以使用了,但是在python 的世界里,还是需要将包上传到pypi 中供全世界的python 开发者使用。
使用 uv 上传到pypi 中
到 pypi 中注册账号,到 Account settings, 创建api token,scope 选择 Entire account(all projects),
记录一下这个pypi 开头的token,这个只显示一次。
之后使用 uv publish --token pypi-xxxxx
命令将项目上传到 pypi 中。
终于上传到pypi 了,pypi.org/project/mcp... 这样全世界的python 开发者都可以看到我们的代码了,虽然什么都用都没有。。。
由于我们没有对项目做任何修改,所以看上去什么都没有,这很不符合规范,应该写一些项目介绍来开发者明白项目是做什么的,怎么启动的。
pyproject.toml 中有很多配置选项,可以参考
hellowac.github.io/pypug-zh-cn...
我挑一些常用的进行编辑
编辑README.md 文件
bash
## 介绍
这是一个使用高德地图的天气接口,获取当前城市天气的demo。
## 使用方法
## MCP 配置
### uvx
```json
{
"mcpServers": {
"duckduckgo": {
"command": "uvx",
"args": [
"mcp-gaodeweather-server"
]
}
}
}
编辑 pyproject.toml 文件,
toml
[project]
name = "mcp-gaodeweather-server"
version = "0.1.1"
description = "这是一个使用高德地图的天气接口,获取当前城市天气的demo"
readme = "README.md"
authors = [
{ name = "yangyanxing", email = "[email protected]" }
]
keywords= ["mcp", "gaodeweather", "server"]
requires-python = ">=3.11"
dependencies = []
[project.scripts]
mcp-gaodeweather-server = "mcp_gaodeweather_server:main"
[project.urls]
Homepage = "https://github.com/kevinkelin/mcp-gaodeweather-server"
Issues = "https://github.com/kevinkelin/mcp-gaodeweather-server/issues"
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
添加了 project.urls 配置,并写了一些描述,注意要提升一下版本号。
之后再运行 uv build
和 uv publish --token xxxxx
将package 上传到pypi 中。
现在我们就可以在任何地方使用刚才编写的工具了。新创建一个项目。
bash
# 先卸载之前安装的 mcp-gaodeweather-server
uv tool uninstall mcp-gaodeweather-server
# 安装 mcp-gaodeweather-server 工具
uv tool install mcp-gaodeweather-server
# 运行
uv tool run mcp-gaodeweather-server
# 再卸载
uv tool uninstall mcp-gaodeweather-server
# 直接使用 uvx 运行
uvx mcp-gaodeweather-server
直接使用 uvx mcp-gaodeweather-server
运行工具时,uv 如果发现本地没有这个工具,则会先从pypi 中下载,之后再运行这个工具,此时我们再任何地方使用我们编写的工具了。
总结
本文简单介绍了如何使用uv 来管理python 项目的创建和打包发布
- uv python install 安装python
- uv init xxxx 创建项目
- uv venv 创建虚拟环境
- uv add 下载第三方包
- uv remove 卸载第三方包
- uv pip 执行 pip 命令
- uv run 执行脚本
- uv tool run 或者 uvx 执行包中的script 命令,类似于npm run
- uv build 打包
- uv publish 发布
熟练使用uv 工具可以很方便的管理python 项目,之后我们再来学习MCP 服务的开发。