MCP(模型上下文协议)进阶实战:从手写 Server 到协议本质
核心结论:MCP 协议本质上是“函数注册与使用”的规范,它只规定了 client 与 server 之间如何发现和调用工具,并不规定与模型的交互方式——模型通过 MCP host(如 Claude Desktop、Cherry Studio)间接使用工具,MCP 协议的命名存在误导性,它实际上是让模型感知外部环境的桥梁。
核心要点
- 本视频定位:MCP 终极指南系列的进阶篇,承接基础篇内容,要求观众具备一定编程基础(不要求很厉害,但必须有)。
- 三大目标:
- 动手编写自己的 MCP server
- 截获 MCP server 的输入输出并逐行分析,彻底理解协议运作机制
- 在掌握协议细节后,反思 MCP(模型上下文协议)在大模型应用中的真正角色
- 代码获取:所有代码已上传至 GitHub 仓库,方便自行尝试。
- 语言选择:使用 Python 讲解 MCP server 开发(考虑使用人数和编程难度),但强调 MCP 协议与语言无关——理解了协议本质后,甚至可以用 bash 脚本编写 MCP server。
- 视频末尾预告:MCP host 与模型的交互方式留待观众评论区反馈后决定是否制作后续视频。
第一部分:手写 MCP Server
环境准备
| 工具/依赖 | 说明 |
|---|---|
| Python | 版本需 ≥ 3.10(macOS 自带,Windows 需到官网下载安装) |
| uv | Python 包管理器 |
| VS Code | 写代码的软件 |
| Cline | VS Code 插件,用作 MCP host |
项目创建步骤
步骤 1:初始化项目与虚拟环境
uv init weather # 新建名为 weather 的项目
cd weather # 进入项目目录
uv venv # 新建虚拟环境
source .venv/bin/activate # 激活虚拟环境(括号中出现 weather 即表示成功)要点:新建虚拟环境的目的是防止安装的各种依赖影响系统环境。
步骤 2:安装依赖
uv add "mcp[cli]" httpxmcp[cli]:开发 MCP server 的核心依赖httpx:用于调用 HTTP 接口(查询天气需要)
步骤 3:创建 weather.py 文件
在 VS Code 中打开 weather 项目目录,新建 weather.py 文件。
代码详细解析
(1)导入与初始化
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("weather", log_level="ERROR")FastMCP函数用于快速构建 MCP server,传入 server 名称,返回对象命名为mcp,后续注册工具和启动 server 都使用该对象。- ⚠️ 与官方代码的关键差异:官方代码没有
log_level="ERROR"这一参数,是作者自行添加的。不加这段,MCP server 极有可能运行不起来,因为默认情况下程序会输出各种日志干扰 MCP server 运行。
(2)导入常量与工具函数
- 导入美国气象局的 URL 地址常量(用于请求美国天气信息)
- 导入请求标识:表明身份信息(
weather app/1.0),类似浏览器请求时携带 Chrome 相关信息 make_nws_request(url):请求天气数据的函数,接受一个 URL 参数,内部使用 httpx 库调用该 URL 并返回结果format_alert(data):对告警数据做格式化的工具类函数
(3)第一个工具:get_alerts
@mcp.tool
def get_alerts(state: str) -> str:
"""获取美国某个州的天气预警"""
# 调用美国气象局接口(make_nws_request)
# 检测调用是否失败
# 检查是否不存在预警信息,不存在则立即返回结果
# 对预警信息做格式化,只取接口返回中需要的部分- 输入参数:美国州代码(字符串)
- 功能:返回该州的天气预警信息
- 实现逻辑:直接调用美国气象局接口 → 对结果做检测(确保调用成功)→ 检查地区是否无预警(无则提前返回)→ 格式化预警信息
(4)第二个工具:get_forecast
@mcp.tool
def get_forecast(latitude: float, longitude: float) -> str:
"""获取某个地区的天气预告"""- 输入参数:纬度和经度(均为浮点数)
- 功能:获取指定地区的天气预告
- 实现逻辑(与 get_alerts 类似但更复杂):
- 第一次调用:获取对应的天气预告办公室(weather forecast office)信息,存放在
points_data中(因为美国很大,不同地区的天气预告由不同办公室负责) - 从返回数据中提取对应天气预告的 URL,存入
forecast_url变量 - 第二次调用:用
forecast_url再次请求美国气象局,才算真正拿到天气预告数据 - 对天气预告信息进行格式化并返回
装饰器的作用机制
两个函数头上的 @mcp.tool 装饰器的作用:
- 将函数注册为 tool
- 从函数注释(docstring)中提取:函数用途、每个参数的含义
- 提取的内容最终转换为 tool 的信息,在实际调用时传给模型,帮助模型做决策
以 get_alerts 为例,提取内容包括:
- 函数名:
get_alerts - 参数:
state,类型是字符串 - 函数的功能描述
- 每个参数的功能描述
启动 MCP Server
if __name__ == "__main__":
mcp.run(transport="stdio")- `transport="stdio"`:表示 MCP server 与 client 的沟通方式为标准输入输出(stdin/stdout),目前市面上大部分 MCP server 都采用这种方式。
注册到 Cline 并测试
- 点击侧边栏 Cline 图标 → 打开 MCP server 配置 → 点击 "Installed"
- 点击 "Configure MCP servers"
- 输入启动命令:
uv --directory /path/to/weather run weather.py注意:
--directory参数告诉 uv 项目目录在哪里(外部依赖和虚拟环境配置都从该目录查找),目录必须改成自己的路径。
- 保存配置文件后,Cline 会自动注册 MCP server
- 新建对话输入“纽约明天的天气怎么样?”,模型找到
get_forecast工具,点击 approve 同意调用,模型拿到调用结果后整理成答案返回。
第二部分:截获分析 MCP Server 输入输出
问题与方案
问题:MCP server 由 Cline 启动,其输入输出只有 Cline 能看到,用户无法直接查看。
方案:编写一个中间人脚本 mcp_logger.py:
- 脚本参数:启动 MCP server 的命令(如
uv --directory ... run weather.py) - 功能:在 client 与真实 MCP server 之间插入一层,截获双方的输入输出并写入外部文件
- 效果:通过
uv run mcp_logger.py uv --directory ... run weather.py启动,即可查看 client 与 MCP server 的全部通信内容 - 该脚本由作者让 Gemini 2.5 Pro 编写生成,经测试运行良好
修改 Cline 配置:
command改为python- 参数中加上
mcp_logger.py的路径 - 输入输出日志写入与
mcp_logger.py同目录的mcp_io.log文件
日志格式说明
- 每行开头有
>>>(输入)和<<<(输出)标记及冒号,这些是mcp_logger.py加的标记,并非 client 与 MCP server 的原始交互内容 - 标记后的 JSON 才是真正的交互数据
- 输入(incoming):client → MCP server
- 输出(outgoing):MCP server → client
协议交互逐行分析
阶段一:初始化握手(initialize)
第一行(client → server):
- client 向 MCP server 打招呼,告知自身信息:软件版本
3.12.3,使用的 MCP 协议版本为2024-11-05(2024 年 11 月 5 日出品的版本)
第二行(server → client):
- server 回应:使用相同协议版本
2024-11-05 - 声明自身能力(capabilities),告知哪些能力不支持
- 自我介绍:名字
weather,版本号1.6.0
阶段二:通知初始化完成(notifications/initialized)
第三行(client → server):
- client 告知 server“已收到初始化信息”,可以开始工作了
- 这是通知(notification)类型,server 不会回复
阶段三:工具列表请求(tools/list)
client → server:请求获取 MCP server 中包含哪些工具
server → client 响应:包含两个工具定义:
get_alertsget_forecast
工具定义的格式详解(以 get_forecast 为例):
{
"name": "get_forecast",
"description": "获取某个地区的天气预告",
"inputSchema": {
"type": "object",
"properties": {
"latitude": {"type": "number"},
"longitude": {"type": "number"}
},
"required": ["latitude", "longitude"]
}
}- `description` 字段:来源于函数的注释(docstring),由
@mcp.tool装饰器提取后放入此字段,作为工具描述,模型据此选择与用户问题最匹配的工具 - `inputSchema` 字段:定义工具入参规范,由
@mcp.tool装饰器根据函数参数定义自动生成
JSON Schema 原理:
- JSON Schema 本身就是一段 JSON,特殊功能是描述另一个 JSON 的结构
- 上述
inputSchema描述的目标 JSON 有两个属性(latitude和longitude),类型均为 number,且二者必须存在(required数组) - 模型不仅需要选择最匹配的工具,还需要从用户问题中提取工具参数,且参数必须符合
inputSchema规范,才能成功调用工具背后的函数
阶段四:资源与资源模板查询(resources/list)
client → server:询问是否有资源和资源模板可用
server 响应:resources 和 resources_templates 结果均为空列表
- Resource(资源):指文件、报告之类的东西
- Resource templates(资源模板):可理解为动态资源
- 本 MCP server 不涉及资源,视频中不展开讲解
至此,摸底结束。这一切发生在注册工具的一瞬间,之后 client 静默等待合适时机使用该 MCP server。
阶段五:工具调用(tools/call)
触发:新开对话,问“纽约明天的天气怎么样?”→ 模型找到对应工具 → 用户允许执行
client → server 的调用请求:
- 工具名:
get_forecast - 参数:
latitude=40.7128,longitude=-74.006(北纬 40 度,西经 74 度,即纽约) - 参数结构符合此前
inputSchema的定义,说明模型提取参数时确实遵守了inputSchema规范
server → client 的响应:
- 核心内容在
text字段中 - 换行符被转义为
\n,不易阅读,需格式化 - 格式化后内容包含未来几天的天气:今晚、星期五、星期五晚上、星期六、星期六晚上的预报——都是
get_forecast函数返回的结果
交互结束:
- client 拿到工具执行结果后,与 MCP server 的交互结束
- client 将结果发给模型,让模型总结,最终呈现给用户
第三部分:脱离 MCP Host 直接通信
操作演示
前提:掌握了协议细节后,不需要 MCP host 也可以直接与 MCP server 通信,只需保证发送的数据符合协议格式。
步骤:
- 在终端中直接运行 MCP server 命令(即 Cline 配置中的内容:
uv --directory ... run weather.py),不经过mcp_logger.py - 手动发送打招呼消息(复用此前日志中的第一行输入),修改 client 名称(如改为“马克的技术工作坊”),版本号改为 1.0.0 → server 正常回复(打招呼 + 能力声明)
- 发送
notifications/initialized消息 → server 不回复(与日志表现一致) - 发送工具列表请求(复制日志中的对应行)→ server 返回工具列表
- 发送调用工具请求 → server 返回调用结果
结论:不借助 MCP host 也能与 MCP server 通信,此时用户自己就是 MCP host。甚至不需要 MCP 库也能开发 MCP server,只需要保证符合 MCP 规范即可(当然自己做会比较麻烦)。
回顾基础篇的交互流程
之前日志中看到的所有交互均可对应到基础篇画的交互流程图:
- Client 与 MCP server 打招呼 → server 自我介绍
- Client 询问有哪些工具 → server 回复
get_forecast和get_alerts - 后续流程中 client 请求调用工具 → server 调用后返回结果
关键结论:日志中看不到模型的影子。
MCP 协议的本质解析
协议规定范围
MCP 协议规定的内容仅限于 client 与 server 之间的交互,与模型没有直接关系。具体包含两大部分:
- 每个 MCP server 有哪些函数(工具)可以用 —— 工具发现
- 如何调用这些函数 —— 工具调用
(部分 MCP server 内部还有资源可以使用,但视频中忽略不计。)
这两点总结为:函数的注册与使用。
关于模型交互的重要澄清
- MCP 协议没有规定与模型的交互方式,这部分由各 MCP host 自行处理
- 不同 MCP host 与模型的交互存在巨大差异:
- Cline:使用 XML 格式与模型通信
- Cherry Studio:使用 function calling 格式与模型通信(function calling 是 OpenAI 提出的规定模型如何调用函数的协议)
这一点必须反复强调:MCP 协议并没有规定如何与模型进行交互。
"模型上下文协议"名称的剖析
- 什么是模型上下文? 上下文 = 环境
- 什么是环境? 环境就是周围有哪些函数可以用来调用,从而获取外界信息(天气、网络、文件信息等)
- MCP 的本质:让模型感知外部环境的协议,因此叫“模型上下文协议”
命名的争议与批判:
- 误导性:容易让人误以为该协议规定的是模型交互内容,但实际上最多只能说“该协议是给模型服务的”
- 过于玄妙:乍看之下不知道是什么意思,可能是创造者故意起得高大上
- 专业术语的谜团:MCP 领域各个专业名词都蒙着一层雾,需要打开、揉碎才能理解
核心概念总结表
| 概念 | 定义 | 关键说明 |
|---|---|---|
| MCP Server | 提供工具(函数)的服务端 | 与语言无关,可使用 Python、Node.js、Java、C# 等实现 |
| MCP Host | 使用 MCP server 的客户端(如 Cline) | 负责与模型交互,不同 host 的模型交互方式不同 |
| Tool | 注册在 MCP server 上的函数 | 通过 @mcp.tool 装饰器注册,包含描述和参数规范 |
| docstring | Python 函数注释 | 被装饰器提取后作为工具描述(description)传给模型 |
| JSON Schema | 描述 JSON 结构的 JSON | 用于定义工具入参规范,模型提取参数时遵守此规范 |
| stdio transport | 标准输入输出通信方式 | 当前大部分 MCP server 采用的通信方式 |
| initialize | 初始握手 | client 与 server 交换协议版本和能力信息 |
| tools/list | 工具列表查询 | 获取 server 可用工具及参数规范 |
| tools/call | 工具调用 | 传入工具名和符合 inputSchema 的参数 |
| Function Calling | OpenAI 的模型调用函数协议 | 是 MCP host 与模型交互的一种方式,与 MCP 协议本身无关 |
方法与步骤速查
创建 MCP server 步骤:
uv init weather→ 创建项目cd weather && uv venv→ 创建虚拟环境source .venv/bin/activate→ 激活虚拟环境uv add "mcp[cli]" httpx→ 安装依赖- 创建
weather.py:
- 导入
FastMCP并实例化(添加 `log_level="ERROR"` 防止日志干扰运行) - 定义工具函数并添加
@mcp.tool装饰器(善用 docstring 描述功能和参数) - 调用
mcp.run(transport="stdio")启动
配置到 Cline:
- Cline → MCP server 配置 → Installed → Configure MCP servers
- 配置启动命令:
uv --directory <项目路径> run weather.py - 保存后自动注册,即可在对话中调用
查看通信日志:
- 用
mcp_logger.py包裹启动命令 - 交互数据写入
mcp_io.log - 按行解析 JSON 内容,理解各阶段交互
脱离 MCP host 直接通信:
- 终端直接运行 MCP server 命令
- 手动构造 JSON-RPC 消息,按协议规范逐步交互
限制与待确认问题
- 资源(resources)和资源模板(resource templates):视频中仅提及概念,未展开讲解,因示例 server 不涉及
- MCP host 与模型的交互机制:视频明确表示不在 MCP 协议范围内,不同 host 交互方式差异很大(Cline 用 XML,Cherry Studio 用 function calling),但具体细节未深入展开,取决于观众反馈决定是否制作后续内容
- Python 语法细节:视频明确说明不讲解 Python 内置函数用法,不熟悉 Python 的观众需自行区分 Python 问题与 MCP 问题
- HTTP 接口输入输出格式转换:视频中一带而过,视为无关紧要内容
- 缺失信息:视频未提供
mcp_logger.py的具体代码实现细节(仅说明由 Gemini 2.5 Pro 生成)
总结
- 通过动手编写 MCP server 和逐行分析通信日志,可以彻底理解 MCP 协议的运作机制:
initialize握手 →notifications/initialized通知 →tools/list工具发现 →tools/call工具调用 - MCP 协议本质上是工具(函数)的注册与使用规范,与模型无关,但为模型服务——它让模型感知外部环境(天气、网络、文件等)
- MCP 协议与编程语言无关,理解了协议本质后,即可用任何语言甚至 bash 脚本实现 MCP server
- 即使没有 MCP host,只要数据格式符合协议规范,也可以直接与 MCP server 通信
- "模型上下文协议"这个名字有误导性且过于玄妙,实际含义是:模型感知外部环境的桥梁