博客配套代码发布于github:10 MCP 基础
本系列所有博客均配套Gihub开源代码,开箱即用,仅需配置API_KEY。
如果该Agent教学系列帮到了你,欢迎给我个Star⭐!
知识点:Stdio传输 | Streamable HTTP | FastMCP框架 | Transport通信
序章:从“造轮子”到“插 USB”
在第 07 篇中,为了让 Agent 能查询天气,我们手写了一个 get_weather 函数,并用 @tool 装饰器将其注册为工具。
但现实需求远不止于此:
- 想查高德地图的驾车路线?
- 想通过 GitHub 自动创建 Issue?
- 想用飞书机器人发送通知?
难道我们要为每一个服务去阅读 API 文档、处理认证、封装错误、维护版本,再手写几百个 @tool 函数吗?
这正是 “工具孤岛” 问题——每个 Agent 都在重复造轮子,无法共享、难以复用、维护成本极高。
Model Context Protocol(MCP)的诞生,就是为了解决这个问题。
MCP 把工具剥离出来,放到独立的Server里:
- 它可以是个 USB 设备(支持 Stdio 插拔);
- 也可以是个 Wifi 热点 (支持 HTTP 连接)。
有了MCP的Agent,才可以自由调用成百上千个工具,让Agent的能力飞速扩张。
对于MCP的基础篇与进阶篇,我们将逐个学习MCP的服务端与客户端搭建。
一、为什么先学服务端
按常理,我们或许应该先学会“如何接入别人的 MCP 服务”(客户端),再学习“如何发布自己的服务”(服务端)。 但在 MCP 的学习路径中,这个顺序恰恰应该反过来。 原因很简单:
服务端适合“极速上手”,而客户端适合“深度进阶”。
✅ 服务端:极低门槛,所见即所得
得益于 FastMCP 这样高质量框架的存在,服务端的开发已经变得极其简单。你不需要处理复杂的协议握手,只需写一个带类型提示的普通 Python 函数,加个装饰器,它就立刻变成了一个标准工具。配合 CherryStudio 这样的可视化客户端,你能立刻看到自己的代码跑起来,获得极强的正反馈。
⚠️ 客户端:不仅是调用,更是架构
虽然社区已经出现了如 langchain-mcp-adapters 这样的封装库,能让你一键连接。但作为Agent 全栈工程师,如果只学会调用一个封装好的 load_tools() 函数,你永
远无法理解 MCP 的真正威力:
- 它是如何跨越进程(Stdio)与 Node.js 服务对话的?
- 它是如何通过 HTTP (SSE) 实现远程流式传输的?
- 当连接断开或报错时,如何优雅地处理资源释放?
这些底层通信机制和生命周期管理,被封装库完美地“隐藏”了。 正因如此,官方和主流工具(如 Cursor、Trae)都鼓励你先成为“工具提供者”。
因此,我们的策略是:
- 先攻服务端 (本章) :利用 FastMCP 的便利性,5分钟构建出可用的工具,建立信心,理解 MCP 的“双模”(Stdio/HTTP)形态。
- 后磨客户端 (下一章) :不满足于“调包”,我们将亲手拆解通信黑盒,手写一个生产级的双模客户端。只有造过一次轮子,你才有资格说自己“精通”了 MCP 架构。 (当然,在彻底理解原理后,我们也会介绍官方库作为生产环境的替代方案)。
二、极速上手:开发第一个基于FastMCP的Stdio Server
我们先从最简单的开始:本地管道模式(Stdio) 。
这是Cursor、Trae 等本地应用连接工具的标准方式。
在没有框架之前,我们写MCP Server需要处理各种复杂的协议格式。但借助FastMCP,我们只需会写Python函数即可:
# stdio_server.py
from mcp.server.fastmcp import FastMCP
# 初始化服务
# WeatherService 是服务的名字
mcp = FastMCP("WeatherService")
# 业务逻辑工具
@mcp.tool()
async def get_weather(city:str):
"""
查询指定城市的实时天气。
如果是此时此刻的天气请求,调用此工具。
"""
# 模拟真实的网络请求(你可以换成自己的天气API)
# 在MCP中,工具函数可以是async的,FastMCP会自动处理
return f"{city}的天气是:晴,气温25℃,风力3级"
# 启动入口
if __name__ == '__main__':
# 默认运行方式:Stdio(标准输入输出)
# 这种模式下,程序启动后会“挂起”等待指令,不会有任何打印输出。
mcp.run()
代码解析:
-
- 自动Schema生成:
- 看代码中的city:str。FastMCP 会自动读取这些类型注释(Type Hints)。生成JSON Schema 告诉 LLM:“这个工具需要一个字符串类型的city参数’”。
- 切记:写MCP工具,必须写 Type Hints,不然LLM不知道如何调用。
-
- Docstring 说明书:
- 函数下方的注释("""查询指定...""")会被自动提取为文档描述。LLM 靠这个来决定什么情况下调用这个工具。
-
- Stdio 管道连接:
- mcp.run() 默认开启 Stdio模式。此时 FastMCP 会接管标准输入/输出,将MCP消息通过JSON流处理。
- 尽量不要在Server或工具函数中随意使用print()输出消息,因为Stdout 是 MCP协议通道,随意输出会破坏消息格式,导致客户端解析失败。
三、测试MCP链接:CherryStudio初体验
Server 代码写好了,写起来感觉非常简单,但我们怎么知道它能不能用?我们还没写 Client 测试代码(那是下一章的事)。这时候,现成的Cherry Studio作为目前对MCP支持最好的客户端之一,就是非常好的调试器:
1. 下载与安装
点开 www.cherry-ai.com/ ,
下载该文件并安装好(安装流程非常简易)。
2. 配置MCP
下载好后,点击右上角设置:
点击MCP进到这里,再点击添加-快速创建:
在这里,我们把如下信息填充完整:
- 类型 (Type):stdio | 通信方式
- 名称 (Name):Weather-Local | MCP工具名
- 命令 (Command): python.exe的绝对路径 | 启动py脚本的解释器
- 参数 (Args): 绝对路径/path/to/weather_server.py | 你的服务器本地位置
⚠️ 如果你的python.exe解释器是在venv环境下,一定要明确指定。
如图,配置完成后,先按保存,并按确认:
当看到有工具与小版本号时,意味着你已连接成功。
返回首页,我们测试下效果:
测试成功。
⚠️ 推荐这里使用Qween3作为测试模型,默认的GLM-4.5对MCP支持有点小问题。
在这里,你可以尝试反复修改你的MCP服务器,并不断开关MCP按钮,来测试MCP的连接效果如何。
四、理解MCP的 Transport 通信机制
在上一节中,我们成功使用 Stdio 模式 启动了一个本地 MCP Server。你可能会疑惑:
“既然 Stdio 是本地通信,为什么我用
npx @amap/...时,感觉像是‘远程调用了高德地图’?”
其实,这种“远程感”是一种部署层面的错觉,通信本身仍是纯本地的。下面我们就来彻底厘清 Stdio 与 Streamable HTTP 的本质区别。
1. Stdio:真正的本地进程间通信(IPC)
Stdio(Standard Input/Output)的本质是 父子进程之间的匿名管道通信。它不经过网络协议栈,完全在操作系统内存中完成数据交换。
场景一:纯本地开发(前文所用的方法)
- 运行 python weather_server.py
- 客户端(如 CherryStudio)通过subprocess 启动该脚本
- 双方通过 stdin/stdout 交换 JSON-RPC 消息
- ✅ 零网络开销,延迟极低(~0.01ms)
- ❌ 仅限同一台机器
场景二:“伪远程”——通过 npx/uvx 临时执行
当你执行:
npx @amap/amap-maps-mcp-server YOUR_KEY
实际发生了三步:
- 联网下载(一次性):
npx从 npm 仓库拉取包并缓存到本地; - 本地启动:在你的机器上立即运行这个 Node.js 脚本,生成一个子进程;
- Stdio 通信:客户端通过 stdin/stdout 与这个本地子进程对话。
🔑 关键点:无论代码来自哪里,只要用 Stdio 启动,服务端进程就一定运行在你的本地机器上。
因此,这仍然是 本地 IPC,只是“工具来源”是远程的,通信过程从未离开本机。
2. Stdio 的局限:为何不能用于生产?
尽管 Stdio 简单高效,但它天生不适合生产环境:
| 问题 | 说明 |
|---|---|
| ❌ 无法跨机器 | 服务端必须与客户端同机运行,无法部署到云服务器供团队共享 |
| ❌ 无持久化 | 每次调用都需重新启动进程,冷启动开销大 |
| ❌ 无并发支持 | 一个 Stdio 连接 = 一个进程,难以支撑多用户或多 Agent 并发 |
| ❌ 可靠性差 | 进程崩溃、环境变量错误、权限问题都会导致连接瞬间断裂 |
💡 Stdio 的定位很明确:快速验证、本地开发、桌面应用。
它是“造工具”的最佳起点,但不是“用工具”的终极形态。
3. 升级到生产:为什么必须用 Streamable HTTP?
当我们要将 MCP 工具部署到云端、供多人或多个 Agent 共享时,必须切换到 Streamable HTTP。
⚠️ 注意:这里的“Streamable HTTP”不是老的 HTTP/1.1,而是一种支持真正双向实时通信的现代协议(基于 HTTP/2 或 HTTP/3),用起来跟 WebSocket 差不多,但部署更简单。
为什么不用常规 HTTP?
| 特性 | 常规 HTTP/1.1 | Streamable HTTP(MCP 推荐) |
|---|---|---|
| 连接模型 | 短连接(请求-响应后关闭) | 持久长连接,作为通信隧道 |
| 通信方向 | 半双工(客户端问 → 服务端答) | 全双工,双方可随时主动发消息 |
| 状态管理 | 无状态(每次请求独立) | 强有状态,可追踪任务上下文 |
| 效率 | 高并发但高开销(重复建连、头部冗余) | 多路复用,单连接承载多 RPC 流 |
| 适用场景 | 静态资源、简单 API | 实时协作、Agent 控制、流式推理 |
Streamable HTTP 的三大核心优势
- 持续的状态追踪
Agent 的推理常是多步的(Plan → Act → Observe → Reflect)。Streamable HTTP 通过长连接维持上下文,避免反复传输冗余历史。 - 原生全双工 JSON-RPC
服务端可在执行中主动推送日志、中间结果、权限请求(如“请授权访问你的日历”),无需等待客户端轮询。 - 流式反馈与控制
不仅返回最终结果,还能实时输出思考链(Chain-of-Thought)、函数调用轨迹、进度条等,极大提升用户体验与可调试性。
4. 两种 Transport 对比
| 维度 | Stdio | Streamable HTTP |
|---|---|---|
| 通信机制 | 父子进程 + 匿名管道 | 全双工 JSON-RPC over HTTP/2 |
| 延迟 | ~0.01ms | ~1–10ms |
| 安全性 | ⭐⭐⭐⭐⭐(进程隔离,无网络暴露) | ⭐⭐⭐⭐(需配合 TLS、认证) |
| 部署场景 | 本地开发、桌面应用 | 云服务、团队共享、生产环境 |
| 是否占用端口 | ❌ 否 | ✅ 是 |
| 典型启动方式 | python server.py 或 npx pkg | uvicorn server:app --host 0.0.0.0 --port 8000 |
📌 一句话总结:
Stdio 是“插 USB”,即插即用,但只能在自己电脑上用;
Streamable HTTP 是“连 WiFi”,稍复杂,但能让全世界的 Agent 都用上你的工具。
前文我们已经讲了Stdio的生产实现,下一小节我们将继续针对 Streamable HTTP 的实际实现。
注:本文针对工程细节并不会讲的过深,诸如父子进程、JSON-RPC、Schema等各种术语,会在下一章进行详细拆解。此处仅做简单了解即可。
⚠️ 另外,针对Transport的另一种通讯机制sse,因为它只支持单向限制,已被官方弃用,正逐步淘汰,故此处不做过多提及。
五、架构升级 —— 一键开启 Streamable HTTP 远程模式
既然要支持远程调用,代码会不会变得复杂?
完全不会!FastMCP 已将底层细节全部封装,你只需改动两行配置。
# streamable_http_server.py
# ...
# 创建服务实例,指定监听地址和端口
mcp = FastMCP("WeatherService",host="0.0.0.0",port=8001)
# ...
# 启动入口
if __name__ == '__main__':
# 运行方式:Streamable HTTP
# 这会自动启动 uvicorn 服务器,支持远程调用
mcp.run('streamable-http')
在mcp初始化处,我们为其填上host与port的主机与端口,再在末尾run内指定streamable-http即可。
运行查看控制台:
服务启动,再去CherryStudio里验证:
MCP链接成功,再去对话:
没有问题,Agent 成功调用你的远程服务,并返回结果。
💡 更棒的是:得益于 Streamable HTTP 的流式特性,工具调用过程支持实时日志输出与中间状态反馈,体验比 Stdio 模式更丰富(尤其在复杂工具场景下)。
总结、
本章我们彻底掌握了 MCP 服务端 (Server) 的开发。
- 我们使用 FastMCP,仅用装饰器就将 Python 函数变成了 AI 工具。
- 我们理解了 Stdio(本地管道)和 HTTP/SSE(远程网络)两种暴露方式的区别。
- 我们证明了:业务逻辑(查天气)和 传输协议(Stdio/HTTP)是完全解耦的。
预告:11 篇《MCP 进阶篇》
现在工具造好了,CherryStudio也能连上了。但是,如果我们自己的py代码想连接这个Server,该怎么写代码?下一篇,我们将深入 客户端(Client)开发,手写代码去对接我们造出来的这个双模传输 Server,完成最后的闭环。