MCP服务端创建实战:用uv搭建stdio与StreamableHttp双通道 1. 从零跑通一个 MCP 服务端为什么我建议先用 uv 把双通道都试一遍MCP 服务端说白了就是给大模型外挂的一台「工具机」模型本身只会聊天但通过 MCP 协议它可以调用你写的函数去查数据库、算数、读文件。而uv是这两年 Python 圈里跑得最快的包与环境管理器装 Python、建虚拟环境、加依赖、打包发布一条龙特别适合拿来搭 MCP 这种「小、快、独立」的服务端项目。这篇聚焦一件事用 uv 从零搭一个 MCP 服务端并且把stdio和StreamableHttp两种传输通道都配出来顺带说清楚 SSE 现在还能不能用、什么时候该用。适合谁看刚接触 MCP、想本地先跑通一次工具调用、又不想被环境问题卡半天的同学。全程命令可复制最后我会用 MCP Inspector 和 curl 各验证一次确保你是真的「跑通了」而不是「看起来跑通了」。先说结论方便你带着预期往下看stdio是本地进程直连客户端把服务端当子进程拉起来走标准输入输出通信StreamableHttp是服务端独立部署客户端通过 HTTP 远程调用适合多客户端共享、要暴露公网或内网地址的场景。SSE 是 StreamableHttp 之前的过渡方案现在新项目基本不用它了但老客户端可能还认所以我会讲清楚它的边界在哪。2. 前置准备uv 装好MCP 依赖加对2.1 安装 uv 并确认 Python 版本uv 的安装各平台都有官方脚本装完先确认版本。我习惯先看一眼本机有哪些 Python再决定项目用哪个版本。# 安装 uvmacOS / Linux curl -LsSf https://astral.sh/uv/install.sh | sh # Windows PowerShell powershell -c irm https://astral.sh/uv/install.ps1 | iex # 查看 uv 版本 uv --version # 查看已安装的 Python uv python list # 安装指定版本 Python可选指定目录 uv python install 3.13这里有个小坑uv python list会同时列出「已安装」和「可安装」的版本带downloadable标记的是还没装的别看到一堆版本号就以为本机全有。2.2 初始化项目并加入 MCP 依赖MCP 官方提供了 Python SDK带 CLI 工具装mcp[cli]就够了。# 在项目目录初始化指定 Python 3.13 uv init . -p 3.13 # 加入 MCP SDK含 CLI uv add mcp[cli]初始化后目录里会多出pyproject.toml、.python-version和一个入口文件。uv add会自动创建虚拟环境并把依赖写进pyproject.toml不用你手动source activate跑命令时 uv 会自己接管环境。2.3 pyproject.toml 关键字段说明初始化出来的pyproject.toml大致长这样我标一下几个关键点[project] name mcp-server-demo version 0.1.0 description A demo MCP server with stdio and streamable-http readme README.md requires-python 3.13 dependencies [ mcp[cli]1.2.0, ] [project.scripts] mcp-server-demo mcp_server_demo.server:main [build-system] requires [hatchling] build-backend hatchling.build[project.scripts]这一节很关键它把mcp-server-demo这个命令映射到server.py里的main函数。等你后面用uvx从 PyPI 拉包运行时靠的就是这个入口。requires-python建议写3.10MCP SDK 对版本有要求3.13 是当前比较稳的选择。3. 可复制配置一份 server 骨架两种传输通道3.1 用 FastMCP 写工具与资源MCP 的 Python SDK 提供了FastMCP写法跟 FastAPI 很像装饰器一挂就是工具。下面这份server.py同时包含一个加法工具和一个动态问候资源# server.py from mcp.server.fastmcp import FastMCP # 创建 MCP 服务端实例名字会显示在客户端里 mcp FastMCP(Demo) mcp.tool() def add(a: int, b: int) - int: Add two numbers return a b mcp.resource(greeting://{name}) def get_greeting(name: str) - str: Get a personalized greeting return fHello, {name}! def main() - None: # 默认走 stdio本地进程直连 mcp.run(transportstdio) if __name__ __main__: main()注意mcp.tool()下面那行 docstring它不是写给人看的注释而是给大模型看的「工具说明书」。模型靠这段文字判断什么时候该调这个函数所以描述要写清楚输入输出别偷懒。3.2 stdio 通道本地进程直连stdio 模式下客户端会把你的服务端当子进程启动通过标准输入输出收发 JSON-RPC 消息。启动命令就是uv run server.py或者用入口脚本uv run mcp-server-demo这个模式下服务端不监听任何端口所以你在浏览器里是访问不到的它只跟拉起它的父进程对话。好处是零网络配置、启动快、权限隔离干净坏处是一个服务端实例只能服务一个客户端。3.3 StreamableHttp 通道独立部署远程调用把main里的 transport 换掉即可def main() - None: # 独立 HTTP 服务默认监听 127.0.0.1:8000 mcp.run(transportstreamable-http)启动后服务端会监听http://127.0.0.1:8000MCP 端点路径是/mcp。这个模式下服务端是常驻进程多个客户端可以同时连也方便你把它部署到内网服务器上给团队共用。3.4 SSE 的适用边界SSE 是 StreamableHttp 之前的方案端点路径是/sse。它的工作方式是客户端先建一条 SSE 长连接接收服务端推送再另开一条 POST 通道发请求。现在新项目不建议再用 SSE原因有两个一是它需要维护两条连接断线重连逻辑复杂二是 StreamableHttp 已经用单端点 流式响应覆盖了同样的能力协议更简洁。那什么时候还会碰到 SSE主要是老版本客户端或老教程里配的服务端。如果你手上的客户端只认 SSE那就把 transport 设成sse临时兼容一下但新写的服务端优先选 StreamableHttp。通道端点部署方式适用场景stdio无端口客户端拉起子进程本地单客户端、IDE 插件StreamableHttp/mcp独立常驻服务多客户端、内网/公网共享SSE/sse独立常驻服务兼容老客户端新项目不推荐4. 验证请求Inspector 与 curl 各跑一次4.1 用 MCP Inspector 可视化验证MCP 官方有个 Inspector 工具能直接连你的服务端、列出工具、手动调用特别适合调试。# 启动 Inspector它会自动打开浏览器 uv run mcp dev server.pyInspector 起来后左侧会显示连接状态中间列出add工具和greeting资源。点add填a3、b5执行右侧应该返回8。这一步能过说明你的工具注册和 stdio 通道都没问题。4.2 用 curl 验证 StreamableHttpStreamableHttp 模式下你可以直接用 curl 打/mcp端点。MCP 的 HTTP 传输走 JSON-RPC先发一个初始化请求curl -X POST http://127.0.0.1:8000/mcp \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -d { jsonrpc: 2.0, id: 1, method: initialize, params: { protocolVersion: 2024-11-05, capabilities: {}, clientInfo: {name: curl-test, version: 1.0} } }如果返回里带serverInfo和capabilities说明服务端握手成功。接着调工具curl -X POST http://127.0.0.1:8000/mcp \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -d { jsonrpc: 2.0, id: 2, method: tools/call, params: {name: add, arguments: {a: 3, b: 5}} }返回里content字段会包含结果8。注意Accept头必须同时带application/json和text/event-stream少了后者服务端可能直接拒绝这是 StreamableHttp 的协议要求。4.3 客户端侧配置示例如果你用 CherryStudio 这类客户端配置逻辑是这样的stdio 类型填命令uv参数--directory 项目路径 run server.pyStreamableHttp 类型填 URLhttp://127.0.0.1:8000/mcpSSE 类型填http://127.0.0.1:8000/sse。保存后客户端会去连连上就能在对话里让模型调用你的工具。5. 本篇常见错排查5.1 端口被占用或连不上StreamableHttp 启动时报Address already in use说明 8000 端口被别的进程占了。换端口可以在FastMCP初始化时传参或者先lsof -i :8000找到进程杀掉。curl 连不上时先确认服务端是不是真的在跑curl http://127.0.0.1:8000/mcp返回 405 也正常因为 GET 不被支持用 POST 才对。5.2 transport 名字写错mcp.run(transportstreamable-http)里是连字符不是下划线写成streamable_http会报错。stdio 和 sse 都是小写单词别写成STDIO。5.3 工具没被识别模型不调用你的工具八成是 docstring 写得太模糊。Add two numbers这种还行但如果写成do something模型根本不知道啥时候用。把功能、参数含义、返回类型写清楚识别率会明显提升。5.4 uvx 运行找不到入口从 PyPI 拉包用uvx跑时如果报command not found检查pyproject.toml里的[project.scripts]有没有配对包名和命令名是否一致。发布前本地先uv build打个包用uvx --from dist/xxx.whl 命令试跑一次能省掉很多来回。6. 把服务端接进你的日常工具链跑通之后下一步就是让它真正干活。如果你只是本地调试、验证模型能不能正确调用工具可以直接在模型对话里挂上这个 MCP 服务端试几轮看看工具选择准不准。要是你打算长期用 MCP 做编码辅助或 Agent 工作流建议把服务端独立部署配合 Coding Plan 这类长期方案来管理调用配额和稳定性比每次本地拉起子进程省心得多。接入前记得先去控制台把 API Keys 配好再对照接入文档确认端点和鉴权方式避免因为 header 少带一个字段卡半天。工具调用的验证动作做完整个链路就算真正闭环了。