MCP Host 与模型的通信机制:以 Claude Code 的 XML 协议为例
Claude Code 作为 MCP Host,通过自定义的 XML 格式与语言模型进行通信,其本质是 2022 年论文提出的 ReAct 模式(Thought-Action-Observation)的一种具体实现,理解了这一点即可自行构建类似 Claude Code 的 Agent 程序。
背景与核心概念
MCP 协议的边界
- MCP(Model Context Protocol)协议仅规定了 MCP Host 与 MCP Server 之间的通信协议,包括工具发现、调用、资源访问等。
- MCP 协议 没有对模型(Model)的输入和输出格式提出任何要求,因此不同的 MCP Host 可能使用完全不同的格式与模型交互。
- Claude Code 使用一种自定义的 XML 格式(x-mail) 与模型进行沟通,这是本期视频演示的核心对象。
本期目标
- 演示 Claude Code 与模型交互的具体细节(通过截获模型的输入和输出)。
- 剖析 Claude Code 使用的 XML 协议的结构。
- 讲解构建 Agent 常用的 ReAct 模式。
- 梳理 Claude Code 的 XML 协议与 ReAct 模式之间的关系。
- 最终目标是借学习此协议,理解如何编写一个能够与模型持续交互的 Agent 程序。
实验环境搭建:截获模型请求与返回
设计思路
- 原始方案:Claude Code 直接与 OpenAI 兼容的模型提供商(如 DeepSeek)通信,无法直接观察到通信内容。
- 中转方案:在 Claude Code 与模型之间插入一个本地服务器作为中间人。所有请求与响应必须经过该服务器,服务器会将内容写入日志文件,由此获取完整的交互链路。
配置 Claude Code 连接本地服务器
- Claude Code 的 API Provider 中除了常见的 OpenAI、DeepSeek 等提供商之外,存在一种特殊选项 OpenAI compatible。
- 它的含义是:目标模型提供商虽然不是 OpenAI,但其 API 完全兼容 OpenAI 的格式规范。
- 配置要点:
- 选择
OpenAI compatible - 将 Base URL 填入本地服务器地址:
http://localhost:8000 - API Key 和 Model ID 与 OpenRouter(或目标提供商)保持一致,因为转中服务器需要将这些信息转发给上游。
本地中转服务器代码解析
中转服务器使用 Python 编写,核心基于 FastAPI 框架。代码逻辑围绕“先记录请求,再记录返回”展开。
关键组件:
| 变量/对象 | 作用 |
|---|---|
app_log | 日志写入函数,将 message 写入指定 log 文件 |
app | FastAPI 生成的 Web 应用实例,用于定义 POST 接口 |
logger | 基于 app_log 生成的日志记录器,负责记录模型请求和返回,输出到 lm.log 文件 |
接口定义:
- 定义了一个 POST 接口,路径为
/chat/completions,Claude Code 会将模型请求放入 POST body 中请求此路径。 - 接口内部逻辑:
- 获取 Claude Code 发来的请求(post body),写入日志文件。
- 转发请求至 OpenRouter,注意保留流式返回标志(
accept text/event-stream,即 SSE 协议)。 - 接收上游模型的流式响应,将每一行返回(line)写入日志。
- 将上游响应实时转发回 Claude Code。
- 服务器监听端口为 8000。
流程图示(文字版):
Client → (1) POST请求 → 本地服务器 → (2) 记录请求日志 → (3) 转发 → OpenRouter
Client ← (7) 转发响应 ← 本地服务器 ← (6) 记录返回日志 ← (5) SSE响应 ← OpenRouterSSE(Server-Sent Events)简介
- 中文含义为“服务器推送事件”,是模型流式返回的专业术语(缩写为 SSE)。
- 解决的问题:普通 HTTP 交互方式为“一去一回”,无法处理服务器连续多次响应的场景。
- 工作原理:
- 浏览器只需请求一次。
- 服务器接收到请求后连续发送多次响应,每次响应内容为数个字(token)。
- 浏览器接到几个字就显示几个字,实现打字机效果,提升用户体验。
- 所有结果发送完毕后,服务器发送一个完成标识,浏览器收到后关闭 SSE 连接,对话结束。
- 在日志中,上游的每一条 SSE 返回均以
data:开头,包含一段 JSON。将每条data中的content值拼接起来,即为模型的完整返回内容。 - 如果某行以
:开头,则该行为注释,实际内容无意义,主要作用是保持连接,防止等待期间因超时而断开连接。
启动步骤
# 1. 创建虚拟环境(防止依赖影响系统环境)
python -m venv .venv
# 2. 激活虚拟环境
source .venv/bin/activate
# 3. 安装依赖
pip install -r requirements.txt
# 依赖仅有三个:fastapi(定义接口)、uvicorn(运行服务器)、httpx(向 OpenRouter 发起 HTTP 请求)
# 4. 启动本地服务器
python lm_logger.py
# 输出显示端口为 8000使用 Claude Code 验证
- 在 Claude Code 中点击配置 API Provider,选择
OpenAI compatible,Base URL 填写http://localhost:8000,保存。 - 向 Claude Code 随便发送打招呼消息(如“嗨”),等待回复完成后,打开日志文件,可看到模型请求和模型返回两部分内容。
模型请求解析(无工具调用场景)
请求整体结构
模型请求包含以下几大核心部分:
- model:使用的模型(如 deepseek)。
- messages:消息列表,存放 Client 发给模型的请求以及模型的历史回答。
- temperature:温度参数,控制模型输出的确定性。值越小,输出越稳定保守;值越大,输出越多样、富有想象力。
- stream:代表使用 SSE 流式输出。
System Prompt 分析
第一条消息的 role 为 system,即系统提示词。其功能是设定模型需要提前感知的信息,包括:模型扮演的角色、可用的工具列表、返回结果的格式等。
基本规模数据:
- 系统性提示词总共有 626 行(格式化后),部分行非常长。
- 总字符数为 48671 个字符。
- 这些内容都会作为 token 发送给模型,占用上下文长度。
System Prompt 基本框架(折叠后的小标题):
- 工具使用格式(Tool Use Format)
- 工具列表(Tools)
- 工具使用示例(Tool Use Examples)
- 工具使用指南(Tool Usage Guidelines)
- 已连接的 MCP 服务器(Connected MCP Servers)
- 客户端内置函数:
write_to_file、replace_in_file等 - 工具选择的指导
- 自动格式化注意事项
- 工作流程提示
- 首选语言(Primary Language)
工具的分类
Claude Code 中的“工具”包含两类:
- Claude Code 内置工具:如写入文件、替换文件内容、读取文件、运行终端命令等,用于操作本地环境和项目。
- MCP 工具:通过 MCP 服务器提供的工具,如常用的天气预告和气象预警工具。
工具调用格式:XML 协议详解
- Claude Code 规定客户端工具调用请求必须使用 XML 格式传递。
- 外层标签写工具名称,内层标签写参数的名称和参数值。
读取文件工具格式示例:
外层写工具名称 read,内层写参数名 file_path(原视频中实际参数名为 paas 或 src,以实际内容为准),参数值即为文件路径,例如 src/index.js。
模型调用工具的实现流程:
- 用户提出需要读取文件的问题(如“src/index.js 这个文件写了什么”)。
- Client 将问题发给模型。
- 模型发现需要调用
read工具来获取文件内容,于是按照 system prompt 中的 XML 格式返回工具调用请求。 - Client 接到请求后执行读取操作,并将文件内容返回给模型。
- 模型自己总结答案并回复用户。
关键原则:只要模型按照 client 规定的 XML 格式返回,client 就可以帮助模型调用任何其想要调用的工具。
常用工具列表
| 工具名 | 功能 | 参数与备注 |
|---|---|---|
execute_command | 执行终端命令 | 包含两个参数:命令内容和是否需要用户同意。一些可能影响系统的命令(如删除文件、安装文件)需用户同意。 |
read | 读取文件 | 参数为文件路径 |
write_to_file | 写入文件内容 | 参数为文件路径和内容 |
replace_in_file | 替换文件内容 | 参数为文件路径和替换内容 |
search_files | 搜索文件 | — |
list_files | 列举当前项目目录中的文件列表 | — |
list_code_definition_names | 列举指定目录顶层源代码文件中使用的定义名称(类、函数、方法等) | — |
browser_action | 浏览器操作 | — |
重点工具:use_mcp_tool
use_mcp_tool 是本期视频的重点,用于调用 MCP 服务器提供的工具,参数共三个:
| 参数名 | 含义 |
|---|---|
server_name | MCP 服务器的名称 |
tool_name | MCP 工具的名称 |
arguments | MCP 工具的输入参数(通常以 JSON 形式) |
示例:以下 XML 表示模型想要调用 weather 服务器下的 get_forecast 工具,参数为纬度 41.7128、经度 -74.006(即纽约)。
<use_mcp_tool>
<server_name>weather</server_name>
<tool_name>get_forecast</tool_name>
<arguments>{"latitude": 41.7128, "longitude": -74.006}</arguments>
</use_mcp_tool>其他重要标签
- access_mcp_resource:用于获取 MCP 资源,需填入 MCP 服务器名称和资源 URI。本期视频暂不涉及。
- ask_follow_up_question:模型向用户提问时使用的工具标签,用于澄清问题。
- attempt_completion:表示回答结束的标志。模型在调用一系列工具后,认为自己已完成任务或已得到答案,会将最终结论放入
attempt_completion的result参数。Client 接到后显示结果并结束对话。 - 另一个可选参数为
command,存储一条用于展示最终结果的命令,本期不使用。
Thinking 强制思考机制
- Claude Code 要求模型在做任何事情之前,都必须先使用
thinking标签进行思考,包括使用工具之前和输出最终答案之前。 - 这是一种强制让模型“思考”的机制,有利于提高模型决策的准确率。
已连接的 MCP 服务器信息
- Claude Code 会将用户在客户端上配置的 MCP 服务器及每个服务器包含的 MCP 工具都列举在 system prompt 中,供模型选用。
- 包含的信息:每个 MCP 工具的用途、参数列表(帮助模型判断何时调用)、input schema(告诉模型调用工具时的参数格式)。
- Input schema 本质上是 JSON Schema,该概念在 MCP 进阶指南中已详细讲解。
用户请求消息解析
用户请求(role 为 user)分为两部分:
- 用户原始问题:放在
<task>标签内,例如<task>嗨</task>,告诉模型这是需要完成的任务。 - 当前系统环境:同样以 XML 表示,放在一个长字符串中,包含当前可见的文件、打开的 tab、当前时间等信息,辅助模型做决策。
模型返回解析(无工具调用场景)
流式返回结构
- 模型采用 SSE 方式逐量返回。日志中每一行
data:对应一次返回。 - 首行为注释(
data: : OPENROUTER PROCESSING),以冒号开头,作用是保持连接,无有效信息。 - 后面的每一行
data:是 JSON 数据,其中大部分字段(如 id、provider、model)均相同。 - 每一条消息中唯一不同的是 content 字段,其值为增量返回,将每条 content 拼接起来即为模型的完整返回。
示例
如果模型最终完整返回为“纽约明天的温度是24摄氏度”,则 SSE 返回可能包含多条 data,例如:
data: ...content="纽约"data: ...content="明天的"data: ...content="温度"data: ...content="是"data: ...content="24摄氏度"data: ...content="[DONE]"(结束标识符)
对应展示页面
- Claude Code 收到流式消息后逐条展示给用户。
- Thinking 内容展示在特定区域,
ask_follow_up_question的内容展示在对应位置。
MCP 工具调用完整流程实例
实例一:仅查询天气并返回答案
场景:问“纽约明天的天气怎么样?”,Claude Code 调用 weather MCP 服务器下的 get_forecast 工具。
完整交互流程(文字版):
第一轮请求(Client → 模型):
- System prompt(与前文分析一致,包含工具列表与格式说明)。
- 用户问题(role 为 user,包含 task 标签与系统环境信息)。
模型第一次返回:
- 包含两个 XML 标签:
<thinking>内容:模型推理过程。<use_mcp_tool>内容:请求调用weather服务器的get_forecast工具,参数为纽约的经纬度。
第二轮请求(Client → 模型,在工具执行后):
- 将之前的 system prompt、用户问题以及模型工具调用请求(role 为 assistant)作为历史消息重新发送给模型(因模型无记忆)。
- 新增消息(role 为 user):
- 第一部分:展示 MCP 工具调用的结果。
- 第二部分:具体结果内容(如纽约明天的天气温度数据)。
- 第三部分:当前环境信息。
模型第二次返回:
- 同样包含两个 XML 标签:
<thinking>内容:模型思考。<attempt_completion>内容:最终结论回答。
流程图示:
用户提问 → 模型返回 (thinking + use_mcp_tool) → Client 调用 MCP 工具
→ 工具结果返回给模型 → 模型返回 (thinking + attempt_completion) → 显示最终答案,结束。实例二:查询天气并写入文件
场景:将任务改为“纽约明天的天气怎么样?把结果写入到 result.md 文件中”。
修改后的流程:
- 用户提问。
- 模型返回
thinking+use_mcp_tool(调用 get_forecast 工具)。 - Client 调用工具,将天气结果返回给模型。
- 模型第一次思考后发现任务未完成(还需要写入文件),不会使用 attempt_completion。
- 模型再次请求调用工具,这次使用 `write_to_file`(Claude Code 内置工具),并指定文件路径和写入内容。
- Client 执行文件写入,将“写入成功”通知返回给模型。
- 模型最终返回
thinking+attempt_completion,告知任务完成。
关键认知:在每一轮工具调用中,模型可以持续调用工具直到任务完成,不限于只调用一个 MCP 工具,也可以混合使用 MCP 工具和内置工具。
ReAct 模式深度解析
ReAct 思想概述
- ReAct 是 2022 年一篇论文中提出的概念,是 Reasoning 和 Acting 两个单词的合体。
- 重要性:论文提出可以在不需要人干预的情况下,让模型自主思考、自主调用各类外部工具,从而完成用户诉求。
- 符合 ReAct 模式思想的程序被称为 Agent——即能够持续思考、持续调用外部工具直至解决用户问题的一个程序。
- Claude Code 本质上就是一个 Agent,其 Agent 流程正是基于 ReAct 思想构建的。
ReAct 三要素
| 中文 | 英文 | 含义 |
|---|---|---|
| 思考 | Thought | 模型的推理过程 |
| 行动 | Action | 获取或改变外部环境的行为(调用工具) |
| 观察 | Observation | 行动的结果或反馈 |
循环模式:思考 → 行动 → 观察 → 思考 → 行动 → 观察 → ... → 最终答案
Claude Code 的 XML 标签与 ReAct 的对应关系
| Claude Code 的 XML 标签 | ReAct 概念 | 说明 |
|---|---|---|
<thinking> | Thought | 思考 |
<use_mcp_tool>、<write_to_file>、<read> 等工具标签 | Action | 行动 |
| 工具执行后返回的结果消息 | Observation | 观察 |
<attempt_completion> | Final Answer | 最终答案 |
XML 与 ReAct 的关系
- XML 只是数据传输格式,ReAct 是思想/方法论,二者不绑定。
- ReAct 论文原作者并未使用 XML 或 JSON,而是使用了近乎纯文本的方式实现类似效果。
- 例如,可以使用以下纯文本格式替代:
Thought: 我需要获取纽约的天气
Action: get_forecast({"latitude": 41.7128, "longitude": -74.006})
Observation: 纽约明天的温度是24摄氏度
Thought: 任务已完成
Final Answer: 纽约明天的天气是24摄氏度。使用 ReAct 格式复现 Claude Code 交互流程(示例)
假设将 Claude Code 的 XML 交互改为 ReAct 论文中提到的纯文本格式:
- 模型第一个回答:
Thinking:替换为Thought:。 - 工具调用:
<use_mcp_tool>替换为Action:+ 工具名称与参数。 - 工具结果:替换为
Observation:+ 结果内容。 - 工具执行后再次返回
Thought:。 - 最终答案使用
Final Answer:标记,结束对话。 - 前提:需配套修改 system prompt,说明返回格式、工具调用示例、注意事项和工具列表。
实际演示:用开源模型 Gemma 3 演示 ReAct 格式
- 作者使用 Ollama 启动 Google 的开源模型 Gemma 3(4B 参数)。
- 将之前展示过的 ReAct system prompt 与用户问题“纽约明天的天气怎么样?结果写入到 result.md 文件中”合并后直接发给模型(注:正式使用时应将 system prompt 与用户问题分开传入,Ollama 设置 system prompt 略复杂,为了演示简化)。
- 模型返回格式:包含
Thought:和Action:两部分,符合 ReAct 要求。 - 假设调用工具完成并拿到结果,在结果前加上
Observation:返回给模型。 - 模型返回
Thought:和Action:(要求使用write_to_file工具写入文件)。 - 继续模拟写入完成,返回消息给模型。
- 模型再次思考后,输出
Final Answer:并结束流程。
用 DeepSeek 复现 XML 格式调用
- 由于 Gemma 3 模型仅 4B,难以产出符合格式的 XML,因此使用 DeepSeek 演示。
- 找到 Claude Code 的 system prompt,末尾加上问题“纽约明天的天气怎么样?结果写入到 result.md 文件中”,将整个文件内容复制给 DeepSeek。
- DeepSeek 返回
thinking和use_mcp_tool标签,与 Claude Code 中见到的一致。 - 模拟工具执行,返回结果,模型继续返回
thinking和write_to_file请求。 - 模拟写入成功,模型最终返回
attempt_completion,流程结束。 - 注意:本示例中 DeepSeek 在
attempt_completion之前未返回 thinking 标签,作者指出这是模型输出的小失误。
格式对比
| 格式 | 优点 | 适用场景 |
|---|---|---|
| XML | 表达精确,工具参数结构化 | 生产环境、复杂工具调用 |
| 纯文本(Thought/Action/Observation) | 格式简单、易于理解 | 简单演示、轻量级 Agent |
| JSON | 结构化、易解析 | 通用格式选项 |
结论:可以使用任意格式与模型通信,只要该格式遵循 ReAct 模式即可。
构建 Agent 的三要素(核心总结)
要实现一个类似 Claude Code 的 Agent 程序,需要完成以下三步:
- 明确告知模型返回结果的格式:可以是 XML、纯文本(Thought/Action/Observation)或 JSON,但必须明确指定。
- 告知模型可用的工具列表:包括工具名称、参数、用途等详细信息。
- 明确要求模型遵循 ReAct 模式:每次回答需先思考(Thought),随后给出工具调用请求(Action)或最终答案(Final Answer/Attempt Completion)。
将上述要求写入 system prompt,配合用户问题触发,Agent 即可运作。
局限与待确认问题
- Claude Code 未提供修改 system prompt 的场所,XML 格式是写死在代码内部的,因此用户无法让 Claude Code 直接改用其他格式(如纯文本或 JSON)与模型交互。
- 视频中演示的 ReAct 纯文本格式与 XML 格式的对比仅作为示例说明,前者未经过生产环境验证,其可用性需自行判断。
- 视频作者建议优先采用 XML 格式,因为其表达精度更高;但并未给出基于实测的性能数据。
- 关于
attempt_completion参数中result的具体展示方式,以及command参数何时会被使用,视频未深入展开。 - System prompt 中剩余未讲解的部分(如工具使用示例、工具使用指南、Play Mode 和 X Mode 的区别、new task 等标签)被作者以“不重要”为由跳过,具体功能与实现未详述。
关键数据与事实汇总
| 项目 | 数值/事实 |
|---|---|
| System Prompt 行数 | 626 行 |
| System Prompt 总字符数 | 48,671 个字符 |
| MCP 协议边界 | 仅规定 Host 与 Server 通信,不规定与模型的格式 |
| Claude Code 与模型的通信格式 | XML(x-mail) |
| ReAct 论文发表年份 | 2022 年 |
| ReAct 全称 | Reasoning + Acting |
| 中转服务器端口 | 8000 |
| 模型示例 | DeepSeek、Gemma 3(4B,Ollama 启动) |
| 演示 MCP 服务器 | weather(官方示例,工具含 get_alerts、get_forecast) |
行动清单(用于理解或复现)
- 克隆 GitHub 仓库(作者提供的 lm_logger.py 源码)。
- 执行环境搭建:创建虚拟环境 → 安装依赖(fastapi、uvicorn、httpx)→ 启动服务器。
- 在 Claude Code 中配置 OpenAI compatible Provider,Base URL 指向本地服务器。
- 发送一条简单的打招呼消息,观察日志文件(lm.log)中的模型请求与返回结构。
- 配置并连接 weather MCP 服务器,提问“纽约明天的天气怎么样?”,观察工具调用链路。
- 修改问题为“纽约明天的天气怎么样?把结果写入 result.md 文件中”,观察模型调用 mcp 工具后再调用内置
write_to_file工具的完整流程。 - 使用 Ollama 启动 Gemma 3 模型,用 ReAct 纯文本格式模拟 Agent 的 thought-action-observation 循环。
- 使用 DeepSeek 模型(或任意 OpenAI 兼容模型)复现 XML 格式的 tool calling 流程。