🔌 第6章 动手写一个MCP
📄 一句话概括

MCP 没有自己的运行时。一个 Python 解释器加一个虚拟环境,就够写 Server 了。 uv 干的事只有一件:把「解释器、虚拟环境、依赖清单」三样合成一条命令。 这一节里六条命令覆盖九成日常,前三条是跑通的,后三条是协作和排错才用。

📝 本章和前面几章的关系

到这里,原理、平台、找 Server 都讲完了。从这一节开始,自己动手写。 教材用的是 uv。这台机器上真有 uv 0.9.13,所以这一节是真跑出来的。 装不上也没关系:pip 一样能跑,只是多一步建虚拟环境。

一、MCP 没有自己的运行时

先破一个误会:MCP 不是框架,也没有专属的运行时、专属的解释器、专属的包管理。 一个 Server 就是一个普通程序,读一行 JSON、算一下、写一行 JSON。 用 Python 写、用 Node 写、用 Go 写都一样,只要会说那套报文。 所以「搭环境」这件事,本质上是「准备好写一个普通程序的环境」。

需要四样:解释器(3.10 以上)、pip(标准流程要用)、一个能建虚拟环境的工具、还有虚拟环境本身。 少一样,用别的顶上就行,代码不用改。 这台机器上四样都齐:3.13.14、pip 26.1.2、uv 0.9.13、venv 自带。

二、uv init:建一个空项目

一条命令建出六项:.git、.gitignore、.python-version、README.md、main.py、pyproject.toml。 注意 pyproject.toml 里那行 requires-python 写的是 3.12,不是本机的 3.13。 这不是笔误。uv 会按这一行去挑解释器,而不是用手上这个——所以第一次运行的时候,它会去下载 3.12.12。

这个设计挺好:项目声明自己能跑的最低版本,用哪个解释器由 uv 决定,不由你的机器决定。 换台机器也是同一个版本。 代价是第一次会多花几秒下载。要改就改这一行,别去改本机的默认解释器。

三、uv run:第一次会自动备好环境

第一次跑 uv run 的时候,它会做两件事:挑解释器、在项目目录里建 .venv。 演示里那两句 stderr 就是这两件事:Using CPython 3.12.12、Creating virtual environment。 【这两句是排错时最先要看的东西】, 比任何日志都有用——它会告诉你「用的是哪个解释器」「环境建在哪」。

很多「我明明装了包,为什么它说找不到」的问题,答案就在这两行里:装到了另一个环境里。 整段耗时 0.4 秒。这个数包含建目录、拷解释器、写配置,比手工 python -m venv 快不少。

四、uv add 与那半句缓存

uv add requests 干的事:算依赖、下载、装进 .venv、写进 pyproject.toml。 演示里两行:Resolved 6 packages in 33ms、Installed 5 packages in 190ms。 Resolved 是算要装哪些,Installed 才是真落盘。 缓存命中时 Resolved 几毫秒就完;第一次装一个没缓存过的包,它会慢到十几秒。

输出里还有一条警告,说硬链接失败、改用复制。原因很简单:项目在 I 盘、缓存在 C 盘。 【uv 快的一半原因就是硬链接】——同盘装包几乎不占额外空间、也不花复制时间。跨盘就只能复制。 这不是配置错了,是盘的位置决定的。

五、六条命令,够用

uv init 建项目、uv add 装依赖、uv run 跑脚本——前三条覆盖日常九成。 uv pip list 列依赖、uv sync 按锁文件还原、 uv run --no-project 临时跑一次不带依赖——后三条是协作和排错才用。 跟 pip 比,差别只在「少一条命令」:pip 要先 python -m venv 建环境,uv run 自动建。

至于快多少,这一节不给结论。演示里那两组数缓存状态不同,不能并排看。 同一台机器上装 requests,pip 冷装两次分别落在 50 秒和 170 秒——同一个包差三倍多, 说明网络波动比工具差异大得多。 要真比,得把两边缓存都清掉、同一分钟内各跑一遍。为这个折腾不值。 能确定的事实只有一条:uv 少一条命令。

六、动手 Demo:uv 就在这台机器上,真跑一遍

先体检四样东西,再依次跑 uv init、uv run、uv add,每一步都留下真实输出和耗时。 最后把 uv 和 pip 摆在一起,只对比能确定的那一条。 项目建在系统临时目录里,跑完不留在仓库中。

# 一、体检
for name, cmd in [('python', [sys.executable, '-V']),
                   ('pip', [sys.executable, '-m', 'pip', '-V']),
                   ('uv', ['uv', '--version'])]:
    r = subprocess.run(cmd, capture_output=True, text=True)
    print(name, (r.stdout or r.stderr).strip())

# 二、建项目、跑一次、装一个包
subprocess.run(['uv', 'init', 'notes-mcp'], cwd=tmp)
subprocess.run(['uv', 'run', 'main.py'], cwd=proj)
subprocess.run(['uv', 'add', 'requests'], cwd=proj)

# 三、pyproject 里到底写了什么
print(open(os.path.join(proj, 'pyproject.toml'), encoding='utf-8').read())

实跑输出:

--------------------------------------------------------------------
uv 就在这台机器上,真跑一遍
--------------------------------------------------------------------


  ① 体检:四样东西齐不齐
  --------------------------------------------------------------------

| 项 | 结果 | 说明 |
|---|---|---|
| 解释器 | 3.13.14 | 3.10 以上都能跑 |
| pip | pip 26.1.2 | 标准流程要用 |
| uv | uv 0.9.13 (7ca92dcf6 2025-11-26) | 有就用它,没有就用 pip |
| venv | 有 | 标准库自带 |

  MCP 没有自己的运行时。这四样里少一样,用别的顶上就行,代码不用改。

  ② uv init:建一个空项目
  --------------------------------------------------------------------
  Initialized project `notes-mcp` at `C:\Users\admin\AppData\Local\Temp\uv55_yqwcd57j\notes-mcp`
  耗时 316 毫秒
  建出来 6 项:.git、.gitignore、.python-version、README.md、main.py、pyproject.toml

pyproject.toml 里是这样:
  [project]
  name = "notes-mcp"
  version = "0.1.0"
  description = "Add your description here"
  readme = "README.md"
  requires-python = ">=3.12"
  dependencies = []
  注意 requires-python 写的是 3.12,不是本机的 3.13——
  【uv 会按这一行去挑解释器】,而不是用手上这个。

  ③ uv run:第一次会自动备好环境
  --------------------------------------------------------------------
  3.12.12 (main, Nov 19 2025, 22:43:30) [MSC v.1944 64 bit (AMD64)]
  · Using CPython 3.12.12
  · Creating virtual environment at: .venv
  整段耗时 0.4 秒
  项目里多出 .venv 目录:是
  两句 stderr 说清了两件事:用哪个解释器(3.12.12)、环境建在哪(.venv)。
  【这两句是排错时最先要看的东西】,比任何日志都有用。

  ④ uv add:装一个包
  --------------------------------------------------------------------
  Resolved 6 packages in 33ms
  Installed 5 packages in 190ms
  + certifi==2026.10.23
  + charset-normalizer==3.5.2
  + idna==3.20
  整段耗时 0.59 秒,装上 5 个包
  这句说的是:缓存目录和项目目录不在同一个盘上,硬链接用不了,只能复制。
  【uv 快的一半原因就是硬链接】——同盘装包几乎不占额外空间、也不花复制时间。
  本机项目在 I 盘、缓存在 C 盘,所以这条警告必然出现,不是配置错了。

上面两行各管一段:Resolved 是【算要装哪些包】,Installed 才是真落盘。
缓存命中时 Resolved 几毫秒就完;第一次装一个没缓存过的包,它会慢到十几秒。

  ⑤ 换 pip 要多一步
  --------------------------------------------------------------------

| 要做的事 | uv | pip |
|---|---|---|
| 准备环境 | uv run 自动建 | 先 python -m venv |
| 装包 | uv add requests | pip install requests |
| 装到哪 | 默认进项目 .venv | 看你在哪个环境里 |
| 记进依赖清单 | 自动写 pyproject | 要手动 freeze |


要比快,这里只有一组真数据:同一台机器上装 requests,
pip 冷装(带 no-cache)两次分别落在 50 秒和 170 秒。
uv 这一次是 0.6 秒——但它是命中缓存的,两个数不能并排看。
所以这些数【不能当结论】:

| 为什么不能比 | 说明 |
|---|---|
| 缓存状态不同 | 一个命中本机缓存,一个强制从网上下 |
| 网络波动很大 | 同一个包,两次差了三倍多 |
| 盘的位置 | 跨盘硬链接失效,uv 也会退化成复制 |

  要真比,得把两边缓存都清掉、同一分钟内各跑一遍——为这个折腾不值。
  【能确定的事实只有一条:uv 少一条命令。】快多少,看你机器。

  ⑥ 六条命令,够用了
  --------------------------------------------------------------------

| 要做的事 | 命令 | 刚才真跑了吗 |
|---|---|---|
| 建项目 | uv init <名字> | 跑了,建出 6 项 |
| 装依赖 | uv add <包名> | 跑了,装上 5 个包 |
| 跑脚本 | uv run <文件> | 跑了,自动建 .venv |
| 列依赖 | uv pip list | 没跑,纯查询 |
| 按锁文件还原 | uv sync | 没跑,要已有 uv.lock |
| 临时跑一次不带依赖 | uv run --no-project | 没跑 |

  前三条覆盖日常九成。后三条是协作和排错时才用。

①第一张表四行,最后一行「venv 有」是标准库自带的。四样里少了 uv,pip 那条路一样通。 ②第二段里那行 requires-python 是本节最该注意的地方。写 3.12 就会去下 3.12,不管你本机装的是几。 ③第三段那两句 stderr 值得抄下来。以后任何「包找不到」的问题,先看这两行说的是哪个环境。 ④第四段那条跨盘警告是真的会出现:项目在 I 盘、缓存在 C 盘。它不影响功能,只影响速度和磁盘占用。 ⑤第五段那张对照表只有四行,重点在最后一行:uv 会自动写 pyproject,pip 要手动 freeze。 这一条差别比快慢重要。

30 秒实验

  1. 在任意目录跑一次 uv init,打开 pyproject.toml 看 requires-python 那一行。
  2. 用 pip 走一遍同样的流程:建虚拟环境、装包、freeze。数一数差几条命令。
  3. 把 requires-python 改成 3.13,再跑一次 uv run,看它还会不会去建环境。

常见误区

  1. 「写 MCP Server 要装专门的 MCP 框架。」一个 Server 就是普通程序,标准库够用。 框架是方便,不是必需。
  2. 「requires-python 是本机版本。」它是项目声明的最低版本,uv 会按它去挑解释器。
  3. 「uv 一定比 pip 快好几倍。」快慢看缓存和盘的位置。能确定的只有「少一条命令」。
自测三问(点开对答案) 1. 为什么说 MCP 没有自己的运行时? —— 一个 Server 就是普通程序,读一行 JSON、算一下、写一行 JSON。用哪个语言写都行。 2. pyproject 里 requires-python 写 3.12,本机是 3.13,会发生什么? —— uv 会按 3.12 去挑解释器,可能现场下载一个 3.12。它不看本机默认版本。 3. uv 和 pip 能确定的一条差别是什么? —— uv 少一条命令:uv run 自动建虚拟环境,pip 要先 python -m venv。快多少不能一概而论。

环境搭好了,下一节就写一个真能用的 Server:三条工具、一百多行、不带任何框架。

相关笔记