返回
查看原链接原链接
Bilibili27分21秒 · —

MCP(模型上下文协议)进阶实战:从手写 Server 到协议本质

MCP(模型上下文协议)进阶实战:从手写 Server 到协议本质

核心结论:MCP 协议本质上是“函数注册与使用”的规范,它只规定了 client 与 server 之间如何发现和调用工具,并不规定与模型的交互方式——模型通过 MCP host(如 Claude Desktop、Cherry Studio)间接使用工具,MCP 协议的命名存在误导性,它实际上是让模型感知外部环境的桥梁。


核心要点

  • 本视频定位:MCP 终极指南系列的进阶篇,承接基础篇内容,要求观众具备一定编程基础(不要求很厉害,但必须有)。
  • 三大目标
  1. 动手编写自己的 MCP server
  2. 截获 MCP server 的输入输出并逐行分析,彻底理解协议运作机制
  3. 在掌握协议细节后,反思 MCP(模型上下文协议)在大模型应用中的真正角色
  • 代码获取:所有代码已上传至 GitHub 仓库,方便自行尝试。
  • 语言选择:使用 Python 讲解 MCP server 开发(考虑使用人数和编程难度),但强调 MCP 协议与语言无关——理解了协议本质后,甚至可以用 bash 脚本编写 MCP server。
  • 视频末尾预告:MCP host 与模型的交互方式留待观众评论区反馈后决定是否制作后续视频。

第一部分:手写 MCP Server

环境准备

工具/依赖说明
Python版本需 ≥ 3.10(macOS 自带,Windows 需到官网下载安装)
uvPython 包管理器
VS Code写代码的软件
ClineVS Code 插件,用作 MCP host

项目创建步骤

步骤 1:初始化项目与虚拟环境

uv init weather          # 新建名为 weather 的项目
cd weather               # 进入项目目录
uv venv                  # 新建虚拟环境
source .venv/bin/activate  # 激活虚拟环境(括号中出现 weather 即表示成功)

要点:新建虚拟环境的目的是防止安装的各种依赖影响系统环境。

步骤 2:安装依赖

uv add "mcp[cli]" httpx
  • mcp[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 类似但更复杂):
  1. 第一次调用:获取对应的天气预告办公室(weather forecast office)信息,存放在 points_data 中(因为美国很大,不同地区的天气预告由不同办公室负责)
  2. 从返回数据中提取对应天气预告的 URL,存入 forecast_url 变量
  3. 第二次调用:用 forecast_url 再次请求美国气象局,才算真正拿到天气预告数据
  4. 对天气预告信息进行格式化并返回

装饰器的作用机制

两个函数头上的 @mcp.tool 装饰器的作用:

  • 将函数注册为 tool
  • 从函数注释(docstring)中提取:函数用途、每个参数的含义
  • 提取的内容最终转换为 tool 的信息,在实际调用时传给模型,帮助模型做决策

get_alerts 为例,提取内容包括:

  1. 函数名:get_alerts
  2. 参数:state,类型是字符串
  3. 函数的功能描述
  4. 每个参数的功能描述

启动 MCP Server

if __name__ == "__main__":
    mcp.run(transport="stdio")
  • `transport="stdio"`:表示 MCP server 与 client 的沟通方式为标准输入输出(stdin/stdout),目前市面上大部分 MCP server 都采用这种方式。

注册到 Cline 并测试

  1. 点击侧边栏 Cline 图标 → 打开 MCP server 配置 → 点击 "Installed"
  2. 点击 "Configure MCP servers"
  3. 输入启动命令:
uv --directory /path/to/weather run weather.py

注意--directory 参数告诉 uv 项目目录在哪里(外部依赖和虚拟环境配置都从该目录查找),目录必须改成自己的路径

  1. 保存配置文件后,Cline 会自动注册 MCP server
  2. 新建对话输入“纽约明天的天气怎么样?”,模型找到 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_alerts
  • get_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 有两个属性(latitudelongitude),类型均为 number,且二者必须存在(required 数组)
  • 模型不仅需要选择最匹配的工具,还需要从用户问题中提取工具参数,且参数必须符合 inputSchema 规范,才能成功调用工具背后的函数
阶段四:资源与资源模板查询(resources/list)

client → server:询问是否有资源和资源模板可用

server 响应resourcesresources_templates 结果均为空列表

  • Resource(资源):指文件、报告之类的东西
  • Resource templates(资源模板):可理解为动态资源
  • 本 MCP server 不涉及资源,视频中不展开讲解

至此,摸底结束。这一切发生在注册工具的一瞬间,之后 client 静默等待合适时机使用该 MCP server。

阶段五:工具调用(tools/call)

触发:新开对话,问“纽约明天的天气怎么样?”→ 模型找到对应工具 → 用户允许执行

client → server 的调用请求

  • 工具名:get_forecast
  • 参数:latitude=40.7128longitude=-74.006(北纬 40 度,西经 74 度,即纽约)
  • 参数结构符合此前 inputSchema 的定义,说明模型提取参数时确实遵守了 inputSchema 规范

server → client 的响应

  • 核心内容在 text 字段中
  • 换行符被转义为 \n,不易阅读,需格式化
  • 格式化后内容包含未来几天的天气:今晚、星期五、星期五晚上、星期六、星期六晚上的预报——都是 get_forecast 函数返回的结果

交互结束

  • client 拿到工具执行结果后,与 MCP server 的交互结束
  • client 将结果发给模型,让模型总结,最终呈现给用户

第三部分:脱离 MCP Host 直接通信

操作演示

前提:掌握了协议细节后,不需要 MCP host 也可以直接与 MCP server 通信,只需保证发送的数据符合协议格式。

步骤

  1. 在终端中直接运行 MCP server 命令(即 Cline 配置中的内容:uv --directory ... run weather.py),不经过 mcp_logger.py
  2. 手动发送打招呼消息(复用此前日志中的第一行输入),修改 client 名称(如改为“马克的技术工作坊”),版本号改为 1.0.0 → server 正常回复(打招呼 + 能力声明)
  3. 发送 notifications/initialized 消息 → server 不回复(与日志表现一致)
  4. 发送工具列表请求(复制日志中的对应行)→ server 返回工具列表
  5. 发送调用工具请求 → server 返回调用结果

结论:不借助 MCP host 也能与 MCP server 通信,此时用户自己就是 MCP host。甚至不需要 MCP 库也能开发 MCP server,只需要保证符合 MCP 规范即可(当然自己做会比较麻烦)。

回顾基础篇的交互流程

之前日志中看到的所有交互均可对应到基础篇画的交互流程图:

  1. Client 与 MCP server 打招呼 → server 自我介绍
  2. Client 询问有哪些工具 → server 回复 get_forecastget_alerts
  3. 后续流程中 client 请求调用工具 → server 调用后返回结果

关键结论:日志中看不到模型的影子。


MCP 协议的本质解析

协议规定范围

MCP 协议规定的内容仅限于 client 与 server 之间的交互,与模型没有直接关系。具体包含两大部分:

  1. 每个 MCP server 有哪些函数(工具)可以用 —— 工具发现
  2. 如何调用这些函数 —— 工具调用

(部分 MCP server 内部还有资源可以使用,但视频中忽略不计。)

这两点总结为:函数的注册与使用

关于模型交互的重要澄清

  • MCP 协议没有规定与模型的交互方式,这部分由各 MCP host 自行处理
  • 不同 MCP host 与模型的交互存在巨大差异:
  • Cline:使用 XML 格式与模型通信
  • Cherry Studio:使用 function calling 格式与模型通信(function calling 是 OpenAI 提出的规定模型如何调用函数的协议)

这一点必须反复强调:MCP 协议并没有规定如何与模型进行交互

"模型上下文协议"名称的剖析

  • 什么是模型上下文? 上下文 = 环境
  • 什么是环境? 环境就是周围有哪些函数可以用来调用,从而获取外界信息(天气、网络、文件信息等)
  • MCP 的本质:让模型感知外部环境的协议,因此叫“模型上下文协议”

命名的争议与批判

  1. 误导性:容易让人误以为该协议规定的是模型交互内容,但实际上最多只能说“该协议是给模型服务的”
  2. 过于玄妙:乍看之下不知道是什么意思,可能是创造者故意起得高大上
  3. 专业术语的谜团:MCP 领域各个专业名词都蒙着一层雾,需要打开、揉碎才能理解

核心概念总结表

概念定义关键说明
MCP Server提供工具(函数)的服务端与语言无关,可使用 Python、Node.js、Java、C# 等实现
MCP Host使用 MCP server 的客户端(如 Cline)负责与模型交互,不同 host 的模型交互方式不同
Tool注册在 MCP server 上的函数通过 @mcp.tool 装饰器注册,包含描述和参数规范
docstringPython 函数注释被装饰器提取后作为工具描述(description)传给模型
JSON Schema描述 JSON 结构的 JSON用于定义工具入参规范,模型提取参数时遵守此规范
stdio transport标准输入输出通信方式当前大部分 MCP server 采用的通信方式
initialize初始握手client 与 server 交换协议版本和能力信息
tools/list工具列表查询获取 server 可用工具及参数规范
tools/call工具调用传入工具名和符合 inputSchema 的参数
Function CallingOpenAI 的模型调用函数协议是 MCP host 与模型交互的一种方式,与 MCP 协议本身无关

方法与步骤速查

创建 MCP server 步骤

  1. uv init weather → 创建项目
  2. cd weather && uv venv → 创建虚拟环境
  3. source .venv/bin/activate → 激活虚拟环境
  4. uv add "mcp[cli]" httpx → 安装依赖
  5. 创建 weather.py
  • 导入 FastMCP 并实例化(添加 `log_level="ERROR"` 防止日志干扰运行)
  • 定义工具函数并添加 @mcp.tool 装饰器(善用 docstring 描述功能和参数)
  • 调用 mcp.run(transport="stdio") 启动

配置到 Cline

  1. Cline → MCP server 配置 → Installed → Configure MCP servers
  2. 配置启动命令:uv --directory <项目路径> run weather.py
  3. 保存后自动注册,即可在对话中调用

查看通信日志

  1. mcp_logger.py 包裹启动命令
  2. 交互数据写入 mcp_io.log
  3. 按行解析 JSON 内容,理解各阶段交互

脱离 MCP host 直接通信

  1. 终端直接运行 MCP server 命令
  2. 手动构造 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 通信
  • "模型上下文协议"这个名字有误导性且过于玄妙,实际含义是:模型感知外部环境的桥梁