当前位置: 首页 > news >正文

MCP终极指南 - 番外篇:抓包分析 Cline 与模型的交互协议

目录

    • 前言
    • 1. 简述
    • 2. 截获模型出入参的原理
    • 3. 中转服务器代码解释
    • 4. 配置中转服务器
    • 5. 解读简单场景下 Cline 发往模型的请求
    • 6. 解读简单场景下模型发往 Cline 的返回
    • 7. 分析调用 MCP 工具时模型的请求和返回
    • 8. 解构本质:Cline 的 XML 协议与 ReAct 的关系
    • 结语
    • 参考

前言

学习 UP 主 马克的技术工作坊 的 MCP终极指南 - 番外篇:抓包分析 Cline 与模型的交互协议 视频,了解下 MCP Host 与模型的交互协议,记录下个人学习笔记,仅供自己参考😄

video:MCP终极指南 - 番外篇:抓包分析 Cline 与模型的交互协议

code:https://github.com/MarkTechStation/VideoCode

1. 简述

这篇文章我们来聊聊 MCP Host 是如何与模型进行沟通的,之前在基础篇和进阶篇里面都提到过 MCP 只规定了 MCP Host 与 MCP Server 之间的沟通协议,并没有对模型的输入和输出格式提出要求

在这里插入图片描述

因此,不同的 MCP Host 就可能会用不同的格式来与模型沟通,比如 Cline 用的就是 XML,这里将以 Cline 为例给大家演示一下 MCP Host 是如何与模型进行沟通的

为了让大家能够彻底理解,我们将截获模型的输入和输出为大家展示 Cline 与模型交互的具体细节,在最后还会给大家讲解如何构建 Agent 常用的 ReAct 模式以及 Cline 用的 XML 这种协议与 ReAct 模式之间的关系,让大家能够透过现象看本质

我们的目标不是单纯的了解 Cline 的 XML 协议,而是通过学习这个协议来了解如何编写一个像 Cline 一样能够与模型持续交互的程序

废话少说,下面让我们开始吧

2. 截获模型出入参的原理

我们还是用之前的 weather 这个 MCP Server 举例,下面是我们基础篇里面提到过的交互图:

在这里插入图片描述

我们今天要了解的就是 Cline 和模型交互这一部分的内容,既然要了解 Cline 和模型是如何交互的,我们就最好能够抓取到 Cline 发送给模型的请求和模型给 Cline 的返回

那怎么才能做到这一点呢,很简单,我们原来不是 Cline 与模型直接沟通吗,现在我们不这么做了,我们起一个本地服务器作为中间人,无论是 Cline 发送请求给模型还是模型返回答案给 Cline 都要先经过这个本地服务器才行,如下图所示:

在这里插入图片描述

本地服务器在接收到 Cline 请求和模型的返回之后会把具体的内容写入到一个日志文件中,这样我们查询文件的内容就可以知道 Cline 与模型说了些什么

那 Cline 支持连接我们的本地服务器吗?答案是肯定的,Cline 的 API provider 中除了常见的 OpenAI、DeepSeek、OpenRuler 等模型提供商之外,还有一种特殊的选项叫做 OpenAI Compatible,它的意思是对应模型提供商虽然不是 OpenAI,但是呢它的 API 完全兼容 OpenAI 的格式

在这里插入图片描述

我们选择它之后,把本地服务器的地址填到 base url 里面,然后再填好 API key、model id 等信息就可以了,剩下要做的事情就是去编写这个本地服务器,并且确保这个本地服务器的输入和输出符合 OpenAI 的格式规范

在这里插入图片描述

Note:博主这边选择的是 DeepSeek 的 API Key

3. 中转服务器代码解释

UP 已经按照这个思路把本地服务器的代码写好了,大家可以在对应的 GitHub 仓库中找到对应的源代码,如下所示:

import httpx
from fastapi import FastAPI, Request
from starlette.responses import StreamingResponseclass AppLogger:def __init__(self, log_file="llm.log"):"""Initialize the logger with a file that will be cleared on startup."""self.log_file = log_file# Clear the log file on startupwith open(self.log_file, 'w') as f:f.write("")def log(self, message):"""Log a message to both file and console."""# Log to filewith open(self.log_file, 'a') as f:f.write(message + "\n")# Log to consoleprint(message)app = FastAPI(title="LLM API Logger")
logger = AppLogger("llm.log")@app.post("/chat/completions")
async def proxy_request(request: Request):body_bytes = await request.body()body_str = body_bytes.decode('utf-8')logger.log(f"模型请求:{body_str}")body = await request.json()logger.log("模型返回:\n")async def event_stream():async with httpx.AsyncClient(timeout=None) as client:async with client.stream("POST","https://openrouter.ai/api/v1/chat/completions",json=body,headers={"Content-Type": "application/json","Accept": "text/event-stream","Authorization": request.headers.get("Authorization"),},) as response:async for line in response.aiter_lines():logger.log(line)yield f"{line}\n"return StreamingResponse(event_stream(), media_type="text/event-stream")if __name__ == "__main__":import uvicornuvicorn.run(app, host="0.0.0.0", port=8000)

这段代码不是很长,就是一段 Python 脚本,我先给大家简要说明一下这个脚本在干什么

class AppLogger:def __init__(self, log_file="llm.log"):"""Initialize the logger with a file that will be cleared on startup."""self.log_file = log_file# Clear the log file on startupwith open(self.log_file, 'w') as f:f.write("")def log(self, message):"""Log a message to both file and console."""# Log to filewith open(self.log_file, 'a') as f:f.write(message + "\n")# Log to consoleprint(message)

最上面的 AppLogger 呢是用来写日志的,其中的 message 参数就是要写的日志内容,它会把 message 写入到指定的 log 文件中

app = FastAPI(title="LLM API Logger")
logger = AppLogger("llm.log")

这里呢则是对应了两个变量,其中一个叫做 app 是用 FastAPI 生成的,FastAPI 是 Python 的一个 web 开发框架,用来快速生成外部应用,我们后面会使用这个 app 变量快速定义一个 post 的接口

而这个 logger 呢则是用了我们前面所说的 AppLogger 生成的用于记录模型的请求和返回,所有的日志内容呢都会记录到 llm.log 文件中

@app.post("/chat/completions")
async def proxy_request(request: Request):body_bytes = await request.body()body_str = body_bytes.decode('utf-8')logger.log(f"模型请求:{body_str}")body = await request.json()logger.log("模型返回:\n")async def event_stream():async with httpx.AsyncClient(timeout=None) as client:async with client.stream("POST","https://openrouter.ai/api/v1/chat/completions",json=body,headers={"Content-Type": "application/json","Accept": "text/event-stream","Authorization": request.headers.get("Authorization"),},) as response:async for line in response.aiter_lines():logger.log(line)yield f"{line}\n"return StreamingResponse(event_stream(), media_type="text/event-stream")

再往后呢我们就使用 app 定义了一个 post 接口,路径是 /chat/completions,Cline 会去请求这个路径,并且把具体的模型请求放入到 post body 里面

在这个接口内部,我们首先获取了 Cline 发给模型的请求,并且记录到了日志文件中,之后呢则都是处理模型返回的代码。后面的这一段呢看起来有点复杂,它写的这么复杂是因为它要处理模型的流式返回结果,对应上面的 "Accept": "text/event-stream",也就是要求模型使用流式返回

这个流式返回的专业名词叫做 Server-Sent Events 简称 SSE,鉴于很多人可能不太清楚 SSE 是什么,这里简单解释一下

一般情况下我们使用 http 访问一个网站的时候我们的浏览器会发送给目标服务器一个请求,比如说要请求一个 html 网页,一张图片,一个 js 脚本之类的,目标服务器会返回对应的结果,一去一回一次交互就完成了

在这里插入图片描述

这个交互方式有个缺陷,它处理不了服务器连续发回多次响应的情况,比如我们经常用的大模型聊天页面,模型的结果呢都是几个字几个字的返回,只是一去一回的话显然无法做到这种效果,所以目前主流的大模型聊天页面用的都是 SSE,它的特点是浏览器只需要请求一次,服务器接收到请求之后会连续发送多次响应,每次想要的内容都是几个字,而浏览器接到几个字就显示几个字

在这里插入图片描述

这样用户就可以及时接收到模型的返回,出来几个字就看几个字,体验呢就会好很多,等到所有的结果都显示完毕之后,服务器会发送一个完成的标识,浏览器接收到标识之后关闭 SSE 连接页面,显示模型回答完毕,这个流程就结束了

知道了原理之后,我们再回到看代码:

async def event_stream():async with httpx.AsyncClient(timeout=None) as client:async with client.stream("POST","https://openrouter.ai/api/v1/chat/completions",json=body,headers={"Content-Type": "application/json","Accept": "text/event-stream","Authorization": request.headers.get("Authorization"),},) as response:async for line in response.aiter_lines():logger.log(line)yield f"{line}\n"return StreamingResponse(event_stream(), media_type="text/event-stream")

client.stream 是请求了 openrouter 的接口,把 Cline 的请求转发给了 openrouter,再之后的每一个 line 就代表服务器的一次返回,我们把这些返回记录下来,然后这里面的 yield 就会把这个响应再发回给 Cline

比如说假如模型最终的完整返回信息是:纽约明天的温度是 24 摄氏度,那么这里面的 yield 就可能会执行六次,分别是:纽约、明天的、温度、是、24摄氏度,最后呢,在 yield 一个结束的标识符,如下图所示:

在这里插入图片描述

说实话这里面的代码确实是有点复杂,而且是用了一些 Python 的语法糖,在别的编程语言里面不一定会见到,如果你现在还没完全理解的话,建议先不用管,知道个大概意思就行,我们不是来学 Python 的,而且我后面会给你展示实际的 SSE 返回结果,每一条信息都有,那个时候呢我们会再详细解释一下代码的

if __name__ == "__main__":import uvicornuvicorn.run(app, host="0.0.0.0", port=8000)

最后呢则是启动这个本地服务器端口是 8000

这个呢就是整个代码的结构了,简单来说就是先记录请求,再记录返回,给整个过程画一个流程图的话就是下面这样样子:

在这里插入图片描述

Cline 请求我们本地服务器的 /chat/completions 接口,我们记录 Cline 请求,把请求转发给 openrouter,openrouter 处理请求返回结果,本地服务器接收到结果之后,把它记录到文件之中,再返回给 Cline

当然正如前面所说,由于使用了 SSE,openrouter 会连续发送多个信息,但我们对每个信息的处理过程都是一样的,先发给中转服务器,中转服务器写入日志再转发给 Cline,openrouter 的每一个返回都是如此

为了加深你的记忆,我再重复一遍,先发给中转服务器,中转服务器写入日志,然后再转发给 Cline,如此循环下去,这样当整个对话结束的时候我们就可以到日志文件里面去找我们所需要的内容了,一共是包含两部分,分别是模型的请求和模型的返回,这个就是我们的整个流程了

4. 配置中转服务器

本地服务器有了,下面我们要做的事情就是启动并且使用它,首先 clone 该项目

git clone https://github.com/MarkTechStation/VideoCode.git

接着进入到项目目录里执行如下指令:

cd VideoCode/MCP终极指南-番外篇
python -m venv .venv

这里新建了一个虚拟环境,使用虚拟环境是为了防止我们后续安装的依赖影响到系统,虚拟环境好了之后,执行 source .venv/bin/activate 启动这个虚拟环境

然后再执行 pip install -r requirements.txt 安装相关依赖

requirements.txt 内容如下:

fastapi==0.109.2
uvicorn==0.27.1
httpx==0.26.0

我们的依赖非常简单,只有 fastapi、uvicorn 和 httpx,其中 fastapi 用于定义 post 接口,uvicorn 用于运行服务器,httpx 用于向 openrouter 发起 http 请求

Note:博主是在 windows 下操作的,因此直接利用 conda 创建了一个新的虚拟环境然后安装了上述第三方库

安装完依赖我们再执行 python llm_logger.py 就可以启动我们的本地服务器了:

在这里插入图片描述

可以看出端口是 8000,然后呢我们来到 Cline 这边确保 API provider 已经修改成 OpenAI Compatible,接着随便打个招呼试试:

在这里插入图片描述

Cline 回复完毕,我们打开日志文件看看:

在这里插入图片描述

可以发现里面已经有内容了,而且格式跟我们想的一样,一个是模型的请求,一个是模型的返回,日志的内容还是挺多的,这个对话没有用到任何的 MCP Server 或者是外部工具,比较简单,很适合我们先对模型的请求和返回的格式做一下分析

后面我们还会让模型用下 MCP Server,那个时候的日志内容也会跟这种简单的情况差不多,只是会多一些调用 MCP Tool 的地方,我们先把这种简单的情况弄懂,后面调用 MCP Tool 的链路就会好懂很多

那下面我们就来仔细看一下,这个日志文件里面到底写了些什么

5. 解读简单场景下 Cline 发往模型的请求

我们首先来看一下模型请求:

模型请求:{"model": "deepseek-chat","messages": [{"role": "system","content": "You are Cline, a highly skilled software engineer with extensive knowledge in many programming languages, frameworks, design patterns, and best practices..."},},{"role": "user","content": [{"type": "text","text": "<task>\nHi\n</task>"},{"type": "text","text": "<environment_details>\n# VSCode Visible Files\nllm_logger.py\n\n# VSCode Open Tabs\nllm_logger.py\nllm.log\n\n# Current Time\n2025/6/14 上午9:31:34 (Asia/Shanghai, UTC+8:00)\n\n# Current Working Directory (c:/Users/lenovo/Desktop/mcp) Files\nllm_logger.py\nllm.log\n\n# Context Window Usage\n0 / 64K tokens used (0%)\n\n# Current Mode\nACT MODE\n</environment_details>"}]}],"temperature": 0,"stream": true,"stream_options": {"include_usage": true}
}     

日志中有我们要用的模型 DeepSeek,有消息列表 message,里面主要存放了 Cline 发给模型的请求以及模型的回答,第一个消息,它的 role 写的是 system,对应的含义就是 system prompt,也就是系统提示词

它的功能主要是设定模型需要提前感知到的一些信息,比如说模型需要扮演的角色,模型可以用的工具列表,模型返回结果的格式等等,这些呢都是 Cline 写的

所有的内容都被放在了 content 字符串里面,我们格式化一下:

在这里插入图片描述

格式化脚本如下:

#! /bin/bashvim +' %s/\\\"/”/g | %s/\\n/\r/g | wq' system_prompt.md

格式化之后如上图所示,一共有 573 行,再看看总字符数总计是 47987 个字符,我不知道大家看到这个是什么反应,反正我第一次看到的时候都震惊了,这些可都是要发给模型占用 token 的啊,这都是白花花的银子😂

那这么多字到底写了些什么呢?它是英文的,看起来还是有些吃力,我再给大家转换成中文,我们先来把所有的内容都折叠起来只显示小标题:

在这里插入图片描述

先看看这段信息的基本框架,小标题包括工具使用格式,工具列表,工具使用示例,工具使用指南,已连接的 MCP 服务器,然后还有两个函数,分别是 write_to_filereplace_in_file,看起来分别用于写入文件和替换文件内容,都是很常用的功能

然后是 Cline 告诉模型如何选择合适的工具,一些自动格式化方面的注意事项,工作流程方面的提示,有一点大家注意一下,这里所说的工具跟 MCP 的工具并非完全一回事,它这里所说的工具一共是包含两部分的内容,一个是 Cline 内置的工具,比如写入文件、替换文件内容、读取文件、运行终端命令等,还有一个呢才是 MCP 工具,比如说我们之前常用的天气预报和气象预警,所以模型在这里是可以用到这两种工具的

讲完了工具我们再回来继续看一下 Cline 给出的 system prompt,大家也都看到了,这里的内容非常多,我们逐行逐句分析是不可能的,我们能做的就是给大家大体过一遍并着重讲一下其中重要的内容

首先是工具的使用格式:

# 工具使用格式工具使用采用 XML 风格的标签进行格式化。工具名称包含在开始和结束标签中,每个参数也类似地包含在其自己的一组标签中。结构如下:<tool_name>
<parameter1_name>value1</parameter1_name>
<parameter2_name>value2</parameter2_name>
...
</tool_name>例如:<read_file>
<path>src/main.js</path>
</read_file>始终遵守此格式进行工具使用,以确保正确解析和执行。

Cline 规定了工具调用请求必须要使用 XML 格式传递,外层写工具的名称,内层写参数的名称和参数值,比如一个读取文件内容的工具,外层写的就是 read_file,里面的 path 就是参数名了,src/main.js 就是参数值,它代表的意思就是我要使用 read_file 这个工具去读取 src/main.js 这个文件的内容

大家可能还是不太明白模型是怎么使用这个 XML 的,我们来举个具体点的例子:

在这里插入图片描述

假设用户的问题是:src/main.js 这个文件写了些什么,Cline 会把这个问题发送给模型,模型接到问题后发现它需要先调用 read_file 这个工具来读取 src/main.js 的文件内容,于是它就按照 system prompts 里面给出的这个 XML 格式向 Cline 请求读取这个文件的内容,Cline 接到请求后读取了 main.js 的内容并且返回给了模型,模型再往后呢就可以自己总结出答案了,不过那就是后面的事情了,流程图里也就不给大家展示了

所以大家也都看到了,只要是模型按照 Cline 规定的这种 XML 格式返回,Cline 就可以帮助模型调用各种它想要调用的工具,那到底有哪些工具可以用呢,模型是怎么知道读取文件用的工具名是 read_file 的呢

很简单,Cline 在下一部分,也就是工具这一部分会给模型详细解释有哪些工具可以用,它也会告诉模型每个工具的名称、参数格式、用途等等之类的信息,我们来仔细看一下:

# 工具## execute_command
描述:请求在系统上执行 CLI 命令。当你需要执行系统操作或运行特定命令以完成用户任务中的任何步骤时,请使用此工具。你必须根据用户的系统定制命令,并清楚地解释该命令的作用。对于命令链,请使用用户 shell 的适当链接语法。优先选择执行复杂的 CLI 命令,而不是创建可执行脚本,因为它们更灵活且更易于运行。命令将在当前工作目录中执行:c:/Users/lenovo/Desktop/mcp
参数:
- command: (必需) 要执行的 CLI 命令。该命令应适用于当前操作系统。确保命令格式正确且不包含任何有害指令。
- requires_approval: (必需) 一个布尔值,指示在用户启用了自动批准模式的情况下,此命令在执行前是否需要用户明确批准。对于可能产生重大影响的操作(如安装/卸载软件包、删除/覆盖文件、系统配置更改、网络操作或任何可能产生意外副作用的命令),请设置为 'true'。对于安全操作(如读取文件/目录、运行开发服务器、构建项目以及其他非破坏性操作),请设置为 'false'。
用法:
<execute_command>
<command>你的命令在此处</command>
<requires_approval>true 或 false</requires_approval>
</execute_command>## read_file
描述:请求读取指定路径下文件的内容。当你需要检查一个你不知道内容的文件时使用此工具,例如分析代码、查看文本文件或从配置文件中提取信息。自动从 PDF 和 DOCX 文件中提取原始文本。可能不适用于其他类型的二进制文件,因为它以字符串形式返回原始内容。
参数:
- path: (必需) 要读取的文件的路径(相对于当前工作目录 c:/Users/lenovo/Desktop/mcp)
用法:
<read_file>
<path>文件路径在此处</path>
</read_file>## write_to_file
描述:请求将内容写入指定路径的文件。如果文件存在,它将被提供的内容覆盖。如果文件不存在,它将被创建。此工具将自动创建写入文件所需的任何目录。
参数:
- path: (必需) 要写入的文件的路径(相对于当前工作目录 c:/Users/lenovo/Desktop/mcp)
- content: (必需) 要写入文件的内容。始终提供文件的完整预期内容,不得有任何截断或遗漏。你必须包括文件的所有部分,即使它们没有被修改。
用法:
<write_to_file>
<path>文件路径在此处</path>
<content>
你的文件内容在此处
</content>
</write_to_file>## replace_in_file
描述:请求使用 SEARCH/REPLACE 块替换现有文件中的内容部分,这些块定义对文件特定部分的确切更改。当你需要对文件的特定部分进行有针对性的更改时,应使用此工具。
参数:
- path: (必需) 要修改的文件的路径(相对于当前工作目录 c:/Users/lenovo/Desktop/mcp)
- diff: (必需) 一个或多个 SEARCH/REPLACE 块,遵循以下确切格式:\```<<<<<<< SEARCH[要查找的确切内容]=======[用于替换的新内容]>>>>>>> REPLACE\```关键规则:1. SEARCH 内容必须与要查找的关联文件部分完全匹配:* 逐字符匹配,包括空格、缩进、换行符* 包括所有注释、文档字符串等。2. SEARCH/REPLACE 块只会替换第一个匹配项。* 如果需要进行多处更改,请包含多个唯一的 SEARCH/REPLACE 块。* 在每个 SEARCH 部分中仅包含足够的行,以唯一匹配需要更改的每组行。* 当使用多个 SEARCH/REPLACE 块时,请按照它们在文件中出现的顺序列出。3. 保持 SEARCH/REPLACE 块简洁:* 将大的 SEARCH/REPLACE 块分解为一系列较小的块,每个块更改文件的一小部分。* 仅包括更改的行,以及为确保唯一性所需的少量周围行。* 不要在 SEARCH/REPLACE 块中包含大量未更改的行。* 每行必须完整。切勿中途截断行,这可能导致匹配失败。4. 特殊操作:* 移动代码:使用两个 SEARCH/REPLACE 块(一个从原始位置删除 + 一个插入到新位置)* 删除代码:使用空的 REPLACE 部分
用法:
<replace_in_file>
<path>文件路径在此处</path>
<diff>
搜索和替换块在此处
</diff>
</replace_in_file>## search_files
描述:请求在指定目录中的文件之间执行正则表达式搜索,提供内容丰富的结果。此工具在多个文件中搜索模式或特定内容,显示每个匹配项及其封装上下文。
参数:
- path: (必需) 要搜索的目录的路径(相对于当前工作目录 c:/Users/lenovo/Desktop/mcp)。将递归搜索此目录。
- regex: (必需) 要搜索的正则表达式模式。使用 Rust 正则表达式语法。
- file_pattern: (可选) 用于筛选文件的 Glob 模式(例如,'*.ts' 用于 TypeScript 文件)。如果未提供,则将搜索所有文件 (*)。
用法:
<search_files>
<path>目录路径在此处</path>
<regex>你的正则表达式模式在此处</regex>
<file_pattern>文件模式在此处(可选)</file_pattern>
</search_files>## list_files
描述:请求列出指定目录中的文件和目录。如果 recursive 为 true,它将递归列出所有文件和目录。如果 recursive 为 false 或未提供,它将仅列出顶级内容。不要使用此工具来确认你可能已创建的文件的存在,因为用户会告知你文件是否已成功创建。
参数:
- path: (必需) 要列出其内容的目录的路径(相对于当前工作目录 c:/Users/lenovo/Desktop/mcp)
- recursive: (可选) 是否递归列出文件。对于递归列表,使用 true;对于仅顶级列表,使用 false 或省略。
用法:
<list_files>
<path>目录路径在此处</path>
<recursive>true 或 false(可选)</recursive>
</list_files>## list_code_definition_names
描述:请求列出指定目录顶层源代码文件中使用的定义名称(类、函数、方法等)。此工具提供对代码库结构和重要构造的见解,封装了对于理解整体架构至关重要的高级概念和关系。
参数:
- path: (必需) 要列出其顶层源代码定义的目录的路径(相对于当前工作目录 c:/Users/lenovo/Desktop/mcp)。
用法:
<list_code_definition_names>
<path>目录路径在此处</path>
</list_code_definition_names>## browser_action
描述:请求与 Puppeteer 控制的浏览器进行交互。除 `close` 外的每个操作都将以浏览器当前状态的屏幕截图以及任何新的控制台日志作为响应。每条消息只能执行一个浏览器操作,并等待用户响应(包括屏幕截图和日志)以确定下一个操作。
- 操作序列**必须始终以**在 URL 处启动浏览器开始,并**必须始终以**关闭浏览器结束。如果需要访问无法从当前网页导航到的新 URL,则必须先关闭浏览器,然后在新的 URL 处重新启动。
- 浏览器活动期间,只能使用 `browser_action` 工具。在此期间不应调用其他工具。只有在关闭浏览器后才能继续使用其他工具。例如,如果遇到错误需要修复文件,则必须关闭浏览器,然后使用其他工具进行必要的更改,然后重新启动浏览器以验证结果。
- 浏览器窗口的分辨率为 **900x600** 像素。执行任何单击操作时,请确保坐标在此分辨率范围内。
- 在单击任何元素(如图标、链接或按钮)之前,必须查阅提供的页面屏幕截图以确定元素的坐标。单击应针对**元素的中心**,而不是其边缘。
参数:
- action: (必需) 要执行的操作。可用操作包括:* launch: 启动一个新的由 Puppeteer 控制的浏览器实例,并导航到指定的 URL。这**必须始终是第一个操作**。- 与 `url` 参数一起使用以提供 URL。- 确保 URL 有效并包含适当的协议(例如 http://localhost:3000/page、file:///path/to/file.html 等)* click: 在特定的 x,y 坐标处单击。- 与 `coordinate` 参数一起使用以指定位置。- 始终根据从屏幕截图派生的坐标单击元素的中心(图标、按钮、链接等)。* type: 在键盘上输入一个文本字符串。你可以在单击文本字段后使用此操作来输入文本。- 与 `text` 参数一起使用以提供要键入的字符串。* scroll_down: 向下滚动页面一个页面高度。* scroll_up: 向上滚动页面一个页面高度。* close: 关闭由 Puppeteer 控制的浏览器实例。这**必须始终是最后一个浏览器操作**。- 示例:`<action>close</action>`
- url: (可选) 用于为 `launch` 操作提供 URL。* 示例:<url>https://example.com</url>
- coordinate: (可选) `click` 操作的 X 和 Y 坐标。坐标应在 **900x600** 分辨率范围内。* 示例:<coordinate>450,300</coordinate>
- text: (可选) 用于为 `type` 操作提供文本。* 示例:<text>Hello, world!</text>
用法:
<browser_action>
<action>要执行的操作(例如,launch、click、type、scroll_down、scroll_up、close)</action>
<url>启动浏览器的 URL(可选)</url>
<coordinate>x,y 坐标(可选)</coordinate>
<text>要输入的文本(可选)</text>
</browser_action>## use_mcp_tool
描述:请求使用已连接 MCP 服务器提供的工具。每个 MCP 服务器可以提供具有不同功能的多个工具。工具有定义的输入模式,用于指定必需和可选参数。
参数:
- server_name: (必需) 提供工具的 MCP 服务器的名称
- tool_name: (必需) 要执行的工具的名称
- arguments: (必需) 一个 JSON 对象,包含工具的输入参数,遵循工具的输入模式
用法:
<use_mcp_tool>
<server_name>服务器名称在此处</server_name>
<tool_name>工具名称在此处</tool_name>
<arguments>
{”param1”: ”value1”,”param2”: ”value2”
}
</arguments>
</use_mcp_tool>## access_mcp_resource
描述:请求访问已连接 MCP 服务器提供的资源。资源表示可用作上下文的数据源,例如文件、API 响应或系统信息。
参数:
- server_name: (必需) 提供资源的 MCP 服务器的名称
- uri: (必需) 标识要访问的特定资源的 URI
用法:
<access_mcp_resource>
<server_name>服务器名称在此处</server_name>
<uri>资源 URI 此处</uri>
</access_mcp_resource>## ask_followup_question
描述:向用户提问以收集完成任务所需的其他信息。当遇到歧义、需要澄清或需要更多详细信息才能有效进行时,应使用此工具。它通过与用户直接通信来实现交互式问题解决。明智地使用此工具,以在收集必要信息和避免过多来回沟通之间保持平衡。
参数:
- question: (必需) 要问用户的问题。这应该是一个清晰、具体的问题,以解决你需要的信息。
- options: (可选) 供用户选择的 2-5 个选项的数组。每个选项都应该是一个描述可能答案的字符串。你可能不总是需要提供选项,但在许多情况下,它可以帮助用户避免手动输入响应,因此可能很有用。重要提示:切勿包含切换到“行动”模式的选项,因为如果需要,你需要指导用户手动执行此操作。
用法:
<ask_followup_question>
<question>你的问题在此处</question>
<options>
选项数组在此处(可选),例如 [”选项 1”, ”选项 2”, ”选项 3]
</options>
</ask_followup_question>## attempt_completion
描述:每次使用工具后,用户将响应该工具使用的结果,即成功或失败,以及任何失败原因。一旦收到工具使用的结果并可以确认任务已完成,请使用此工具向用户呈现你的工作结果。你可以选择提供一个 CLI 命令来展示你的工作成果。如果用户对结果不满意,他们可能会提供反馈,你可以利用这些反馈进行改进并重试。
重要说明:此工具必须在用户确认先前所有工具使用均成功后才能使用。否则将导致代码损坏和系统故障。在使用此工具之前,你必须在 <thinking></thinking> 标签中自问是否已从用户处确认先前所有工具使用均成功。如果否,则不要使用此工具。
参数:
- result: (必需) 任务的结果。以最终且不需要用户进一步输入的方式表述此结果。不要以问题或提供进一步帮助的提议来结束你的结果。
- command: (可选) 执行以向用户实时演示结果的 CLI 命令。例如,使用 `open index.html` 显示创建的 html 网站,或使用 `open localhost:3000` 显示本地运行的开发服务器。但不要使用像 `echo``cat` 这样仅打印文本的命令。此命令应适用于当前操作系统。确保命令格式正确且不包含任何有害指令。
用法:
<attempt_completion>
<result>
你的最终结果描述在此处
</result>
<command>用于演示结果的命令(可选)</command>
</attempt_completion>## new_task
描述:请求创建一个新任务,预加载迄今为止与用户的对话以及继续新任务的关键信息。使用此工具,你将创建迄今为止对话的详细摘要,密切关注用户的明确请求和你的先前操作,重点关注新任务所需的最相关信息。
在其他重要关注领域中,此摘要应全面捕获对于继续新任务至关重要的技术细节、代码模式和架构决策。用户将看到你生成的上下文的预览,并可以选择创建新任务或在当前对话中继续聊天。用户可以随时选择开始新任务。
参数:
- Context: (必需) 用于预加载新任务的上下文。如果适用于当前任务,则应包括:1. 当前工作:详细描述在请求创建新任务之前正在进行的工作。特别注意最近的消息/对话。2. 关键技术概念:列出所有讨论过的可能与新任务相关的重要技术概念、技术、编码约定和框架。3. 相关文件和代码:如果适用,列举为任务继续而检查、修改或创建的特定文件和代码部分。特别注意最近的消息和更改。4. 问题解决:记录迄今为止已解决的问题以及任何正在进行的故障排除工作。5. 待处理任务和后续步骤:概述所有已明确要求你处理的待处理任务,并列出你将为所有未完成工作采取的后续步骤(如果适用)。在它们增加清晰度的地方包括代码片段。对于任何后续步骤,请包括最近对话中的直接引述,准确显示你正在处理的任务以及你离开的位置。这应该是逐字记录,以确保任务之间的上下文中没有信息丢失。在此处详细说明非常重要。
用法:
<new_task>
<context>用于预加载新任务的上下文</context>
</new_task>## plan_mode_respond
描述:响应用户的询问,以努力规划用户任务的解决方案。当你需要对用户关于你计划如何完成任务的问题或陈述做出响应时,应使用此工具。此工具仅在计划模式下可用。environment_details 将指定当前模式,如果不是计划模式,则不应使用此工具。根据用户的消息,你可能会提出问题以澄清用户的请求,为任务设计解决方案,并与用户集思广益。例如,如果用户的任务是创建一个网站,你可以首先提出一些澄清问题,然后根据上下文提出一个详细的计划来说明你将如何完成任务,并可能进行来回讨论以在用户将你切换到行动模式以实施解决方案之前最终确定细节。
参数:
- response: (必需) 提供给用户的响应。不要尝试在此参数中使用工具,这只是一个聊天响应。(你必须使用 response 参数,不要简单地将响应文本直接放在 <plan_mode_respond> 标签内。)
用法:
<plan_mode_respond>
<response>你的回复在此处</response>
</plan_mode_respond>## load_mcp_documentation
描述:加载有关创建 MCP 服务器的文档。当用户请求创建或安装 MCP 服务器时(用户可能会问你类似“添加一个具有某种功能的工具”之类的问题,换句话说,就是创建一个 MCP 服务器,该服务器提供工具和资源,例如可以连接到外部 API。你有能力创建 MCP 服务器并将其添加到配置文件中,然后该文件将公开工具和资源供你与 `use_mcp_tool``access_mcp_resource` 一起使用),应使用此工具。该文档提供了有关 MCP 服务器创建过程的详细信息,包括设置说明、最佳实践和示例。
参数:无
用法:
<load_mcp_documentation>
</load_mcp_documentation>

比如 execute command 用于执行终端命令,它的参数一共是包含两部分,一个是命令内容,一个是是否需要用户同意,有一些命令呢可能会对系统产生影响,比如说是删除文件、安装文件等等,一般情况下这都要经过用户的同意才能够继续

再后面呢就是其它的工具了,这里我们大概过一遍,其中大部分工具我们在这里都用不到,我说一遍呢是为了让大家能够对 Cline 的工具用途有个大致的概念和感觉

比如说 read_file 用来读取文件的内容,write_to_file 用来写入文件内容,replace_in_file 用于替换文件内容,search_files 用于搜索文件,list_files 用来列举当前项目目录中的文件列表,list_code_definition_names 用于列举指定目录顶层原代码文件中使用到的定义名称,比如说是类、函数、方法等,这个工具有点绕,你不明白的话也没有关系,还有一个 browser_action 这个也跳过

我们要重点看的呢是后面的 use_mcp_tool,这是用来使用 MCP 工具的,这个与我们息息相关,它的参数呢一共是分成三个,第一个是 server name 也就是 MCP 服务的名称,第二个是 tool name 是 MCP 工具的名称,第三个呢是 arguments 是 MCP 工具的输入

<use_mcp_tool>
<server_name>weather</server_name>
<tool_name>get_forecast</tool_name>
<arguments>
{"latitude": "40.7128","longitude": "-74.006"
}
</arguments>
</use_mcp_tool>

比如说上面这个 XML 它代表模型想要调用 weather 这个 MCP Server 下的 get_forecast 工具,使用工具时的参数为 latitude 41.7128,longitude -74.006

再下面的 access mcp resource 工具呢是用来获取 MCP 资源的,填入 MCP 服务和资源的 url 就行,我们暂时不涉及 MCP 资源,所以呢这个略过,再后面的 ask_followup_question 呢是模型向用户提问的时候所使用的工具,也不是特别重要,我们也略过

再后面呢是 attempt_completion 这个我们后面会用到,所以请大家重点留意一下,这个工具用于返回最终结论,比如模型调用了一系列的工具之后,它可能会认为自己已经完成了用户给出的任务,或者已经知道了用户问题的答案,此时呢它就会把最终的结论放到 attempt_completion 的 result 参数里面,Cline 接到这个 XML 标签之后就会把 result 里面的结果给显示出来,对话到这里就结束了

举个具体点的例子:

在这里插入图片描述

比如用户的问题是:纽约明天的天气怎么样?,模型可能接到问题之后呢会先调用我们的 get_forecast MCP 工具,用的就是我们前面所说的 use_mcp_tool 这个标签,Cline 接收到请求之后调用了 MCP Server 中对应的函数,并且给模型返回了结果。模型接到工具的调用结果之后呢,Cline 就会把这个 results 里面的内容原封不动的给用户查看,作为整个对话的总结,到此为止 Cline 的回答就算是结束了

那你可能还有个问题,如果模型使用了 use_mcp_tool 之后发现还是没有解决用户的问题怎么办呢,很简单,继续使用别的工具就好了,模型可以一直这么调用下去,直到它认为它拿到了最终答案为止,到那个时候模型就可以使用 attempt_completion 返回最终的结论了,所以从这里也可以看出 attempt_completion 基本上是标志着回答的结束

那看完了交互流程我们再回头看一下 attempt_completion 的参数列表,除了 result 之外 attempt_completion 还有一个参数叫做 command,它存储的是一条命令用于展示最终的结果,这个参数呢是可选的,而且我们也用不到,所以暂时就忽略掉。不过这里面要注意一下,Cline 提到过模型在使用 attempt_completion 之前呢必须要先使用 thinking 标签来思考一下

实际上不只是在使用 attempt_completion 之前需要 thinking,模型做任何事情之前都会使用 thinking 标签来思考,这相当于是 Cline 强行让模型思考,有利于提高模型角色的准确率,所以再回到刚才的图里面,实际上之前的图并不特别准确,模型在使用 use_mcp_tool 之前会先加上 thinking 标签,把它的思考过程放到标签里面,而在最后使用 attempt_completion 之前呢也会使用 thinking 标签思考一下,所以它要修改成下面这个样子:

在这里插入图片描述

说了这么多,你可能还觉得有些抽象,我们不如看个具体的例子,比如我们之前经常问的这个经典问题:纽约明天的天气怎么样?下面的红框就是 thinkig 的内容了:

在这里插入图片描述

对应的 XML 就是下面这个样子的,模型先会思考一下:

<thinking>
1. 用户询问纽约明天的天气情况,这是一个需要获取实时天气数据的任务。
2. 查看已连接的 MCP 服务器,发现有一个名为 "weather" 的服务器,它提供了获取天气预报的工具 "get_forecast"。
3. 要使用 get_forecast 工具,需要提供经纬度坐标作为参数。纽约市的坐标大约是纬度40.7128,经度-74.0060。
4. 由于这是明天的天气预报,工具应该能自动返回未来24小时的预报数据。
5. 不需要用户额外批准,因为这只是查询天气数据,不会对系统产生任何影响。
</thinking>

下面的则是 use_mcp_tool 的内容,对应的 XML 是这个样子的:

<use_mcp_tool>
<server_name>weather</server_name>
<tool_name>get_forecast</tool_name>
<arguments>
{"latitude": "40.7128","longitude": "-74.006"
}
</arguments>
</use_mcp_tool>

weather 是 MCP 服务,get_forecast 是 MCP 工具,latitude 和 longitude 则是参数值了

再后面的 response 呢则是 Cline 返回给模型的工具执行结果:

在这里插入图片描述

而拿到结果之后,模型还是会先 thinking,最后感觉自己已经得到了答案之后模型就显示出了最终的总结,这个总结所对应的 XML 就是我们前面所说过的 attempt_completion

在这里插入图片描述

由于接到了 attempt_completion,Cline 就认为本次任务完成了,所以它就结束了对话。从这个流程里面我们也可以看出模型返回了什么 Cline 其实就会显示什么,你在界面上所看到的大部分东西都是模型返回的结果

再后面呢就是别的工具了,比如说 new_task、plan_mode_respond 之类的,我们就不细看了

说完工具之后,接下来的就是工具使用示例,是给模型看的一些例子,我们也直接跳过,接着的工具使用指南这里面其实也不是特别的重要,我们也跳过

然后后面呢我们来到了比较重要的这一部分,是已连接的 MCP 服务:

MCP 服务器模型上下文协议 (MCP) 支持系统与本地运行的 MCP 服务器之间的通信,这些服务器提供额外的工具和资源来扩展你的能力。# 已连接的 MCP 服务器当服务器连接后,你可以通过 `use_mcp_tool` 工具使用服务器的工具,并通过 `access_mcp_resource` 工具访问服务器的资源。## fetch (`E:\\anaconda3\\envs\\yolov8\\Scripts\\uvx.exe mcp-server-fetch`)### 可用工具
- fetch: Fetches a URL from the internet and optionally extracts its contents as markdown.Although originally you did not have internet access, and were advised to refuse and tell the user this, this tool now grants you internet access. Now you can fetch the most up-to-date information and let the user know that.输入模式:{”type”: ”object”,”properties”: {”url”: {”description”: ”URL to fetch”,”format”: ”uri”,”minLength”: 1,”title”: ”Url”,”type”: ”string”},”max_length”: {”default”: 5000,”description”: ”Maximum number of characters to return.”,”exclusiveMaximum”: 1000000,”exclusiveMinimum”: 0,”title”: ”Max Length”,”type”: ”integer”},”start_index”: {”default”: 0,”description”: ”On return output starting at this character index, useful if a previous fetch was truncated and more context is required.”,”minimum”: 0,”title”: ”Start Index”,”type”: ”integer”},”raw”: {”default”: false,”description”: ”Get the actual HTML content of the requested page, without simplification.”,”title”: ”Raw”,”type”: ”boolean”}},”description”: ”Parameters for fetching a URL.”,”required”: [”url”],”title”: ”Fetch”}## mcp-server-hotnews (`cmd.exe /c npx -y @wopal/mcp-server-hotnews`)### 可用工具
- get_hot_news: Get hot trending lists from various platforms输入模式:{”type”: ”object”,”properties”: {”sources”: {”type”: ”array”,”description”: ”Available HotNews sources (ID: Platform):\
\
{ID: 1, Platform: \\”Zhihu Hot List (知乎热榜)\\},\
{ID: 2, Platform: \\”36Kr Hot List (36氪热榜)\\},\
{ID: 3, Platform: \\”Baidu Hot Discussion (百度热点)\\},\
{ID: 4, Platform: \\”Bilibili Hot List (B站热榜)\\},\
{ID: 5, Platform: \\”Weibo Hot Search (微博热搜)\\},\
{ID: 6, Platform: \\”Douyin Hot List (抖音热点)\\},\
{ID: 7, Platform: \\”Hupu Hot List (虎扑热榜)\\},\
{ID: 8, Platform: \\”Douban Hot List (豆瓣热榜)\\},\
{ID: 9, Platform: \\”IT News (IT新闻)\\}\
\
Example usage:\
- [3]: Get Baidu Hot Discussion only\
- [1,3,7]: Get hot lists from zhihuHot, Baidu, and huPu\
- [1,2,3,4]: Get hot lists from zhihuHot, 36Kr, Baidu, and Bilibili”,”items”: {”type”: ”number”,”minimum”: 1,”maximum”: 9}}},”required”: [”sources”]}## weather (`E:\\anaconda3\\envs\\yolov8\\python.exe C:\\Users\\lenovo\\Downloads\\videos\\weather.py`)### 可用工具
- get_alerts: Get weather alerts for a US state.Args:state: Two-letter US state code (e.g. CA, NY)输入模式:{”type”: ”object”,”properties”: {”state”: {”title”: ”State”,”type”: ”string”}},”required”: [”state”],”title”: ”get_alertsArguments”}- get_forecast: Get weather forecast for a location.Args:latitude: Latitude of the locationlongitude: Longitude of the location输入模式:{”type”: ”object”,”properties”: {”latitude”: {”title”: ”Latitude”,”type”: ”number”},”longitude”: {”title”: ”Longitude”,”type”: ”number”}},”required”: [”latitude”,”longitude”],”title”: ”get_forecastArguments”}

它会把用户在 Cline 上配置好的 MCP 服务和每个 MCP 服务所包含的 MCP 工具都列举出来供模型选用,具体来说呢就是对应了下面这一部分的配置内容:

在这里插入图片描述

例如这里面配置了一个 MCP 服务叫做 weather,在 system prompts 里面呢我们也能找到 weather 这个 MCP 服务,之前可能大家不是特别清楚 Cline 是如何告诉模型有哪些 MCP 服务和 MCP 工具可用的,现在应该是比较清楚了,Cline 其实就是在这个地方告诉模型的

当然光是列举出 MCP 服务是没有用的,模型还需要知道每个 MCP 服务下面有哪些 MCP 工具可以用这些信息呢,Cline 也都列举在了下面,比如说我们这边 weather 有两个可用的 MCP 工具分别叫做 get_alertsget_forecast,可以看到 Cline 把每个 MCP 工具的用途、参数和 input schema 都给了出来,其中用途和参数列表用于帮助模型判断何时去调用这个 MCP 工具

而 input schema 呢则是告诉模型调用 MCP 工具的时候参数要以什么样的格式给出,input schema 本质是一个 json schema,json schema 的具体含义我们在进阶篇里面已经讲过了,这里就不再重复了

再往后的内容呢就是一些比较有用的工具,一些给模型看的运行指南、工作流程提示等等,这些对于我们来说呢其实都不是很重要,这里就不再讲了

前面我们讲完了 system prompt 后面呢我们来看一下用户的请求,也就是下面这部分的内容:

{"role": "user","content": [{"type": "text","text": "<task>\nHi\n</task>"},{"type": "text","text": "<environment_details>\n# VSCode Visible Files\nllm_logger.py\n\n# VSCode Open Tabs\nllm_logger.py\nllm.log\n\n# Current Time\n2025/6/14 上午9:31:34 (Asia/Shanghai, UTC+8:00)\n\n# Current Working Directory (c:/Users/lenovo/Desktop/mcp) Files\nllm_logger.py\nllm.log\n\n# Context Window Usage\n0 / 64K tokens used (0%)\n\n# Current Mode\nACT MODE\n</environment_details>"}]
}

可以看到用户请求呢一共是分成了两个部分,第一部分呢是用户的原始问题,也就是我们的这个 Hi,可以看到这个 Hi 被放在了一个 task 标签里面,这里的 task 呢是在告诉模型这就是你要完成的任务,当然严格来说,目前的这个 Hi 并不能算作是一个任务

第二部分呢则是把当前的系统环境告知给了模型,辅助模型来做决策,这里的系统环境呢也是用 XML 来表示的,放在一个字符串里面看着不是很清楚,格式化之后是下面这样样子:

<environment_details># VSCode Visible Files
llm_logger.py# VSCode Open Tabs
llm_logger.py
llm.log# Current Time
2025/6/14 上午9:31:34 (Asia/Shanghai, UTC+8:00)# Current Working Directory (c:/Users/lenovo/Desktop/mcp) Files
llm_logger.py
llm.log# Context Window Usage
0 / 64K tokens used (0%)# Current Mode
ACT MODE</environment_details>

可以看到这里面写了目前可见的一些文件,打开的 Tab,当前的时间等等一系列的信息都会放在这里面,这些都是给模型的一些辅助信息

那看完了用户请求,我们再来看一下剩余的配置部分:

"temperature": 0,
"stream": true,
"stream_options": {
"include_usage": true
}

这里面的 temperature 呢是用来控制模型确定性的,那大家对 LLM 整个推理流程熟悉的应该知道,这是温度采样的一个调节参数,这个值越小模型就越倾向于选择概率更高的词语,输出的结果就更加的稳定保守,重复性强

而更大的值时模型就更倾向于选择概率更低的词语,输出的结果就越多样化,越富有想象力,但也不一定合理,一般情况下我们也不大用管这个配置

stream 呢就代表流式输出了,也就是我们前面所说的使用 SSE 的方式逐量返回,不知道大家还记不记得 SSE 是什么,我们再来复习一下,简单来说 SSE 就是一次请求多次响应,每次响应会携带一部分模型的回答,

stream_options 则是代表把使用量也返回回来,不重要,我们也不管了,那到这个地方请求呢就终于结束了,后面呢就是返回了

6. 解读简单场景下模型发往 Cline 的返回

那下面我们就来看一看模型到底返回了一些什么东西

正如前面所说,模型采用的是 SSE 的方式逐量返回的,下面的每一行都代表模型的一次返回

在这里插入图片描述

如果大家仔细看一下的话就可以发现这些 json 的内容呢基本上是一模一样的,比如说 id、provider、model 都是一样,那什么不一样呢,就是后面的 content,content 呢包含的就是模型实际返回的内容,content 的值是增量返回的意思,就是说每次返回的都是完整内容的一部分,我们把每一行的 content 拼起来就可以发现模型实际返回的就是下面这么一段话了:

<thinking>
1. 用户只发送了简单的问候"Hi",没有提供具体的任务或请求
2. 当前工作目录中有两个文件:llm_logger.py和llm.log
3. 没有其他上下文或明确的任务指示
4. 需要询问用户他们想要完成什么任务或需要什么帮助
</thinking><ask_followup_question>
<question>
你好!我看到当前目录中有llm_logger.py和llm.log文件。你想让我帮你做什么?例如:查看日志文件内容、分析Python脚本、其他任务
</question>
</ask_followup_question>

所以你可以发现我们虽然只请求了服务器一次,但我们拿到了这么多的响应,每个响应呢都携带着一部分的信息,Cline 接收到这些信息之后呢会给用户挨个的展示出来,下面就是这段模型返回结果所对应的 Cline 展示页面:

在这里插入图片描述

thinking 内容就放到了红色框区域,ask_followup_question 的内容就放到绿色框区域,ask_followup_question 我们之前在 system prompts 里面见过,顾名思义就是模型给用户问问题用的标签,我们只打了个招呼,模型不知道我们要干什么,所以要问我们才行

7. 分析调用 MCP 工具时模型的请求和返回

前面我们对 Cline 打了个招呼并且分析了下整个链路所发生的事情,下面我们不妨试试让 Cline 调用一个 MCP 工具,并看看日志里面会记录什么

首先重启一下我们的中转服务器,我们执行的呢还是跟之前一样的命令,中转服务器每次在启动的时候都会自动清空日志文件,这样的话之前的日志就不会残留在里面了,方便我们分析

然后我们需要配置上我们之前用过的 weather MCP 服务,weather 这个 MCP 服务在我们 MCP 终极指南的基础篇和进阶篇里面都有提过,已经重复过很多次了,这里呢也就不再给大家赘述了,这就是官方的一个 MCP 服务,我们用它来获取某个地区的天气预报

紧接着我们新起一个对话,然后问一下我们的经典问题:纽约明天的天气怎么样?

在这里插入图片描述

在这里插入图片描述

跟之前一样 Cline 找到了我们的 MCP Server,并且发现了其中的 get_forecast 工具是可以解决这个问题的,我们同意调用,工具执行完毕,模型先是思考了的一段话,然后给出了最终的答案,到这里整个任务就算是结束了,随后呢我们就来到日志文件里面:

在这里插入图片描述

一开始的请求呢除了这个问题之外,别的都跟我们之前分析的链路是一模一样的,由于我们之前已经分析过了,所以这里也就不再重复分析模型请求了,我们重点来看一下模型的返回

那我们来仔细看一下 data 里面到底写了些什么东西,当然我们之前也说过 data 的签名这一部分呢其实也是没有什么太大用处的,因为每一条信息都是一样的,我们要重点看的是每一条信息里面的 content

它的值是增量返回的,每一条消息都不一样,我们把每一个消息的值都拼接在一起就可以知道模型到底返回了些什么东西,我们把这几条信息都给拼接起来,让大家看一下完整的模型返回是什么样子的:

<thinking>
1. 用户询问纽约明天的天气情况
2. 查看已连接的 MCP 服务器,发现有一个名为 "weather" 的服务器提供天气相关工具
3. weather 服务器提供了两个工具:- get_alerts: 获取美国各州的天气警报(不适用,因为我们需要的是天气预报)- get_forecast: 获取某个位置的天气预报(这正是我们需要的)
4. 要使用 get_forecast 工具,需要提供经纬度坐标
5. 纽约市的经纬度是已知的(大约纬度40.7128,经度-74.0060)
6. 可以立即使用 get_forecast 工具获取纽约的天气预报
</thinking><use_mcp_tool>
<server_name>weather</server_name>
<tool_name>get_forecast</tool_name>
<arguments>
{"latitude": 40.7128,"longitude": -74.006
}
</arguments>
</use_mcp_tool>

可以看出模型的回答呢一共是分为了两个部分,第一部分是 thinking,也就是模型思考,这个跟之前一样,模型做任何事情之前都要在 thinking 标签里面思考一番,这个是 Cline 在 system prompts 里面要求的,我们在讲 system prompt 的时候也提过这点

第二部分则是模型决定调用我们的 get_forecast 工具来获取纽约未来几天的天气,这里面的标签呢是 use_mcp_tool,我们之前在讲 Cline 的 system prompt 的时候讲过这个标签,这段 XML 的意思就是说它想要调用 weather 这个 MCP 服务下面的 get_forecast 工具,参数呢是这个 Cline 指定的参数,调用了指定的 MCP 服务之后会再次对模型发起请求:

在这里插入图片描述

这个呢就是第二次请求的内容了,前面的部分呢是跟第一次请求一样,有 system prompt、用户的问题和环境信息等内容,因为模型是没有任何记忆的,所以我们必须把前面发生过的事情也告诉模型,这样模型才能够更好的做出判断

后面这段内容的 role 设置为了 assistant,role 是角色,assistant 就是助手,其实也就是模型的意思,role 设置成了 assistant 代表的意思就是说这段消息的提供者是模型,这里面的内容都被放在了一个字符串里面,我们把这段文本抽取出来放在一个文件里面,并做一下格式化,这样看得更清楚一些:

在这里插入图片描述

这段内容是不是很熟悉呢,模型先思考了下如何获取纽约的天气,然后就请求调用 MCP 工具,这段我们之前见过,就是之前模型流式返回结果中的 content 的完整内容,一模一样,别看这段内容之前是模型返回的,如果你不把这段内容再次发给模型的话,模型可记不清楚之前到底发生了些什么,因为模型是没有任何记忆力的

再往后的这个消息呢 role 是 user,代表消息是用户提供的,里面的内容呢分为三个部分,第一个表示后面将会展示 MCP 工具的调用结果,第二个则是结果的具体内容,我们格式化下:

在这里插入图片描述

可以看出这一段的返回与 Cline 回答中 Response 的内容是一模一样的

第三部分就是当前的环境信息了,比如说打开的 Tab、当前时间之类的,这些呢我们之前也讲过,这里呢就不再重复了

那总结一下,Cline 一开始是发送了 system prompt,然后发送了用户的问题,紧接着发送了模型的工具调用请求,最后发送了工具的调用结果,其实只有工具的调用结果是新的信息,前面的都是历史信息,这些历史信息呢就是为了告诉模型之前发生过什么事情

知道了请求内容,我们就紧接着看一下模型到底返回了些什么,模型接到了调用的结果之后,依然是做了流式的返回,跟之前一样,我们要看的呢就是这里面 content 的内容,当然现在是流式返回,我们这么看不是很方便,所以把它放在文件里面,这样大家看的会更清楚一些:

<attempt_completion>
<result>
纽约明天(6月15日,周日)的天气预报:- 白天:多云,有30%的降雨概率,最高气温约65°F(18°C),东风约10 mph
- 夜间:多云,有30%的降雨概率,最低气温约63°F(17°C),东风约9 mph
</result>
</attempt_completion>

可以看到整个结果被放在了 attempt_completion 标签里面

Cline 接到了模型的返回之后呢就显示出来了:

在这里插入图片描述

因为接到了 attempt_completion,所以呢对话到此结束了

8. 解构本质:Cline 的 XML 协议与 ReAct 的关系

我们再来画个流程图给大家复习一下整个流程:

在这里插入图片描述

首先我们问了一个问题:纽约明天的天气怎么样?模型返回的答案中包含两个 XML 标签,一个是 thinking 代表模型先思考了一番,另外一个是 use_mcp_tool,表示模型想要使用 weather 这个 MCP 服务下面的 get_forecast 工具

Cline 根据这个请求调用了对应的工具,之后把工具的结果返回给了模型,模型接到结果之后做了第二次回复,这个回复同样是包含两个 XML 标签,其中第一个还是 thinking,思考过后呢,模型认为自己已经得到了答案,因此就把最终的结论放到了 attempt_completion 里面,返回给了 Cline,整个过程呢就到此结束了

假如说我们把问题换一下后面加上一句:把结果写入到 results.md 文件中,此时会发生什么呢?

在这里插入图片描述

前面的部分都一样,模型在拿到 MCP 工具调用结果之后也会照例使用 thinking 去思考一番,但是思考过后它会发现任务还没有完成,因为结果还没有写入到文件中,所以呢此时它也不会着急使用 attempt_completion 总结

它会再次请求调用工具,只不过这次调用的工具是 Cline 的内置工具 write_to_file,这个工具用于写入文件内容,它会把写入的文件路径和内容放到这个工具的参数里面,Cline 接到这个工具的调用请求之后会向指定的文件中写入指定内容,写完后通知模型写入成功,这个时候模型才会返回 thinking 和 attempt_completion 告诉 Cline 任务执行完成了

实际上如果我们仔细看一下就会发现在每一轮的工具调用过程中 Cline 与模型的交互是有固定模式的,首先是 thinking 也就是思考,然后是获取或改变外部环境,比如说获取天气信息、写入文件、读取文件之类的,我们暂时给这一步起个名字叫做行动,模型拿到行动的结果之后对这个结果做了观察之后,它又重复了一遍这个流程,思考行动观察,在最后一轮的思考之后,模型会发现它已经完成任务了,它就会给出最终答案

思考行动观察,这三个中文词对应的英文分别是 thought、action、observation,它们可是 ReAct 思想中最重要的三个词,你可能会问什么是 ReAct,ReAct 是 2022 年的论文 ReAct: Synergizing Reasoning and Acting in Language Models 中所提出来的一个词,它是 reasoning 和 acting 两个单词的合体

在这里插入图片描述

这篇论文在当时可谓是名极一时,因为它所提出的这个 ReAct 理念可以在不需要人工干预的情况下让模型自主思考自主调用各类外部工具,从而完成用户的诉求,就像我们的 Cline 一样

说白了,这就是一个 Agent,什么是 Agent 呢,简单来说 Agent 就是一种能持续思考持续调用外部工具直至解决用户问题的一个程序,所以呢从某种意义上来说 Cline 就是一个 Agent,而且本质上 Cline 也是根据这个 ReAct 的思想来构建它的 Agent 的流程的

那意思就是说 ReAct 用的也是 Cline 这种 XML 格式来与模型沟通的咯,其实并不是!大家要明白 XML 只是数据传入的一个格式而已,ReAct 呢是个思想,它并不跟某个格式绑定,ReAct 的精髓在于我们前面所提到的 thought、action、observation 这三个词

实际上在这篇论文中作者并没有使用 XML 也没有使用 Json,他使用了近乎于纯文本的方式实现了类似 Cline 的效果

我们还是拿出之前 Cline 与模型交互的流程图,那假如说我们希望 Cline 使用 ReAct 论文中提到的格式,而不是我们之前研究过的那种 XML 格式,这个流程图该怎么改呢

在这里插入图片描述

我们来一个一个看,首先看模型的第一个回答,它原来返回的不是 thinking 标签吗?现在要换成 Thought:,后面接具体的思考内容,紧接着下一步不是 use_mcp_tool 吗,那我们需要换成 Action:,后面接需要调用的工具名称和参数,工具调用完毕之后返回的结果前面也要加上 Observation:

然后呢后面的流程也是照样改掉,thinking 改成 Thought,调用工具时使用 Action,我们假设 write_to_file 工具的执行结果是一个字符串,字符串的内容是写入成功,在接到成功的提示之后模型会再次使用 Thought 进行思考,最终用一个特殊的标记 FinalAnswer 来给出最终的总结,并结束此次对话

当然这里还要提一点,想要模型这么返回的话我们也要给模型一个配套的 system prompt,还记得我们之前看过的 Cline 的那个 system prompt 吗?那个 system prompt 告诉了模型具体的返回格式、工具调用示例、可用工具列表等等信息,如果要改成 Thought、Action、Observation 这种格式的话我们也需要有一个类似的 system prompt

比如说下面这个:

你需要解决一个任务。为此,你需要将任务分解为多个步骤。对于每个步骤,首先使用 `Thought:` 思考要做什么,然后使用可用工具之一决定一个 `Action:`。接着,你将根据你的行动从环境/工具中收到一个 `Observation:`。持续这个思考和行动的过程,直到你有足够的信息来提供 `FinalAnswer:`。这里有一些例子:---示例 1:Question: 埃菲尔铁塔有多高?
Thought: 我需要找到埃菲尔铁塔的高度。可以使用搜索工具。
Action: get_height("埃菲尔铁塔")
Observation: 埃菲尔铁塔的高度约为330米(包含天线)。
Thought: 搜索结果显示了高度。我已经得到答案了。
FinalAnswer: 埃菲尔铁塔的高度约为330米。---示例 2:Question: 帮我找一个简单的番茄炒蛋食谱,并看看家里的冰箱里有没有西红柿。
Thought: 这个任务分两步。第一步,找到番茄炒蛋的食谱。第二步,检查冰箱里是否有西红柿。我先用 `find_recipe` 工具找食谱。
Action: find_recipe(dish="番茄炒蛋")
Observation: 简单的番茄炒蛋食谱:将2个鸡蛋打散,2个番茄切块。热油,先炒鸡蛋,盛出。再热油,炒番茄至软烂,加入鸡蛋,放盐调味即可。
Thought: 好的,我已经有食谱了。食谱需要西红柿。现在我需要用 `check_fridge` 工具看看冰箱里有没有西红柿。
Action: check_fridge(item="西红柿")
Observation: 冰箱检查结果:有3个西红柿。
Thought: 我找到了食谱,并且确认了冰箱里有西红柿。可以回答问题了。
FinalAnswer: 简单的番茄炒蛋食谱是:鸡蛋打散,番茄切块。先炒鸡蛋,再炒番茄,混合后加盐调味。冰箱里有3个西红柿。---请严格遵守:
- 输出Action后立即停止生成
- 等待返回真实的Observation
- 擅自生成Observation将导致错误---本次任务可用工具:- get_forecast(latitude, longitude): 获取指定坐标的天气预报。返回包含预报信息的字符串。
- write_to_file(filename, content): 将指定内容写入指定文件。成功时返回 "写入成功"

这个 system prompt 说明了返回格式,给出了工具调用示例、注意事项以及工具列表,之后呢模型就会按照指定的格式返回了

当然由于 Cline 并没有给出修改 system prompt 的地方,它的格式呢是写在代码里面的,所以我们实际上其实是没有办法让 Cline 按照这种格式与模型进行沟通的,这里呢只是给大家一个示例而已

不过话说回来,这种示例并不是没有任何意义的,比如说假设我们自己想写一个 Agent,我们是完全可以使用这种 Thought、Action、Observation 的格式的,实际上我现在可以用一个开源的模型给你举个例子

先找到之前演示过的 Cline 的 system prompt 最后加上用户的问题:纽约明天的天气怎么样,结果写入 results.md 文件中,然后我们把整个内容复制给 deepseek

在这里插入图片描述

deepseek 返回了两部分的内容,一个是 thinking 一个是 use_mcp_tool,跟我们在 Cline 那里见到的一模一样,如果是 Cline 的话它就会去调用 get_forecast 这个工具了,因为我们在模拟运行,所以呢我们直接假设工具调用完成了,返回结果给模型告诉它纽约明天的天气:

在这里插入图片描述

模型接到工具调用结果之后返回了 thinking 和 write_to_file 两个标签,要求写入文件,同样我们模拟写入成功的消息:

在这里插入图片描述

模型最后返回了 attempt_completion,整个流程就结束了

注意 attempt_completion 之前呢应该还有个 thinking,deepseek 失误了没有把那一部分返回给我们,这点你知道就好了

所以大家也都看到了 XML 呢与我们之前所讲的那种 Thought、Action、Observation 本质上都是一样的,都遵循 ReAct 的模式,而且模型都能理解,只是从逻辑上来讲 XML 的效果更好,因为它的表达更为精确,但这也不代表大家就一定要使用 XML 来返回数据,实际上呢你可以使用任何格式,比如 Json 就是一个很好的选择,只要这个格式本质上用的是 ReAct 的模式,那都是可以的

所以做一个类似 Cline 这样的 Agent 需要什么呢,首先我们需要告诉模型返回结果的格式,是用那我们前面提到的 Thought、Action、Observation 这样的格式还是用 Cline 这样的 XML 格式,还是说使用 Json 呢,哪一个都行,但一定要明确告知模型

其次我们还需要告诉模型可用的工具列表,最后呢我们还需要告诉模型一定要使用 ReAct 模式,也就是说每次回答的时候都要先思考一下,思考之后呢再接上一个工具这样请求或者是最终答案,我们把这些要求写入 system prompt 里面,再加上一个用户问题作为触发,整个 Agent 就可以跑起来了

在这里插入图片描述

以上就是本篇文章的全部内容了

结语

这篇文章我们跟随 UP 学习了 MCP Host 和模型的交互协议,以 Cline 的 XML 为例进行了讲解

首先解读了 Cline 发往模型的请求,最开始是一些 system prompt 即系统提示词,包括工具、已连接的 MCP 服务器、工作流程提示等,接着是用户的请求,包括两部分:用户的原始问题以及当前的系统环境。接着解读了模型发往 Cline 的返回,它是一个 SSE 流式返回结果,存储在 data 下的 content 内容中

我们还分析了当调用 MCP 工具时模型的请求和返回,和前面的区别是模型通过 thinking 标签思考并通过 use_mcp_tool 调用相关工具,最后我们简单分析了 Cline 的 XML 协议与 ReAct 的关系,无论是 XML 还是 Thought、Action、Observation 本质都是遵循 ReAct 的模式

OK,以上就是本篇文章的全部内容了

大家感兴趣的可以自己动动手截获下 Cline 和模型交互的内容,也可以多看看 UP 的视频,非常的不错🤗

参考

  • MCP终极指南 - 番外篇:抓包分析 Cline 与模型的交互协议
  • https://github.com/MarkTechStation/VideoCode

相关文章:

  • ollama常见属性设置
  • AIGC 使用层 | 关于AI prompt
  • Spring常用的注解详细介绍 原理分析,与实际案例
  • 黑客是如何攻击一部手机的?
  • Objective-C与Swift混合编程
  • 使用SVN checkout时报错Unable to connect to a repository at URL
  • LINUX616 问题:vim编辑器;粘滞位(其他需回顾);配置静态网络报错;more,less,head命令
  • 如何确定驱动480x320分辨率的显示屏所需的MCU主频
  • 微信小程序页面容器弹出层
  • Next.js面试题:API深度解析
  • LangChain 与 Milvus 的碰撞:全文检索技术实践
  • java 设计模式_行为型_23状态模式
  • 论文笔记:CAN LLMS UNDERSTAND TIME SERIES ANOMALIES?
  • Kafka Connect实战:从环境搭建到全流程操作
  • solana 编写智能合约 然后调用它
  • C#/.NET/.NET Core技术前沿周刊 | 第 42 期(2025年6.9-6.15)
  • Debian 编译安装 ruby3.2
  • webpack到vite的改造之路
  • SOME/IP学习随笔
  • Trae - 非科班在建模比赛中的 AI 编程手|AI编程社知识库精选
  • 微应用和微网站的区别/希爱力双效片副作用
  • 个人网站的名称/腾讯企点下载
  • 佛山定制建站公司推荐/网站收录查询爱站
  • 网站多久才能做起来/免费数据统计网站
  • 分栏型网站/推广网站怎么制作
  • 邯郸单位网站建设/网站推广什么意思